Search code examples
angularangular-formsangular-validationangular-observable

Can i use a Subject to make a custom async validator?


Is there any chance that i can achieve this with a subject? for the time being i have only acheived this with a Promise. so i call the resolve method and that works correct.

i was trying to do the following.

forbiddenEmail(control: FormControl): Observable<any> {

    const obs = new Subject();
    setTimeout(() => {
      if (control.value === '[email protected]') {
        obs.next({'emailIsForbidden': true});
      } else {
        obs.next(null);
      }
    }, 2000);
    return obs;
  }

Here, im trying to emit the event whenever the value is [email protected], otherwise null as the docs in Angular say about the validators. im trying to simulate lets say a backend service thats why i implement setTimeout fucntion and give a timeout of 2 secs. Now the problem is that when i inspect the input element which i place the validator (an email input element) then class ng-pending is displayed always. so, for some reason im not subscribing to it i know that. but how i could?

this is where i call the validator inside the FormGroup.

ngOnInit() {
    this.signupForm = new FormGroup({
      'userData': new FormGroup({
        'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
        'email': new FormControl(null, [Validators.required, Validators.email], this.forbiddenEmail.bind(this))
      }),
      'gender': new FormControl('male'),
      'hobbies': new FormArray([])
    });
  }

Solution

  • You need to complete your observable:

    setTimeout(() => {
      if (control.value === '[email protected]') {
        obs.next({'emailIsForbidden': true});
      } else {
        obs.next(null);
      }
      obs.complete();
    }, 2000);
    

    But this could be defined in a much simpler way:

    forbiddenEmail2(control: FormControl): Observable<any> {
      const result = control.value === '[email protected]' ? {'emailIsForbidden': true} : null;
      return Observable.of(result).delay(2000);
    }
    

    This is also more correct, since it actually validates the input as it is at the moment the validator is invoked, instead of validating the input as it is 2 seconds later. In a more realistic use-case: you would get the input immediately, and send it to the backend.

    Demo: http://plnkr.co/edit/qAdl7lTaKzn0bUNOp8fy?p=preview