Pari Baker
Pari Baker

Reputation: 716

form builder with autocomplete in angular 6

I have two sets of code that work, but I can't get either to work as I want it. So I was hoping for some conceptual insights.

I have a service grabbing cities from an API. This service auto-completes filter results. When I use form builder, I cannot get the data to filter because it isn't executing the fromLatestWith.

I am using from latest with to use both observables valueChange and the service that is bringing in the cities. It works when I use formControl but not with formBuilder.

When I do not use fromLatestWith I can get the service and autocomplete to work but, since the service loads first I have to type before the autocomplete dropdown shows.

Ideally what I'd like is to use formbuilder and have the autocomplete drop down on initialization. What am I missing?

Here are copies of both instances:

//using formBuilder with latestWith lets me see valueChange but doesnt
//load cities if I don't use formBuilder and use formControl it works

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

@Component({
  selector: "app-city-list",
  templateUrl: "./city-list.component.html",
  styleUrls: ["./city-list.component.css"]
})
export class CityListComponent implements OnInit {
  cities: Observable<City[]>;

  destinationCitySub: Observable<City[]>;
  originCitySub: Observable<City[]>;

  instantFlightForm: FormGroup;


  constructor(public cityService: CityService, private fb: FormBuilder) {

  }

  ngOnInit() {
    this.instantFlightForm = this.fb.group({
      destinationCity: [""],
      originCity: ["", Validators.required],
      startDate: []
    });



    this.cityService.getCities();
    this.cities = this.cityService.getCityUpdateListener();
   this.destinationCitySub = this.valueChange('destinationCity');

  }

  private valueChange(string) {
    console.log(string);
    return this.instantFlightForm.get(string).valueChanges.pipe(
      withLatestFrom(this.cities),
      debounceTime(100),
      map(([term, city]) => {
        console.log(term);
        return this._filter(term, city);
      })
    );
  }

  private _filter(first, second): City[] {
    const filterValue = first.toLowerCase();
    return second.filter(option =>
      option.name.toLowerCase().includes(filterValue)
    );
  }

  onInstantSearch(form: FormGroup) {
    console.log(form.value);
  }
}
<mat-card>

  <form [formGroup]="instantFlightForm" (submit)="onInstantSearch(instantFlightForm)">
    <mat-form-field>
      <input  type="text" id="destinationCity" name="destinationCity" matInput formControlName="destinationCity" [matAutocomplete]="autoDestination">

      <mat-autocomplete #autoDestination="matAutocomplete">
        <mat-option *ngFor="let c of destinationCitySub|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 formControlName="originCity" [matAutocomplete]="autoOrigin">

    <mat-autocomplete #autoOrigin="matAutocomplete">
      <mat-option *ngFor="let c of originCitySub|async" [value]="c">
        {{c}}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
  <mat-form-field>
      <input matInput id="startDate" name="startDate" formControlName="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>

//using it without latestWith - lets me autocomplete but requires
//typing first

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

@Component({
  selector: "app-city-list",
  templateUrl: "./city-list.component.html",
  styleUrls: ["./city-list.component.css"]
})
export class CityListComponent implements OnInit {
  cities: City[];
citySub: Subscription;
  destinationCitySub: Observable<City[]>;
  originCitySub: Observable<City[]>;

  instantFlightForm: FormGroup;

  constructor(public cityService: CityService, private fb: FormBuilder) {

  }

  ngOnInit() {
    this.instantFlightForm = this.fb.group({
      destinationCity: [""],
      originCity: ["", Validators.required],
      startDate: []
    });



    this.cityService.getCities();
    this.citySub = this.cityService.getCityUpdateListener().subscribe(c=>this.cities=c);
   this.destinationCitySub = this.valueChange('destinationCity');

  }

  private valueChange(string) {
    return this.instantFlightForm.get(string).valueChanges.pipe(
     // withLatestFrom(this.cities),
      debounceTime(100),
      map((term) => {
        console.log(term);
        return this._filter(term);
      })
    );
  }

  private _filter(term): City[] {
    const filterValue = term.toLowerCase();
    return this.cities.filter(option =>
      option.name.toLowerCase().includes(filterValue)
    );
  }

  onInstantSearch(form: FormGroup) {
    console.log(form.value);
  }
}

Upvotes: 1

Views: 2076

Answers (1)

bryan60
bryan60

Reputation: 29335

withLatestFrom operates on an observable stream, not a value. You need to set cities to an observable and make sure that it fires at least once.

this.cities = this.cityService.getCityUpdateListener();

Upvotes: 1

Related Questions