startWith 在第一次点击焦点时
startWith on first click of focus
致力于从 API 中提取城市的自动完成功能,然后允许用户搜索行程。
问题是,即使我使用的是 startWith,我首先必须在该字段中单击,然后开始输入才能使其工作,但当用户关注该输入框时,我无法立即显示下拉列表。
作为一种解决方案,我想在填充 cities 变量的订阅之后调用它。我该怎么做?列表应该作为可观察的吗?然后继续重新订阅?
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();
}
}
在这种情况下,startWith
实际上会发出空字符串值和您的 map 函数,但是第一次发出已经在 this.cities
分配之前完成。下一次发射实际上是在 valueChanges
再次发射时。
因此,当第一个 cities
Observable 发出时,我们可以 运行 那个 map
方法代替。在实践中,当 either 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
函数只会 运行 一旦它发射了一些东西。它还从两个 observables 发出一个成对的值,所以一些解构处理了这一点。
我们还可以更改您的 _filter
函数以接受 cities
参数或直接执行内联过滤器,因为我们不再有 this.cities
静态数组值。我喜欢第二种方法,因为它将所有与流相关的数据保存在一个流中。
此外,此更改需要在 cities
上重复时标记中的异步管道。不过这很好,因为 async
管道会自动处理取消订阅。
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)));
}
}
<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 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 currentCity|async" [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>
致力于从 API 中提取城市的自动完成功能,然后允许用户搜索行程。 问题是,即使我使用的是 startWith,我首先必须在该字段中单击,然后开始输入才能使其工作,但当用户关注该输入框时,我无法立即显示下拉列表。 作为一种解决方案,我想在填充 cities 变量的订阅之后调用它。我该怎么做?列表应该作为可观察的吗?然后继续重新订阅?
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();
}
}
在这种情况下,startWith
实际上会发出空字符串值和您的 map 函数,但是第一次发出已经在 this.cities
分配之前完成。下一次发射实际上是在 valueChanges
再次发射时。
因此,当第一个 cities
Observable 发出时,我们可以 运行 那个 map
方法代替。在实践中,当 either 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
函数只会 运行 一旦它发射了一些东西。它还从两个 observables 发出一个成对的值,所以一些解构处理了这一点。
我们还可以更改您的 _filter
函数以接受 cities
参数或直接执行内联过滤器,因为我们不再有 this.cities
静态数组值。我喜欢第二种方法,因为它将所有与流相关的数据保存在一个流中。
此外,此更改需要在 cities
上重复时标记中的异步管道。不过这很好,因为 async
管道会自动处理取消订阅。
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)));
}
}
<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 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 currentCity|async" [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>