Angular 5: 无法使用异步管道更新模板

Angular 5: Can't update template with async pipe

我目前正在学习 Angular5 和 RxJS。我正在尝试为 运行:

创建一个简单的应用程序

我尝试了两种选择,每一种都有一个问题:

a) 订阅组件并更新模板数据:

this.placePredictionService.getPlacePredictions(term).subscribe( data => 
{
  this.results = data;
 });

模板确实更新了 {{results}} 绑定,但仅在第二次函数调用时更新。然后使用第一次调用的结果更新模板。为什么?

b) 使用异步管道返回可观察和更新模板

private results$: Observable<any[]>;
this.results$ = this.placePredictionService.getPlacePredictions(term);

这样,模板中什么也不会发生。我没有得到什么?我的理解哪里不足?非常感谢您提供有关要调查的内容的提示。


2个问题的解决方案:谢谢@Richard Matsen

a) 问题是,Google 地图 API 的调用不在 Angular 区域内,因此不会自动触发更改检测。在 ngZone.run() 函数中包装服务中的 API 调用就可以了:

this.autocompleteService.getPlacePredictions({input: term}, data => {
    this.ngZone.run(() => {
      this.predictions.next(data);
    });

  });

b) 使用主题不会在每次新击键时产生新流解决了异步管道无法正常工作的问题,请参阅下面的代码评论。


完整的组件、服务和模板如下:

app.component.ts

import { Component } from '@angular/core';
import { MapsAPILoader } from '@agm/core';
import { PlacePredictionService } from './place-prediction.service';

import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  private searchTerm: string;
  private results$: Observable<any[]>;

  testResult = [{description: 'test'},{description: 'test'}];

  constructor(
    private mapsAPILoader: MapsAPILoader,
    private placePredictionService: PlacePredictionService
  ){}

  onSearch(term: string){

    this.searchTerm = term;

    if (this.searchTerm === '') return;

    this.results$ = this.placePredictionService.getPlacePredictions(term);

  }

}

地点-prediction.service.ts

import { Injectable } from '@angular/core';
import { MapsAPILoader } from '@agm/core';

import { Observable } from 'rxjs/Observable';

import 'rxjs/add/observable/of';
import 'rxjs/add/observable/bindCallback';

@Injectable()
export class PlacePredictionService {

  private autocompleteService;

  constructor(
    private mapsAPILoader: MapsAPILoader
  ) { 
    this.mapsAPILoader.load().then( () => {
      this.autocompleteService = new google.maps.places.AutocompleteService();
    });
  }

  // Wrapper for Google Places Autocomplete Prediction API, returns observable
  getPlacePredictions(term: string): Observable<any[]>{

    return Observable.create(observer  => {

      // API Call
      this.autocompleteService.getPlacePredictions({input: term}, (data) => {

        let previousData: Array<any[]>;

        // Data validation
        if(data) {
          console.log(data);
          previousData = data;
          observer.next(data);
          observer.complete();
        }

        // If no data, emit previous data
        if(!data){
          console.log('PreviousData: ');
          observer.next(previousData);
          observer.complete();

        // Error Handling
        } else {
          observer.error(status);
        }

      });
    });
  }

}

app.component.html

<h1>Google Places Test</h1>
<p>Angular 5 &amp; RxJS refresher</p>
<input
  type="search"
  placeholder="Search for place" 
  autocomplete="off"
  autocapitalize="off"
  autofocus
  #search
  (keyup)="onSearch(search.value)"/> 
  <p>{{ searchTerm }}</p>
  <ul>
    <li *ngFor="let result of results$ | async "> {{result.description}}</li>
  </ul>

手动调用 ChangeDetectorRef.detectChanges 修复事件滞后。

我猜 api 调用不在 Angular 的自动变化检测范围内,因此每次新结果到达时都需要触发它。

地点-prediction.service.ts

@Injectable()
export class PlacePredictionService {

  predictions = new Subject();
  private autocompleteService;

  constructor(
    private mapsAPILoader: MapsAPILoader
  ) {
    this.mapsAPILoader.load().then( () => {
      this.autocompleteService = new google.maps.places.AutocompleteService();
    });
  }

  // Wrapper for Google Places Autocomplete Prediction API, returns observable
  getPlacePredictions(term: string) {

    // API Call
    this.autocompleteService.getPlacePredictions({input: term}, (data) => {
      this.predictions.next(data);
    });
  }
}

app.component.ts

import { Component, ChangeDetectorRef } from '@angular/core';
...

export class AppComponent  {

  private searchTerm: string;
  private results = [];

  constructor(
    private cdr: ChangeDetectorRef,
    private mapsAPILoader: MapsAPILoader,
    private placePredictionService: PlacePredictionService
  ){}

  ngOnInit() {
    this.placePredictionService.predictions.subscribe(data => {
      this.results = data;
      this.cdr.detectChanges();
    });
  }

  onSearch(term: string) {
    this.searchTerm = term;
    if (this.searchTerm === '') { return; }
    this.placePredictionService.getPlacePredictions(term);
  }
}

app.component.html

<ul>
  <li *ngFor="let result of results"> {{result.description}}</li>
</ul>