Why does ngx-mat-select-search not work with my dynamic data?

Asked

Viewed 200 times

1

I installed the ngx-mat-select-search and followed the example of Stackblitz.

The example worked perfectly, just as it is in the stackblitz. After that I changed my code to load the data dynamically. I loaded the data normally and the values are listed in the UI, but the filter bar does nothing and no error occurs.

HTML:

<mat-form-field class="selectList-full-width">
    <mat-select [formControl]="selectTechnicalRoomCtrl" [placeholder]="'SelectTechnicalRoom' | localize" #singleSelect>
        <mat-option>
        <ngx-mat-select-search [formControl]="selectTechnicalRoomFilterCtrl" [placeholderLabel]="'Search' | localize"></ngx-mat-select-search>
        </mat-option>
        <mat-option *ngFor="let technicalRoom of technicalRooms" [value]="technicalRoom">
        {{technicalRoom.nameRoom}}
        </mat-option>
    </mat-select>
</mat-form-field>
<div class="p-r-25 btn-toolbar">
    <button style="margin-left: 10px; padding: 0 1em;" mat-raised-button color="primary" >{{ 'ListPoints' | localize }}</button>    
</div>

TS:

export class HomeComponent extends AppComponentBase implements OnInit, AfterViewInit, OnDestroy {
    technicalRooms: IdNameTechnicalRoomDto[] = [];
    public selectTechnicalRoomCtrl: FormControl = new FormControl();
    public selectTechnicalRoomFilterCtrl: FormControl = new FormControl();

    /** list of itens filtered by search keyword */
    public filteredTechnicalRooms: ReplaySubject<IdNameTechnicalRoomDto[]> = new ReplaySubject<IdNameTechnicalRoomDto[]>(1);

    @ViewChild('singleSelect', { static: true }) singleSelect: MatSelect;

    protected _onDestroy = new Subject<void>();

    constructor(
        injector: Injector,
        private _technicalRoomsService: TechnicalRoomServiceProxy,
    ) {
        super(injector);
    }

    ngOnInit() {
      this.list();

      // set initial selection
    this.selectTechnicalRoomCtrl.setValue(this.technicalRooms[10]);

    // load the initial itens list
    this.filteredTechnicalRooms.next(this.technicalRooms.slice());

    // listen for search field value changes
    this.selectTechnicalRoomFilterCtrl.valueChanges
      .pipe(takeUntil(this._onDestroy))
      .subscribe(() => {
        this.filterTechnicalRooms();
      });
      }

      list(): void {

          this._technicalRoomsService
              .getList()
              .pipe()
              .subscribe(
                data => this.technicalRooms = data["result"]
              );
      }

      ngAfterViewInit() {
        this.setInitialValue();
      }

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

      /**
       * Sets the initial value after the filteredTechnicalRooms are loaded initially
       */
      protected setInitialValue() {
        this.filteredTechnicalRooms
          .pipe(take(1), takeUntil(this._onDestroy))
          .subscribe(() => {
            this.singleSelect.compareWith = (a: IdNameTechnicalRoomDto, b: IdNameTechnicalRoomDto) => a && b && a.id === b.id;
          });
      }

      protected filterTechnicalRooms() {
        if (!this.technicalRooms) {
          return;
        }
        // get the search keyword
        let search = this.selectTechnicalRoomFilterCtrl.value;
        if (!search) {
          this.filteredTechnicalRooms.next(this.technicalRooms.slice());
          return;
        } else {
          search = search.toLowerCase();
        }
        // filter the technicalRooms
        this.filteredTechnicalRooms.next(
          this.technicalRooms.filter(technicalRoom => technicalRoom.nameRoom.toLowerCase().indexOf(search) > -1)
        );
      }

}
  • places a ngif technicalRooms to wait for it to load

  • @Eduardovargas, I did what you suggested and made the mistake Cannot set property 'compareWith' of undefined

  • vc fez *ngIf="technicalRooms"?

  • Yes, that’s right. I’ve already solved, if no one answers I put my solution here.

1 answer

0


I’ll share the solution here that I got along with Eduardo Vargas' tip in the comments.

I was able to solve my problem by following the instructions of macjohnny, of that Raid on Github.

Briefly, the problem was that I needed to load my filtered list before setting it on input.

the solution given in Issue is this:

@Input() set data(data: any[]) {
  this._data = data;
  // load the initial entity list
  this.filteredEntities.next(this.data.slice());
}
get data(): any[] {
  return this._data;
}
private _data: any[];

But before I created a component at the angle, according to the author’s idea Issue, for whenever I need to use it. I called this component select-search.

The complete solution was like this:

select-search.component.html

<mat-form-field *ngIf="entityFilterCtrl">
    <mat-select [formControl]="entityCtrl" #singleSelect (selectionChange)="onChange($event)">
        <ngx-mat-select-search 
            [formControl]="entityFilterCtrl" 
            [placeholderLabel]="'Search'"
            [noEntriesFoundLabel]="'Not found'">
        </ngx-mat-select-search>
        <mat-option *ngFor="let entity of filteredEntities | async" [value]="entity.url">{{entity.name}}</mat-option>
    </mat-select>
</mat-form-field>

select-search.component.ts

import { Component, OnInit, ViewChild, Input, EventEmitter, Output } from '@angular/core';
import { MatSelect } from '@angular/material';
import { ReplaySubject, Subject } from 'rxjs';
import { FormControl } from '@angular/forms';
import { takeUntil, take } from 'rxjs/operators';

@Component({
  selector: 'app-select-search',
  templateUrl: './select-search.component.html',
  styleUrls: ['./select-search.component.css']
})
export class SelectSearchComponent implements OnInit {

  /** control for the selected entity */
  public entityCtrl: FormControl = new FormControl();

  /** control for the MatSelect filter keyword */
  public entityFilterCtrl: FormControl = new FormControl();

  /** list of entities filtered by search keyword */
  public filteredEntities: ReplaySubject<any[]> = new ReplaySubject<any[]>(1);

  @ViewChild('singleSelect', { static: true }) singleSelect: MatSelect;

  /** Subject that emits when the component has been destroyed. */
  private _onDestroy = new Subject<void>();

  @Input() set data(data: any[]) {
    this._data = data;
    // load the initial entity list
    this.filteredEntities.next(this.data.slice());
  }
  get data(): any[] {
    return this._data;
  }
  private _data: any[];

  @Output() onSelectionChange: EventEmitter<any> = new EventEmitter<any>();

  constructor() { }

  ngOnInit(): void {
    // listen for search field value changes
    this.entityFilterCtrl.valueChanges
      .pipe(takeUntil(this._onDestroy))
      .subscribe(() => {
        this.filterEntities();
      });

  }

  ngAfterViewInit(): void {
    this.setInitialValue();
  }

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

  onChange($event) {
    this.onSelectionChange.emit($event);
  }

  private setInitialValue() {
    this.filteredEntities
      .pipe(take(1), takeUntil(this._onDestroy))
      .subscribe(() => {
        // setting the compareWith property to a comparison function
        // triggers initializing the selection according to the initial value of
        // the form control (i.e. _initializeSelection())
        // this needs to be done after the filteredEntities are loaded initially
        // and after the mat-option elements are available
        if(this.singleSelect)
          this.singleSelect.compareWith = (a: any, b: any) => a.url === b.url;
      });
  }

  private filterEntities() {
    if (!this.data) {
      return;
    }
    // get the search keyword
    let search = this.entityFilterCtrl.value;
    if (!search) {
      this.filteredEntities.next(this.data.slice());
      return;
    } else {
      search = search.toLowerCase();
    }
    // filter the entitys
    this.filteredEntities.next(
      this.data.filter(entity => entity.name.toLowerCase().indexOf(search) > -1)
    );
  }

}

And to use it:

 <app-select-search class="selectList-full-width" [data]="pokemons"></app-select-search>

I put in my Github as an example and also imported to the Stackblitz to be able to see working.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.