Search code examples
javascriptangularmat-autocomplete

Using Angular's matAutocomplete, how do I display an object's property, but reference the object itself elsewhere?


For example I have the markup:


<input type="text" class="form-control" placeholder="Project #" name="project" [(ngModel)]="key" (ngModelChange)="filterProjects(key);"  matInput [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="setProject($event.option.value)">
  <mat-option *ngFor="let project of filtered" [value]="project.ProjNum">
    {{project.ProjNum}}
  </mat-option>
</mat-autocomplete>

<div class="input-group-append">
  <button class="btn btn-info" (click)="load(selectedProject)">Load</button>
</div>

When an option is selected, it calls the setProject() function, which sets the selectedProject used later for my Load button. However, as it currently functions, binding the value to [value]="project.ProjNum" will call setProject with the project number. While it would seem intuitive to set my value to [value]="project" (and this will set my selectedProject to the project object), it will now show [object Object] in my input field.

How do I modify this so I can directly reference the project object outside of my options, and not just the selected and displayed property?

Note: I know I could just use the ProjNum to filter through my list of projects to find the correct one and then set my selectedProject, but I would rather not waste resources cycling through a list when I already have the object I want.


Solution

  • You want to use the displayWith function. It allows you to define a function that returns the value in which you want to be displayed

    From the docs:

    <form class="example-form">
      <mat-form-field class="example-full-width">
        <input type="text" placeholder="Assignee" aria-label="Assignee" matInput [formControl]="myControl" [matAutocomplete]="auto">
        <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
          <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
            {{option.name}}
          </mat-option>
        </mat-autocomplete>
      </mat-form-field>
    </form>
    
    
    
    import {Component, OnInit} from '@angular/core';
    import {FormControl} from '@angular/forms';
    import {Observable} from 'rxjs';
    import {map, startWith} from 'rxjs/operators';
    
    export interface User {
      name: string;
    }
    
    /**
     * @title Display value autocomplete
     */
    @Component({
      selector: 'autocomplete-display-example',
      templateUrl: 'autocomplete-display-example.html',
      styleUrls: ['autocomplete-display-example.css'],
    })
    export class AutocompleteDisplayExample implements OnInit {
      myControl = new FormControl();
      options: User[] = [
        {name: 'Mary'},
        {name: 'Shelley'},
        {name: 'Igor'}
      ];
      filteredOptions: Observable<User[]>;
    
      ngOnInit() {
        this.filteredOptions = this.myControl.valueChanges
          .pipe(
            startWith(''),
            map(value => typeof value === 'string' ? value : value.name),
            map(name => name ? this._filter(name) : this.options.slice())
          );
      }
    
      displayFn(user?: User): string | undefined {
        return user ? user.name : undefined;
      }
    
      private _filter(name: string): User[] {
        const filterValue = name.toLowerCase();
    
        return this.options.filter(option => option.name.toLowerCase().indexOf(filterValue) === 0);
      }
    }