Angular 5 : Can't update template with async pipe

标签 angular templates asynchronous rxjs angular-google-maps

我目前正在学习 Angular5 和 RxJS。我正在尝试运行一个简单的应用程序:

  • 搜索栏输入术语
  • keyup 触发函数,然后调用服务
  • 服务返回封装在 observable 中的 api 调用
  • observable 是通过异步管道和模板更新订阅的,来自 api 调用的结果

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

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

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

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

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

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

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


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

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

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

  });

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


完整的组件、服务和模板是这样的:

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);

  }

}

place-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 的自动变化检测范围内,因此每次新结果到达时都需要触发它。

place-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>

关于 Angular 5 : Can't update template with async pipe,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48670640/

相关文章:

angular-cli ng serve --- 我怎样才能停止它的定期刷新

javascript - 如何在 Angular Typescript 中访问此关键字

c++ - 有没有办法找到任意类类型的直接基类(如果有的话)?

c++ - "Node<T*> *first"和 "Node<T> *first"有什么区别?

javascript - 如何访问返回的 Promise 的值?

Angular Cdk步进器,如何检测何时添加新步骤

angular - 在 URLEncoded Http Post 请求中保留 +(加号)

c++ - typedef 一个专门的嵌套模板

python - 如何在另一个异步循环中使用异步循环

silverlight - 多个异步请求同步