angularangular-reactive-formsformbuilderangular-validationangular-validator

Angular reactive form, Dynamic validator not updating control validity


So I have a form where a control is required only if another control has a value, for this I created the following structure

 profileGroup = this.fb.group({
username: [''],
address: [
  '',
  [
    (control: AbstractControl) => {
      if (this.profileGroup?.get('username')?.value) {
        return (Validators.required)(control);
      }
      return (Validators.nullValidator)(control);
    },
  ],
],

});

this function works well, once the username control gets a value, a Validators.required is added to the address control. However, despite the validator being added, the validity of the address control doesn't update so the control is still marked as valid.

If possible I would like to avoid using something like

this.profileGroup.get('username')?.valueChanges.subscribe(val=>{
  this.profileGroup.get('address')?.updateValueAndValidity()
})

since there may be dynamic controls that I don't know the name of, and don't want to set an observable for each of them.

Here's a stack blitz of a working example https://stackblitz.com/edit/angular-10-formbuilder-example-m8qjq8?file=src/app/app.component.ts


Solution

  • this function works well, once the username control gets a value, a Validators.required is added to the address control. However, despite the validator being added, the validity of the address control doesn't update so the control is still marked as valid.

    Well, exactly that's not what happens. You have defined a custom validator for address FormControl, which gets executed when the address FormControl value is updated. (PS: It's not a dynamic validator)

    When you update the username, the validators associated with it and its ancestors (profileGroup in your case) are called, the sibling FormControl validators are not triggered.

    When you execute the below code, you are manually executing the validators associated with address FormControl, and that doesn't update the FormControl validity. It simply executes the validators associated with the FormControl and return their result.

    const validator = abstractControl.validator({} as AbstractControl);
    

    In order to update the sibling i.e address FormControl validity, you will have to call it's updateValueAndValidity().

    Another alternative could be to define validators at FormGroup level, which will be executed automatically and you don't have to manually call updateValueAndValidity() on any FormControl. But, with this approach the validation errors will be set at FormGroup level.

    since there may be dynamic controls that I don't know the name of, and don't want to set an observable for each of them.

    If you won't be aware of control names, how exactly you will be using them within custom validator as below, where username FormControl is being referred?

    this.profileGroup?.get('username')?.value
    

    There is a way to call updateValueAndValidity() on the formControl, without explicitly specifying it's name, BUT won't recommend it. You can loop over the profileGroup.controls and execute updateValueAndValidity on each control, followed by profileGroup itself. You would also have to take care of passing {onlySelf: true, emitEvent: false} options to updateValueAndValidity to avoid recursion.