angulartypescriptangular-validationangular2-formbuilder

FormBuilder group is deprecated


I migrated my project to angular 11 and I noticed that the global validations that I added make FormBuilder.group deprecated with the message:

group is deprecated: This api is not typesafe and can result in issues with Closure Compiler renaming.
Use the `FormBuilder#group` overload with `AbstractControlOptions` instead.

so this is deprecated:

  ingredientForm = this.fb.group({
    ingredientType: ['', Validators.required],
    ingredientFlavor: [''],
    isMultiFlavor: [''],
    ingredientBrand: [''],
    ingredientName: [''],
    imageFile: ['']
  }, {validators: [ValidateThirdNumber.validate]});

and without the validators option it's not.

my ValidateThirdNumber validator:

class ValidateThirdNumber {
  static validate(control: AbstractControl): void {
      if (control) {
      const isMultiFlavor = control.get('isMultiFlavor')?.value;
      const ingredientFlavor = control.get('ingredientFlavor')?.value;
      const ingredientBrand = control.get('ingredientBrand')?.value;
      const ingredientName = control.get('ingredientName')?.value;
      if (isMultiFlavor && ingredientFlavor.trim().length === 0) {
        control.get('ingredientFlavor')?.setErrors({required_if: true});
      } else {
        control.get('ingredientFlavor')?.setErrors(null);
      }
      if (!ingredientFlavor && !ingredientBrand && !ingredientName) {
        control.get('ingredientName')?.setErrors({required_at_least: true});
        control.get('ingredientFlavor')?.setErrors({required_at_least: true});
        control.get('ingredientBrand')?.setErrors({required_at_least: true});
      } else {
        control.get('ingredientName')?.setErrors(null);
        control.get('ingredientFlavor')?.setErrors(null);
        control.get('ingredientBrand')?.setErrors(null);
      }
      if (ingredientBrand && ingredientName && ingredientName === ingredientBrand) {
        control.get('ingredientName')?.setErrors({not_the_same: true});
        control.get('ingredientBrand')?.setErrors({not_the_same: true});
      }
    }
  }
}

how do I overload it with AbstractControlOptions ?


Solution

  • Problem description

    From the documentation we see two different lines with the group() function

    group(controlsConfig: { [key: string]: any; }, options?: AbstractControlOptions): FormGroup

    AND

    group(controlsConfig: { [key: string]: any; }, options: { [key: string]: any; }): FormGroup

    The 2nd definition is what is deprecated

    The difference in this lines is options?: AbstractControlOptions and options: { [key: string]: any; }

    To understand why angular is throwing this error we will now consider AbstractControlOptions

    interface AbstractControlOptions {
      validators?: ValidatorFn | ValidatorFn[] | null
      asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null
      updateOn?: 'change' | 'blur' | 'submit'
    }
    

    We continue to breakdown the problem by noting that the difference between this structure and your structure is ValidatorFn[]

    interface ValidatorFn {
      (control: AbstractControl): ValidationErrors | null
    }
    

    Overally, the error is thrown in your case because your Validator function is expected to take a control and return ValidationErrors | null. In the line validate(control: AbstractControl): void, your code actually returns void but expected to return a ValidationError | null

    Solution

    From the problem description, the solution is to simply modify the ValidatorFn

    Ensure that your ValidatorFn returns a ValidationError or if no error returns null From ValidationErrors defination

    type ValidationErrors = {
        [key: string]: any;
    };
    

    You will need to return a key value pair object e.g {required_if: true}

    We can change your code by adding return statements as expected

    class ValidateThirdNumber {
      static validate(control: AbstractControl): ValidationErrors | null {
          if (control) {
          const isMultiFlavor = control.get('isMultiFlavor')?.value;
          const ingredientFlavor = control.get('ingredientFlavor')?.value;
          const ingredientBrand = control.get('ingredientBrand')?.value;
          const ingredientName = control.get('ingredientName')?.value;
          if (isMultiFlavor && ingredientFlavor.trim().length === 0) {
            control.get('ingredientFlavor')?.setErrors({required_if: true});
            return ({required_if: true});
          } else {
            control.get('ingredientFlavor')?.setErrors(null);
          }
          if (!ingredientFlavor && !ingredientBrand && !ingredientName) {
            control.get('ingredientName')?.setErrors({required_at_least: true});
            control.get('ingredientFlavor')?.setErrors({required_at_least: true});
            control.get('ingredientBrand')?.setErrors({required_at_least: true});
            return ({required_at_least: true});
          } else {
            control.get('ingredientName')?.setErrors(null);
            control.get('ingredientFlavor')?.setErrors(null);
            control.get('ingredientBrand')?.setErrors(null);
          }
          if (ingredientBrand && ingredientName && ingredientName === ingredientBrand) {
            control.get('ingredientName')?.setErrors({not_the_same: true});
            control.get('ingredientBrand')?.setErrors({not_the_same: true});
            return ({not_the_same: true});
          }
        }
        return null;
      }
    }