Using Angular 12, I want to validate and detect changes in a list.
I have a property grid (a table of key/value pairs, listing properties with editable values, but each value can be of a different type, string/boolean/int/etc.).
I want to add validation and changes detection for that property grid.
Validation should happen for each item and the changes detection only needs to occur for the list as a whole (not caring which row/item was changed).
I've built something like this:
export class InnerSetting {
key!: string;
displayName!: string;
originalValue?: string;
value?: string;
type!: PropertyTypeEnum; //string | int | boolean | ...
validation?: string;
minValue?: number;
maxValue?: number;
isNullable!: boolean
}
constructor(private formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
properties: new FormArray([])
});
}
ngOnInit(): void {
this.settings.forEach(item => this.formArray.push(this.formBuilder.group(
{
key: [item.key],
type: [item.type],
name: [item.displayName],
value: [ item.value, [Validators.required, Validators.minLength(2)] ], //Depends on type and validation stuff, will be added later.
originalValue: [ item.originalValue ]
}
)));
//Not sure which of these will work, so added both for now.
this.form.valueChanges.pipe(debounceTime(500)).subscribe(changes => {
this.areChangesDetected = changes != null;
});
this.formArray.valueChanges.pipe(debounceTime(500)).subscribe(changes => {
this.areChangesDetected = changes != null;
});
}
get formArray() {
return this.form.controls.properties as FormArray;
}
Before using a Form
, I was just using a InnerSetting
list, so bear in mind that I just started replacing the list with the form.
The setting
property was an InnerSetting
object.
<form [formGroup]="form" class="group-wrapper">
<div class="d-flex item-row" *ngFor="let setting of formArray.controls; let i = index">
<div class="item flex-fill d-flex" [ngSwitch]="setting.type" [formGroupName]="i">
<span>{{setting.name}}</span>
<select *ngSwitchCase="'boolean'" class="flex-grow-1" name="role" id="role-select" [(ngModel)]="setting.value">
<option [value]="'0'">False</option>
<option [value]="'1'">True</option>
</select>
<div contenteditable *ngSwitchDefault class="flex-grow-1" type="text" [id]="setting.key" [innerText]="setting.value"></div>
</div>
<button class="remove-relation" (click)="reset(setting)">
<fa-icon [icon]="faUndo"></fa-icon>
</button>
</div>
</form>
Issues
Since I need to display different elements based on the setting type (boolean, string, number, etc). How can I access that information from the formArray.controls
?
Also, how can I bind to non-standard input controls such as my div
with a contenteditable
?
Edit
I noticed that the formArray.controls
is an array of FormGroup
and that I can access the values in this.formArray.controls[0].controls['name'].value
.
Now the issue is setting that control to the fields (select or div or input) based on the type.
Your template will look like this
Helper method to get array of FormGroup
To add FormControl to div we need to implement ControlValueAccessor
*Don't forget to add in declaration array in the module.
Full Demo