6

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

  • `updateValueAndValidity()` is an AbstractControl class method, I guess you can just run this method for `this.profileGroup` and it should work, let me know how you go – Deepak Jha Jan 02 '22 at 07:32
  • What happens when you start inserting a value into address field, does the control shows as valid if you delete what you typed? Since you're setting the address validator only when username is inserted, what stops the user to first insert his address and after that his username? How will you handle that? Not sure if you're willing and if it fits your needs, but I'd suggest you add required validator to both fields on init and at submit handle the invalid states (error msgs or css changes, whatever) - after all the expected valid form will have both the username and address fields filled. – Misha Mashina Jan 02 '22 at 07:59
  • @DeepakJha you mean insert that in the function of the validator? nope it doesn't work – Jhoan Nicolas Andres Becerra Q Jan 02 '22 at 16:35
  • @MishaMashina Yes, the control is correctly marked as invalid if I type something and then clear it. It doesn't matter if the address control has a value despite username being empty, but if username has something address must be required – Jhoan Nicolas Andres Becerra Q Jan 02 '22 at 16:37
  • Reading your comments, is it the case that you're getting the form field elements' names from some service that's not under your control, like from some API? – Misha Mashina Jan 02 '22 at 16:59
  • Kinda, Not exactly from some API but from the database as a list, and that list can be modifid by some users – Jhoan Nicolas Andres Becerra Q Jan 02 '22 at 17:09

1 Answers1

4

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.

Siddhant
  • 2,911
  • 2
  • 8
  • 14
  • Thanks for your answer, um First: I am comfused why isn't this a dynamic validator? Second: the names of the controls are in an array I guess I can get the names from there but as I said, I don't think is optimal to crate a listener to valuechanges for each of them since there may be 1 or 20+ – Jhoan Nicolas Andres Becerra Q Jan 02 '22 at 16:38
  • @Jhoan Why do you think it's a dynamic validator? Maybe that will help me to answer your query. Basically you have just created a function that takes the `control` and based on some logic either return an error object `{required: true}` or return `null` in case of no error. Maybe [this](https://angular.io/guide/form-validation#validator-functions) will help. – Siddhant Jan 02 '22 at 16:53
  • Yeah I just thought that since sometimes it is required and sometimes is not, it was a dynamic validator, thanks for letting me know my mistake there. Btw setting the Validator at group level solved my problem, thanks! – Jhoan Nicolas Andres Becerra Q Jan 02 '22 at 17:12