2016-04-28 109 views
69

我有一个简单的组件,每隔几秒调用一次REST api并接收一些JSON数据。我可以从我的日志语句和网络流量中看到返回的JSON数据正在改变,并且我的模型正在更新,但是视图没有改变。角度2 - 查看模型更改后不更新

我的组件看起来像:

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正在更新对象看,但在表视图不会改变,除非我重新加载页面。

我很努力地看到我在这里做错了什么。谁能帮忙?

回答

107

这可能是您的服务中的代码以某种方式突破了Angulars区域。这打破了变更检测。这应该工作:

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 Angular2 change detection manually

替代方式调用变化检测是

ChangeDetectorRef.detectChanges() 

到immedialtely运行变化检测当前组件,它的childrend

ChangeDetectorRef.markForCheck() 

,包括当前组件的下一次运行的角度变化检测

ApplicationRef.tick() 

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

+6

另一种方法是注入ChangeDetectorRef并调用'cdRef.detectChanges()'而不是'zone.run()'。这可能更有效,因为它不会像'zone.run()'那样在整个组件树上运行变更检测。 –

+0

@MarkRajcok我猜如果你只是改变实例成员,这可能会更有效。对于在Angulars区域以外运行的代码,您如何看待执行例如'this.router.navigate()'的代码(其他组件中有很多副作用)?我想在这里'detectChanges()'可能无法解决这个问题。你怎么看? –

+1

同意。如果您所做的任何更改都是组件及其后代的本地组件,则只使用'detectChanges()'。我的理解是'detectChanges()'将检查组件及其后代,但'zone.run()'和'ApplicationRef.tick()'将检查整个组件树。 –

7

尝试使用@Input() recentDetections: Array<RecentDetection>;

编辑: 之所以@Input()重要的是,因为你要打字稿中的值绑定/ javascript文件的视图(HTML)。如果使用@Input()装饰器声明的值发生更改,视图将自行更新。如果修改@Input()@Output()修饰符,则会触发一个ngOnChanges -event,并且该视图将使用新值更新其自身。您可能会说@Input()将双向绑定该值。

搜索输入的角度此链接:glossary了解更多信息

编辑:学习更多有关角2开发后,我想,做一个@Input()真的是解决不了问题,并提到中的注释

@Input()当数据是通过从 数据绑定在组件外改变只适用(在父绑定的数据改变)不是当 数据从组件内的代码更改。

如果你看看@Günter的答案,它是一个更准确和正确的解决方案的问题。我仍然会在这里保留这个答案,但请按照Günter的回答作为正确的答案。

+2

就是这样。我之前看到过@Input()注解,但总是将它与表单输入关联起来,从来没有想过我会在这里需要它。干杯。 –

+1

@约翰感谢您的补充解释。但是,您添加的内容仅适用于数据从组件外部进行数据绑定(父级绑定数据发生更改)而不是数据从组件内的代码更改时更改的情况。 –

+0

使用@Input()关键字时,会附加一个属性绑定,并且该绑定会确保在视图中更新值。该价值将成为生命周期事件的一部分。 [This post](http://victorsavkin.com/post/118372404541/the-core-concepts-of-angular-2)包含更多关于“@Input()”关键字 – John

5

原本是从@马克Rajcok评论的答案,但我想将在这里作为一个测试,使用ChangeDetectorRef工作作为一个解决方案,我看到一个很好的点这里:

另一种方法是注入ChangeDetectorRef并调用 cdRef.detectChanges(),而不是zone.run()。这可能会更有效率,因为它不会对整个 组件树(如zone.run())执行更改检测。 - 马克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()); 
    } 
} 

编辑:使用.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(); 
    } 

} 
0

就我而言,我有一个非常类似的问题。我在一个父组件调用的函数内更新了我的视图,而在我的父组件中,我忘记了使用@ViewChild(NameOfMyChieldComponent)。为了这个愚蠢的错误,我至少失去了3个小时。 即:我并不需要使用任何这些方法:

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

你能解释你如何用@ViewChild更新你的子组件?谢谢 –

0

而不是处理区域和更改检测 - 让AsyncPipe处理复杂性。这将把可观察的订阅,取消订阅(以防止内存泄漏)并改变角度肩膀上的检测。

更改您的类,以便可观察到的,将发出新的请求的结果:

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的说法,并使其成为:

  • 新请求(使用像上面代码中的);
  • 处理请求错误;
  • 停止浏览器在离线时发出新请求。