Search code examples
angulartypescript2-way-object-databinding

Angular: 2-way binding with mat-select not working, assigning value in code causes it to reset in UI


I'm trying to bind a mat-select to an enum value like this:

<mat-form-field>
  <mat-label>Type</mat-label>
  <mat-select [(ngModel)]="selectedType" [compareWith]="compareType">
    <mat-select-trigger>
      {{types[selectedType]?.name}}
    </mat-select-trigger>
    <mat-option *ngFor="let type of types | keyvalue" [value]="type.key">
      <mat-icon>{{type.value.icon}}</mat-icon> {{type.value.name}}
    </mat-option>
  </mat-select>
</mat-form-field>

And the typescript:

enum Type {
  Hardware = 0,
  Software = 1
}

types: { [TP in number]: { icon: string, name: string }; } = {
  [Type.Hardware]: { icon: 'computer', name: 'Hardware' },
  [Type.Software]: { icon: 'keyboard', name: 'Software' },
};

selectedType: Type;

compareType(type1: Type, type2: Type): boolean {
  return ((type1 as Type) === (type2 as Type));
}

I want the value of the select to be bound to selectedType, which I'm trying to achieve using [(ngModel)]="selectedType" [compareWith]="compareType".

The problem is, that this data-binding doens't fully go both ways. When I assign a value using the UI, the binding updates accordingly, but when I update the value inside my code, it resets the value in the UI, showing the placeholder again. However while the UI doesn't display it correctly, the value is assigned properly.


Solution

  • there is type mismatch problem in your code.

    selectedType has type Type which is a number.

    where you set[value]="type.key" within

    <mat-option *ngFor="let type of types | keyvalue" [value]="type.key">

    type.key is a string due to keyvalue pipe.

    in the code above when you a select an item from dropdown as string value of "0" or "1" is assigned to selectedType. if you assign a value programatically like this.selectedType = Type.Hardware a number value 0 is assigned to selectedType

    and since your compareType function uses strict equality (===) it returns false because 1 === "1" is false

    you can either;

    • make your enum Type have string values;
    enum Type {
      Hardware = "0",
      Software = "1"
    }
    
    • or, convert type.key to number while binding to mat-option
    [value]="+type.key"
    
    • or, use equality (==) instead of strict equality (===) in compareType function. (i personally do not recommend this one!)
      compareType(type1: Type, type2: Type): boolean {
        return ((type1 as Type) == (type2 as Type));
      }
    

    here is a working demo