oividiosCaeremos
oividiosCaeremos

Reputation: 728

Async Validator Doesn't Work on Angular AutoComplete

I want users to pick from autocomplete elements and when they even delete one character from the selected value, I want to show them an error and I also want my FormControl to be invalid. So far I couldn't figure it out. I'm also filtering the autocomplete values as the user types, and that as can be seen here causes the problem. The github issue seems to have resolved status but it still is a problem.

My component.html file:

<form class="example-form">
  <mat-form-field class="example-full-width" appearance="fill">
    <mat-label>User</mat-label>
    <input
      placeholder="Pick one"
      matInput
      [formControl]="myControl"
      [matAutocomplete]="auto"
    />
    <mat-autocomplete
      autoActiveFirstOption
      #auto="matAutocomplete"
      [displayWith]="displayUserFn"
      (optionSelected)="valueChange('UserEntity', $event)"
    >
      <mat-option
        *ngFor="let option of filteredOptions | async"
        [value]="option"
      >
        {{option.name}}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
  <span
    *ngIf="(myControl.touched || myControl.dirty) && (myControl.errors?.required || myControl.errors?.incorrect)"
    style="color: red"
  >
    User must be picked.
  </span>
</form>

My component.ts file:

export class AutocompleteFilterExample implements OnInit {
  myControl = new FormControl();
  options = [
    {
      name: 'Johnny Depp',
    },
    {
      name: 'Jennifer Anniston',
    },
    {
      name: 'Jennifer Hernandez',
    },
    {
      name: 'John Cena',
    },
    {
      name: 'Jenny Something',
    },
    {
      name: 'Angelina Jolie',
    },
  ];

  filteredOptions: Observable<any>;

  ngOnInit() {
    this.myControl = new FormControl('', {
      validators: [Validators.required],
      asyncValidators: [this.requireMatch],
    });

    this.filteredOptions = this.myControl.valueChanges.pipe(
      startWith(''),
      map((value) =>
        typeof value === 'string' ? this._filter(value) : [value]
      )
    );
  }

  requireMatch = (
    control: AbstractControl
  ): Observable<ValidationErrors | null> => {
    console.log(control);
    if (!control.valueChanges || control.pristine) {
      return of(null);
    } else {
      return this.filteredOptions.pipe(
        map((users) => {
          if (
            users.findIndex((user) => `${user.name}` === control.value) === -1
          ) {
            console.log('incorrect !');
            return { incorrect: true };
          }
          console.log('correct !');

          return null;
        })
      );
    }
  };

  private _filter(value) {
    if (value === null || value === '') return [];

    const loweredString = value.toLocaleLowerCase('en-US');

    return this.options
      .filter(
        (user) =>
          `${user.name}`.toLocaleLowerCase('en-US').indexOf(loweredString) !==
          -1
      )
      .sort((a, b) => `${a.name}`.localeCompare(`${b.name}`))
      .slice(0, 20);
  }
}

To clarify what I need:

When I write je and pick one value from the list:

enter image description here

enter image description here

It shouldn't show an error as there is an option with the same value of the input. But when alter the input value, I want to see an error, which I am not at the moment.

enter image description here

How can I achieve this?

I added these to a stackblitz project, can be seen here.

Upvotes: 0

Views: 316

Answers (1)

Matthieu Riegler
Matthieu Riegler

Reputation: 54579

This a known bug, currently you'll have to poll the status to get what you want :

const ctrl = new FormControl();
const statusChanges$ = merge(ctrl.statusChanges, timer(0, 500)).pipe(
  map(() => ctrl.status),
  distinctUntilChanged()
);

Upvotes: 3

Related Questions