Search code examples
angularfire2angular-material2angular2-observables

FirebaseListObservable<any[]> as list for material 2 autocomplete not filtering


I am trying to use angularfire2 and I want to use the angular material2 autocomplete component. with my current setup, the autocomplete list is properly populated from firebase. However the filter function does not seem to be working and I can't figure out why. Is it because I am using switchmap instead of map like the material example is using(if I use map then the list is not populated and it throws errors)? does the filter function work differently for a FirebaseListObservable vs a normal Observable?

The component file

import { Component, OnInit } from '@angular/core';
import { MdDialogRef } from '@angular/material';
import { FormControl } from '@angular/forms';
import { AngularFire, FirebaseListObservable } from 'angularfire2';

@Component({
  selector: 'budget-new-transaction',
  templateUrl: './new-transaction.component.html',
  styleUrls: ['./new-transaction.component.css']
})
export class NewTransactionComponent implements OnInit {
  categories: FirebaseListObservable<any[]>;
  categoryCtrl: FormControl;
  filteredCategories: any;

  constructor(public dialogRef: MdDialogRef<NewTransactionComponent>, public af: AngularFire, ) {
    this.categories = af.database.list('/items');
    this.categoryCtrl = new FormControl();
    this.filteredCategories = this.categoryCtrl.valueChanges
      .startWith(null)
      .switchMap(name => this.filterCategories(name));
  }
  filterCategories(val: string) {
    return val ? this.categories.filter(s => new RegExp(`^${val}`, 'gi').test(s))
      : this.categories;
  }



  ngOnInit() {
  }

}

The html file

<h3>Add User Dialog</h3>
<form #form="ngForm" (ngSubmit)="dialogRef.close(form.value)" ngNativeValidate>
  <div fxLayout="column" fxLayoutGap="8px">
    <md-input-container>
      <input mdInput placeholder="Category" [mdAutocomplete]="auto" [formControl]="categoryCtrl">
    </md-input-container>
    <md-autocomplete #auto="mdAutocomplete">
      <md-option *ngFor="let category of filteredCategories | async" [value]="category">
        {{ category.$value }}
      </md-option>
    </md-autocomplete>

    <md-input-container>
      <textarea mdInput ngModel name="details" placeholder="Details" rows="15" cols="60" required></textarea>
    </md-input-container>

    <div fxLayout="row" fxLayoutGap="24px">
      <md-checkbox ngModel name="isAdmin">Is Admin?</md-checkbox>
      <md-checkbox ngModel name="isCool">Is Cool?</md-checkbox>
    </div>
  </div>
  <md-dialog-actions align="end">
    <button md-button type="button" (click)="dialogRef.close()">Cancel</button>
    <button md-button color="accent">Save User</button>
  </md-dialog-actions>
</form>

Solution

  • The problem is that you are using the RxJS filter operator when it appears that you should be using Array.prototype.filter.

    this.categories is a FirebaseListObservable, so it will emit an array contianing the database reference's child items. That means you are passing an array to the regular expression's test method.

    You should do something like this:

    import 'rxjs/add/operator/map';
    
    filterCategories(val: string) {
      return val ?
        this.categories.map(list => list.filter(
          s => new RegExp(`^${val}`, 'gi').test(s.$value)
        )) :
        this.categories;
    }
    

    Also, you might want to move the creation of the RegExp to outside of the implicit filter loop.