this is kind of a continuation of an improved version of an old question of mine
so basically I have a recursive Angular form and I’m using to manage a folder hierarchy. Each folder has a radio button for selecting between Exclusive and Regular folder.
Now I’m trying to add a validation so that
- When a folder is selected as Exclusive at any level in the hierarchy the Exclusive button should be disabled for all other folders at any level. (basically 1 exclusive folder in the folder hierarchy)
- The Regular option should remain available at all levels regardless of whether Exclusive is selected at any other level
How should I even approach a problem like this because the notification should happen from parent to child level and also from child level to parent level. Is this possible to be done with @Input()
or is there a better approach.
I present demo version here for clarity. still a bit long to read but here is reproducible demo in stackblitz with same code
form component
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, Validators, AbstractControl } from '@angular/forms';
import { duplicateFolderName } from '../validators/duplicate-folder-name.validator';
@Component({
selector: 'my-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css'],
})
export class FormComponent implements OnInit {
myForm!: FormGroup;
isHierarchyVisible = false;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
folderHierarchy: this.fb.array([]),
});
if (!this.folderHierarchy.length) this.isHierarchyVisible = false;
}
addFolder() {
this.folderHierarchy.push(
this.fb.group({
name: [null, [Validators.required, duplicateFolderName()], { updateOn: 'blur' }],
isExclusive: false,
subFolders: this.fb.array([]),
level: 0,
})
);
this.isHierarchyVisible = true;
}
removeFolder(index: number) {
this.folderHierarchy.removeAt(index);
if (!this.folderHierarchy.length) this.isHierarchyVisible = false;
}
get folderHierarchy() { return this.myForm.get('folderHierarchy') as FormArray; }
getForm(control: AbstractControl) { return control as FormGroup; }
}
<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 *ngIf="!folderHierarchy.valid" class="folder-hierarchy-error">invalid folder hierarchy</div>
<div class="folderContainer">
<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>
</form>
folder-hierarchy component
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { duplicateFolderName } from '../validators/duplicate-folder-name.validator';
@Component({
selector: 'folder-hierarchy',
templateUrl: './folder-hierarchy.component.html',
styleUrls: ['./folder-hierarchy.component.css'],
})
export class FolderHierarchyComponent {
@Output() remove = new EventEmitter();
@Input() folder!: FormGroup;
@Input() index!: number;
@Input() parentDirectory?: FormGroup;
constructor(private fb: FormBuilder) {}
addSubFolder(folder: FormGroup): void {
(folder.get('subFolders') as FormArray).push(
this.fb.group({
name: [null, [Validators.required, duplicateFolderName(this.parentDirectory)]],
isExclusive: false,
subFolders: this.fb.array([]),
level: folder.value.level + 1,
})
);
}
updateIsExclusive(folder: FormGroup, value: Event, isExclusive: boolean): void {
folder.get('isExclusive')?.setValue(isExclusive ? (value.target as HTMLInputElement).checked : !(value.target as HTMLInputElement).checked);
}
getControls(folder: FormGroup): FormArray {
return folder.get('subFolders') as FormArray;
}
removeSubFolder(folder: FormGroup, index: number): void {
(folder.get('subFolders') as FormArray).removeAt(index);
}
removeFolder(folder: FormGroup): void { this.remove.emit(folder); }
disableAdd(folder: FormGroup): boolean { return this.folder.invalid || folder.invalid; }
get nameControl(): FormControl { return this.folder.get('name') as FormControl; }
}
<div *ngIf="folder" #folderRow class="folder-row" [formGroup]="folder">
<div class="folder-header">
<div class="folder-name-container">
<label for="folderName" class="folder-name-label">Name:</label>
<input #folderName id="folderName" [ngClass]="nameControl.errors ? 'invalid-input' : ''"
class="folder-name-input" placeholder="Folder Name" type="text" maxlength="50" autocomplete="off"
name="name" formControlName="name" (keyup.enter)="folderName.blur()" />
</div>
<div class="folder-type-container">
<div class="folder-type-option">
<input type="radio" [value]="true" [checked]="folder.value.isExclusive" (change)="updateIsExclusive(folder, $event, true)" />
<label for="exclusive">Exclusive</label>
</div>
<div class="folder-type-option">
<input type="radio" [value]="false" [checked]="!folder.value.isExclusive" (change)="updateIsExclusive(folder, $event, false)" />
<label for="regular">Regular</label>
</div>
</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 getSubFoldersControls(folder); let i = index" class="subfolder-item">
<folder-hierarchy (remove)="removeSubFolder(folder, i)" [folder]="subFolder" [parentDirectory]="getSubFolderParent(subFolder)"></folder-hierarchy>
</div>
</div>
<div *ngIf="nameControl.errors?.required" class="folder-hierarchy-error">Name is required.</div>
<div *ngIf="nameControl.errors?.duplicateName" class="folder-hierarchy-error">Name already exists</div>
</div>
Help is much appreciated