___ ifadesi kontrol edildikten sonra değişti


287

Neden bu basit yığındaki bileşen?

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message:string = 'loading :(';

  ngAfterViewInit() {
    this.updateMessage();
  }

  updateMessage(){
    this.message = 'all done loading :)'
  }
}

atma:

İSTİSNA: 'App @ 0: 5'teyim {{message}}' ifadesi kontrol edildikten sonra değişti. Önceki değer: 'Yüklüyorum :('. Geçerli değer: 'Tümü yüklemeyi tamamladım :)' [App @ 0: 5] 'de {{message}}

tek yaptığım, görüşüm başlatıldığında basit bir bağlayıcıyı güncellemek mi?


7
Hata hakkında bilmeniz gereken her şeyExpressionChangedAfterItHasBeenCheckedError makalede davranışı ayrıntılı olarak açıklamaktadır.
Max Koretskyi

Stackoverflow.com/questions/39787038/…ChangeDetectionStrategydetectChanges()
Luis Limas

Sadece bir giriş kontrolü olduğunu düşünün ve bir yöntemde veri dolduruyorsunuz ve aynı yöntemde ona bir değer ataıyorsunuz. Derleyici yeni / önceki değerle kesinlikle karışacaktır. Dolayısıyla, bağlayıcı ve nüfus farklı yöntemlerle gerçekleşmelidir.
MAC

Yanıtlar:


294

Drewmoore tarafından belirtildiği gibi, bu durumda uygun çözüm mevcut bileşen için değişiklik tespitini manuel olarak tetiklemektir. Bu, herhangi bir üst bileşenin güncellenmesini sağlayan nesnenin detectChanges()yöntemi ChangeDetectorRef(içe aktarılan angular2/core) veya yöntemi kullanılarak yapılır markForCheck(). İlgili örnek :

import { Component, ChangeDetectorRef, AfterViewInit } from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App implements AfterViewInit {
  message: string = 'loading :(';

  constructor(private cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.message = 'all done loading :)'
    this.cdr.detectChanges();
  }

}

Burada ayrıca her durumda ngOnInit , setTimeout ve enableProdMode yaklaşımlarını gösteren Plunkers'lar bulunmaktadır .


7
Benim durumumda bir model açıyordum. Modal açıldıktan sonra "İfade ___ kontrol edildikten sonra değişti" mesajını gösteriyordu, bu yüzden çözümüm buna eklendi. Cdr.detectChanges (); modumu açtıktan sonra. Teşekkürler!
Jorge Casariego

Cdr özelliği için bildiriminiz nerede? Beyanname cdr : anyaltında böyle bir çizgi veya böyle bir şey görmeyi beklerdim message. Sadece birţeyi özlediđimden endiţeleniyor musun?
CodeCabbie

1
@CodeCabbie yapıcı argümanlarında.
slasky

1
Bu çözüm sorunumu çözdü! Çok teşekkürler! Çok açık ve basit bir yol.
lwozniak

3
Bu, birkaç değişiklikle bana yardımcı oldu. Ben ngFor döngü ile oluşturulan li öğeleri stil vardı. Bir sıralama borusu parametresi olarak kullanılan bir bool güncellenen 'sırala' tıkladıktan sonra liste öğelerinin içinde yayılma rengini değiştirmek gerekiyordu (sıralanan sonuç verilerin bir kopyasıydı, böylece stilleri alamadım sadece ngStyle ile güncellenmiştir). - Yerine 'AfterViewInit' kullanmak yerine, kullandığım 'AfterViewChecked' - Ben de emin yapılan ithalat ve uygulamak AfterViewChecked. not: boruyu 'saf: yanlış' olarak ayarlamak işe yaramadı, bu ekstra adımı eklemek zorunda kaldım (:
Chloe Corrigan

170

İlk olarak, bu istisnanın yalnızca uygulamanızı dev modunda çalıştırdığınızda atılacağını unutmayın (beta-0'dan itibaren varsayılan olarak durum budur): enableProdMode()Uygulamayı önyüklerken çağrı yaparsanız , atılmaz ( bkz. güncellenmiş paket ).

İkincisi, bunu yapmayın, çünkü bu istisna iyi bir nedenle atılmaktadır: Kısacası, dev modundayken, her değişiklik algılamasının hemen ardından, ilkin sonundan bu yana hiçbir bağlantının değişmediğini doğrulayan ikinci bir tur gelir, Bu, değişikliklerin değişiklik algılamasının kendisinden kaynaklandığını gösterir.

Gövdenizde, bağlama {{message}}, ilk değişiklik algılama dönüşünün bir parçası olarak gerçekleşen kancada setMessage()gerçekleşen çağrınızla ngAfterViewInitdeğiştirilir. Bu kendi başına sorunlu değildir - sorun, setMessage()bağlamayı değiştirmesi, ancak yeni bir değişiklik algılama turunu tetiklememesidir; bu, gelecekteki bir değişiklik saptama turu başka bir yerde tetiklenene kadar bu değişikliğin algılanmayacağı anlamına gelir.

Paket servisi olan şey : Bağlamayı değiştiren her şeyin, bir değişiklik algılaması gerçekleştirdiğinde tetiklenmesi gerekir.

Bunun nasıl bir örnek için tüm isteklerine yanıt olarak Güncelleme Tycho çözüm eserleri @ gibi içinde üç yöntem yapın: cevabın @MarkRajcok dikkat çekti. Ama açıkçası, hepsi bana çirkin ve yanlış geliyor, tıpkı ng1'de alıştığımız bir tür hack gibi.

Vardır, Emin olmak için ara sıra bir tercihten başka bir şeye bunları kullanıyorsanız bu kesmek uygun koşullar ancak çok ara sıra esasına, bu tam olarak reaktif doğasını kucaklayan yerine çerçeveyi kavga etmen bir işarettir.

IMHO, daha deyimsel, "Angular2 yol" yaklaşan bu çizgisinde bir şeydir: ( plunk )

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message | async}} </div>`
})
export class App {
  message:Subject<string> = new BehaviorSubject('loading :(');

  ngAfterViewInit() {
    this.message.next('all done loading :)')
  }
}

13
SetMessage () neden yeni bir değişiklik algılama turunu tetiklemiyor? Kullanıcı arayüzündeki bir şeyin değerini değiştirdiğinizde Açısal 2'nin otomatik olarak değişiklik algılamayı tetiklediğini düşündüm.
Danny Libin

4
@drewmoore "Bir bağlamayı değiştiren her şey, bir değişiklik algılamasında tetikleme gerektiriyor". Nasıl? İyi bir uygulama mı? Her şey tek seferde tamamlanmamalı mı?
Daniel Birowsky Popeski

2
@Tycho, gerçekten. O yorum yazdı beri, ben açıklamak başka bir soru Yanıtım çalışma değişimi algılama için 3 yol içerir detectChanges().
Mark Rajcok

4
sadece mevcut soru gövdesinde, çağrılan yöntemin adlandırılır updateMessage, değilsetMessage
superjos

3
@Daynil, yorumda verilen blogu okuyana kadar aynı duyguyu yaşadım: blog.angularindepth.com/… Bunun neden manuel olarak yapılması gerektiğini açıklıyor. Bu durumda, açısalın değişim algılamasının bir yaşam döngüsü vardır. Bir şey bu yaşam döngüleri arasında bir değer değiştiriyorsa, güçlü bir değişiklik algılamasının çalıştırılması gerekir (veya bir sonraki olay döngüsünde çalışan ve değişiklik algılamayı yeniden tetikleyen bir settimeout).
Mahesh

51

ngAfterViewChecked() benim için çalıştı:

import { Component, ChangeDetectorRef } from '@angular/core'; //import ChangeDetectorRef

constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
   //your code to update the model
   this.cdr.detectChanges();
}

Belirtildiği gibi, açısalın iki fazlı olan değişim algılama döngüsü, çocuğun görünümü değiştirildikten sonra değişiklikleri tespit etmek zorundadır ve bunu yapmanın en iyi yolunun, açısal kendisi tarafından sağlanan yaşam döngüsü kancasını kullanmak ve açısal olarak manuel olarak sormak olduğunu hissediyorum. değişikliği tespit edip bağlayın. Benim kişisel görüşüm, bunun uygun bir cevap gibi görünmesi.
Joey587

1
Bu iş benim için, hiyerarşi dinamik bileşenini birlikte yüklemek için.
Mehdi Daustany

47

Bunu açısal çekirdekten ChangeDetectionStrategy ekleyerek çözdüm.

import {  Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'page1',
  templateUrl: 'page1.html',
})

Bu benim için çalıştı. Acaba bu ve ChangeDetectorRef kullanma arasındaki fark nedir
Ari

1
Hmm ... Daha sonra değişiklik dedektörünün modu başlangıçta CheckOnce( dokümantasyon ) olarak ayarlanacak
Alex Klaus

Evet, hata / uyarı gitti. Ancak büyük olan 5-7 saniyelik farktan daha fazla yükleme süresi alıyor.
Kapil Raghuwanshi

1
@KapilRaghuwanshi Yüklemeye this.cdr.detectChanges();çalıştığınız her şeyin peşinden koşun . Çünkü değişiklik tespiti tetiklemiyor olabilir
Harshal Carpenter

36

ngOnInitÜye değişkenini değiştirdiğiniz için kullanamaz messagemısınız?

Bir alt bileşene yapılan bir başvuruya erişmek istiyorsanız @ViewChild(ChildComponent), bununla birlikte beklemeniz gerekir ngAfterViewInit.

Kirli bir düzeltme, bir updateMessage()sonraki olay döngüsünde örneğin setTimeout ile çağırmaktır .

ngAfterViewInit() {
  setTimeout(() => {
    this.updateMessage();
  }, 1);
}

4
Kodumu ngOnInit yöntemine değiştirmek benim için çalıştı.
Faliorn

29

Bunun için yukarıdaki cevapları denedim birçok Angular'ın en son sürümünde çalışmıyor (6 veya üstü)

İlk bağlama yapıldıktan sonra değişiklik yapılması gereken Malzeme kontrolü kullanıyorum.

    export class AbcClass implements OnInit, AfterContentChecked{
        constructor(private ref: ChangeDetectorRef) {}
        ngOnInit(){
            // your tasks
        }
        ngAfterContentChecked() {
            this.ref.detectChanges();
        }
    }

Cevabımı eklemek, bazılarının belirli sorunu çözmesine yardımcı olur.


Bu aslında benim durumum için çalıştı, ama bir yazım hatası var, AfterContentChecked uyguladıktan sonra ngAfterViewInit değil, ngAfterContentChecked çağırmak gerekir.
Tomas Lukac

Şu anda 8.2.0 sürümündeyim :)
Tomas Lukac

1
@ TomášLukáč Cevabınız için teşekkürler, cevabımı güncelledim (y)
MarmiK

1
Yukarıdakilerin hiçbiri işe yaramazsa bu yanıtı öneririm.
Amir Choubani

DOM yeniden hesaplandığında veya yeniden kontrol edildiğinde afterContentChecked çağrılır. Açısal versiyon 9'dan gelen
Imran Faruqi

24

Hata hakkında bilmeniz gereken her şeyExpressionChangedAfterItHasBeenCheckedError makalede davranışı ayrıntılı olarak açıklamaktadır.

Kurulumunuzdaki sorun, ngAfterViewInitdeğişiklik tespiti işlenmiş DOM güncellemelerinden sonra yaşam döngüsü kancasının yürütülmesidir. Ve bu kancadaki şablonda kullanılan özelliği etkili bir şekilde değiştiriyorsunuz, bu da DOM'un yeniden oluşturulması gerektiği anlamına geliyor:

  ngAfterViewInit() {
    this.message = 'all done loading :)'; // needs to be rendered the DOM
  }

ve bu başka bir değişiklik algılama döngüsü gerektirir ve Angular by design, sadece bir özet döngüsünü çalıştırır.

Temel olarak nasıl düzelteceğiniz iki alternatifiniz var:

  • kullanarak ya uyumsuz özelliği güncelleştirmek setTimeout, Promise.thenya da asenkron gözlemlenebilir şablonunda başvuruda

  • özellik güncellemesini DOM güncellemesinden önce bir kancada gerçekleştirin - ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.


Makalelerinizi okuyun: blog.angularindepth.com/… , Yakında başka bir blog.angularindepth.com/… okuyacak . Yine de bu sorunun çözümü bulunamamıştır. ngDoCheck veya ngAfterContentChecked yaşam döngüsü kancasını ekleyip bu this.cdr.markForCheck () 'yi eklersem ne olacağını söyleyebilir misiniz? (cdD for ChangeDetectorRef) içine yerleştirin. Yaşam döngüsü kancası ve sonraki kontrol tamamlandıktan sonra değişiklikleri kontrol etmek doğru bir yol değil mi?
Harsimer

9

Sadece mesajınızı sağ yaşam döngüsü kancasında güncellemeniz gerekir, ngAfterContentCheckedbunun yerine ngAfterViewInitngAfterViewInit'te değişken mesaj için bir kontrol başlatıldı, ancak henüz bitmedi.

bkz. https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#

kod sadece olacak:

import { Component } from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message: string = 'loading :(';

  ngAfterContentChecked() {
     this.message = 'all done loading :)'
  }      
}

Plunker ile ilgili çalışma demosuna bakın.


Gözlemlenebilir tarafından doldurulan @ViewChildren()uzunluk tabanlı bir sayaç kombinasyonu kullanıyordum . Benim için işe yarayan tek çözüm buydu!
msanford

2
Kombinasyonu ngAfterContentCheckedve ChangeDetectorRefyukarıda benim için çalıştı. On ngAfterContentChecked-this.cdr.detectChanges();
Kunal Dethe

7

Bu hata, mevcut değer başlatıldıktan hemen sonra güncellendiği için geliyor. Bu nedenle, mevcut değer DOM'da oluşturulduktan sonra yeni değeri güncelleyecekseniz, o zaman iyi çalışır. Bu makalede belirtildiği gibi Açısal Hata Ayıklama "İfade kontrol edildikten sonra değişti"

örneğin kullanabilirsiniz

ngOnInit() {
    setTimeout(() => {
      //code for your new value.
    });

}

veya

ngAfterViewInit() {
  this.paginator.page
      .pipe(
          startWith(null),
          delay(0),
          tap(() => this.dataSource.loadLessons(...))
      ).subscribe();
}

Gördüğünüz gibi ben setTimeout yönteminde zaman bahsetmedim. Bir JavaScript API değil, tarayıcı tarafından sağlanan API olduğundan, bu tarayıcı yığınında ayrı olarak çalışacak ve çağrı yığını öğeleri bitene kadar bekleyecektir.

Tarayıcı API'sı kavramı Youtube videosundan birinde Philip Roberts tarafından nasıl açıklanır (Olay döngüsü nedir?).


4

NgOnInt () - Metodunda updateMessage () çağrınızı da yapabilirsiniz, en azından benim için çalışıyor

ngOnInit() {
    this.updateMessage();
}

RC1'de bu istisnayı tetiklemez


3

Ayrıca rxjs Observable.timerişlevini kullanarak bir zamanlayıcı oluşturabilir ve ardından aboneliğinizdeki mesajı güncelleyebilirsiniz:                    

Observable.timer(1).subscribe(()=> this.updateMessage());

2

NgAfterViewInit () çağrıldığında kodunuz güncellendiğinden bir hata atar . NgAfterViewInit gerçekleştiğinde başlangıç ​​değerinizin değiştiği, bunu ngAfterContentInit () ' de çağırırsanız hata atmayacağı anlamına gelir .

ngAfterContentInit() {
    this.updateMessage();
}

0

Yeterince üne sahip olmadığım için @Biranchi'nin yazısına yorum yapamadım, ancak benim için sorunu düzeltti.

Dikkat edilmesi gereken bir şey! Ekliyorsanız changeDetection: ChangeDetectionStrategy.OnPush bileşeni üzerinde çalışmaya vermedi ve onun bir alt bileşen (dilsiz bileşeni) da ebeveyne eklemeyi deneyin.

Bu hatayı düzeltti, ama bunun yan etkilerinin ne olduğunu merak ediyorum.


0

Datatable ile çalışırken benzer bir hata aldım. Ne olur? * NgFor başka bir * ng için kullandığınızda, datatable için açısal değişim döngüsünü kesiştiği için bu hatayı atın. SO datatable içinde datatable kullanmak yerine, normal bir tablo kullanın veya mf.data yerine dizi adı. Bu iyi çalışıyor.


0

Bence en basit çözüm aşağıdaki gibi olabilir:

  1. İşlev veya ayarlayıcı aracılığıyla bir değişkene değer atamanın bir uygulamasını yapın.
  2. (static working: boolean)Bu işlevin bulunduğu sınıfta bir sınıf değişkeni oluşturun ve işlevi her aradığınızda, istediğinizi doğru yapın. İşlev içinde, çalışma değeri doğruysa, hiçbir şey yapmadan hemen geri dönün. yoksa istediğiniz görevi gerçekleştirin. Görev tamamlandıktan sonra, yani kod satırının sonunda veya değerleri atamayı tamamladığınızda abone olma yöntemi içinde bu değişkeni false olarak değiştirdiğinizden emin olun!
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.