角度2-视图在模型更改后没有更新

我有一个简单的组件,它每隔几秒钟调用一个 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)的结果中可以看到 recentDettions 对象正在更新,但是视图中的表从未更改,除非我重新加载页面。

我在纠结自己到底做错了什么,有人能帮忙吗?

271029 次浏览

可能是你服务的代码不知怎么突破了 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());
}
}

有关调用变化检测的其他方法,请参阅 以角度手动触发变化检测

调用变化检测的其他方法包括

ChangeDetectorRef.detectChanges()

立即为当前组件及其子组件运行变化检测

ChangeDetectorRef.markForCheck()

在下次安格拉变化检测运行时包括当前分量

ApplicationRef.tick()

运行整个应用程序的变化检测

它最初是@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()可能会导致发出 < a href = “ https://stackoverflow./questions/37849453/try-to-use-a-structed-view-dettchanges”> 尝试使用一个已破坏的视图: dettChanges

要解决这个问题,你需要在销毁组件之前用 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();
}


}

在我的情况下,我有一个非常相似的问题。我在父组件调用的函数中更新视图,在父组件中我忘记使用@ViewChild (NameOfMyChieldComponent)。就因为这个愚蠢的错误,我至少浪费了3个小时。 即 我不需要用任何一种方法:

  • ChangeDetectorRef.dettChanges ()
  • ChangeDetectorRef.markForCheck ()
  • 申请表格 Ref.tick ()

不要处理区域和变化检测ーー让 Asyncpie 处理复杂性。这将把可观察的订阅,取消订阅(以防止内存泄漏)和改变检测角的肩膀。

更改您的类,使其成为可观察的,将发出新请求的结果:

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,如上面的代码) ;
  • 处理请求错误;
  • 停止浏览器在离线时发出新的请求。

我知道这是一个老问题,但我想分享我的情况。我在一个团队工作,有人把变化检测策略设置为“推”基本上就是禁用自动变化检测。不知道这是否有用,但以防万一

@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})

Https://angular.io/api/core/changedetectionstrategy