使用“自动完成”功能从API中提取城市,然后允许用户搜索旅行。 问题是,即使我使用的是startWith,我也必须先在该字段中单击,然后开始键入内容才能正常工作,但是当用户将注意力集中在该输入框上时,我无法立即显示下拉菜单。 作为解决方案,我想在填充城市变量的订阅之后调用它。我该怎么做?该列表是否应可观察?然后继续订阅?
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(
debounceTime(100),
startWith(''),
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>
代码已更新
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, filter, startWith, withLatestFrom, debounceTime } from "rxjs/operators";
import { FormGroup, FormControl, Validators, NgForm } from "@angular/forms";
import {forkJoin} from 'rxjs';
import { pipe } from "../../../node_modules/@angular/core/src/render3/pipe";
@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[]>;
testCities: Observable<City[]>;
destinationCity: FormControl = new FormControl();
originCity: FormControl = new FormControl();
startDate: FormControl = new FormControl();
constructor(public cityService: CityService) {}
ngOnInit() {
this.cityService.getCities();
this.testCities = this.cityService
.getCityUpdateListener();
this.currentCity = this.destinationCity.valueChanges
.pipe(
withLatestFrom(this.testCities),
debounceTime(100),
map((x) =>{
return this._filter(x);
}
));
}
private _filter(value): City[]{
const filterValue = value.toLowerCase();
return(this.testCities.filter(option => option.name.toLowerCase().includes(filterValue)));
}
ngOnDestroy() {
this.citiesSub.unsubscribe();
}
}
答案 0 :(得分:1)
在这种情况下,startWith
实际上将发出该空字符串值和您的map函数,但是在分配this.cities
之前已经完成了第一次发射。实际上,下一次发射是valueChanges
再次发射时。
因此,当第一个map
Observable发出时,我们可以运行该cities
方法。实际上,我们只想在发出任一 Observable时运行该map
方法。我们可以通过一点重构和withLatestFrom
来实现:
ngOnInit() {
this.cityService.getCities();
this.cities = this.cityService.getCityUpdateListener();
this.currentCity = this.destinationCity.valueChanges
.pipe(
debounceTime(100),
withLatestFrom(this.cities)
map([value, cities] => cities.filter(s => s.name.toLowerCase().includes(value.toLowerCase)));
));
}
withLatestFrom
将等待给定的Observable发出至少一个值,然后再继续流。由于此处的Observable较慢,因此map
函数仅在发出某些内容后才运行。它还从两个可观察值发出一个成对的值,因此进行了一些分解处理。
我们也可以更改您的_filter
函数以接受一个cities
参数,或者只进行内联过滤器,因为我们已经没有this.cities
静态数组值了。我喜欢第二种方法,因为它可以使所有数据与一个流中包含的流相关。
此外,在cities
上重复执行此更改时,还需要标记中的异步管道。不过,这很好,因为async
管道会自动处理退订。
答案 1 :(得分:0)
int i;
for(i=0;i<=10;i++)
{
...
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, Validators, NgForm } 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[]>;
private citiesSub: Subscription;
currentCity: Observable<City[]>;
testCities: Observable<City[]>;
destinationCity: FormControl = new FormControl();
originCity: FormControl = new FormControl();
startDate: FormControl = new FormControl();
constructor(public cityService: CityService) {}
ngOnInit() {
this.cityService.getCities();
this.cities = this.cityService.getCityUpdateListener();
this.currentCity = this.destinationCity.valueChanges
.pipe(
withLatestFrom(this.cities),
debounceTime(100),
map(
([first, second]) =>{
return this._filter(first,second);
}
)
);
}
private _filter(first, second): City[]{
const filterValue = first.toLowerCase();
return(second.filter(option => option.name.toLowerCase().includes(filterValue)));
}
}