Angular 2 - Model değişikliklerinden sonra güncellenmeyen görünüm


129

Birkaç saniyede bir REST api çağıran ve bazı JSON verilerini geri alan basit bir bileşenim var. Günlük ifadelerimden ve döndürülen JSON verilerinin değiştiğini ve modelimin güncellendiğini ağ trafiğinden görebiliyorum, ancak görünüm değişmiyor.

Bileşenim şöyle görünüyor:

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

Benim görüşüm şuna benziyor:

<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>

Sonuçlardan console.log(this.recentDetections[0].macAddress), RecentDetections nesnesinin güncellenmekte olduğunu görebiliyorum, ancak sayfayı yeniden yüklemediğim sürece görünümdeki tablo asla değişmiyor.

Burada neyi yanlış yaptığımı anlamaya çalışıyorum. Biri yardım edebilir mi?


Kodu daha temiz ve daha az karmaşık hale
getirmenizi

Yanıtlar:


216

Hizmetinizdeki kod bir şekilde Angular'ın bölgesinden çıkmış olabilir. Bu, değişim tespitini bozar. Bu çalışmalı:

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

Değişiklik algılamayı başlatmanın diğer yolları için bkz.Angular'da değişiklik algılamasını manuel olarak tetikleme

Değişiklik algılamayı başlatmanın alternatif yolları şunlardır:

ChangeDetectorRef.detectChanges()

mevcut bileşen ve alt bileşenleri için hemen değişiklik algılamayı çalıştırmak

ChangeDetectorRef.markForCheck()

bir dahaki sefere geçerli bileşeni dahil etmek için Açısal çalışmalar değişiklik algılaması

ApplicationRef.tick()

tüm uygulama için değişiklik algılamayı çalıştırmak


9
Başka bir alternatif, ChangeDetectorRef'i enjekte etmek ve cdRef.detectChanges()yerine zone.run(). Bu, daha verimli olabilir, çünkü zone.run()yaptığı gibi tüm bileşen ağacında değişiklik algılamasını çalıştırmayacaktır .
Mark Rajcok

1
Katılıyorum. Yalnızca detectChanges()yaptığınız herhangi bir değişiklik bileşen ve onun soyundan gelenler için yerelse kullanın. Anladığım kadarıyla, bu detectChanges()bileşen ve onun soyundan gelenleri kontrol edecek , ancak zone.run()ve ApplicationRef.tick()tüm bileşen ağacını kontrol edecek.
Mark Rajcok

2
tam olarak aradığım şey .. kullanmak @Input()mantıklı gelmedi ve işe yaramadı.
yorch

15
Neden bu kadar kılavuz malzemeye ihtiyacımız olduğunu veya açısal2 tanrıların neden bu kadar gerekli olduğunu anlamıyorum. @Input, bileşenin gerçekten de üst bileşenin değişen verisine bağlı olduğunu söylediği şeye gerçekten bağlı olduğu anlamına gelmiyor? Sadece işe yaramaması son derece uygunsuz görünüyor. Neyi kaçırıyorum?

13
Benim için çalıştı. Ancak bu, neden açısal çerçeve geliştiricilerinin açısal çerçevenin karmaşıklığı içinde kaybolduğunu hissettiğim başka bir örnek. Bir uygulama geliştiricisi olarak açısal kullanırken, bunu ne kadar açısal yaptığını veya yaptığını ve yukarıda sorgulandığı gibi çözümlerin nasıl çözüleceğini anlamak için çok zaman harcamanız gerekir. Terrrible !!
Karl

32

Aslında @Mark Rajcok'un yorumlarında bir cevap, ancak buraya test edilmiş ve ChangeDetectorRef kullanarak bir çözüm olarak çalışmış bir çözüm olarak buraya yerleştirmek istiyorum, burada iyi bir nokta görüyorum:

Diğer bir alternatif ise enjekte etmek ChangeDetectorRefve cdRef.detectChanges()yerine aramaktır zone.run(). Bu, daha verimli olabilir, çünkü zone.run()yaptığı gibi tüm bileşen ağacında değişiklik algılamasını çalıştırmayacaktır . - Mark Rajcok

Yani kod şöyle olmalıdır:

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

Düzenleme : .detectChanges()İç abonenin kullanılması, yok edilmiş bir görünümü kullanma girişimi sorununa neden olabilir : Det Değişiklikleri

unsubscribeÇözmek için bileşeni yok etmeden önce yapmanız gerekir , böylece kodun tamamı şöyle olacaktır:

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 (); sorunumu çözdü. Teşekkür ederim!
mangesh

Öneriniz için teşekkürler, ancak birden fazla arama denemesinden sonra hata alıyorum: başarısızlık: Hata: ViewDestroyedError: Yok edilmiş bir görünümü kullanma girişimi: Detechanges Benim için zone.run () bu hatadan çıkmama yardımcı oluyor.
shivam srivastava

8

Kullanmaya çalışmak @Input() recentDetections: Array<RecentDetection>;

DÜZENLEME: Bunun@Input()önemliolmasının nedeni, typecript / javascript dosyasındaki değeri görünüme (html) bağlamak istemenizdir. Görünüm,@Input()dekoratörlebildirilen bir değerdeğiştirilirsekendini güncelleyecektir. Bir@Input()veya@Output()dekoratör değiştirilirse,ngOnChanges-event tetiklenir ve görünüm kendisini yeni değerle günceller. @Input()Değeri iki yönlü bağlayacağınısöyleyebilirsiniz.

Daha fazla bilgi için bu bağlantıda açısal: sözlük ile Giriş arayın

DÜZENLEME: Angular 2 geliştirme hakkında daha fazla şey öğrendikten sonra@Input(), bir çözümyapmanıngerçektenbirçözüm olmadığınıanladımve yorumlarda belirtildiği gibi,

@Input() yalnızca veriler bileşenin dışından veri bağlama tarafından değiştirildiğinde (üstteki bağlı veriler değiştirildiğinde), veriler bileşenin içindeki koddan değiştirildiğinde geçerli değildir.

@ Günter'in cevabına bir göz atarsanız, soruna daha doğru ve doğru bir çözümdür. Yine de bu cevabı burada tutacağım ama lütfen doğru cevap olarak Günter'in cevabına uyun.


2
İşte buydu. @Input () ek açıklamasını daha önce görmüştüm ama her zaman form girdileriyle ilişkilendirmiştim, burada ihtiyacım olacağını hiç düşünmemiştim. Şerefe.
Matt Watson

1
@John ek açıklama için teşekkürler. Ancak eklediğiniz şey, veriler bileşenin içindeki koddan değiştirildiğinde değil, yalnızca veriler bileşenin dışından veri bağlama ile değiştirildiğinde (üstteki bağlı veriler değiştiğinde) geçerlidir.
Günter Zöchbauer

@Input()Anahtar kelime kullanılırken eklenen bir özellik bağlaması vardır ve bu bağlama, değerin görünümde güncellendiğinden emin olur. değer bir yaşam döngüsü olayının parçası olacaktır. Bu mesaj hakkında biraz daha bilgi içerir @Input()anahtar kelime
John

Vazgeçeceğim. @Input()Burada an'ın nerede yer aldığını veya neden olması gerektiğini anlamıyorum ve soru yeterli bilgi sağlamıyor. Yine de sabrınız için teşekkürler :)
Günter Zöchbauer

3
@Input()daha çok sorunun kökenini gizleyen bir çözümdür. Sorunlar bölgeler ve değişiklik tespiti ile ilgili olmalıdır.
magiccrafter

2

Benim durumumda çok benzer bir sorun yaşadım. Görünümümü bir üst bileşen tarafından çağrılan bir işlevin içinde güncelliyordum ve üst bileşenimde @ViewChild (NameOfMyChieldComponent) kullanmayı unuttum. Bu aptalca hata için en az 3 saat kaybettim. yani: Bu yöntemlerden herhangi birini kullanmam gerekmiyordu :

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

1
@ViewChild ile çocuk bileşeninizi nasıl güncellediğinizi açıklayabilir misiniz? teşekkürler
Lucas Colombo

Sanırım ebeveyn, işlenmemiş ve yalnızca bellekte var olan bir çocuk sınıfının bir yöntemini çağırıyordu
glukki

1

Bölgelerle uğraşmak ve algılamayı değiştirmek yerine AsyncPipe'ın karmaşıklığı yönetmesine izin verin. Bu, gözlemlenebilir abonelik, abonelik iptali (bellek sızıntılarını önlemek için) ve Angular omuzlarda değişiklik algılamasını koyacaktır.

Yeni isteklerin sonuçlarını ortaya çıkaracak bir gözlemlenebilir yapmak için sınıfınızı değiştirin:

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

Ve görünümünüzü AsyncPipe kullanacak şekilde güncelleyin:

<tr *ngFor="let detected of recentDetections$ | async">
    ...
</tr>

intervalTartışmayı gerektirecek bir yöntemle hizmet oluşturmanın daha iyi olduğunu eklemek isterim ve:

  • yeni istekler oluşturun ( exhaustMapyukarıdaki kodda olduğu gibi);
  • istek hatalarını işlemek;
  • tarayıcının çevrimdışıyken yeni isteklerde bulunmasını durdurun.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.