Gambo
Gambo

Reputation: 1572

Async Validator with Value Changes

I want to check for an autocomplete widget with an async validator if the input the user entered matches an item within a list.

If the value the user entered matches an item in the filtered list its valid otherwise not.

Example in pseudo code(states/filtered will come from an async API):

states: ['Michigan', 'Minnesota', 'Mississippi']

user input 'mi':
filteredStates: ['Michigan', 'Minnesota', 'Mississippi'] -> invalid because 'mi' does not match excact any value

user input 'Michigan':
filteredStates: ['Michigan'] -> valid because we have an excact match

The Async Validator is used because I want to reuse the observable I am using to react on value changes and filtering out items based on the value I am getting.

Observable with filtered values:

this.stateCtrl = new FormControl('', [], this.stateExists());
this.filteredStates = this.stateCtrl.valueChanges
    .startWith(null)
    .map(name => this.filterStates(name));

Async validator:

stateExists(states): AsyncValidatorFn  {
return (control: AbstractControl): Observable<any> => {
  if(!control.dirty || !control.value || control.value.length === 0) return Observable.of(null);

  return this.filteredStates
    .switchMap(states => Observable.from(states))
    .filter(state => state === control.value)
    .do(() => console.log('state matches value'))
    .map(() => null)
}

}

Plnkr: http://plnkr.co/edit/vxkbS8Icg7crUGIyfAQK?p=preview (In reality my values are coming back from an API)

I have 2 problems here:

  1. I can't pass the filtered states observable in the validator since it does not exists at that time. Now each time the validator runs it gets a new observable and gets emitted times the validator gets called.

  2. My validator filters out if the value matches but how would I need to change my code to map an object e.g. { notFound: true } if I could not find a value and null if I find a value? In my example it would never get there as its filtered out.

Upvotes: 0

Views: 4197

Answers (1)

AngularChef
AngularChef

Reputation: 14077

I think you're confusing async validator and async field values: your field might get its values asynchronously, but it doesn't mean the validator should be asynchronous too.

The validator doesn't care that the value came from an async HTTP call or from the user typing in the field. When it does its validation job, the value is present (it is "synchronous" if you will).

You can therefore simplify your code as follows:

// Attach a SYNCHRONOUS validator to the field.
this.stateCtrl = new FormControl('', this.stateExistsSync('California'));

// Declare the synchronous validator.
stateExistsSync(validState: string): ValidatorFn {
  return (control: AbstractControl) => {
    return control.value && control.value != validState ? { stateMismatch: true } : null;
  };
}

Plunkr: http://plnkr.co/edit/1WSueXS1oBBmgfh5OCuN?p=preview

Note that the validator accepts the validState as a parameter.

FYI, an asynchronous validator is used when the validation itself triggers an asynchronous operation, e.g. an HTTP request to validate whether a username is already taken or not.

Upvotes: 1

Related Questions