3

I need to make certain form fields required or not based on the value of other fields. The built-in RequiredValidator directive doesn't seem to support this, so I created my own directive:

@Directive({
  selector: '[myRequired][ngControl]',
  providers: [new Provider(NG_VALIDATORS, { useExisting: forwardRef(() => MyRequiredValidator), multi: true })]
})
class MyRequiredValidator {
  @Input('myRequired') required: boolean;

  validate(control: AbstractControl): { [key: string]: any } {
    return this.required && !control.value
      ? { myRequired: true }
      : null;
  }
}

Sample usage:

<form>
  <p><label><input type="checkbox" [(ngModel)]="isNameRequired"> Is Name Required?</label></p>
  <p><label>Name: <input type="text" [myRequired]="isNameRequired" #nameControl="ngForm" ngControl="name" [(ngModel)]="name"></label></p>
  <p *ngIf="nameControl.control?.hasError('myRequired')">This field is required.</p>
</form>

This works fine if the user first toggles the check box and then types or erases text in the text box. However, if the user toggles the check box while the text box is blank, then the validation message doesn't update appropriately.

How can I modify MyRequiredValidator to trigger validation when its required property is changed?

Note: I'm looking for a solution that only involves changes to MyRequiredValidator. I want to avoid adding any logic to the App component.

Plunker: https://plnkr.co/edit/ExBdzh6nVHrcm51rQ5Fi?p=preview

Michael Liu
  • 52,147
  • 13
  • 117
  • 150
  • Wouldn't #nameControl="ngForm" go on the
    tag, not the tag?
    – Ron Newcomb Jun 12 '17 at 01:33
  • 1
    @RonNewcomb: Back in the days of Angular 2 (when I posted this question), the NgModel directive exported itself as `ngForm`, which was super confusing on an `` tag. It has since been changed to `ngModel`. – Michael Liu Jun 12 '17 at 03:18

1 Answers1

3

I would use something like that:

@Directive({
  selector: '[myRequired][ngControl]',
  providers: [new Provider(NG_VALIDATORS, { useExisting: forwardRef(() => MyRequiredValidator), multi: true })]
})
class MyRequiredValidator {
  @Input('myRequired') required: boolean;

  ngOnChanges() {
    // Called when required is updated
    if (this.control) {
      this.control.updateValueAndValidity();
    }
  }

  validate(control: AbstractControl): { [key: string]: any } {
    this.control = control;
    return this.required && !control.value
      ? { myRequired: true }
      : null;
  }
}

See this plunkr: https://plnkr.co/edit/14jDdUj1rdzAaLEBaB9G?p=preview.

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • This seems to work. But in a more complicated scenario, is there a chance that calling `updateValueAndValidity` in `ngOnChanges` could cause the error `"Expression '...' has changed after it was checked."`? (I don't really understand the exact circumstances that cause this error.) – Michael Liu May 02 '16 at 16:14
  • And indeed, if I move the `

    ` for the message before the `

    ` for the check box, then I do get that error when I toggle the check box.

    – Michael Liu May 02 '16 at 16:20
  • 1
    @MichaelLiu Liu In this way you need to add this.cdr.detectChanges() after updateValueAndValidity. Based on http://stackoverflow.com/questions/34364880/expression-has-changed-after-it-was-checked/35243106 – yurzui May 02 '16 at 16:57