angularangular-components

How can I use multiple instance of ngx-mat-select-search without checking for input array length?


I made a reusable ngx-mat-select-search component which looks like this

mat-tooltip-select-all.component.ts

export class MatTooltipSelectAllComponent implements OnInit, AfterViewInit, OnDestroy {

  @Input() placeHolderName: string;

  @Input() isDate: boolean = false;

  /** emit an event when a new option is selected */
  @Output() newOptionSelected = new EventEmitter<SelectOption[]>();

  /** control for the selected option for multi-selection */
  public multiCtrl: FormControl<SelectOption[]> = new FormControl<SelectOption[]>([]);

  /** control for the MatSelect filter keyword multi-selection */
  public multiFilterCtrl: FormControl<string> = new FormControl<string>('');

  /** list of selectOptions filtered by search keyword */
  public filteredMulti: ReplaySubject<SelectOption[]> = new ReplaySubject<SelectOption[]>(1);

  public tooltipMessage = 'Select All / Unselect All';

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

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

  @Input() set selectOptions(selectOptions: SelectOption[]) {
    this._selectOptions = selectOptions;
    // load the initial option list
    this.filteredMulti.next(this.selectOptions.slice());
  }
  get selectOptions(): SelectOption[] {
    return this._selectOptions;
  }

  private _selectOptions: SelectOption[];

  constructor() { }

  ngOnInit() {
    // set initial selection to all
    // this.multiCtrl.setValue(this._selectOptions.slice());

    // listen for select option value changes
    this.multiCtrl.valueChanges
      .pipe(takeUntil(this._onDestroy))
      .subscribe(() => {
        this.newOptionSelected.emit(this.multiCtrl.value);
      });    

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

I use it multiple times on parent component but for some reason the data seems to be loaded only on the first instance of ngx-mat-select-search. For example

               //pdoNames is loaded
                <app-mat-tooltip-select-all
                  #pdoNameFilter
                  class="col-md-6"
                  (newOptionSelected)="handleFilter($event, Filter.PDOName)"
                  [selectOptions]="pdoNames"
                  placeHolderName="PDO Name"
                ></app-mat-tooltip-select-all>
                //beneficiaryClassifications is not loaded/filtered list  is empty
                <app-mat-tooltip-select-all
                  #beneficiaryFilter
                  class="col-md-6"
                  (newOptionSelected)="handleFilter($event, Filter.BeneficiaryClassification)"
                  [selectOptions]="beneficiaryClassifications"
                  placeHolderName="Beneficiary Classification"
                ></app-mat-tooltip-select-all>

Data is fetch in parent component like this

parent component ts

  ngOnInit(): void {
    this.reportService
      .getFundingNeedsSummary()
      .pipe(take(1))
      .subscribe((response: any) => {
      // sets pdoNames and beneficiaryClassifications here
      });
  }

I have to check option length like *ngIf="beneficiaryClassifications?.length > 0" on subsequent instances to make it work. But the problem with this approach is it won't display the component if option length is empty or 0.

How could I make this work without checking option length or make the component display even if option length is 0? I tried using *ngIf="beneficiaryClassifications?.length == 0 || beneficiaryClassifications?.length > 0" but it won't load data/filter options becomes empty.

Full reproducible code: https://stackblitz.com/edit/github-qartsv?file=src%2Fapp%2Fapp.component.html


Solution

  • We need to update the reference of an array, when we do the push operation, since the contents are changed, but the memory location stays the same.

    Angular thinks nothing has changed, hence you are not able to view the list when you only do a push operation.

    When we do array destructuring, (this.clientType = [...this.clientType]) the array has a new memory reference, so angular runs change detection on the @Input and makes the list visible.

    populateFilters() {
        this.tableData.forEach((pdo) => {
          const clientType = {
            id: pdo?.client_type ? pdo?.client_type.id : 'null',
            name: pdo?.client_type ? pdo?.client_type.name : 'null',
          };
          if (!this.isExisting(clientType, this.clientType)) {
            this.clientType.push(clientType);
          }
        });
        this.clientType = [...this.clientType]; // <- changed here!
      }
    

    Stackblitz Demo