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?

1
  • 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 Commented Sep 4, 2019 at 19:53

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

5 Comments

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.
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 stackblitz.com/edit/angular-ejtr91?file=app/… can think a FormArray as "array" of formControls/formGroup
The answer is indeed correct. However, strict typing will generate an error in recent angular versions ( v15 at least ). The argument type is an AbstractControl. You can easily assert it inside the validator function code. The primary reason is that ValidatorFn can be provided in any control, may it be FormArray, FormGroup or a FormControl and you want to be sure that the given function is only applicable to FormArrays
@LookForAngular, The solution is write some like: return (control:AbstractControl){ const formArray=control as FormArray;...rest of the code
@Eliseo Yes, and you may want to be sure to check if the control is indeed a form array. What happen at runtime if you provide this function to a FormControl? it will error for sure. Last time I did something like this, i returned a null and a waning ( via console.warn ). This is something that can only be narrowed at runtime. Moreover FormArray is a class, you can ensure runtime type safety using if ( control instanceOf FormArray )

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.