2
votes

I'm creating a form with angular material mat-table.

I'm using reactive form while writing my app and the form initiation looks something like that:

myForm = this.fb.group({
    settings: this.fb.array([])
});

my form is a formGroup which contains a formArray control (settings).

the settings formArray contains formGroup for each setting (each line in the mat-table is a formGroup and contains number of controls).

My problem starts when I'm trying to add validation to it.

If I just had a formGroup with formControl inside and one of them was Validators.required for example, the form was invalid until I added some value and then it would have changed to valid state.

However while using the formGroup inside the form array, Even after adding some value to the require field, the form states remain invalid (I even console log the form in order to see if the inner value was changed and it was).

In addition when I tried to catch the changes using valueChange().subscribe... the event was fired only when I pushed / removed groups from the formArray and never when existing control inside the setting group was changed.

How can I listen to inner change events in groups inside array and then use some custom valuations on them?

4
To clarify, you have a top-level FormGroup with a FormArray child, where each item in the array is a FormGroup of FormControls?Keenan Diggs
yes, I did it becuse the form will contain additional data and each line in the table (element in the formArray) contains number of fieldsLiran

4 Answers

6
votes

Without seeing more of your code ... it is going to be hard to guess what may be wrong.

But here is some of my code for reference:

Component Code

  get addresses(): FormArray {
    return <FormArray>this.customerForm.get('addresses');
  }

  ngOnInit() {
    this.customerForm = this.fb.group({
      firstName: ['', [Validators.required, Validators.minLength(3)]],
      lastName: ['', [Validators.required, Validators.maxLength(50)]],
      addresses: this.fb.array([this.buildAddress()])
    });
  }

  addAddress(): void {
    this.addresses.push(this.buildAddress());
  }

  buildAddress(): FormGroup {
    return this.fb.group({
      addressType: 'home',
      street1: ['', Validators.required],
      street2: '',
      city: '',
      state: '',
      zip: ''
    });
  }

Template

    <div formArrayName="addresses"
         *ngFor="let address of addresses.controls; let i=index">

        <div class="form-group row mb-2">
          <label class="col-md-2 col-form-label"
                 attr.for="{{'street1Id' + i}}">Street Address 1</label>
          <div class="col-md-8">
            <input class="form-control"
                   id="{{'street1Id' + i}}"
                   type="text"
                   placeholder="Street address (required)"
                   formControlName="street1"
                   [ngClass]="{'is-invalid': (address.controls.street1.touched || 
                                              address.controls.street1.dirty) && 
                                              !address.controls.street1.valid }">
            <span class="invalid-feedback">
              <span *ngIf="address.controls.street1.errors?.required">
                Please enter your street address.
              </span>
            </span>
          </div>
        </div>
    </div>

You can find the complete project here: https://github.com/DeborahK/Angular-ReactiveForms/tree/master/Demo-Final

6
votes

I was doing the following :!

const array = (this.fg.get('addresses') as FormArray).controls;
array.push(this.fb.group({...}));

While it should have been (no controls)

const array = (this.fg.get('addresses') as FormArray);
array.push(this.fb.group({...}));
0
votes

The nested structure you are using should not affect the validation rules. If a FormControl is invalid, the invalid state will bubble up to the root AbstractControl (your 'myForm' FormGroup in this case). Angular will handle all of this for you.

Check out this stackblitz example with the same form structure that you've described: https://stackblitz.com/edit/angular-xhppjs?file=src%2Fapp%2Fapp.component.html

Bonus Info:

Don't try to catch the valueChanges to enforce validation rules. You will have an easier time if you simply use validator functions the way Angular intended. (https://angular.io/guide/form-validation#reactive-form-validation)

Here is the list of Angular built-ins: https://angular.io/api/forms/Validators

Here is the section on creating custom validator functions, should the built-ins not suit your needs: https://angular.io/guide/form-validation#custom-validators

0
votes

Thanks for your responses, I managed to solve the problem which was in the html file.

When I initiated the mat-table i did the following: [dataSource] = settings.value

doing that present the values in the table but didn't updated the controls in the formArray...

I solved it by changing it to settings.controls and added [formGroupName]=index to each mat-cell

Thanks for the help.