Home > Net >  Reactive form validation for dynamic and hidden fields
Reactive form validation for dynamic and hidden fields

Time:02-23

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. enter image description here

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"
    >
  • Related