Angular 2 - 在模型更改后查看未更新

时间:2016-04-28 15:40:09

标签: angular

我有一个简单的组件,它每隔几秒就调用一次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对象正在更新,但除非我重新加载页面,否则视图中的表永远不会改变。

我很难在这里看到我做错了什么。有人可以帮忙吗?

5 个答案:

答案 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个小时。 即:我不需要使用这些方法中的任何一种:

  • ChangeDetectorRef.detectChanges()
  • ChangeDetectorRef.markForCheck()
  • ApplicationRef.tick()

答案 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);
  • 处理请求错误;
  • 停止浏览器在离线状态下发出新请求。