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>