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

时间:2018-02-07 18:10:35

标签: angular templates asynchronous rxjs angular-google-maps

我正在学习Angular5& RxJS此刻。我试图让一个简单的应用程序运行:

  • 搜索栏输入字词
  • keyup触发函数然后调用服务
  • service返回包含在observable中的api调用
  • 使用api调用的结果,使用异步管道和模板更新订阅
  • observable

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

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 Maps API的调用不在Angular Zone中,因此更改检测不会自动触发。在ngZone.run()函数中包装服务中的API调用就可以了:

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

  });

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

完整的组件,服务&amp;模板是这样的:

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>

1 个答案:

答案 0 :(得分:1)

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>