I’m not sure if my question title is clear enough but I will try to give more details. I’m trying to create a folder hierarchy form using angular forms. The form can have unlimited nesting. My problem is that now I can add 2 folders with the same name on a certain level but this should not be possible and should warn the user. This is logical because in a normal file system 2 folders cannot have same name
I present a simplified version here for clarity. still a bit long to read but here is reproducible demo in stackblitz with same code
form component
@Component({
selector: 'my-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css'],
})
export class FormComponent implements OnInit {
myForm!: FormGroup;
isHierarchyVisible: boolean = false;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.myForm = this.formBuilder.group({
folderHierarchy: this.formBuilder.array([]),
});
if (this.folderHierarchy.length === 0) this.isHierarchyVisible = false;
}
removeFolder(index: number): void {
this.folderHierarchy.removeAt(index);
if (this.folderHierarchy.length === 0) this.isHierarchyVisible = false;
}
addFolder(): void {
this.folderHierarchy.push(
this.formBuilder.group({
name: [null, [Validators.required]],
subFolders: this.formBuilder.array([]),
level: 0,
})
);
this.isHierarchyVisible = true;
}
getForm(control: AbstractControl): FormGroup {
return control as FormGroup;
}
get folderHierarchy(): FormArray {
return this.myForm.get('folderHierarchy') as FormArray;
}
}
<p>folder form. type in form name and press enter</p>
<form [formGroup]="myForm">
<div formArrayName="folderHierarchy">
<label for="folderHierarchy">create folder</label>
<div>
<button type="button" class="btn btn-custom rounded-corners btn-circle mb-2" (click)="addFolder()" [disabled]="!folderHierarchy.valid">
Add
</button>
<span class="pl-1">new folder</span>
</div>
<div>
<div *ngIf="!folderHierarchy.valid" class="folder-hierarchy-error">invalid folder hierarchy</div>
<div class="folderContainer">
<div>
<div *ngFor="let folder of folderHierarchy.controls; let i = index" [formGroupName]="i">
<folder-hierarchy (remove)="removeFolder(i)" [folder]="getForm(folder)" [index]="i"></folder-hierarchy>
</div>
</div>
</div>
</div>
</div>
</form>
folder-hierarchy component
@Component({
selector: 'folder-hierarchy',
templateUrl: './folder-hierarchy.component.html',
styleUrls: ['./folder-hierarchy.component.css'],
})
export class FolderHierarchyComponent implements OnInit {
constructor(private formBuilder: FormBuilder) {}
@Output() remove = new EventEmitter();
@Input() folder!: FormGroup;
@Input() index!: number;
tempName: string = '';
ngOnInit() {}
addSubFolder(folder: FormGroup): void {
(folder.get('subFolders') as FormArray).push(
this.formBuilder.group({
name: [null, [Validators.required]],
subFolders: this.formBuilder.array([]),
level: folder.value.level + 1,
})
);
}
getControls(folder: FormGroup): FormGroup[] {
return (folder.get('subFolders') as FormArray).controls as FormGroup[];
}
removeSubFolder(folder: FormGroup, index: number): void {
(folder.get('subFolders') as FormArray).removeAt(index);
}
removeFolder(folder: { value: { subFolders: string | any[] } }): void {
this.remove.emit(folder);
}
disableAdd(folder: { invalid: any }): void {
return this.folder.invalid || folder.invalid;
}
onKeyup(event: KeyboardEvent): void {
this.tempName = (event.target as HTMLInputElement).value;
}
updateName(folder: FormGroup, name: string): void {
folder.get('name')?.setValue(name);
if (this.isInvalid(folder)) {
folder.get('name')?.updateValueAndValidity();
return;
}
}
isInvalid(folder: FormGroup): boolean {
return !folder.get('name')?.valid;
}
}
<div *ngIf="folder" #folderRow class="folder-row">
<div class="folder-header">
<div class="folder-name-container">
<label for="folderName" class="folder-name-label">Name:</label>
<input #folderName id="folderName" [ngClass]="isInvalid(folder) ? 'invalid-input' : ''" class="folder-name-input" placeholder="Folder Name" type="text" (keyup)="onKeyup($event)" maxlength="50" (keyup.enter)="updateName(folder, $any($event.target).value)" [value]="folder.value.name" autocomplete="off" />
</div>
<button type="button" class="btn-remove-folder" (click)="removeFolder(folder)">Remove</button>
<button type="button" class="btn-add-subfolder" [disabled]="disableAdd(folder)" (click)="addSubFolder(folder)">Add Subfolder</button>
</div>
<div *ngIf="folder && folder.value.subFolders.length > 0" class="subfolder-container">
<div *ngFor="let subFolder of getControls(folder); let i = index" class="subfolder-item">
<folder-hierarchy (remove)="removeSubFolder(folder, i)" [folder]="subFolder"></folder-hierarchy>
</div>
</div>
</div>
2
Answers
As you already have
onKeyup
listener in the folder component, you could get the parent form, and check whether there’s an element with the same name already, for example:My answer will be fully working with the Reactive forms.
To summarize your requirements and the work on:
Not allow duplicate names in the same directory.
1.1. Implement the custom validator function
duplicateFolderName
based on the providedparentDirectory
. We have 2 scenarios:1.1.1. The first case is the folder with the first level which itself is the root. We access the
folderHierarchy
form array (control.parent.parent
) to get the same-level object(s).1.1.2. The second case is the folder that has the parent. We provide the
parentDirectory
which contains thesubfolders
form array and through it to get the same-level object(s).1.2. For the
FolderHierarchyComponent
, you need to implement theparentDirectory
@Input
decorator to pass the parent form to the component. Note that for first level folder shouldn’t provide the value to[parentDirectory]
.Perform validation(s) only when pressing Enter key.
2.1. Angular supports the event that triggers the validation only such as
blur
,submit
, andchange
(default). For this scenario, add thevalidateOn: 'blur'
to thename
control. Next, set the(keyup.enter)="folderName.blur()"
to thename
control to blur (lose focus) the control when pressing Enter key.Demo @ StackBlitz