Search code examples
angularrxjs6angular-material-5

Autocomplete with angular materials


I need some help with an auto-complete field using angular materials. First off when the page loads I have a service making an API call to a backend node app that sends requests to a sanbox api. This call brings a list of supported cities. It then feeds the city list component that has a form. When a user types I want to narrow down the cities into an auto-complete box. So far I have set up a new variable as a subject and use the .filter() and .include() methods to filter an array and return it to my subject variable. The return is in the form of an object to my subscription variable, but now on the template I cannot use the returned values to narrow down my results. I figure its because I need to return my results into an array. Can someone help me conceptualize this (currently I can get the filter to narrow down results, but the array doesn't reload the values after it removes them)

city-list Component.ts




import { CityService } from "./services/city-list.service";
import { Component, OnInit, OnDestroy } from "@angular/core";
import { City } from "../cities/models/city";
import { Subscription, Observable } from "rxjs";
import { map, startWith, debounceTime } from "rxjs/operators";
import {FormGroup, FormControl, Validators, NgForm} from '@angular/forms';


@Component({
  selector: "<app-cities></app-cities>",
  templateUrl: "./city-list.component.html"
})
export class CityListComponent implements OnInit, OnDestroy {

  cities: City[];
  private citiesSub: Subscription;
  destinationCity: FormControl = new FormControl();
  currentCity: Subscription;
  filteredCities: City[];

  constructor(public cityService: CityService) {}

  ngOnInit() {

    this.cityService.getCities();
    this.citiesSub = this.cityService
      .getCityUpdateListener()
      .subscribe((cities) => {
       this.cities = cities;

      });
   this.currentCity = this.destinationCity.valueChanges.pipe(
    debounceTime(400),
      startWith(''),
  ).subscribe(term=>{
    if(!term){
      return;
    }
    this._filter(term);
  }
  );
  }
  private _filter(value: string): City[] {
    const filterValue = value.toLowerCase();
    return this.filteredCities = this.cities.filter(option => option.name.toLocaleLowerCase().includes(filterValue));
  }
  ngOnDestroy() {
    this.citiesSub.unsubscribe();
  }

  onSearch(form: NgForm){
    console.log(form.value);
  }
}

city service.ts

import { Subject } from 'rxjs';
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';

import { map } from "rxjs/operators";

import {City} from '../models/city';


@Injectable({ providedIn: "root" })
export class CityService {
  cities: City[] = [];
  private updatedCities = new Subject<City[]>();


  constructor(private http: HttpClient) {}

 getCities() {
  this.http.get<{message: string; cities: City[]}>('http://localhost:3000/cities')
  .pipe(
    map((cityData)=>{
      return cityData.cities.map(city=>{
        return{
          code: city.code,
          name: city.name
        };
      });
    })
)
  .subscribe((transCity) => {
    this.cities = transCity;
   // console.log(this.cities);
    this.updatedCities.next([...this.cities]);

  });
  }

  getCityUpdateListener() {
    return this.updatedCities.asObservable();
  }

}

city-list.html

<mat-card>
  <form #instantFlight="ngForm">
    <mat-form-field>
      <input  type="text" id="destinationCity" name="destinationCity" matInput [formControl]="destinationCity" [matAutocomplete]="autoDestination">

      <mat-autocomplete #autoDestination="matAutocomplete">
        <mat-option *ngFor="let c of cities" [value]="c.code">
          {{c.name}} - {{c.code}}
        </mat-option>
      </mat-autocomplete>
    </mat-form-field>
    <button mat-raised-button type="submit">Search</button>
  </form>
</mat-card>


Solution

  • import { CityService } from "./services/city-list.service";
    import { Component, OnInit, OnDestroy } from "@angular/core";
    import { City } from "../cities/models/city";
    import { Subscription, Observable } from "rxjs";
    import { map, startWith, debounceTime } from "rxjs/operators";
    import { FormGroup, FormControl, Validators, NgForm } from "@angular/forms";
    
    @Component({
      selector: "<app-cities></app-cities>",
      templateUrl: "./city-list.component.html",
      styleUrls: ["./cities-list.component.css"]
    })
    export class CityListComponent implements OnInit, OnDestroy {
      cities: City[]=[];
      private citiesSub: Subscription;
      currentCity: Observable<City[]>;
    
    
      destinationCity: FormControl =  new FormControl();
      originCity: FormControl =  new FormControl();
      startDate: FormControl = new FormControl();
    
    
    
      constructor(public cityService: CityService) {}
    
    
      ngOnInit() {
        this.cityService.getCities();
        this.citiesSub = this.cityService
          .getCityUpdateListener()
          .subscribe(cities => {
            this.cities = cities;
        });
        this.currentCity = this.destinationCity.valueChanges
        .pipe(
          startWith(''),
          debounceTime(10),
          map(x=>{
            return this._filter(x);
          }
        ));
      }
    private _filter(value: string): City[]{
      const filterValue = value.toLowerCase();
      return(this.cities.filter(option => option.name.toLowerCase().includes(filterValue)));
    }
    
      ngOnDestroy() {
        this.citiesSub.unsubscribe();
      }
    }
    <mat-card>
      <form (submit)="onLogin(instantFlight)" #instantFlight="ngForm">
        <mat-form-field>
          <input  type="text" id="destinationCity" name="destinationCity" matInput [formControl]="destinationCity" [matAutocomplete]="autoDestination">
    
          <mat-autocomplete #autoDestination="matAutocomplete">
            <mat-option *ngFor="let c of currentCity | async" [value]="c.code">
              {{c.name}} - {{c.code}}
            </mat-option>
          </mat-autocomplete>
        </mat-form-field>
        <mat-form-field>
        <input  type="text" id="originCity" name="originCity" matInput [formControl]="originCity" [matAutocomplete]="autoOrigin">
    
        <mat-autocomplete #autoOrigin="matAutocomplete">
          <mat-option *ngFor="let c of cities" [value]="c.code">
            {{c.name}} - {{c.code}}
          </mat-option>
        </mat-autocomplete>
      </mat-form-field>
      <mat-form-field>
          <input matInput id="startDate" name="startDate" [formControl]="startDate" [matDatepicker]="picker" placeholder="Choose a date">
          <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
          <mat-datepicker #picker></mat-datepicker>
        </mat-form-field>
        <button mat-raised-button type="submit" color="accent">Search</button>
      </form>
    </mat-card>