我有一个简单的组件,它每隔几秒就调用一次REST api并收回一些JSON数据。我可以从我的日志语句和网络流量中看到返回的JSON数据正在发生变化,我的模型正在更新,但是视图没有变化。
我的组件如下:
import {Component, OnInit} from 'angular2/core';
import {RecentDetectionService} from '../services/recentdetection.service';
import {RecentDetection} from '../model/recentdetection';
import {Observable} from 'rxjs/Rx';
@Component({
selector: 'recent-detections',
templateUrl: '/app/components/recentdetection.template.html',
providers: [RecentDetectionService]
})
export class RecentDetectionComponent implements OnInit {
recentDetections: Array<RecentDetection>;
constructor(private recentDetectionService: RecentDetectionService) {
this.recentDetections = new Array<RecentDetection>();
}
getRecentDetections(): void {
this.recentDetectionService.getJsonFromApi()
.subscribe(recent => { this.recentDetections = recent;
console.log(this.recentDetections[0].macAddress) });
}
ngOnInit() {
this.getRecentDetections();
let timer = Observable.timer(2000, 5000);
timer.subscribe(() => this.getRecentDetections());
}
}
我的观点如下:
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading"><h3>Recently detected</h3></div>
<div class="panel-body">
<p>Recently detected devices</p>
</div>
<!-- Table -->
<table class="table" style="table-layout: fixed; word-wrap: break-word;">
<thead>
<tr>
<th>Id</th>
<th>Vendor</th>
<th>Time</th>
<th>Mac</th>
</tr>
</thead>
<tbody >
<tr *ngFor="#detected of recentDetections">
<td>{{detected.broadcastId}}</td>
<td>{{detected.vendor}}</td>
<td>{{detected.timeStamp | date:'yyyy-MM-dd HH:mm:ss'}}</td>
<td>{{detected.macAddress}}</td>
</tr>
</tbody>
</table>
</div>
我可以从console.log(this.recentDetections[0].macAddress)
的结果中看到recentDetections对象正在更新,但除非我重新加载页面,否则视图中的表永远不会改变。
我很难在这里看到我做错了什么。有人可以帮忙吗?
答案 0 :(得分:171)
可能是您服务中的代码以某种方式突破了Angular的区域。这打破了变化检测。这应该有效:
import {Component, OnInit, NgZone} from 'angular2/core';
export class RecentDetectionComponent implements OnInit {
recentDetections: Array<RecentDetection>;
constructor(private zone:NgZone, // <== added
private recentDetectionService: RecentDetectionService) {
this.recentDetections = new Array<RecentDetection>();
}
getRecentDetections(): void {
this.recentDetectionService.getJsonFromApi()
.subscribe(recent => {
this.zone.run(() => { // <== added
this.recentDetections = recent;
console.log(this.recentDetections[0].macAddress)
});
});
}
ngOnInit() {
this.getRecentDetections();
let timer = Observable.timer(2000, 5000);
timer.subscribe(() => this.getRecentDetections());
}
}
有关调用更改检测的其他方法,请参阅Triggering change detection manually in Angular
调用变更检测的其他方法是
ChangeDetectorRef.detectChanges()
立即对当前组件及其子组件进行更改检测
ChangeDetectorRef.markForCheck()
在下次Angular运行更改检测时包含当前组件
ApplicationRef.tick()
运行整个应用程序的更改检测
答案 1 :(得分:20)
最初是来自@Mark Rajcok的评论的答案,但我想把它放在这里作为一个经过测试并使用ChangeDetectorRef作为解决方案,我在这里看到一个好点:
另一种选择是注入
ChangeDetectorRef
并打电话cdRef.detectChanges()
代替zone.run()
。这可能更多 高效,因为它不会在整个过程中运行变化检测 像zone.run()
这样的组件树。 - Mark Rajcok
所以代码必须像:
import {Component, OnInit, ChangeDetectorRef} from 'angular2/core';
export class RecentDetectionComponent implements OnInit {
recentDetections: Array<RecentDetection>;
constructor(private cdRef: ChangeDetectorRef, // <== added
private recentDetectionService: RecentDetectionService) {
this.recentDetections = new Array<RecentDetection>();
}
getRecentDetections(): void {
this.recentDetectionService.getJsonFromApi()
.subscribe(recent => {
this.recentDetections = recent;
console.log(this.recentDetections[0].macAddress);
this.cdRef.detectChanges(); // <== added
});
}
ngOnInit() {
this.getRecentDetections();
let timer = Observable.timer(2000, 5000);
timer.subscribe(() => this.getRecentDetections());
}
}
修改强>:
在subscibe中使用.detectChanges()
可能导致问题Attempt to use a destroyed view: detectChanges
要解决此问题,在销毁组件之前需要unsubscribe
,因此完整的代码将如下:
import {Component, OnInit, ChangeDetectorRef, OnDestroy} from 'angular2/core';
export class RecentDetectionComponent implements OnInit, OnDestroy {
recentDetections: Array<RecentDetection>;
private timerObserver: Subscription;
constructor(private cdRef: ChangeDetectorRef, // <== added
private recentDetectionService: RecentDetectionService) {
this.recentDetections = new Array<RecentDetection>();
}
getRecentDetections(): void {
this.recentDetectionService.getJsonFromApi()
.subscribe(recent => {
this.recentDetections = recent;
console.log(this.recentDetections[0].macAddress);
this.cdRef.detectChanges(); // <== added
});
}
ngOnInit() {
this.getRecentDetections();
let timer = Observable.timer(2000, 5000);
this.timerObserver = timer.subscribe(() => this.getRecentDetections());
}
ngOnDestroy() {
this.timerObserver.unsubscribe();
}
}
答案 2 :(得分:7)
尝试使用@Input() recentDetections: Array<RecentDetection>;
<强> 编辑: 强>
@Input()
之所以重要的原因是因为您希望将typescript / javascript文件中的值绑定到视图(html)。如果使用@Input()
装饰器声明的值发生更改,则视图将自动更新。如果更改了@Input()
或@Output()
装饰器,则会触发ngOnChanges
- 事件,并且视图将使用新值更新自身。您可以说@Input()
将双向绑定值。
通过angular glossary搜索此链接上的输入以获取更多信息
编辑: 在深入了解Angular 2开发之后,我认为做@Input()
确实不是解决方案,正如评论中所提到的,
@Input()
仅在通过数据绑定更改数据时适用 组件外部(父级中的绑定数据已更改)不在时 数据从组件中的代码更改。
如果您看看@Günter的答案,它是解决问题的更准确和正确的解决方案。我仍然会在这里保留这个答案,但请按照Günter的答案作为正确答案。
答案 3 :(得分:1)
就我而言,我遇到了一个非常类似的问题。我正在一个由父组件调用的函数内更新我的视图,而在我的父组件中,我忘了使用@ViewChild(NameOfMyChieldComponent)。因为这个愚蠢的错误,我至少失去了3个小时。 即:我不需要使用这些方法中的任何一种:
答案 4 :(得分:0)
不要处理区域和更改检测 - 让AsyncPipe处理复杂性。这将提供可观察的订阅,取消订阅(以防止内存泄漏)并更改Angular肩上的检测。
更改您的类以创建一个可观察的,它将发出新请求的结果:
export class RecentDetectionComponent implements OnInit {
recentDetections$: Observable<Array<RecentDetection>>;
constructor(private recentDetectionService: RecentDetectionService) {
}
ngOnInit() {
this.recentDetections$ = Observable.interval(5000)
.exhaustMap(() => this.recentDetectionService.getJsonFromApi())
.do(recent => console.log(recent[0].macAddress));
}
}
并更新您的视图以使用AsyncPipe:
<tr *ngFor="let detected of recentDetections$ | async">
...
</tr>
想要添加,使用interval
参数的方法创建服务更好,并且:
exhaustMap
);