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

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

Angular mat-autocomplete is a great option to use in forms, especially when there's a lot of items to display, for example data in JSON format from an API, since it has a search functionality as well. However, its use might bring up a few challenges, especially if you are trying to validate the value of the input selected if it really is an existing value in the list. In this two part series, I will discuss and show the various ways of using the mat-autocomplete feature to handle validation.

Creating a Mat-Autocomplete input field

Let's create a Mat-Autocomplete field which will be filtering the following data. Do note however, that in this article I will discuss how to handle validation on an array of objects that is hard-coded and not fetched from an API.

salutationOptions: Array<{id: number, title: string}> = [
  {id: 100, title: 'Honorable'},
  {id: 101, title: 'MD-MPH'},
  {id: 102, title: 'Prof. Dr.'},
  {id: 103, title: 'Rev. Dr.'},
  {id: 104, title: 'Snr.'},
  {id: 105, title: 'Mr.'},
  {id: 106, title: 'Ms.'},
  {id: 107, title: 'Mrs.'},
  {id: 108, title: 'Dr.'},
  {id: 109, title: 'H.E.'},
  {id: 110, title: 'Amb.'},
  {id: 111, title: 'Amb. Dr.'},
  {id: 112, title: 'Mr. and Mrs.'},
];

So to create a mat-autocomplete, on the template file, we'll have the following code:

<mat-form-field  appearance="outline">
                <mat-label>Salutation</mat-label>
                <input type="text" matInput formControlName="SalutationValue" [matAutocomplete]="autoSalutation" >
                <mat-autocomplete #autoSalutation="matAutocomplete" [displayWith]="getSalutation.bind(this)">
                    <mat-option *ngFor="let option of salfilteredOptions | async" [value]="option.id">
                      {{option.title}}
                    </mat-option>
                </mat-autocomplete>
</mat-form-field>

And the Typescript file as shown below:

  salfilteredOptions: Observable<any[]>;

    ngOnInit() {
        this.salfilteredOptions = this.newContactForm.controls['SalutationValue'].valueChanges.pipe(
            startWith(''),
            map((value) => (typeof value === 'string' ? value : value.title)),
            map((title) => title ? this._filter(title) : this.salutationOptions.slice())
        );
    }

    getSalutation(salId: string) {
        const result = this.salutationOptions.filter((sal) => sal.id === salId);
        const resTitle = result.map((t) => t.title);
        return resTitle.join('');
    }

With the code above, we can pretty much access the functionalities of the mat-autocomplete as shown below:

Capture2.PNG

However, this does not validate whether the value of the input exists in the lists, thus allowing for values such as this:

Capture.PNG

Thus, to handle this kind of validation, we can create a new Typescript file that will carry the validation code. Creation of a new file will allow for the code to be used in other components. The file will contain the following code which checks whether the value of the input has an ID. If it does, it returns null to confirm that the value exists since it has an ID, else it returns an error object confirming that the value of the input does not exist in the options list.

export function CustomFormValidators(array: any[]): ValidatorFn {
    return (control: AbstractControl): { [key: string]: any } | null => {
      const controlValue = control.value;
      if (!controlValue) return null;
      let res = array.findIndex(el => el.id === controlValue);
      return res !== -1 ? null : { error: true };
    }
}

We thus are able to implement the code as a validator function on the form initialization.

      SalutationValue: ['',Validators.compose([
              Validators.required,
              CustomFormValidators(this.salutationOptions)
      ])],

And implement the validation on the template to throw an error message using mat-error.

<mat-form-field  appearance="outline">
                <mat-label>Salutation</mat-label>
                <input type="text" matInput formControlName="SalutationValue" [matAutocomplete]="autoSalutation" >
                <mat-autocomplete #autoSalutation="matAutocomplete" [displayWith]="getSalutation.bind(this)">
                  <mat-option *ngFor="let option of salfilteredOptions | async" [value]="option.id">
                    {{option.title}}
                  </mat-option>
                </mat-autocomplete>
                <mat-error *ngIf="newContactForm.controls['SalutationValue'].hasError('required')">
                  Required fields must be filled in.
                </mat-error>   
                <mat-error *ngIf="newContactForm.controls['SalutationValue'].hasError('error')">
                  Please select a valid option from the list
                </mat-error>
</mat-form-field>

With those adjustments made, our field is able to throw an error message as shown below:

Capture3.PNG

And with that, we're able to validate the value of the input, restricting the user to only select an option from the list.

In the next section, I will discuss on how to handle this same validation for data that is fetched from an API, since the ways of handling validation are quite different compared to what has been discussed in this article.

Thanks for reading this article and hope to see you in the next one.