Angular 5: 无法使用异步管道更新模板
Angular 5: Can't update template with async pipe
我目前正在学习 Angular5 和 RxJS。我正在尝试为 运行:
创建一个简单的应用程序
- 搜索栏输入术语
- keyup 触发函数,然后调用服务
- 服务 returns api 封装在 observable 中的调用
- observable 已通过异步管道和模板更新订阅 api 调用
我尝试了两种选择,每一种都有一个问题:
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 & 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>
我目前正在学习 Angular5 和 RxJS。我正在尝试为 运行:
创建一个简单的应用程序- 搜索栏输入术语
- keyup 触发函数,然后调用服务
- 服务 returns api 封装在 observable 中的调用
- observable 已通过异步管道和模板更新订阅 api 调用
我尝试了两种选择,每一种都有一个问题:
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 & 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>