\n
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.
\n selectClick(x) {\n const f = this.form;\n let items = this.form.get('justificationItems') as FormArray;\n if (x === '123') {\n items.controls.forEach(i => {\n console.log(i)\n i['controls'].other.setValidators([Validators.required]);\n // i['controls'].other.updateValueAndValidity();\n });\n } else {\n items.controls.forEach(i => {\n i['controls'].other.clearValidators();\n // i['controls'].other.updateValueAndValidity();\n });\n}\n f.updateValueAndValidity();\n }\n
\nI 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?
\nStackBlitz - https://stackblitz.com/edit/angular-prepopulate-dynamic-reactive-form-array-ufxsf9?file=src/app/app.component.ts
\n","author":{"@type":"Person","name":"Tom Rudge"},"upvoteCount":1,"answerCount":2,"acceptedAnswer":{"@type":"Answer","text":"the best way to "clear/add Validators" really is enabled or disabled the formControls. Remember a formControl has as status one of this:
\n type FormControlStatus = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED';\n
\nSo we can simple enabled/disabled the FormControl. Futhermore, when we create the formGroup we can created disabled, so at first will be not INVALID
\nWell, 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
\n get justificationItems()\n {\n return this.form.get('justificationItems') as FormArray;\n }\n
\nIn stead use two differents functions to create the form, we can use and unique
\n createJustificationField(x: any=null): FormGroup {\n x=x || {name:null,description:null,code:null,other:null}\n return new FormGroup({\n name: new FormControl(x.name, [Validators.required]),\n description: new FormControl(x.description, [Validators.required]),\n code: new FormControl(x.code, [Validators.required]),\n other: new FormControl({value:x.other,\n disabled:x.code!=='123'},[Validators.required]),\n });\n }\n
\nSee that we can use as
\nthis.createJustificationField(..an object..)\nthis.createJustificationField()\n
\nOur functions: createForm
, addItem
and selectClick
(I like more another name like codeChange
but is a minor change) becomes like
createForm() {\n this.service.getmodel().subscribe((response:any) => {\n this.form = new FormGroup({\n justificationItems: new FormArray(\n response.justificationItems.map(x=>\n this.createJustificationField(x))\n ),\n });\n });\n }\n\n addItem(): void {\n this.justificationItems.push(this.createJustificationField());\n this.form.updateValueAndValidity();\n }\n\n\n selectClick(x,i) {\n if (x === '123') \n this.justificationItems.at(i).get('other').enable()\n else\n this.justificationItems.at(i).get('other').disable()\n \n this.form.updateValueAndValidity();\n }\n
\nAnd the .html becomes more clear in the way
\n<form *ngIf="form" [formGroup]="form">\n <div formArrayName="justificationItems">\n <div\n *ngFor="\n let orgs of justificationItems.controls;\n let i = index;\n let last = last\n "\n [formGroupName]="i"\n >\n <label>Name </label>\n <input formControlName="name" placeholder="Item name" /><br />\n <label>Description </label>\n <input\n formControlName="description"\n placeholder="Item description"\n /><br />\n <label>Code </label>\n <select\n (change)="selectClick($event.target.value, i)"\n formControlName="code"\n >\n <option value="123">123</option>\n <option value="456">456</option></select\n ><br />\n <ng-container *ngIf="justificationItems.at(i).value.code === '123'">\n <label>Other Code </label>\n <input formControlName="other" placeholder="other" /><br /><br />\n </ng-container>\n <button\n *ngIf="last"\n [disabled]="justificationItems.at(i).invalid"\n type="button"\n (click)="addItem()"\n >\n Add Item\n </button>\n </div>\n </div>\n <button [disabled]="!form.valid" type="button">Submit</button>\n</form>\n\n<p>Is form valid: {{ form?.valid | json }}</p>\n
\nsee the stackblitz
\n","author":{"@type":"Person","name":"Eliseo"},"upvoteCount":1}}}Reputation: 3272
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
Upvotes: 1
Views: 3241
Reputation: 58099
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
Upvotes: 1
Reputation: 157
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"
>
Upvotes: 1