Search code examples
angularangular-forms

set select field to invalid manually in Angular 8


I have a select field in Angular 8 that is tied to a button. I want to be able to click on the button and if nothing is selected in the field I want to make the field invalid.

Here is my select field and button:

<div class="input-group-prepend">
       <button class="btn btn-primary" type="button" (click)="AddNewSchedule()>
          <i class="material-icons post_add" aria-hidden="true"></i>
       </button>
    </div>
    <select class="form-control"
      [(ngModel)]="ScheduleDataObj.modeOfTransportID"
      name="modeOfTransportID"
      #modeOfTransportID="ngModel"
      (change)="ModeValueChange($event)"
    >
      <option [value]="0">Select Mode</option>
      <option *ngFor="let m of Modes" [value]="m.modeOfTransportID">{{ m.mode }}</option>
    </select>

How do I make the field invalid manually?

This is what I've tried using the event handler function binded to the button:

@ViewChild('modeOfTransportID', { static: false }) modeID: HTMLFormElement;

public AddNewSchedule() {
 if (this.ScheduleDataObj.modeOfTransportID === 0) {
      this.modeID.setErrors({ invalid: true });
      return false;
    } else {
      this.modeID.setErrors(null);
   } 
}

On the button click I get an error saying 'setErrors is not a function'. I also tried doing this.modeID.control.invalid = true which didn't work.


Solution

  • @Chellappan வ's answer is totally correct, but there are a few other problems with your code that I wanted to address. First off, per the spec, value will always be of type string. So, this.ScheduleDataObj.modeOfTransportID === 0 will always be false because it needs to be === '0'. However, you can fix it using ngValue. From the docs (emphasis mine):

    ngValue: Tracks the value bound to the option element. Unlike the value binding, ngValue supports binding to objects.

    So you can refactor your view to use ngValue and your code will work:

    <select class="form-control"
      [(ngModel)]="ScheduleDataObj.modeOfTransportID"
      name="modeOfTransportID"
      #modeOfTransportID="ngModel"
      (change)="ModeValueChange($event)">
      <option [ngValue]="0">Select Mode</option>
      <option [ngValue]="1">Some Mode</option>
    </select>
    

    Additionally, instead of thinking about watching the value in terms of html events (which can get tricky because your model value might not be changed exactly when you think it is), you could refactor your code to watch the FormControl itself for value changes. Be sure to wait until after view is initialized so your @ViewChild ref won't be null:

    ngAfterViewInit() {
      // Watch our modeID value changes.
      // Using takeUntil to avoid memory leaks.
      this.modeID.valueChanges
        .pipe(takeUntil(this.unsubscribe))
        .subscribe(value => {
          if (value === 0) {
            // Note: this will remove other validation errors. 
            // If that is not desired behavior, see this discussion:
            // https://github.com/angular/angular/issues/21564
            this.modeID.control.setErrors({ invalid: true });
          } else {
            // This also removes *all* errors, which might not
            // be desired behavior.
            this.modeID.control.setErrors(null);
          }
        });
    }
    

    Putting it all together, here's a working example on stackblitz. You also could easily just build a custom validator for this.