Angular 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) видно, что объект recentDetections обновляется, но таблица в представлении никогда не изменяется, если я не перезагружаю страницу.

Я изо всех сил пытаюсь понять, что я делаю неправильно. Может ли кто-нибудь помочь?

Ответ 1

Возможно, код в вашем сервисе как-то вырывается из зоны углов. Это нарушает обнаружение изменений. Это должно работать:

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());
    }
}

Для других способов вызова обнаружения изменений см. Запуск Angular2 вручную изменить обнаружение

Альтернативные способы вызова обнаружения изменений:

ChangeDetectorRef.detectChanges()

для немедленного запуска обнаружения изменений для текущего компонента и его childrend

ChangeDetectorRef.markForCheck()

включить текущий компонент в следующий раз, когда Angular запускает обнаружение изменений

ApplicationRef.tick()

для запуска обнаружения изменений для всего приложения

Ответ 2

Попробуйте использовать @Input() recentDetections: Array<RecentDetection>;

EDIT: Причина, почему @Input() важна, заключается в том, что вы хотите связать значение в файле typescript/javascript с представлением (html). Представление обновится, если значение, объявленное с помощью декоратора @Input(), будет изменено. Если изменится декоратор @Input() или @Output(), появится ngOnChanges -event, и представление обновится с новым значением. Вы можете сказать, что @Input() будет двусторонне связывать значение.

найти вход по этой ссылке angular: glossary для получения дополнительной информации

EDIT:, узнав больше о Angular 2, я понял, что выполнение @Input() действительно не является решением, и, как упоминалось в комментариях,

@Input() применяется только тогда, когда данные изменяются привязкой данных из вне компонента (связанные данные в родительском измененном), а не когда данные изменяются из кода внутри компонента.

Если вы посмотрите @Günter ответ, это более точное и правильное решение проблемы. Я по-прежнему буду держать этот ответ здесь, но, пожалуйста, следуйте правилу ответа Гюнтера.

Ответ 3

Первоначально это ответ в комментариях от @Mark Rajcok, но я хочу разместить его здесь как проверенный и работающий как решение, используя ChangeDetectorRef, я вижу здесь хороший момент:

Другой альтернативой является ввод ChangeDetectorRef и вызов cdRef.detectChanges() вместо zone.run(). Это может быть больше эффективный, поскольку он не будет запускать обнаружение изменений на протяжении всего дерево компонентов, например zone.run(). - Марк Райкок

Таким образом, код должен выглядеть следующим образом:

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());
    }
}

Edit: Использование .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();
    }

}