Angular Material: Handling Validation for Autocomplete Inputs (Part 2)

Angular Material: Handling Validation for Autocomplete Inputs (Part 2)

Angular mat-autocomplete is a powerful tool to include in your forms, especially due to its search functionality which can be quite helpful especially when displaying an array of multiple objects. In this article, we will discuss how to handle validation for data that is fetched from an API.

Creating a Mat-Autocomplete input field

We'll begin by creating a mat-autcomplete field on the template file

<mat-form-field appearance="outline">
                <mat-label>Contact Person</mat-label>
                <input type="text" matInput formControlName="ContactId" [matAutocomplete]="autoContact">
                <mat-autocomplete  #autoContact="matAutocomplete" [displayWith]="getContact.bind(this)">
                  <mat-option *ngFor="let option of contactFilteredOptions  | async" [value]="option.ContactId">
                    {{option.ContactName}}
                  </mat-option>
                </mat-autocomplete>
</mat-form-field>

And on the template as shown below, where we will fetch our data from the API.

  contactFilteredOptions: Observable<Contact[]>;
  contact: Contact[] = [];

ngOnInit() {
  this.contactFilteredOptions= this.newContactForm.controls['ContactId'].valueChanges.pipe(
      startWith(''),
      switchMap((value) => this._filter(value))
    );
}

  getContact(value: any) {
    if (!value) return '';
    let index = this.contact.findIndex((c) => c.ContactId=== value);
    let res = this.contact[index].ContactName;
    return this.contact[index].ContactName;
  }

private _filter(value: string): Observable<any> {
    return this.service.getAllContacts().pipe(
      filter((data) => !!data),
      map((data) => {
        const contacts = JSON.parse(JSON.stringify(data));
        this.contact = contacts.Value;
        const filterValue = value.toLowerCase();
        return contacts.Value.filter(
          (option: { ContactName: string }) =>
            option.ContactName.toLowerCase().includes(filterValue)
        );
      })
    );
  }

To include custom validation, we'll have to make some few adjustments on the existing code by including a (change) method on the template file. This will check for validation based on the changes made on the input value. We will also add the error messages to be displayed to the user.

<mat-form-field appearance="outline">
                <mat-label>Contact Person</mat-label>
                <input type="text" (change)="ContactValidate()" matInput formControlName="ContactId" [matAutocomplete]="autoContact">
                <mat-autocomplete  #autoContact="matAutocomplete" [displayWith]="getContact.bind(this)">
                  <mat-option *ngFor="let option of contactFilteredOptions  | async" [value]="option.ContactId">
                    {{option.ContactName}}
                  </mat-option>
                </mat-autocomplete>
                <mat-error *ngIf="newContactForm.controls['ContactId'].hasError('required')">
                  Required fields must be filled in.
                </mat-error> 
                <mat-error *ngIf="newContactForm.controls['ContactId'].hasError('error')">
                  Please select a valid option from the list
                </mat-error>   
</mat-form-field>

We'll then add the (change) function on the Typescript file, which will check whether the value of the input has an existing ID or not.

 ContactValidate(): ValidatorFn {
    return (control: AbstractControl): { [key: string]: any } | null => {
      const controlValue = control.value;
      let res = this.contact.findIndex(el => el.ContactId === controlValue);
      return res !== -1 ? null : { match: true };
    }
  }

And on the form initialization section, we will add the validation functions.

    ContactId: ['',Validators.compose([
        Validators.required,
        this.ContactValidate()
      ])],

With that, we are able to display an error message if the input is invalid and thus restrict the user from adding any item that does not exist in the API.

Capture4.PNG

Thanks for reading, hope to see you in the next one.