javascripthtmlangularangular-materialmat-autocomplete

Angular Mat AutoComplete doesn't retain mat-psuedo-checbox on the selected option when selected Programatically


When using Angular Mat Auto complete and the value to the auto complete is set programmatically, the selected option won't show the selected tick mark beside the selected option

I'm using Angular V15 and Angular Material V15

Stackblitz Link

Thanks in Advance!


Solution

  • This is a small issue which is easily replicable on the examples.

    Would suggest a small workaround. When you programmatically update the value, then you can fire a function. This will find the correct option then call the method _selectViaInteraction which will trigger the selection as well as show the checkbox.

    ngAfterViewInit() {
      this.triggerProgrammaticSelection();
    }
    
    triggerProgrammaticSelection() {
      const formValue = this.stateForm.get('stateGroupFiltered')!.value;
      const foundOption = this.autoComplete.options.find(
        (matOption: MatOption) => matOption.value === formValue
      );
      if (foundOption) {
        foundOption._selectViaInteraction();
      }
    }
    

    Github issue which helped to solve

    Full Code:

    TS:

    import { Component, OnInit, ViewChild } from '@angular/core';
    import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
    import { Observable } from 'rxjs';
    import { startWith, map } from 'rxjs/operators';
    import { NgFor, AsyncPipe } from '@angular/common';
    import {
      MatAutocomplete,
      MatAutocompleteModule,
    } from '@angular/material/autocomplete';
    import { MatInputModule } from '@angular/material/input';
    import { MatFormFieldModule } from '@angular/material/form-field';
    import { MatOption } from '@angular/material/core';
    
    export interface StateGroup {
      letter: string;
      names: string[];
    }
    
    export const _filter = (opt: string[], value: string): string[] => {
      const filterValue = value.toLowerCase();
    
      return opt.filter((item) => item.toLowerCase().includes(filterValue));
    };
    
    /**
     * @title Option groups autocomplete
     */
    @Component({
      selector: 'autocomplete-optgroup-example',
      templateUrl: 'autocomplete-optgroup-example.html',
      standalone: true,
      imports: [
        FormsModule,
        ReactiveFormsModule,
        MatFormFieldModule,
        MatInputModule,
        MatAutocompleteModule,
        NgFor,
        AsyncPipe,
      ],
    })
    export class AutocompleteOptgroupExample implements OnInit {
      @ViewChild(MatAutocomplete) autoComplete!: MatAutocomplete;
      stateForm = this._formBuilder.group({
        stateGroupFiltered: 'Alaska',
        stateGroupNotFiltered: 'Alaska',
      });
      trackByFn = (_: number, item: StateGroup): string => {
        return item.letter;
      };
      stateGroups: StateGroup[] = [
        {
          letter: 'A',
          names: ['Alabama', 'Alaska', 'Arizona', 'Arkansas'],
        },
        {
          letter: 'C',
          names: ['California', 'Colorado', 'Connecticut'],
        },
        {
          letter: 'D',
          names: ['Delaware'],
        },
        {
          letter: 'F',
          names: ['Florida'],
        },
        {
          letter: 'G',
          names: ['Georgia'],
        },
        {
          letter: 'H',
          names: ['Hawaii'],
        },
        {
          letter: 'I',
          names: ['Idaho', 'Illinois', 'Indiana', 'Iowa'],
        },
        {
          letter: 'K',
          names: ['Kansas', 'Kentucky'],
        },
        {
          letter: 'L',
          names: ['Louisiana'],
        },
        {
          letter: 'M',
          names: [
            'Maine',
            'Maryland',
            'Massachusetts',
            'Michigan',
            'Minnesota',
            'Mississippi',
            'Missouri',
            'Montana',
          ],
        },
        {
          letter: 'N',
          names: [
            'Nebraska',
            'Nevada',
            'New Hampshire',
            'New Jersey',
            'New Mexico',
            'New York',
            'North Carolina',
            'North Dakota',
          ],
        },
        {
          letter: 'O',
          names: ['Ohio', 'Oklahoma', 'Oregon'],
        },
        {
          letter: 'P',
          names: ['Pennsylvania'],
        },
        {
          letter: 'R',
          names: ['Rhode Island'],
        },
        {
          letter: 'S',
          names: ['South Carolina', 'South Dakota'],
        },
        {
          letter: 'T',
          names: ['Tennessee', 'Texas'],
        },
        {
          letter: 'U',
          names: ['Utah'],
        },
        {
          letter: 'V',
          names: ['Vermont', 'Virginia'],
        },
        {
          letter: 'W',
          names: ['Washington', 'West Virginia', 'Wisconsin', 'Wyoming'],
        },
      ];
    
      stateGroupOptions: Observable<StateGroup[]>;
    
      constructor(private _formBuilder: FormBuilder) {}
    
      ngAfterViewInit() {
        this.triggerProgrammaticSelection();
      }
    
      triggerProgrammaticSelection() {
        const formValue = this.stateForm.get('stateGroupFiltered')!.value;
        const foundOption = this.autoComplete.options.find(
          (matOption: MatOption) => matOption.value === formValue
        );
        if (foundOption) {
          foundOption._selectViaInteraction();
        }
      }
    
      ngOnInit() {
        this.stateGroupOptions = this.stateForm
          .get('stateGroupFiltered')!
          .valueChanges.pipe(
            startWith(''),
            map((value) => this._filterGroup(value || ''))
          );
      }
    
      private _filterGroup(value: string): StateGroup[] {
        if (value) {
          return this.stateGroups
            .map((group) => ({
              letter: group.letter,
              names: _filter(group.names, value),
            }))
            .filter((group) => group.names.length > 0);
        }
    
        return this.stateGroups;
      }
    }
    

    HTML:

    <form [formGroup]="stateForm">
      <mat-form-field>
        <mat-label>Filtered States Group</mat-label>
        <input
          type="text"
          matInput
          formControlName="stateGroupFiltered"
          required
          [matAutocomplete]="autoGroup"
        />
        <!-- #docregion mat-autocomplete -->
        <mat-autocomplete #autoGroup="matAutocomplete">
          <mat-optgroup
            [label]="group.letter"
            *ngFor="let group of stateGroupOptions | async; trackBy: trackByFn"
          >
            <mat-option *ngFor="let name of group.names" [value]="name">
              {{name}}
            </mat-option>
          </mat-optgroup>
        </mat-autocomplete>
        <!-- #enddocregion mat-autocomplete -->
      </mat-form-field>
      <br />
      <mat-form-field>
        <mat-label>Not Filtered States Group</mat-label>
        <input
          type="text"
          matInput
          formControlName="stateGroupNotFiltered"
          required
          [matAutocomplete]="autoGroup2"
        />
        <!-- #docregion mat-autocomplete -->
        <mat-autocomplete #autoGroup2="matAutocomplete">
          <mat-optgroup *ngFor="let group of stateGroups" [label]="group.letter">
            <mat-option *ngFor="let name of group.names" [value]="name">
              {{name}}
            </mat-option>
          </mat-optgroup>
        </mat-autocomplete>
        <!-- #enddocregion mat-autocomplete -->
      </mat-form-field>
    </form>
    

    Stackblitz Demo