Reputation: 716
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
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