26

I have an array that comes from my API and I'm using Material2#AutoComplete to filter this... it's working so far, however I'm in trouble to display the another property instead of the binded value in option.

I know I have to use displayWith, however it isn't working as I'm expecting. The function called as [displayWith]="displayFn.bind(this)"> just returns me the id, how can I get the full object and so return the name on function.

BTW, I still want to have the id binded in my FormControl.

Some code:

Component:

export class AutocompleteOverviewExample {
  stateCtrl: FormControl;
  filteredStates: any;

  states = [
    { 
      id: 1,
      name: 'Alabama'
    },
    {
      id: 2,
      name: 'North Dakota'
    },
    {
      id: 3,
      name: 'Mississippi'
    }
  ];

  constructor() {
    this.stateCtrl = new FormControl();
    this.filteredStates = this.filterStates('');
  }

  onKeyUp(event: Event): void {
    this.filteredStates = this.filterStates(event.target.value);
  }

  filterStates(val: string): Observable<any> {
    let arr: any[];
    console.log(val)
    if (val) {
      arr = this.states.filter(s => new RegExp(`^${val}`, 'gi').test(s.name));
    } else {
      arr = this.states;
    }

    // Simulates request
    return Observable.of(arr);
  }

  displayFn(value) {
    // I want to get the full object and display the name
    return value;
  }
}

Template:

<md-input-container>
  <input mdInput placeholder="State" (keyup)="onKeyUp($event)" [mdAutocomplete]="auto" [formControl]="stateCtrl">
</md-input-container>

<md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn.bind(this)">
  <md-option *ngFor="let state of filteredStates | async" [value]="state.id">
    {{ state.name }}
  </md-option>
</md-autocomplete>

Basically, it's almost the same as this question (unfortunately both answers are incorrect or throw errors).

Here's the PLUNKER.

Stefan Falk
  • 23,898
  • 50
  • 191
  • 378
dev_054
  • 3,448
  • 8
  • 29
  • 56

1 Answers1

37

If you want the entire object to be binded with md-options, then you should bind to option with state and return state.name at displayFn and this way you don't have to bind this.

<md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn">
  <md-option *ngFor="let state of filteredStates | async" [value]="state">
    {{ state.name }}
  </md-option>
</md-autocomplete>

displayFn(state) {
  return state.name;
}

demo plunker.


and if you want to bind only state.id to md-options, you have to loop through states to find state.name based on state.id and this way binding this is needed.

<md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn.bind(this)">
  <md-option *ngFor="let state of filteredStates | async" [value]="state.id">
    {{ state.name }}
  </md-option>
</md-autocomplete>

displayFn(id) {
  if (!id) return '';

  let index = this.states.findIndex(state => state.id === id);
  return this.states[index].name;
}

demo plunker.

Pengyy
  • 37,383
  • 15
  • 83
  • 73
  • Thanks for your answer. Well, as I specified in question *"BTW, I still want to have the id binded in my FormControl."*, the **1st** option isn't what I want... I don't want to bind the full `object` (because back-end expects the `id`) to be sent in my form. And about the 2nd approach: It doesn't fill want I'm trying to achieve also... in plunker I used `states` hardcoded that way only for the purpose of demonstrating... in my application I have like 10 autocomplete all of them are `Observable`, I didn't subscribe them (and I don't want to), so I don't have the values that way. – dev_054 May 29 '17 at 13:21
  • @dev_054 some how complex than I imagined. according to your comments, maybe the 1st option is much fitter here for you can simply retrieve `satet.id`. :-) – Pengyy May 29 '17 at 13:33
  • Well, the problem here is that the `[displayWith]` is almost inflexible... I'll have to choice one of the 2 ways (both are workarounds for me)... anyway thanks. – dev_054 May 29 '17 at 13:50
  • @dev_054 agreed! glad to help :-) – Pengyy May 29 '17 at 13:54
  • 1
    Btw, I just created an [**issue**](https://github.com/angular/material2/issues/4863) right now. – dev_054 May 29 '17 at 13:55
  • 1
    @pengyy i am trying to load the autocomplete values from server before retrieving it's executing displaywith function is there any way to load function after retrieving – Vignesh Jun 12 '17 at 16:58
  • @Vignesh I have seen your comment(at another answer), sorry for I'm a little busy right now, will be back tomorrow. :-) – Pengyy Jun 13 '17 at 00:54
  • @Vignesh I have mocked autocomplete with loading json file and didn't get your problem. see https://plnkr.co/edit/puat2uhDDvjUbDgL8EjG?p=preview, hope I have got what you meant. If you have further problems, maybe you should post a new question. – Pengyy Jun 13 '17 at 11:05
  • sorry @Pengyy .Based on ngModel value need to load value in textbox. https://plnkr.co/edit/mxBHvaMpZWvm28E1DAKw?p=preview – Vignesh Jun 13 '17 at 11:47
  • @Pengyy before loading values from server the displaywith function gets executed check in plunker sample. – Vignesh Jun 13 '17 at 11:53
  • @Vignesh mmm... you have initialize `this.model` too early(before service got data back), you can check whether `states` have data or not, see fixed plunker https://plnkr.co/edit/kBZdT9rsKkm2X4oimjrh?p=preview. but still maybe `@angular/material` should do some improvements for this. – Pengyy Jun 13 '17 at 12:03
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146528/discussion-between-vignesh-and-pengyy). – Vignesh Jun 13 '17 at 12:09
  • 1
    Perhaps I'm missing something, but every one of the Plukr links in this article result in nothing more than a "Loading Material Docs example..." message (and a bunch of JavaScript errors in the Console). I'm genuinely not sure if my laptop's not set up correctly, or if these libraries have moved on, breaking all the existing code.... ;-( – Mike Gledhill Dec 13 '17 at 08:52
  • @MikeGledhill many things has changed since I answered this question, I'll fix the examples links when I get some time. – Pengyy Dec 15 '17 at 00:40
  • 10
    using `[displayWith]="displayFn.bind(this)"` "solution" works, but man do I feel dirty... – BlackICE Mar 05 '19 at 21:30
  • @BlackICE This is just another option in case you didn't bind the whole object to options of select and then you can access the component context to do the filter part. – Pengyy Mar 06 '19 at 01:47
  • I had to use `[displayWith]="displayFn.bind(this)">` today, but I also had to add "this" to the function like `displayFn(this, object:any) {` – Carl Kroeger Ihl Aug 08 '19 at 03:18
  • @BlackICE There's nothing "dirty" about using `bind()`. It's a core JavaScript functionality to manually set function context. ;-) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind – Thomas Ebert Jan 20 '21 at 18:03