FormBuilder group is deprecated
Asked Answered
C

5

57

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 ?

Canescent answered 5/12, 2020 at 8:46 Comment(2)
I think you can find the solution here in the section where the deprecation is explained: angular.io/api/forms/FormBuilderStereoisomerism
@Stereoisomerism I read the docs before posting this question, unfortunately did not help much, no idea what to doCanescent
Y
56

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;
  }
}
Yerxa answered 6/12, 2020 at 6:13 Comment(1)
what if the custom validator has a complex logic that wouldn't be able to tell if setErrors block will run until runtime? e.g. something.forEach(ctl => {if xxx then setError, else ...}), what to expect to return in this scenario?Patchy
B
27

Just change your last line:

}, {validators: [ValidateThirdNumber.validate]});

to

}, {validators: [ValidateThirdNumber.validate]} as AbstractControlOptions);

NOTE: Make sure the AbstractControlOptions is outside of the curly braces!

That's it.

Beware answered 20/7, 2022 at 20:10 Comment(2)
Should the validators be with upper case or lower case "v"? I have followed you and put as lower case and the warning is gone. Also, implemented the return along with setErrors as pointed out by OvenKelvin solution above.Grouper
It depends how you named your variable which you're returning from your custom validation class. It's case sensitive if you named it with lower or upper case.Beware
S
3

While Owen Kelvin's answer is complete in explanation, I want to add a different answer pointing out the important points to have:

  1. When specifying the validator function, only specify the function name (i.e., without any parameters)
  2. That validator function must accept one and only one parameter of type AbstractControl
  3. The validator function must return {key: value} or null, but not just null all the time. Return null when there isn't an error to be displayed.

Here is an example of the form declared in the RegisterPage.ts:

import { Component, OnInit } from '@angular/core';

import {
  AbstractControl,
  FormBuilder,
  FormControl,
  FormGroup,
  Validators,
} from '@angular/forms';

@Component({
  selector: 'app-register',
  templateUrl: './register.page.html',
  styleUrls: ['./register.page.scss'],
})
export class RegisterPage implements OnInit {
  form: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.createForm();
  }

  createForm() {
    this.form = this.fb.group({
      matchEmail: this.fb.group(
        {
          //email textfield
          email: new FormControl(
            '',
            Validators.compose([
              Validators.required,
              Validators.maxLength(64),
              Validators.email,
            ])
          ),
          //confirm email textfield
          confirmEmail: new FormControl(''),
        },
        { validators: this.matchEmail }
      ),
    });
  }
}

The validator function will create a new error type named mismatch and can be inserted in the RegisterPage.ts file:

matchEmail(group: AbstractControl) {
    let email: string = group.get('email').value;
    let confirmEmail: string = group.get('confirmEmail').value;
    if (email.localeCompare(confirmEmail) != 0) {
        //error
        group.get('confirmEmail')?.setErrors({ mismatch: true });
        return { mismatch: true };
    } else {
        //no error, return null (don't return { mismatch: false })
        return null;
    }
}

Your html can be something like this (you cannot access confirmEmail directly, it must be through the group it is a part of which is matchEmail.confirmEmail):

<div *ngIf="form.get('matchEmail.confirmEmail').hasError('mismatch')
    && (form.get('matchEmail.confirmEmail').dirty ||
    form.get('matchEmail.confirmEmail').touched)">
    <small>Confirmation email doesn't match</small>
</div>
Sharrisharron answered 28/5, 2022 at 6:34 Comment(0)
H
2

I also receive same error, I make following changes.

  • make sure that your validator function signature matches like this. (A function that receives a control and synchronously returns a map of validation errors if present, otherwise null.)

    • function Your_Function_Name(ObjectName: AbstractControl): ValidationErrors | null { }
  • and you can change code in the form builder like this.

    • const formOptions: AbstractControlOptions = { validators: Your_Function_Name };
  • and in the formbuilder object pass above formOptions object like this

    • this.formObject = this.formBuilder.group({ fullName: ['', [Validators.required]] }, formOptions);
Heterologous answered 1/2, 2022 at 16:59 Comment(2)
Hey, Can you please add a working code example??Toile
@noahlubko, I have answered this with an example.Sharrisharron
M
2

I had the same problem, and my solution was this:

create a new variable like this:

const formOptions: AbstractControlOptions = {
   validators: your validator...,
   asyncValidators: your validators...
}

then insert this into your form builder group:

form: formGroup = this.fb.group({
  controls...
}, formOptions)

this removes the deprecated warning from my code.

hope it helps you.

Mannikin answered 27/6, 2022 at 22:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.