4

I want to add custom validator to form in order to prevent mat-step switching to next step. All works well when I use FormGroups but I fail to achieve validation when I have to use FormArray.

I've tried at least two variants of assigning validator on form initialization:

  • inside array

statuses: this._formBuilder.array([this.createStatus()], defaultStatusValidator())

  • inside parent form of array

this.productionLineStatuses = this._formBuilder.group({statuses: this._formBuilder.array([this.createStatus()])}, {validator: defaultStatusValidator()});

But this attempts are causing error (probably when casting validator):

TypeError: Cannot convert undefined or null to object 
    at Function.keys (<anonymous>)
    at FormGroup.validate [as validator] (default-status.directive.ts:6)
    at FormGroup._runValidator (forms.js:3438)
    at FormGroup.updateValueAndValidity (forms.js:3399)
    at new FormGroup (forms.js:4097)
    at FormBuilder.group (forms.js:7578)
    at CreateProductionLineComponent.ngOnInit (create-production-line.component.ts:31)

In the following case error is not thrown, but validator is not working too. Here is rest of my code and my custom validator:

ngOnInit() {
    this.productionLineDetails = this._formBuilder.group({
      productType: ['', Validators.required],
      language: ['', Validators.required],
      name: ['', Validators.required],
      description: [''],
    });
    this.productionLineStatuses = this._formBuilder.group({
      statuses: this._formBuilder.array([
        this.createStatus()
      ])
    }, defaultStatusValidator());
    this.statuses = this.productionLineStatuses.get('statuses') as FormArray;
    this.statusError = false;
  }

validator:

export function defaultStatusValidator(): ValidatorFn {
    return function validate (statuses: FormArray) {
    let defaultCounter = 0;
    Object.keys(statuses.value.controls).forEach(key => {
        const control = statuses.value.controls[key];

        if (control.value.default == true) {
            defaultCounter ++;
        }
      });
    return (defaultCounter > 1) ? {moreThanOneStatusIsDefault: true} : null;
    };
}

How should I properly add validator to FormArray?

multim
  • 51
  • 1
  • 2
  • 5
  • You are trying to add defaultStatusValidator as a validator but end up calling it instead defaultStatusValidator(). That is why you get the error. Remove the parenthesis that executes the function call – CodingTurtle Sep 04 '19 at 19:53

1 Answers1

8

You have considered if your FormArray is a FormArray of FormControls or a FormArray of FormGroups, but the problem is how you iterate over the controls,

A simple example of both

export function customValidateArray(): ValidatorFn {
    return (formArray:FormArray):{[key: string]: any} | null=>{
      let valid:boolean=true;
      formArray.controls.forEach((x:FormControl)=>{
          valid=valid && x.value=="a"
      })
      return valid?null:{error:'Not all a'}
    }
  };

export function customValidateArrayGroup(): ValidatorFn {
    return (formArray:FormArray):{[key: string]: any} | null=>{
      let valid:boolean=true;
      formArray.controls.forEach((x:FormGroup)=>{
          valid=valid && x.value.name=="a" 
      })
      return valid?null:{error:'Not all name are a'}
    }
  };

You can see an example in stackblitz

NOTE: To create the form, I use the constructor of FormGroup, FormControl and FormArray, but you can use FormBuilder too.

NOTE 2: it's not necesary enclose a FormArray in a FormGroup

metodribic
  • 1,561
  • 17
  • 27
Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • About note 2: what do you mean? mat-step uses FormGrops per each step. How to use it other way? Even if it is possible, I'll still stick to parent FormGruop to make all code in a module consistent. – multim Sep 05 '19 at 19:30
  • It's only that several times I see create a formGroup with an only one field, that is a FormArray, and it's innecesary) in the stackblitz you can see how manage the formArray, -iterating over formArray.controls and using the variable of the for as formGroup or a formControl-. Anyway, a FormArray is a FormGroup too, so e.g. in a mat-steper you can use a `stepControl` the formArray and as `formGroup` formArray.at(0) and formArray.at(1). see https://stackblitz.com/edit/angular-ejtr91?file=app/stepper-label-position-bottom-example.html.We can think a FormArray as "array" of formControls/formGroup – Eliseo Sep 06 '19 at 06:24