1

I am building a generic selector component with Angular Material. When setting the value manually it works ok but, if I update form value from typescript, the view does not reflect that change.

selector.component.html

<mat-form-field appearance="standard">
  <mat-label>{{ label }}</mat-label>
  <mat-select
    [formControl]="formControl"
    (valueChange)="onChange($event)"
    multiple
  >
    <mat-select-trigger>
      {{ !!selectedData.length ? selectedData[0][elementLabel] : '' }}
      <span *ngIf="selectedData.length > 1" class="additional-selection">
        (+{{ selectedData.length - 1 }}
        {{ selectedData?.length === 2 ? 'other' : 'others' }})
      </span>
    </mat-select-trigger>
    <mat-option
      *ngFor="let element of data; trackBy: trackByFunction"
      [value]="element"
      >{{ element[elementLabel] }}</mat-option
    >
  </mat-select>
</mat-form-field>

selector.component.ts

import {
  ChangeDetectionStrategy,
  Component,
  EventEmitter,
  Input,
  Output,
} from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'selector',
  templateUrl: './selector.component.html',
  styleUrls: ['./selector.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectorComponent {
  @Input() data: any[] = [];
  @Input() set init(value: any[]) {
    this.selectedData = value;
  }
  @Input() label: string = '';
  @Input() elementLabel: string = '';
  @Output() onChangeEvent: EventEmitter<any[]> = new EventEmitter();
  selectionDisplayText = '';
  formControl = new FormControl([]);

  get selectedData(): any[] {
    return this.formControl.value;
  }

  set selectedData(data: any[]) {
    this.formControl.setValue(data);
  }

  trackByFunction(item: any): string {
    return item[this.elementLabel];
  }

  onChange = (data: any[]) => {
    this.selectedData = data;
    this.onChangeEvent.emit(this.selectedData);
  };
}

When I try to update select values from parent component (using init input), the code reaches set selectedData method and formControl value is set OK, but the view displays an empty select. What am I missing?

Thanks

adrisons
  • 3,443
  • 3
  • 32
  • 48

1 Answers1

0

I've found a solution.

Instead of using formControl (which complicated things) I used ngModel to set the mat-select data.

<mat-select
    (valueChange)="onChange($event)"
    [ngModel]="selection"
    [compareWith]="compareFn"
    multiple
>

My problem was that mat-select compares objects by reference (see this answer), so I needed to add the compareWith function to compare based on property. In my case:

compareFn = (obj1: any, obj2: any) => {
    return obj1[this.elementLabel] === obj2[this.elementLabel];
};

And update the onChange function so selection is updated:

onChange = (data: any[]) => {
  const value = this.transform(data);
  this.selection = value;
  this.onChangeEvent.emit(value);
};
adrisons
  • 3,443
  • 3
  • 32
  • 48