Search code examples
htmlangulartypescriptangular-material-5

How trigger immediate mat-error on custom error validation in Angular


How can I get my custom mat-error to trigger immediately? I know there is an example in the Angular docs, but couldn't get that to work. My validation is not for empty nor of a certain type, for example email.

I would like my error to display when I do a search in my drop-down field and no records were found. A normal <div> below the item triggers immediately on *ngIf, but not the mat-error. I have also tried updating the mat-error with .innerHTML, but the field does not exist. I am sure it is because Angular has not yet created it.

Here is my HTML:

<mat-form-field>
    <input type="text" placeholder="Customer Search" id="CustomerId" name="CustomerId" aria-label="Number" matInput [formControl]="myCustomerSearchControl"
      [matAutocomplete]="auto" (keyup)="onCustomerSearch($event)"
      required [errorStateMatcher]="matcher">
    <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete" (optionSelected)="setLoadId($event)">
      <mat-option *ngFor="let customer of customerArray" [value]="customer.Display">
        {{ customer.Display }}
      </mat-option>
    </mat-autocomplete>
    <mat-error id="customer-error" *ngIf="noCustomersFound.value">
      {{noCustomersFound.message}}
    </mat-error>
</mat-form-field>
<div>
    <span class="errorMessage" *ngIf="noCustomersFound.value">
      {{noCustomersFound.message}}
    </span>
</div>

Here is my method in my .ts file and some stuff I have tried:

onCustomerSearch(search) {
    let searchObject = new SearchObject();
    searchObject.Entity = 'Customer';
    searchObject.SearchString = search.key;
    searchObject.SearchClauses = [{Column: 'sCustomerName'}];
    searchObject.DisplayColumn = 'sCustomerName';
    this.loadDataService.searchByEntity(searchObject)
      .subscribe(data => {
        if (data.length < 1) {
          this.noCustomersFound.value = true;
          this.noCustomersFound.message = 'No match found.'
          document.getElementById('customer-error').innerHTML = 'No match found.';
        } else {
          this.noCustomersFound.value = false;
          this.noCustomersFound.message = '';
        }
        this.customerArray = data;
      });
}

Solution

  • First, you need a custom validator.

    You can generate directive which will be used for validating FormControl in angular by typing this command in your CLI:

    ng g directive customValidator
    

    That directive should have function which looks similar to this:

    //make sure to post this code below the class closing curly brackets.
    export function customerSearchIsEmpty(searchResultsLength): ValidatorFn {
      return (control: AbstractControl): { [key: string]: any } | null => {
        const isValid = searchResultsLength ? searchResultsLength > 0 : false;
        return isValid ? null : 
         { 'customerSearchIsEmpty': { value: control.value } }; };}
    

    Once your FormControl is created, you should dynamically add your new validator:

    this.registeredForm.controls['myCustomerSearchControl'].setValidators([customerSearchIsEmpty(null)])
    

    In the TS file where your onCustomerSearch function is, you should add one more function:

      myCustomerSearchControlIsInvalid() {
        this.myCustomerSearchControl.updateValueAndValidity();
        //instead of registeredForm, use your FormGroup name.
        if (this.registeredForm.hasError('customerSearchIsEmpty')) {
          this.noCustomersFound.message = 'No customer found'; //or any other message
        } else {
          return false;
        } return true;
     }
    

    You should modify your to react to the myCustomerSearchControlIsInvalid() function's result:

    <mat-error *ngIf="myCustomerSearchControlIsInvalid()">
      {{noCustomersFound.message}}
    </mat-error>
    

    And, in the end you should add this line of code in your onCustomerSearch() function.

    onCustomerSearch(search) {
    let searchObject = new SearchObject();
    searchObject.Entity = 'Customer';
    searchObject.SearchString = search.key;
    searchObject.SearchClauses = [{Column: 'sCustomerName'}];
    searchObject.DisplayColumn = 'sCustomerName';
    this.loadDataService.searchByEntity(searchObject)
      .subscribe(data => {
      customerSearchIsEmpty(data.length);
      this.customerArray = data;
      });}
    

    I hope this helps.