Search code examples
angular-material2

Autocomplete - select option programmatically


I am having an issue when trying to programmatically select an option in an autocomplete.

MatAutocomplete has no select method, so I tried using the select method on the MatOption. This does not appear to do anything.

matAutocomplete.options.find(opt => opt.id === 1).select();

Using the Autocompelete _emitSelectEvent(option) method results in firing the optionSelected method but does not update the UI or actually set the option to be selected.

matAutocomplete._emitSelectEvent(option);

Is there a way to programmatically select an option so that it updates the UI and calls the optionSelected event emitter?

<input [matAutocomplete]="autocomplete1" />
<mat-autocomplete #autocomplete1="matAutocomplete" [displayWith]="display1()" (optionSelected)="selected1($event.option.value)">
    <mat-option *ngFor=let opt of filteredOptions | async" [value]="opt">
        {{ opt.name }}
    </mat-option>
</mat-autocomplete>

<input [matAutocomplete]="autocomplete2" />
<mat-autocomplete #autocomplete2="matAutocomplete" [displayWith]="display2()" (optionSelected)="selected2($event.option.value)">
    <mat-option *ngFor="let opt of filteredOptions2 | async" [value]=opt>
        {{ opt.name }}
    </mat-option>
</mat-autocomplete>

    export class obj {
        public id: number;
        public name: string;
    }

    @ViewChild("autocomplete2") autocomplete2: MatAutocomplete;
    
    selected1(value: obj): void {
        const opt = this.autocomplete2.options.find(opt => (opt.value as obj).id === 1);

        // Does nothing
        opt.select();

        // Fires optionSelected on autocomplete2, does not update the UI
        opt._selectViaInteraction();

        // Fires optionSelected on autocomplete2,
        // does not set the option to be selected or update the UI
        this.autocomplete2._emitSelectEvent(opt);
    }

I am using Angular & Material version 5.2.4


Solution

  • I was able to solve this issue by setting the value of the input2 with the full object that I wanted to select from the second autocomplete.

    This would then be displayed in the input using the displayWith function set in the mat-autocomplete.

    <input [matAutocomplete]="autocomplete1" [formControl]="input1" />
    <mat-autocomplete #autocomplete1="matAutocomplete" (optionSelected)="selected1($event.option.value) [displayWith]="display1()">
        <mat-option *ngFor="let opt of options1" [value]="opt">
            {{ opt.name }}
        </mat-option>
    </mat-autocomplete>
    
    <input [matAutocomplete]="autocomplete2" [formControl]="input2" />
    <mat-autocomplete #autocomplete2="matAutocomplete" (optionSelected)="selected2($event.option.value) [displayWith]="display2()">
        <mat-option *ngFor="let opt of options2" [value]="opt">
            {{ opt.name }}
        </mat-option>
    </mat-autocomplete>
    

    export class Obj {
        public id: number;
        public name: string;
    }
    
    let randomObj: Obj = new Obj();
    
    input2: FormControl = new FormControl();
    select1(value: Obj): void {
        this.input2.setValue(this.randomObj);
    }
    

    You do not need to go into the autocomplete and get the specific option that you want, you can just set anything to the value that can then be displayed with the displayWith function.

    For example, if you displayWith function is as follows:

    displayWith(value: Obj): string {
        return value.name;
    }
    

    if you set the value of the input with any object that has a name property then it will display as the text in the input.

    input.setValue({ name: "HelloWorld" });
    

    Even though the object passed into the setValue method is not of the type Obj it will still work with the displayWith function. If you set the value to an object that cannot be displayed using the displayWith function then all that happens is that the text in the input will be blank (whilst the value of the input is still set to the object set).

    so input.setValue({ foo: "bar" }) will display nothing in the textbox but input.value will be { foo: "bar" }