angulartypescriptangular-materialmat-autocomplete

Make second mat-autocomplete update with filtered data


I've got an Angular Material table with a DataSource set, and am using Material Table Filter to filter the data. Instead of regular inputs, I've got a couple material autocompletes that populate their options from a source of unique values from the table. On their own, these autocompletes work fine - if you search for a name in one, the options filter down to what is available in the table. The issue arises with multiple autocompletes, as I can't find a way to make the second autocomplete update its options with the current filtered data and not show every value. It should only display options from the current filtered data.

Here's one of my autocompletes:

                <!-- Filter by Course Code -->
            <label for="courseCode">Course Code</label>
            <input type="text"
                id="courseCode"
                [matAutocomplete]="auto1"
                [formControl]="filterCourseCodesControl"
                [(ngModel)]="filterEntity.courseCode"
                #filterCourseCode
                >
            <mat-autocomplete #auto1="matAutocomplete" >
            <mat-option [value]="item" *ngFor="let item of filteredCodes | async" [value]="item">
                {{item}}
            </mat-option>
            </mat-autocomplete>

filteredCodes in the *ngFor is an observable: filteredCodes: Observable<string[]>; and the second autocomplete is using it's own list filteredClaimedBy: Observable<string[]>;.

OnInit I'm getting a list of unique codes/claimedBy's from a service, which is being used here:

    this.filteredCodes = this.filterCourseCodesControl.valueChanges.pipe(
     startWith(''),
     map(value => this._filterCodes(value))
    );

  private _filterCodes(value: string): string[] {
    const filterValue = this._normalizeValue(value);
    return this.uniqueCodes.filter(item => this._normalizeValue(item).includes(filterValue));
  }

I believe that's everything; the second autocomplete is using similar functions as the first, with the uniqueCodes changing to uniqueClaimedBy, etc.

I have looked at the [displayWith] directive as a way to possibly make the inputs update, but for one, couldn't figure out exactly how to use it, and even using that, I don't know that it would make the other autocompletes update their values when the first changes, and vice versa.


Solution

  • Once again I find myself answering my own question. This may not be the cleanest way of doing this, but I was able to get all of the autocompletes to restrict themselves to the data from the filtered table. Here's what I did:

    First, on each input that does filtering, I added a function on (keyup) and (click) of the input and mat-option respecively.

          <div>
            <!-- Filter by Course Code -->
            <label for="courseCode">Course Code</label>
            <input type="text"
                name="courseCode"
                id="courseCode"
                [matAutocomplete]="auto1"
                [formControl]="filterCourseCodesControl"
                [(ngModel)]="filterEntity.courseCode"
                (keyup)="updateAutocomplete()"
                >
            <mat-autocomplete #auto1="matAutocomplete" >
            <mat-option (click)="updateAutocomplete()" [value]="item" *ngFor="let item of filteredCodes | async" [value]="item">
                {{item}}
            </mat-option>
            </mat-autocomplete>
        </div> 
    

    This function creates a new array to hold the new possible values from the filtered table, waits a full second for the dataSource.filteredData to be populated with the correct items, filters the resultant data to be only unique, and then calls updateCodes.

     updateAutocomplete(){
        let newCodes: string[]  = [];
    
        setTimeout(() => {
          this.dataSource.filteredData.forEach(e => {
            newCodes.push(e.courseCode.toString());
          });
    
          newCodes = newCodes.filter(this.onlyUnique);
          
          this.updateCodes(newCodes);
    
          
        }, 1000);
      }
    

    Update codes simply takes updates the pipe valueChanges data source from being the full list of unique items to the shorter, filtered list of the possible values.

    updateCodes(source){
    this.filteredCodes = this.filterCourseCodesControl.
    valueChanges.pipe(startWith(''), map(value => this._filterValues(value, source)));
      }
    

    Do this same flow for each input, and all your autocompletes now restrict themselves to the filtered data and not the complete list.