Search code examples
htmlangulartypescriptangular-materialmat-select

Angular - Implement NgxMatSelectSearch in mat-select


I am trying to use a search box in a mat-select that works correctly only when using data loaded by default. I want to use data from an api. But it does not work properly, the data is not displayed in the mat-select when loading the page, but it is displayed when a focus occurs in the mat-select tag.

I have a model where I use the data from a test API

export interface DataModel {
  id: number;
  title: string;
  userId: number;
}

export const DataModels: DataModel[] = [
  { id: 1, title: 'Option A', userId: 23 },
  { id: 2, title: 'Option B', userId: 24 },
  { id: 3, title: 'Option C', userId: 25 },
  { id: 4, title: 'Option D', userId: 26 }
];

My service where I make the call

@Injectable()
export class DataloadService {
  constructor(private http: HttpClient) {}

  LoadData(): Observable<any> {
    return this.http.get('https://jsonplaceholder.typicode.com/albums');
  }
}

The component where the search filter is performed and controls are set. Following the documentation NgxMatSelectSearch

constructor(private service: DataloadService) {}
  dataModel: DataModel[] = []; //DataModels
  dataCtrl: FormControl = new FormControl();
  dataFilterCtrl: FormControl = new FormControl();
  filteredData: ReplaySubject<DataModel[]> = new ReplaySubject<DataModel[]>(1);
  @ViewChild('singleSelect', { static: true }) singleSelect: MatSelect;

  _onDestroy = new Subject<void>();

  ngOnInit() {
    this.load();
    this.filteredData.next(this.dataModel.slice());
    this.dataFilterCtrl.valueChanges
      .pipe(takeUntil(this._onDestroy))
      .subscribe(() => {
        this.filterData();
      });
  }

  ngOnDestroy() {
    this._onDestroy.next();
    this._onDestroy.complete();
  }

  filterData() {
    if (!this.dataModel) {
      return;
    }
    let search = this.dataFilterCtrl.value;
    if (!search) {
      this.filteredData.next(this.dataModel.slice());
      return;
    } else {
      search = search.toLowerCase();
    }
    this.filteredData.next(
      this.dataModel.filter(
        (x: any) => x.title.toLowerCase().indexOf(search) > -1
      )
    );
  }
  load() {
    return this.service.LoadData().subscribe(res => {
      this.dataModel = res;
    });
  }

And the HTML

<mat-card>
  <mat-toolbar>Demo</mat-toolbar><br />
  <mat-card-content>
    <mat-select [formControl]="dataCtrl" placeholder="Data" #singleSelect>
      <mat-option>
        <ngx-mat-select-search
          [formControl]="dataFilterCtrl"
        ></ngx-mat-select-search>
      </mat-option>

      <mat-option *ngFor="let x of filteredData | async" [value]="x.id">
        {{x.title}}
      </mat-option>
    </mat-select>
  </mat-card-content>
</mat-card>

If I use the data that is by default in the model to simulate the process using "dataModels"

dataModel: DataModel[] = []; //DataModels

Instead of initializing it empty. It works normally but if I load the data with the request made to the API, the problem arises that it is not loaded after a focus occurs.

The demo I have in Stackblitz: Demo Stackblitz


Solution

  • You should add this line

    this.filteredData.next(this.dataModel.slice());
    

    into subscribe event of this.service.LoadData() as it is asynchronous. So that when the response result is returned, the filteredData is bonded with the response result.

    load() {
      return this.service.LoadData().subscribe(res => {
        this.dataModel = res;
        this.filteredData.next(this.dataModel.slice());
      });
    }
    

    Sample Solution on StackBlitz