First off, I have an Angular reactive form that has a button that can add another FormArray
to the form. All validation good and working as expected. Things have gotten a little tricky when introducing another dynamic control to the already dynamic form group. This control is shown/hidden based on a selection made in another form control.
When the control is shown I introduce validation, when the control is hidden the validation is cleared. This ensures that my form remains valid/invalid correctly.
Its acting a little buggy e.g. when I complete a group of inputs and add another dynamic group of inputs, both triggering the hidden control... then to amend the previous hidden input - the form remains true. e.g.
Selecting 123
triggers the "Other Code" control, removing the value should make the form invalid, but it stays true at this point.
I have a change function assigned to the select to determine whats been selected.
selectClick(x) {
const f = this.form;
let items = this.form.get('justificationItems') as FormArray;
if (x === '123') {
items.controls.forEach(i => {
console.log(i)
i['controls'].other.setValidators([Validators.required]);
// i['controls'].other.updateValueAndValidity();
});
} else {
items.controls.forEach(i => {
i['controls'].other.clearValidators();
// i['controls'].other.updateValueAndValidity();
});
}
f.updateValueAndValidity();
}
I suspect when changing the select property to trigger the above it does not do it to the correct index item, and it does it for all?
StackBlitz - https://stackblitz.com/edit/angular-prepopulate-dynamic-reactive-form-array-ufxsf9?file=src/app/app.component.ts
CodePudding user response:
the best way to "clear/add Validators" really is enabled or disabled the formControls. Remember a formControl has as status one of this:
type FormControlStatus = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED';
So we can simple enabled/disabled the FormControl. Futhermore, when we create the formGroup we can created disabled, so at first will be not INVALID
Well, the code is a bit confussed. first the use of i['controls'].other (really you can use i.controls.other
an a estrange mix using FormBuilder and new Form.
As always we has a FormArray we create a getter
get justificationItems()
{
return this.form.get('justificationItems') as FormArray;
}
In stead use two differents functions to create the form, we can use and unique
createJustificationField(x: any=null): FormGroup {
x=x || {name:null,description:null,code:null,other:null}
return new FormGroup({
name: new FormControl(x.name, [Validators.required]),
description: new FormControl(x.description, [Validators.required]),
code: new FormControl(x.code, [Validators.required]),
other: new FormControl({value:x.other,
disabled:x.code!=='123'},[Validators.required]),
});
}
See that we can use as
this.createJustificationField(..an object..)
this.createJustificationField()
Our functions: createForm
, addItem
and selectClick
(I like more another name like codeChange
but is a minor change) becomes like
createForm() {
this.service.getmodel().subscribe((response:any) => {
this.form = new FormGroup({
justificationItems: new FormArray(
response.justificationItems.map(x=>
this.createJustificationField(x))
),
});
});
}
addItem(): void {
this.justificationItems.push(this.createJustificationField());
this.form.updateValueAndValidity();
}
selectClick(x,i) {
if (x === '123')
this.justificationItems.at(i).get('other').enable()
else
this.justificationItems.at(i).get('other').disable()
this.form.updateValueAndValidity();
}
And the .html becomes more clear in the way
<form *ngIf="form" [formGroup]="form">
<div formArrayName="justificationItems">
<div
*ngFor="
let orgs of justificationItems.controls;
let i = index;
let last = last
"
[formGroupName]="i"
>
<label>Name </label>
<input formControlName="name" placeholder="Item name" /><br />
<label>Description </label>
<input
formControlName="description"
placeholder="Item description"
/><br />
<label>Code </label>
<select
(change)="selectClick($event.target.value, i)"
formControlName="code"
>
<option value="123">123</option>
<option value="456">456</option></select
><br />
<ng-container *ngIf="justificationItems.at(i).value.code === '123'">
<label>Other Code </label>
<input formControlName="other" placeholder="other" /><br /><br />
</ng-container>
<button
*ngIf="last"
[disabled]="justificationItems.at(i).invalid"
type="button"
(click)="addItem()"
>
Add Item
</button>
</div>
</div>
<button [disabled]="!form.valid" type="button">Submit</button>
</form>
<p>Is form valid: {{ form?.valid | json }}</p>
see the stackblitz
CodePudding user response:
Root cause: When selectClick
trigger, you clear or set validation for all controls other
in array form. You should set only for one form in formArray.
I rewrite your function:
selectClick(x, index) {
const f = this.form;
let items = this.form.get('justificationItems') as FormArray;
if (x === '123') {
items.controls[index]['controls'].other.setValidators([Validators.required]);
} else {
items.controls.forEach(i => {
items.controls[index]['controls'].other.clearValidators();
i['controls'].other.updateValueAndValidity();
});
}
items.controls[index]['controls'].other.updateValueAndValidity();
}
change code in template:
<select
(change)="selectClick($event.target.value, i)"
formControlName="code"
>