0

I am trying to create a drop-down list of customers (or anything), making use of Angular 5 Material's Autocomplete functionality. But unlike the examples provided on the Angular website, my data is not static, but gets returned after a data call getAllCustomer().

The problem I am having, seems to be the assigning of the filterOptions before the data has been returned from my getAllCustomer() method.

How can I make sure to only assign my filterOptions after my data returns?

Here is my code:

filteredOptions: Observable<string[]>;

constructor(private loadDataService: LoadDataService, private assetDataService: AssetDataService, private router: Router, private toastr: ToastrService) { }

ngOnInit() {
    this.getAllCustomers();
    this.filteredOptions = this.myCustomerSearchControl.valueChanges.pipe(
      startWith(''),
      map(val => this.filter(val))
    );
}

filter(val: string): string[] {
    return this.customerArray.filter(option => option.toLowerCase().indexOf(val.toLowerCase()) === 0);
}

getAllCustomers() {
    this.loadDataService.getAllCustomers()
    .subscribe(data => {
      this.customerArray = data;
    });
}

This is my HTML:

<mat-form-field>
    <input type="text" placeholder="Customer Search" aria-label="Number" matInput [formControl]="myCustomerSearchControl" [matAutocomplete]="auto">
        <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
            <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
            {{ option }}
        </mat-option>
    </mat-autocomplete>
</mat-form-field>

And as a bonus, how would I be able to implement the same, but with an actual search function that returns the data as a user types in the search box - i.e., a search by string method?

This is my searchByString function:

searchForCustomerByString(string) {
    this.loadDataService.getCustomer(string)
      .subscribe(data => {
        this.returnedCustomers = data;
      });
}
onmyway
  • 1,435
  • 3
  • 29
  • 53

2 Answers2

0

You can define the variable in the result of the subscribe like this:

getAllCustomers() {
    this.loadDataService.getAllCustomers()
    .subscribe(data => {
      this.customerArray = data;
      this.filteredOptions = this.myCustomerSearchControl.valueChanges.pipe(
          startWith(''),
          map(val => this.filter(val))
      );
    });
}

But the variable filteredOptions might not be initialized so maybe you can use something like a BehaviorSubject to initialize the variable.

hanego
  • 1,595
  • 1
  • 14
  • 27
0

Even better and cleaner solution to this would be to use cold observables.

filteredOptions: Observable<string[]>;
customers: Observable<string[]>; // -> assuming the type here, bc your code doesn't provide the customerArray type

constructor(private loadDataService: LoadDataService, private assetDataService: AssetDataService, private router: Router, private toastr: ToastrService) { }

ngOnInit() {
    this.customers = this.loadDataService.getAllCustomers();
    this.filteredOptions = Observable.combineLatest(
       this.customers,
       this.myCustomerSearchControl.valueChanges.startWith(null)
    ).map(([customers, filter]) => {
      if(!customers || customers.length === 0) {
          return [];
      }

      if (!filter || filter === '') {
         return customers;
      }

       return customers.filter(.....); // apply your filter.
    })
}

No direct subscription that needs to be stored and unsubscribed on-destroy of your component.

alsami
  • 8,996
  • 3
  • 25
  • 36
  • Thanks SamiAI90! Will test it out. I am intrigued with how this will compare to the other solution. – onmyway Mar 08 '18 at 07:49
  • 1
    Both solutions are working. Keep in mind when using hot observable to always store subscriptions and unsubscribe them in ngOnDestroy(). – alsami Mar 08 '18 at 07:50