Angular Material Autocomplete and API Objects

1.3k views Asked by At

I'm fighting with Material's Autocomplete for several days. I've used tons of examples from here, and still can't get it work. What I want to achieve is get Users from API, load them to autocomplete as the name should be visible but I want a value as object. This is what I've got till now:

***API Service:***

export class UserService {
  constructor(private httpClient: HttpClient) {}

  getData() {
    return this.httpClient.get<any>('https://jsonplaceholder.typicode.com/users');
  }
}

***component.ts***

constructor(private usc: BreweryService, private formBuilder: FormBuilder) { 
    this.users = this.usc.getData();
    
  }
  
  ngOnInit() {
    this.form = this.formBuilder.group({
      myControl: ['', Validators.required],
      name: ['', Validators.required]
    });
 
    this.filteredUsers = this.form.controls['myControl'].valueChanges.pipe(
      startWith(''),
      debounceTime(200),
      distinctUntilChanged(),
      switchMap(val => {
        return this.filter(val || '')
        
      })
    )    
}

filter(val: string) {
  return this.users.pipe(
    map(response => response.filter(option => {
      return option.name.toLowerCase().indexOf(val.toLowerCase()) === 0
    }))
  )
}

displayFn(user): string {
  return user.name;
}

***html***
<mat-form-field class="example-full-width">
  <mat-label>Assignee</mat-label>
  <input type="text" matInput formControlName="myControl" [matAutocomplete]="auto">
  <mat-autocomplete #auto="matAutocomplete" 
  [displayWith]="displayFn">
    <mat-option *ngFor="let user of filteredUsers | async" 
        [value]="user">
      <span>{{ user.name }}</span>
    </mat-option>
  </mat-autocomplete>  
</mat-form-field>

The console throws error:

"ERROR TypeError: val.toLowerCase is not a function"

and autocomplete not working on typing.

I've created stackblitz to show the problem

2

There are 2 answers

1
A.khalifa On

You need to do like this

  myControl = new FormControl();

After

this.filteredUsers = this.myControl.valueChanges.pipe(
  startWith(""),
  map(value => (typeof value === "string" ? value : value.name)),
  map(name => (name ? this.filter(name) : this.users.slice()))
);

Check if this.users is not empty and return list of object with name in

And be sure filteredUsers is Observable

filter(val: string) {
    const filterValue = name.toLowerCase();
    return this.users.filter(
      option => option.name.toLowerCase().indexOf(filterValue) === 0
    );
}

Hope useful

0
tha On

This is because when you choose one option, your function gets the whole object into val (You can see in the console the last print before the error), then val is not a string and you get "ERROR TypeError: val.toLowerCase is not a function". You should change your return code statement as follows:

 return val === "string" ? option.name.toLowerCase().indexOf(val.toLowerCase()) === 0 : val;

see in stackblitz.