Angular 2-モデルの変更後にビューが更新されない


128

数秒ごとに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オブジェクトが更新されていることがわかりますが、ページを再ロードしない限り、ビューのテーブルは変更されません。

ここで私が間違っていることを確認するのに苦労しています。誰か助けてもらえますか?


私はコードをよりクリーンで
シンプル

回答:


215

サービス内のコードがどういうわけか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());
    }
}

変更検出を呼び出す他の方法については、Angularで手動で変更検出をトリガーするを参照してください

変更検出を呼び出す別の方法は、

ChangeDetectorRef.detectChanges()

現在のコンポーネントとその子の変更検出をすぐに実行するには

ChangeDetectorRef.markForCheck()

次にAngularが変更検出を実行するときに現在のコンポーネントを含める

ApplicationRef.tick()

アプリケーション全体の変更検出を実行する


9
別の方法は、ChangeDetectorRefを挿入してのcdRef.detectChanges()代わりに呼び出すことですzone.run()。これは、コンポーネントツリー全体に対して変更検出を実行しないため、より効率的zone.run()です。
Mark Rajcok

1
同意します。detectChanges()加えた変更がコンポーネントとその子孫に対してローカルである場合にのみ使用します。私の理解では、つまりdetectChanges()コンポーネントとその子孫をチェックしますが、zone.run()そしてApplicationRef.tick()全体のコンポーネントツリーをチェックします。
Mark Rajcok

2
私が探していたものとまったく同じです。使用@Input()しても意味がなく、機能しませんでした。
ヨーロフ

15
なぜこのようなマニュアルが必要なのか、なぜangular2の神々がそのような必要性を残したのか、私には本当にわかりません。@Inputが、コンポーネントが本当に変化するデータに依存する親コンポーネントが言ったことに本当に依存していることを意味しないのですか?それだけではうまくいかないというのは非常に洗練されていないようです。何が欠けていますか?

13
私のために働いた。しかし、これは、角度フレームワークの複雑さで角度フレームワーク開発者が失われていると感じているもう1つの例です。angularをアプリケーション開発者として使用する場合、angularがこれをどのように実行するか、およびそれを実行する方法と、上記のような問題を回避する方法を理解するために、多くの時間を費やす必要があります。ひどい!!
Karl

32

元々は@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());
    }
}

編集.detectChanges()subscibe内で使用すると、破棄されたビューを使用しようとすると問題が発生する可能性があります: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();
    }

}

this.cdRef.detectChanges(); 私の問題を修正しました。ありがとうございました!
mangesh

あなたの提案に感謝しますが、呼び出しを複数回試行した後、エラーが発生します。
shivam srivastava

8

使ってみる @Input() recentDetections: Array<RecentDetection>;

編集:@Input()重要な 理由は、typescript / javascriptファイルの値をビュー(html)にバインドするためです。@Input()デコレータで宣言された値が変更されると、ビューはそれ自体を更新します。場合@Input()@Output()デコレータが変更され、ngOnChanges-eventがトリガされます、そしてビューが新しい値で自分自身を更新します。@Input()willは双方向に値をバインドすると言うかもしれません。

このリンクの入力を角度で検索:詳細については用語集

編集: Angular 2の開発についてさらに学習した後、私は@Input()本当に行うことは解決策ではないことを理解し、コメントで述べたように、

@Input() コンポーネント外部からのデータバインディングによってデータが変更された場合(親のバインドされたデータが変更された場合)にのみ適用されます。コンポーネント内のコードからデータが変更された場合は適用されません。

@Günterの答えを確認すると、問題に対するより正確で正しい解決策になります。この答えはここに残しておきますが、正しい答えはギュンターの答えに従ってください。


2
それだけでした。以前に@Input()アノテーションを見たことがありますが、常にフォーム入力に関連付けられており、ここでそれが必要になるとは思いもしませんでした。乾杯。
マットワトソン

1
@ジョンは追加の説明をありがとう。ただし、追加した内容は、コンポーネント外部からのデータバインディングによってデータが変更された場合(親のバインドされたデータが変更された場合)にのみ適用され、コンポーネント内のコードからデータが変更された場合には適用されません。
ギュンターZöchbauer

@Input()キーワードを使用するときにプロパティバインディングがアタッチされており、そのバインディングにより、ビューで値が確実に更新されます。値はライフサイクルイベントの一部になります。この投稿には、@Input()キーワードに関するいくつかの詳細情報が含まれています
John

あきらめるよ。ここにan @Input()が関係している場所や理由がわかりません。質問では十分な情報が得られません。とにかく我慢してくれてありがとう:)
ギュンター・ツェヒバウアー

3
@Input()問題の根本を隠すより多くの回避策です。問題は、ゾーンと変更検出に関連している必要があります。
魔法製作者2016

2

私の場合、非常によく似た問題がありました。親コンポーネントによって呼び出されている関数内でビューを更新していて、親コンポーネントで@ViewChild(NameOfMyChieldComponent)を使用するのを忘れていました。私はこの愚かな間違いのために少なくとも3時間を失った。つまり、私はこれらの方法を使用する必要はありませんでした

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

1
@ViewChildで子コンポーネントを更新する方法を説明できますか?感謝
ルーカスコロンボ


1

ゾーンと変更検出を処理する代わりに、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引数を取るメソッドを使用してサービスを作成することをお勧めします。

  • exhaustMap上記のコードのように使用して)新しいリクエストを作成します。
  • リクエストエラーを処理します。
  • オフライン時にブラウザが新しいリクエストを行うのを停止します。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.