Search code examples
angularangular-materialangular-reactive-formsformarray

Dynamically set mat-select-options when using ngx-mat-select-search in formArray in angular 8


I have a formArray which contains two mat-selects. One of them uses ngx-mat-select-search to search from the values in the options of the mat-select. My problem is that when the form array contains multiple elements, and I search in one of the mat-select from these elements, the value from all the mat-selects disappears while I'm searching and it reappears after a value has been selected. Here is the portion of my template file:

<ng-container formArrayName='cpUserAccounts'
                        *ngFor='let account of cpUserAccounts.controls; let i=index'>
                        <div class="d-flex align-items-center flex-wrap col-md-12" [formGroupName]='i'>
                            <div class="col-sm-6">
                                <mat-form-field class="task-array">
                                    <mat-label>Client</mat-label>
                                    <mat-select formControlName="clientId" required>
                                        <mat-option>
                                            <ngx-mat-select-search [formControl]="bankFilterCtrl"
                                                placeholderLabel='Search' noEntriesFoundLabel="'No match found'">
                                            </ngx-mat-select-search>
                                        </mat-option>
                                        <mat-option *ngFor="let client of filteredOptions | async" [value]="client.id">
                                            {{client.companyName}}
                                        </mat-option>
                                    </mat-select>
                                    <mat-hint class="error"
                                        *ngIf="findDuplicate(account.controls.clientId.value, 'cpUserAccounts')">
                                        {{constants.errorMessage.duplicateClientLabel}}
                                    </mat-hint>
                                </mat-form-field>
                            </div>
                            <div class="col-sm-4">
                                <mat-form-field class="task-array">
                                    <mat-label>Role</mat-label>
                                    <mat-select formControlName="roleId" required>
                                        <mat-option *ngFor="let role of accountRoles" [value]="role.id">
                                            {{role.name}}
                                        </mat-option>
                                    </mat-select>
                                </mat-form-field>
                            </div>
                            <div class="col-sm-2 d-flex justify-content-end">
                                <mat-icon class="remove-task-button" title="Remove" (click)='removeAccount(i)'
                                    matSuffix>
                                    remove
                                </mat-icon>
                            </div>
                        </div>
                    </ng-container>

and the .ts file contains the following code:

filteredOptions: ReplaySubject<ClientModel[]> = new ReplaySubject<ClientModel[]>(1);
public bankFilterCtrl: FormControl = new FormControl();
protected onDestroy = new Subject<void>();

ngOnInit() {
      this.bankFilterCtrl.valueChanges
      .pipe(takeUntil(this.onDestroy))
      .subscribe(() => {
      this.filterBanks();
            });
    }

getClients() {
        const subscription = this.clientContactService.getClients().subscribe(clients => {
            this.clients = clients.data;
            this.filterBanks();
        });
        this.subscription.push(subscription);
    }

protected filterBanks() {
        if (!this.clients) {
            return;
        }
        // get the search keyword
        let search = this.bankFilterCtrl.value;
        if (!search) {
            this.filteredOptions.next(this.clients.slice());
            return;
        } else {
            search = search.toLowerCase();
        }
        // filter the clients
        this.filteredOptions.next(
            this.clients.filter(client => client.companyName.toLowerCase().indexOf(search) > -1)
        );
   }

Here are the images of the flow of what is happening: In the starting, it is like this: enter image description here

Then:

enter image description here

The problem is here:

enter image description here


Solution

  • since you are using the filteredOptions | async element whithin *ngFor, the same options will be used for each "client"-select element displayed. Hence when filtering, it will filter the options of all client select boxes, and thus the originally selected value is not available anymore.

    To resolve this, you could move the content of the outermost *ngFor to a separate component, which should implement the ControlValueAccessor interface (https://angular.io/api/forms/ControlValueAccessor#description).