Angular: ExpressionChangedAfterItHasBeenCheckedError: Ekspresi telah berubah setelah diperiksa pada ng 4

Dibuat pada 16 Jun 2017  ·  201Komentar  ·  Sumber: angular/angular

Saya mengirimkan ...


[ ] Regression (behavior that used to work and stopped working in a new release)
[X ] Bug report #14748 
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Perilaku saat ini


Setelah menggunakan objek yang dapat diamati di template saya menggunakan async, saya menerima:
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Ekspresi telah berubah setelah diperiksa. Nilai sebelumnya: ''. Nilai saat ini: 'sesuatu'

Perilaku yang diharapkan

Beri tahu kami tentang lingkungan Anda

<br i="15"/>
Angular version: 4.2.2

<p i="16">Browser:</p>

<ul i="17">
<li i="18">[X] Chrome (desktop) version Version 58.0.3029.110 (64-bit)</li>
</ul>

<p i="19">For Tooling issues:</p>

<ul i="20">
<li i="21">Node version: v6.10.3</li>
<li i="22">Platform: Mac</li>
</ul>
core regression bufix

Komentar yang paling membantu

Saya mendapatkan masalah yang sama setelah memutakhirkan dari 4.1.3 ke 4.2.3. Menggunakan setTimeout memperbaiki masalah.

dari:

PanictUtil.getRequestObservable (). Subscribe (data => this.requesting = data);

untuk:

PanictUtil.getRequestObservable (). Subscribe (data => setTimeout (() => this.requesting = data, 0));

Semua 201 komentar

Punya masalah yang sama dengan menggunakan redux. Ini adalah tautan ke masalah yang saya posting di repositori mereka. Ketika pada sudut 2 tidak ada kesalahan tetapi pada sudut 4.2.2 saya mendapatkan ekspresi berubah setelah itu diperiksa kesalahannya.
https://github.com/angular-redux/store/issues/428

Masalahnya muncul pada saya pada komponen saya saat beralih dari 4.1.3 ke 4.2.x. Mengembalikan ke 4.1.3 memperbaiki masalah bagi saya, tetapi itu bukan sesuatu yang ingin saya simpan selamanya.

Saya dapat mereproduksi pada proyek minimal, saya masih mencoba mencari cara untuk memperbaiki regresi (mungkin) ini. Tapi sepertinya pemeriksaan kotor telah diterapkan di dev. mode dari 4.2.x. Tapi changelog tidak jelas tentang itu. Tip apapun diterima.

Saya mendapatkan masalah yang sama setelah memutakhirkan dari 4.1.3 ke 4.2.3. Menggunakan setTimeout memperbaiki masalah.

dari:

PanictUtil.getRequestObservable (). Subscribe (data => this.requesting = data);

untuk:

PanictUtil.getRequestObservable (). Subscribe (data => setTimeout (() => this.requesting = data, 0));

@jingglang Bisakah Anda mencoba mereproduksi masalah di http://plnkr.co/edit/tpl : AvJOMERrnz94ekVua0u5 dengan kode seminimal mungkin?

Saya mendapatkan masalah yang sama - ditingkatkan dari 2.4 ke 4.2, dan beberapa logika sembunyikan / tampilkan saya sekarang rusak. Ini adalah logika gaya akordeon, di mana visibilitas bergantung pada perbandingan panel saat ini dengan yang dapat diamati yang menahan panel mana yang harus terlihat (antara lain). Komponen menggunakan @select untuk mendapatkan observable, dan itu terikat dengan | async di template - berfungsi seperti pesona di 2.4, sekarang dapatkan ExpressionChangedAfterItHasBeenCheckedError dan panel tidak bersembunyi dan ditampilkan pada klik pertama, hanya pada yang kedua.

Saya mendapatkan masalah yang sama saat memperbarui 4.1.3 ke 4.2.2.

Tapi, saya menemukan solusi.
Injeksi ChangeDetectionRef , dan panggil fungsi detectionChanges() pada titik kesalahan.

constructor(private cdr: ChangeDetectionRef) {
}

onChange(): void {
  this.val += 1; // <- get error after
  this.cdr.detectionChanges();
}

Saya rasa ini adalah masalah dengan perubahan mengikuti yang dibuat di 4.2.

https://github.com/angular/angular/pull/16592

untuk mengatasi masalah ini: https://github.com/angular/angular/issues/14321

Apakah Anda memiliki konten di dalam tag ng-content ?

Saya memiliki masalah yang sama dengan formulir di dalam tag ng-content yang sekarang mengalami kesalahan dengan kesalahan ContentChangedAfter.

Saya mendapatkan masalah yang sama saat memperbarui 4.1.3 ke 4.2.0 atau lebih tinggi

Saya melampirkan proyek kecil untuk mereproduksi bug

app-error.zip

Menindaklanjuti, saya menurunkan versi ke 4.1.3 (seperti yang disarankan di atas) dan kesalahan hilang, jadi apa pun konfliknya, ini khusus untuk 4.2. Saya tidak punya waktu untuk menyusun plunkr minggu ini (atau berikutnya), tetapi tampaknya ini terkait dengan satu tindakan yang memperbarui dua observable terpisah di toko, yang masing-masing berdampak pada UI. Salah satu pembaruan UI terjadi, yang lainnya menyebabkan pengecualian.

Hmmm,
Kembalikan ke versi 4.1.3 pasti akan menyelesaikan masalah dan juga menerapkan batas waktu seperti yang ditunjukkan oleh @jingglang.

hai @tyts. Saya membuat http://plnkr.co/edit/XAxNoV5UcEJOvsAbeLHT untuk masalah ini. semoga membantu.

solusi yang diberikan oleh @jingglang berfungsi (untuk saya) di 4.2.0 atau lebih tinggi
ATAU juga mengubah acara pemancar anak menjadi konstruktornya tidak menimbulkan kesalahan lagi

@umens Anda memperbarui komponen induk setelah diperiksa sehingga Anda tidak menyimpan aliran data searah

@ alexzuza bagaimana saya bisa mencapai itu? Saya selalu melakukannya dengan cara ini tanpa kesalahan. Mengapa menempatkan SetTimout tidak menimbulkan kesalahan lagi. (Saya memperbarui plunkr) Saya ragu itu mengganggu siklus hidup atau apakah itu?

hai @umens terima kasih atas waktunya. Masalahnya adalah apa yang dikatakan @alexzuza : Anda memperbarui bidang komponen induk setelah dicentang. Kode Anda kira-kira sama dengan ini: http://plnkr.co/edit/ksOdACtXScZKw3VRAYTm?p=preview (perhatikan saya menghapus layanan, untuk mengurangi kode).
Mengapa ini berhasil? Mungkin secara tidak sengaja versi lama sudut atau Rxjs memiliki bug di sini. Bisakah Anda menjelaskan versi mana yang digunakan? Atau bahkan dimasukkan ke dalam plunker yang berfungsi?

Mengapa menempatkan SetTimout tidak menimbulkan kesalahan lagi.

Karena Anda mengubah bidang secara asinkron, yang mengikuti aliran data satu arah.

Mari kita pertimbangkan dari sebaliknya: Mengapa kesalahan dimunculkan? Pemeriksaan pertama Deteksi Perubahan dimulai dari atas ke bawah. Ia memeriksa app.toolsConfig nilai terikat ke template. Kemudian merender <child-cmp> yang secara sinkron memperbarui app.toolsConfig . Rendering selesai. Sekarang menjalankan pemeriksaan kedua (hanya mode dev) dari Deteksi Perubahan untuk memastikan bahwa status aplikasi stabil. Tapi app.toolsConfig sebelum merender anak tidak sama dengan app.toolsConfig setelahnya. Semua ini terjadi selama "putaran", "siklus" Deteksi Perubahan.

baik. terima kasih atas jawaban rinci. Saya tidak dapat menemukan informasi tentang kapan siklus hidup komponen anak terjadi.
untuk versi "berfungsi" sebelumnya yang saya gunakan:
@ angular / *: 4.1.1 (kecuali compiler-cli -> 4.1.0)
rxjs: 5.3.1

@umens di sini adalah kesalahan yang direproduksi dengan versi lama yang Anda sebutkan: http://plnkr.co/edit/kfoKmigXzFXwOGb2wyT1?p=preview. Ini melempar, jadi mungkin beberapa ketergantungan pihak ke-3 mempengaruhi perilaku atau instruksi reproduksi tidak lengkap?

tidak tahu.
ini yarn.lock saya jika Anda tertarik untuk melangkah lebih jauh: https://pastebin.com/msARLta1
tetapi saya tidak tahu apa yang bisa berbeda

Saya melihat kesalahan serupa "ExpressionChanged ..." setelah beralih dari 4.1.2 ke 4.2.3 .

Dalam kasus saya, saya memiliki dua komponen yang berinteraksi melalui layanan. Layanan ini disediakan melalui modul utama saya, dan ComponentA dibuat sebelum ComponentB .

Di 4.1.2 , sintaks berikut bekerja dengan baik tanpa kesalahan:

Template KomponenA:

<ul *ngIf="myService.showList">
...

Kelas KomponenB:

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

Di 4.2.3 , saya harus memodifikasi template ComponentA sebagai berikut untuk menghindari kesalahan "ExpressionChanged ...":

Template KomponenA:

<ul [hidden]="!myService.showList">
...

Saya masih mencoba mencari tahu mengapa ini baru saja menjadi masalah, dan mengapa peralihan dari *ngIf menjadi [hidden] berfungsi.

Saya memiliki masalah yang sama, dan itu terjadi sebagian besar ketika saya menggunakan pipa asinkron seperti ini:
<div>{{ (obs$ |async).someProperty }}</div>
Jika saya menggunakannya seperti ini:

<div *ngIf="obs$ | async as o">
  <div>{{ o.someProperty }}</div>
</div>

Kemudian kesalahan tersebut hilang, jadi saya tidak begitu yakin itu hanya karena banyak orang membuat kesalahan yang tidak terdeteksi sebelumnya: Saya pikir ada bug yang diperkenalkan di 4.2…

Masalah yang sama bagi saya. Masalahnya sepertinya berasal dari @ angular / router

Saya juga sampai pada kesimpulan itu terkait dengan router. Sudah mencoba memposting sebanyak kemarin, tetapi sepertinya postingan tersebut gagal. Saya menyelidiki lebih lanjut dan menyederhanakan kode untuk melihat apakah saya dapat melacak kesalahan tersebut. Kode berikut berfungsi dengan sempurna ketika sumber utama tindakan adalah klik pada header panel, tetapi gagal dengan kesalahan ExpressionChanged saat dipicu melalui perutean.

<span class="glyphicon pull-right" [ngClass]="{'glyphicon-chevron-up' : (ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes], 'glyphicon-chevron-down' : !((ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes])}"></span>

Ini membantu saya memecahkan masalah! Anda harus secara eksplisit memicu perubahan pada induknya.

Seperti @acidghost , saya dapat mengatasi masalah ini dengan menjalankan ChangeDetectorRef#detectChanges di AfterViewInit dari komponen induk saya.

Jika Anda memiliki plunkr repro, saya bersedia memeriksanya karena kami memiliki beberapa keluhan serupa tentang gitter dan selalu berakhir menjadi masalah pengguna.

Tidak dapat memastikan apakah bug diperkenalkan di 4.2 atau jika bug itu lebih merupakan kurangnya kesalahan di <4.2 dan diselesaikan di 4.2.

Kesalahan: ExpressionChangedAfterItHasBeenCheckedError
larutan:
componentRef.changeDetectorRef.detectChanges ();

Di bawah ini adalah bagian dari kode saya (komponen yang dibuat secara dinamis):
componentRef = viewContainerRef.createComponent (componentFactory); // komponen dibuat
(componentRef.instance) .data = input_data; // mengubah data komponen secara manual di sini
componentRef.changeDetectorRef.detectChanges (); // itu akan menghasilkan deteksi perubahan

Untuk komponen sederhana, cukup google cara mengakses "componentRef" dan kemudian jalankan
componentRef.changeDetectorRef.detectChanges ();
setelah mengatur / mengubah data / model komponen.

Solusi 2:
Saya sekali lagi mendapatkan kesalahan ini saat membuka dialog "this.dialog.open (DialogComponent)".
jadi meletakkan kode dialog terbuka ini di dalam fungsi dan kemudian menerapkan setTimeout () pada fungsi itu menyelesaikan masalah.
ex:
openDialog () {
biarkan dialogRef = this.dialog.open (DialogComponent);
}
ngOnInit (): void {
setTimeout (() => this.openDialog (), 0);
}
meskipun sepertinya hack .. bekerja !!!

Sejak 4.2 saya memiliki masalah yang sama dan tidak ada solusi atau solusi yang berhasil untuk saya :( Saya membuat komponen secara dinamis dengan pabrik komponen dalam metode ngAfterViewInit. Komponen ini menggunakan arahan yang memiliki bidang @Input() . Saya mengikat ini menjadi string statis. Tampaknya bidang @Input() dari direktif tidak terdefinisi hingga tampilan komponen dibuat dan diperiksa dan baru kemudian diinisialisasi dengan string statis.

https://plnkr.co/edit/E7wBQXm8CVnYypuUrPZd?p=info

staticComponent.ts

export class StaticComponent implements AfterViewInit {
  @ViewChild('dynamicContent', {read: ViewContainerRef})
  dynamicContent: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  ngAfterViewInit(): void {
    const componentFactory: ComponentFactory<DynamicComponent> = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
    this.dynamicContent.createComponent(componentFactory);
  }
}

test.directive.ts

@Directive({
  selector: '[testDirective]'
})
export class TestDirective {
  @Input()
  testDirective: string;
}

dynamic.component.ts

@Component({
  selector: 'dynamic-component',
  template: `<div [testDirective]="'test'">XXX</div>`
})
export class DynamicComponent {
}

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Ekspresi telah berubah setelah diperiksa. Nilai sebelumnya: 'undefined'. Nilai saat ini: 'test'. Sepertinya tampilan telah dibuat setelah induk dan turunannya diperiksa kotor. Apakah itu telah dibuat dalam hook deteksi perubahan?

Saya agak bingung membaca utas ini. Apakah ini bug yang diketahui, yang akan diselesaikan dalam rilis mendatang? Atau, apakah ini perilaku yang diharapkan? Jika ya, apa solusinya? Terima kasih.

Tidak tahu apakah itu terkait atau tidak, tapi saya benci pesan ini dengan penuh semangat! ini HANYA muncul untuk saya saat unit testing dengan perintah 'ng test' di luar kotak. Saya kira saya bisa saja bingung tentang cara berhasil mengejek layanan sederhana. Juga terkejut tidak ada orang lain yang melihat ini dalam pengujian, tidak dapat menemukan pengujian apa pun yang terkait. Saya akan mengamati topik ini dan setiap penelusuran untuk pengujian unit ExpressionChangedAfterItHasBeenCheckedError +.

Saya telah mencoba untuk menyusun sebuah plnkr, dan merasa sangat sulit untuk mereproduksi kesalahan tersebut - namun, debugging yang ekstensif, saya pikir, membuat saya sedikit lebih dekat untuk melihat apa yang salah (meskipun tidak memutuskan apakah itu bug, atau kesalahan saya).

Saya membuat aplikasi sampel kecil menggunakan versi pengurangan dari reduksi yang sama yang digabungkan dalam urutan yang sama seperti di aplikasi nyata, dengan routerReducer terakhir. Yang membuat saya frustrasi, kesalahan itu tidak terjadi. Jadi saya menetapkan breakpoint dan mengamati aliran tindakan melalui reduksi. Apa yang saya temukan adalah dua aplikasi mengirimkan tindakan dalam urutan terbalik.

Peredam UI saya mengelola visibilitas, dan merupakan sumber perubahan status yang memicu error. Berikut adalah versi pengurangan peredam, yang menunjukkan tindakan yang relevan:


export const userInterfaceReducer = (state = INITIAL_UI(), action: any) => {
    switch (action.type) {
        case IngredientActions.LIST_INGREDIENTS:
            if (action.hideTypes) {
                return Object.assign(state, { visiblePanel: VisiblePanel.Ingredients });
            }
            return state;
        default:
            return state;
    }
}

Saat saya menjalankan ini di aplikasi pengujian kecil saya, ini berfungsi - pertama, tindakan LIST_INGREDIENTS diaktifkan dan status baru dikembalikan; kemudian tindakan @ angular-redux / router :: UPDATE_LOCATION diaktifkan, dan kasus default dipukul, mengembalikan status yang sama.

Saat saya menjalankannya di aplikasi sebenarnya, urutan atau tindakannya dibalik. @ angular-redux / router :: UPDATE_LOCATION diaktifkan terlebih dahulu, mengembalikan status lama dari default. Kemudian LIST_INGREDIENTS diaktifkan, mengembalikan status baru (diubah) - dan kesalahan dimunculkan.

Saya sepenuhnya terbuka dengan gagasan bahwa saya telah melakukan sesuatu yang bodoh dan ini bukan bug yang sebenarnya - tetapi jika demikian, apakah ada orang lain yang melihat apa yang telah saya lakukan?

(Sebagai catatan kaki, saya memeriksa versi 4.1.3 ... dan itu juga mengaktifkan reduksi redux dalam urutan 'benar' - yaitu, perubahan lokasi diaktifkan setelah tindakan bahan daftar, yang mungkin itulah mengapa berhasil, tetapi versi 4.2 tidak).

Menderita bug mengganggu yang sama ketika salah satu komponen mengubah properti layanan. Diselesaikan dengan memindahkan * ngIf ke komponen induk. Jadi ini pasti bug

mampu mengatasi ini dengan menggunakan metode ngDoCheck () dan memanggil metode ChangeDetectorRef.detectChanges.

public ngDoCheck(): void {
    this.cdr.detectChanges();
}

@pappacurds Jangan lakukan itu. Anda telah menyebabkan siklus Deteksi Perubahan tak terbatas dengan itu.

mungkin kesalahan yang sama di sini https://plnkr.co/edit/IeHrTX0qil17JK3P4GBb?p=preview

kesalahan: previous undefined after undefiend

setelah memperbarui kesalahan yang sama

Sama untuk ku

Semuanya baik-baik saja di 4.0.0, setelah memutakhirkan ke 4.2.6 saya mendapatkan kesalahan yang sama.

Artikel Semua yang perlu Anda ketahui tentang kesalahan ExpressionChangedAfterItHasBeenCheckedError menjelaskan secara mendalam mengapa kesalahan ini terjadi dan kemungkinan perbaikan

perbaikan sudah jelas, tetapi mengubah kerangka kerja sedemikian rupa sehingga pembaruan menyebabkan aplikasi pengguna harus ditulis ulang untuk "mendukung" pembaruan platform minor baru adalah kabel. JS masih mempertahankan kesalahan lama untuk mempertahankan kompatibilitas ...

Kesalahan yang sama di sini

Saya juga melihat masalah ini di ~4.2.4 . Menurunkan versi ke ~4.1.3 menyelesaikan masalah.

Masalahnya adalah dengan mengubah nilai ContentChildren , bahkan saat menggunakan ChangeDetectorRef detectChanges() pada akhir AfterViewInit induk (di mana ia membuat perubahan). Anehnya masalah ini hanya terjadi dengan komponen dan bukan arahan. Saya mengubah arahan yang saya kerjakan di 4.2.4 menjadi komponen dengan template <ng-content></ng-content> dan kemudian mulai membuang kesalahan ExpressionChangedAfterItHasBeenCheckedError .

Saya mendapatkan ini saat bekerja dengan kontrol input materi 2. Mencoba untuk mengatur nilainya

ngAfterViewInit(): void {
this.numberFormControl.setValue(200);
}

Saya telah mengumpulkan Plunker sederhana yang menunjukkan kasus di mana kesalahan ExpressionChangedAfterItHasBeenCheckedError hanya mulai muncul dimulai dengan Angular 4.2.x .

Saya telah membuat plunker sederhana yang menunjukkan kesalahan saat mengubah nilai ContentChild bahkan saat menggunakan ChangeDetectorRef detectChanges() dalam komponen induk ngAfterViewInit .

Sunting: Juga dibuat plunker sederhana berdasarkan contoh pertama saya tetapi dengan Petunjuk orang tua, bukan Komponen dan tidak ada kesalahan.

@saverett Saya melihat pesan kesalahan yang berbeda dengan plunk Anda

@JSMike Saya memiliki kasus yang persis sama seperti di plunker Anda (mengatasinya dengan membungkus kode yang merujuk ContentChild dalam setTimeout , plunker )

@JSMike Terima kasih atas perhatiannya. Sepertinya ada masalah dengan angular / zone.js # 832. Saya telah menetapkan versi zone.js ke 0.8.12 untuk saat ini sampai mereka memperbaiki zone.js. Plunker seharusnya bekerja kembali.

@ matkokvesic sebenarnya bukan solusi, yang hanya menyebabkan perubahan terjadi di luar pengait siklus proses. Contoh kode yang diberikan tidak menyebabkan error saat menggunakan Angular v4.1.3

@JSMike Saya setuju, Ini solusi sementara. Menggunakan elemen yang direferensikan oleh ContentChildren di ngAfterViewInit berfungsi dengan baik sebelum Angular 4.2.6.

Saya telah memperbaikinya dengan menggunakan changeDetection: ChangeDetectionStrategy.OnPush dan this.changeDetector.markForCheck(); dalam kode saya

@touqeershafi jika Anda menggunakan this.changeDetector.markForCheck(); bukan berarti Anda tidak menggunakan sesuatu dengan cara yang benar

Ini masih terjadi. Ini jelas bug, bukan perilaku yang diharapkan dan harus diperbaiki.

@istiti saya mengerti itu bukan cara yang tepat tetapi setidaknya Anda dapat menghilangkan kesalahan ini untuk sementara, Tapi sejauh menyangkut proyek saya, saya telah menambahkan TODO dengan url bug.

Ini masih terjadi. Ini jelas bug, bukan perilaku yang diharapkan dan harus diperbaiki.

Saya rasa saya harus setuju dengan @rwngallego ... Sebelum pembaruan 4.2.x, saya sudah menulis sejumlah komponen stlye / ng-konten yang dapat digunakan kembali yang bergantung pada @ViewChildren , @ContentChildren dan membuat hubungan antara orang tua dan anak melalui ini saluran (mendapatkan / mengatur properti dan sebagainya).

Saya menemukan diri saya terus-menerus harus menyuntikkan contoh ChangeDetectorRef ke semua konstruktor ini (termasuk kelas anak yang memperluas komponen ini), dan itu tidak terasa seperti perilaku yang diinginkan atau benar.

Untuk yang menggunakan komponen dinamis seperti @saverett (menggunakan router menggunakan komponen dinamis) saya pikir masalahnya disebutkan di sini https://github.com/angular/angular/issues/15634.

Bagi siapa pun yang melakukan perubahan di dalam ngAfterViewInit / ngAfterViewChecked saya pikir itu sudah diharapkan.

Bagi yang lain, (belum melihat satupun di sini) masih menjadi misteri bagi saya.

Ini terjadi pada saya dalam skenario berikut.
Memiliki komponen induk yang menggunakan komponen anak. Komponen anak membutuhkan data yang akan disediakan oleh induknya tetapi data ini berasal dari hasil promise di konstruktor induk. Begitu:

induk

constructor {
dataservice.getData().then((result) => {
this.source = result;
});
}

Template induk

<app-child [source]="source"></app-child>

komponen anak

@Input() source:any[];

Kode ini menampilkan kesalahan ini, namun menambahkan ini ke komponen induk menyelesaikan masalah:

constructor(private cdRef: ChangeDetectorRef) {
dataservice.getData().then((result) => {
this.source = result;
 this.cdRef.detectChanges();
});
    }

Sekarang apakah ini solusi yang direkomendasikan atau merupakan solusi untuk bug dan apa implikasi dari perubahan tersebut? bagi saya sepertinya Anda memberi tahu Angular untuk mendeteksi perubahan yang menurut saya adalah sesuatu yang harus Anda hindari jadi ...

@sulhome ... masalah Anda adalah bahwa promise dalam konstruktor induknya mengubah @Input pada komponen anak ... selama siklus CD dan konfirmasinya dalam mode dev. Itulah alasan kesalahannya.

Baris this.cdRef.detectChanges(); menambahkan siklus CD kedua jadi konfirmasi setelah berhasil dan => tidak ada kesalahan.

Jadi semuanya berjalan seperti yang diharapkan. Penting untuk memahami cara kerjanya menggunakan konstruksi yang benar dan logika umum dalam kode Anda untuk menghindari jenis masalah ini. Anda dapat membacanya dalam penjelasan mendalam di sini: https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

@ mlc-mlapis jadi Anda mengatakan bahwa saya melakukannya dengan benar dengan menambahkan this.cdRef.detectChanges(); dan ini tidak memiliki implikasi apa pun?
Saya berpikir dengan mendengarkan ngOnChanges pada anak maka saya harus memilih perubahan di @Input dari induk. bukankah itu pendekatan yang tepat untuk melakukannya? Harap dicatat bahwa saya telah melakukan itu dan saya masih menerima kesalahan tetapi saya pikir untuk itulah ngOnChanges dirancang

@sulhome Ya, this.cdRef.detectChanges(); adalah yang membantu tetapi juga memiliki konsekuensi bahwa Anda memiliki 2 CD sekarang ... jadi itu tergantung bagaimana dan di mana Anda menggunakan OnPush strategi pada komponen untuk menghilangkan overhead satu siklus CD lagi.

Jika benar bahwa dataservice.getData mengambil beberapa data menggunakan http maka mungkin solusi termudah adalah memiliki observable di layanan itu dan berlangganan dari komponen anak. Jadi ketika data akan dipancarkan di sungai, anak dapat bereaksi.

@ghetolay sebagai catatan, ada beberapa kasus ketika itu adalah bug, lihat contoh # 18129 yang saya buka.

Saya mengalami masalah ini ketika saya mendapatkan data dari komponen anak dengan @Output.

di komponen induk rumah

drag_second(trigger){ console.log("dragged222222"+trigger); if(trigger){ this.isactive=true; }else{ this.isactive=false; } }
dan home.html
<div class="upper" [ngClass]="{'isactive':isactive}">

isactive diubah saat dragstart dan dragend ....

pesan kesalahan adalah
ExpressionChangedAfterItHasBeenCheckedError: Ekspresi telah berubah setelah diperiksa. Nilai sebelumnya: 'true'. Nilai saat ini: 'false'.

atau adakah cara lain untuk mengubah / menambahkan atribut kelas?

Masalah yang sama di 4.2.x dan 4.3.0 tetapi tidak di 4.1.x.

Kesalahan hanya terjadi pada rute yang menggunakan ng-template .

Solusi: Menempatkan semua kode di ngAfterViewInit menjadi setTimeout memecahkan masalah bagi saya (terima kasih / kredit untuk @jingglang).

public ngAfterViewInit(): void {
    setTimeout(() => {
        //my code
    });
}

@Wernerson , lihat komentar JSMike tentang setTimeout. Penting untuk diperhatikan bahwa Anda sebenarnya hanya mengubah kode di luar pengait siklus proses.

Masalah saya adalah saya menggunakan ActivatedRoute params Observable untuk mengubah beberapa keadaan di ngOnInit() . Tampaknya, Router memancarkan sinkronisasi params , jadi pembaruan dilakukan selama CD.
Solusi sederhana saya untuk ini saat ini:

  // !!! see delay(0)
  // results are consumed by async Pipe, where I also had the CD issue (null vs. undefined)
  this.results = this._activatedRoute.params.delay(0)
    .do(params => this.searchParameters = this._createSearchParameters(params))
    .switchMap(p => {
      let key = this.searchParameters.key();
      return this._searchResultCache.results.map(r => r.get(key));
    });

(Saya tahu ini bukan kode reaktif / fungsional terindah yang pernah ada;))
Jadi solusinya adalah delay(0) parameter emisi oleh satu tick .

Semoga saya dapat membantu untuk orang lain yang memiliki masalah serupa.
Juga semua keprihatinan disambut, bagaimana solusi saya salah atau berbahaya.

Apa status masalah ini? Itu masih terlihat di Angular 4.2.6.
Apakah ini masalah dalam produksi build? Karena kesalahan ini hanya akan terjadi di debug build.

Saya benci semua solusi dengan menjalankan deteksi perubahan secara manual, atau bahkan menjalankannya di setTimeout () ...

setTimeout() tidak efisien, karena menyebabkan siklus deteksi perubahan baru pada seluruh aplikasi, sedangkan detectChanges() hanya memeriksa komponen saat ini AFAIK.

Tim saya memiliki kesalahpahaman inti tentang apa itu ngOnInit dan bukan. Kata dokumen

ngOnInit() : Inisialisasi direktif / komponen setelah Angular pertama kali menampilkan properti terikat data dan menyetel properti masukan direktif / komponen. Dipanggil sekali, setelah ngOnChanges () pertama.

Kami berlangganan layanan kami (yang melacak status aplikasi) dari ngOnInit . Langganan kami adalah BehaviorSubjects sinkron ; mereka memecat pada waktu berlangganan. Jadi "setelah Angular dulu ... menyetel properti input direktif / komponen", kami segera mengubah nilainya. Berikut adalah Plunker yang sangat sederhana yang menggambarkan bahwa menjadi masalah.

Contoh dalam Tour Of Heroes pada akhirnya menggunakan http yang bersifat asinkron , dan oleh karena itu tidak menimbulkan masalah. Saya pikir inilah sumber kebingungan kita.

Apa yang kami lakukan sekarang adalah berlangganan status sinkron kami dari konstruktor kami, sehingga nilai komponen kami akan segera disetel. Orang lain di utas ini menyarankan untuk memulai deteksi perubahan kedua melalui batas waktu, menyetel nilai nanti, atau secara eksplisit memberi tahu angular untuk melakukannya. Melakukannya akan mencegah kesalahan muncul, tetapi mungkin itu tidak perlu.

Artikel yang dibagikan @maximusk harus dibaca : Semua yang perlu Anda ketahui tentang kesalahan ExpressionChangedAfterItHasBeenCheckedError .

Jika tidak ada yang lain, utas ini akan menghasilkan peringatan mendalam tentang semua ini di dokumen sudut resmi.

Artikel yang dibagikan @maximusk harus dibaca: Semua yang perlu Anda ketahui tentang kesalahan ExpressionChangedAfterItHasBeenCheckedError.

Terima kasih

@adil

Apakah ini berfungsi dalam kasus penggunaan @ViewChildren @ContentChildren untuk mengubah properti anak dari induknya, karena anak-anak tidak tersedia hingga siklus hidup ngAfterViewInit () telah dijalankan?

Harap diingat bahwa bagian dari masalah ini adalah bug yang dikonfirmasi dan bukan kesalahan pengguna:
https://github.com/angular/angular/issues/15634

Masalah yang dialamatkan @saverett benar, menggunakan *ngIf menyebabkan kesalahan.

* menonton ...

Ini bisa terjadi jika Anda meneruskan objek ke dialog Material Angular dan kemudian dalam dialog menggunakannya secara langsung daripada melakukan this.myDataInput = Object.assign({}, dialogDataInput) . Yaitu this.myDataInput = dialogDataInput; dan kemudian melakukan sesuatu seperti this.myDataInput.isDefault = !this.myDataInput.isDefault; . Seperti dijelaskan di atas, komponen anak (dialog) mengubah nilai yang diekspos dalam tampilan komponen induk.

ganti atribut dengan [atribut]

Adakah yang tahu pasti apakah ini adalah kesalahan atau bug yang diinginkan?
Bukankah benar memuat data di ngOnInit? Karena jika saya memindahkan kode ke konstruktor (memuat melalui Observable) kesalahan tidak terjadi ...

@Tokopedia . Apakah Anda memiliki * ngIf di html Anda? Berhati-hatilah karena segala sesuatunya akan dirender setelah ngOnInit selesai, solong, semua yang ada di dalam * ngIf tidak berfungsi dan mungkin menyebabkan kesalahan ini.

Itu harus dikerjakan ulang. Dengan "ngIf" dan kontrol khusus Anda mendapatkan pesan kesalahan ini. Waktu tunggu tentu bukan solusi.

@ j-nord jika Anda membaca komentar, Anda akan tahu bahwa timeout tidak diperlukan.

@ j-nord / @dgroh Saya pernah mengalami hal yang sama. Saya telah menggunakan petunjuk-ng yang menggunakan perintah ngIf dan saya tidak bisa menghilangkan kesalahan ini. Apakah Anda berhasil menerapkan solusi?

@Wernerson ya, sebagian dari masalah ini adalah kesalahan yang disengaja atau bug: https://github.com/angular/angular/issues/15634

@ mawo87 itu bekerja untuk saya dengan tersembunyi karena tidak menghapus elemen dari DOM. Untuk kebutuhan kami, ini tidak masalah dan juga merupakan solusi yang bersih.

@ muhammad.a
Pertama, Anda harus menemukan variabel yang dimaksud dengan pesan kesalahan tersebut. @ Tim : Akan menyenangkan untuk memiliki nama variabel dalam pesan kesalahan. Ini tidak mudah ditemukan oleh mask dan Webpack yang lebih besar. Jika Anda menemukan perintah di tempat itu terjadi, perintah di baris berikut akan membantu:

this.cdr.detectionChanges();

Lihat contoh dari vermilion928 dari 19. Juni.

Terima kasih @dgroh dan @ j-nord.
@ j-nord Saya juga melihat metode detectionChanges, tetapi saya mungkin tidak menggunakan pendekatan ini seperti pada artikel berikut ini disarankan untuk tidak melakukannya:
https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

Dalam kasus saya, saya menggunakan layanan PageTitle dan berlangganan di komponen induk, dan kemudian ketika komponen anak dimuat ke dalam tampilan, mereka mengatur PageTitle dalam layanan dan induk mendapatkan nilai dan menampilkannya melalui langganan. Ini bekerja tanpa kesalahan apa pun di 4.1.x, tetapi memunculkan kesalahan ExpressionChangedAfterItHasBeenCheckedError di 4.2x dan yang lebih baru.

Semuanya masih berfungsi seperti yang diharapkan, dan kesalahan tidak ditampilkan dalam pembuatan yang diterapkan / produksi (karena kurangnya siklus deteksi perubahan kedua), tetapi dalam pengembangan ada banyak warna merah di konsol saya.

Saya masih sangat tidak jelas seperti apa perbaikan yang tepat (bahkan jangka pendek). Langganan komponen induk saya berada di konstruktor, dan pembaruan komponen anak melalui layanan berada di ngOnInit.

Saya telah membaca posting 'semua yang perlu Anda ketahui tentang ...', yang informatif, tetapi tidak membuat saya percaya bahwa saya melakukan sesuatu yang salah, dan tidak memberikan solusi yang berfungsi untuk masalah tersebut.

@alignsoft jika Anda akan mengatur pagetitle di komponen anak di ngOnInit, maka saya yakin kesalahan itu valid ... Lihat artikel yang ditautkan sebelumnya.

@alignsoft Saya setuju, ada banyak diskusi tentang kesalahan ini, tetapi tanpa instruksi yang jelas tentang apakah itu valid atau tidak ... apakah ini lebih merupakan "peringatan" untuk dev tentang standar dan data binding / manipulasi?

Dalam kasus khusus saya, saya berurusan dengan komponen induk / anak menggunakan @ViewChild dan @ContentChild , harus memanggil detectChanges () berulang kali untuk menghentikan kesalahan agar tidak muncul di konsol ...

@rolandoldengarm Saya sudah mencoba berlangganan di konstruktor induk dan menyetel nilai di AfterViewInit di komponen anak dan masih mendapatkan kesalahan. Apakah itu cara yang benar untuk menanganinya berdasarkan siklus deteksi aliran dan perubahan, dan kesalahan berpotensi menjadi bug?

Tolong, alangkah baiknya memiliki semacam jawaban resmi di sini ...

Lihat ini lagi. Sepertinya contoh saya dari https://github.com/angular/angular/issues/17572#issuecomment -315096085
seharusnya menggunakan AfterContentInit daripada AfterViewInit ketika mencoba memanipulasi ContentChildren sebagai bagian dari siklus hidup komponen.

Berikut plnkr yang diperbarui dengan AfterContentInit dan tidak ada lagi kesalahan: http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview

@JSMike Saya mengubah AfterViewInit menjadi AfterContentInit , masih kesalahan yang sama. Bahkan jika saya memperbarui data saya di dalam OnInit kesalahan terjadi. Sejauh yang saya tahu ini sesuai dengan siklus hidup Angular benar (kan?) Untuk menginisialisasi data selama OnInit . Bersama dengan fakta bahwa kesalahan ini tidak terjadi di versi sebelumnya, saya menganggap ini bug. Namun saya ingin mendapatkan semacam pernyataan dari tim pengembangan jika demikian ..? : D

@Wernerson Anda tidak boleh memperbarui data di dalam ngOnInit kecuali data tersedia di hook siklus hidup itu. Anda harus memindahkan logika Anda menjadi ngAfterContentInit jika ada hubungannya dengan konten anak-anak.

@JSMike Tidak membantu, kesalahan masih terjadi. Saya baru saja berlangganan perubahan status dalam aplikasi setelah saya menerima data yang diminta (secara asinkron) di ngAfterContentInit .

@Wernerson apakah Anda memiliki contoh plnkr?

Sampel dasar dengan StackBlitz
Komponen anak yang memancarkan peristiwa dan komponen induk memperbarui variabel lokal.
Dapatkan kesalahan di konsol js:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'hello world'.

Ketahui, ngAfterContentInit hanya berfungsi jika anak-anak statis, masih gagal saat membuat anak menggunakan *ngFor http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview

@soldiertt Anda hanya perlu detectChanges () https://stackblitz.com/edit/angular-qfu74y

@JSMike jika saya melakukan ini, ngAfterViewInit komponen anak tidak terpicu: https://stackblitz.com/edit/angular-bdwmgf

@soldiertt yang tampak aneh, tetapi tampaknya masalah terpisah yang harus dilaporkan.

@JSMike Pengaturan saya cukup rumit dengan layanan, pustaka pihak ketiga, informasi rahasia, dll. Sayangnya saya tidak dapat mereproduksi bug di plunker: /

Injeksi ChangeDetectorRef berhasil untuk saya!

Saya memiliki masalah ini di carousel, karena saya perlu menyetel beberapa properti pada komponen item carousel, properti yang hanya diketahui setelah dihitung dalam komponen carousel induk. Dan semua item masuk melalui ng-content , jadi saya hanya dapat bekerja dengan item di ngAfterContentInit komponen korsel induk. Sesuatu seperti:
korsel:

@ContentChildren(ItemComponent) carouselItems;

ngAfterContentInit() {
  // ... some computations
  this.carouselItems.forEach((item: ItemComponent, currentIndex) => {
    item.setPropertyX(someComputedValue);
    // OR:
    item.x = someComputedValue; // of course, this would work as well
  });
}

barang:

setPropertyX(value) {
  this.x = value;
}

Selain peretasan setTimeout() , apakah satu-satunya solusi lain adalah menggunakan layanan dan subjek agar item berkomunikasi dengan korsel induk? Maksud saya, itu konyol ... Artinya, jika saya memiliki 100 item di carousel, maka saya akan memiliki 100 langganan. Dan itu berlipat ganda jika saya memiliki 2 carousel. Tentunya itu tidak bisa menjadi cara yang direkomendasikan ?! Baik?

Lebih sering daripada tidak, kita perlu mengatur beberapa properti pada komponen kita, properti yang tidak dapat diketahui sebelum AfterContentInit ... Dan dalam banyak kasus (ketika kita tidak membicarakan tentang 1 atau 2 komponen seperti itu, melainkan 50 atau 100 seperti di carousel) memiliki persepuluhan atau ratusan langganan akan konyol.

Apakah ada bagian Angular yang sangat keren yang dapat membantu saya di sini, yang belum saya temukan?

NB Saya juga mencoba menyuntikkan ChangeDetectorRef dan menggunakan detach() pada komponen carousel dan komponen item. Saya mengharapkan ini untuk memperbaiki kesalahan karena, pada dasarnya saya mengatakan "deteksi perubahan _screw, saya sebenarnya tidak membutuhkannya sama sekali. Tolong, tolong: jangan mendeteksi perubahan APA PUN _" ... tapi kesalahannya masih terlempar! Jadi, apa gunanya metode detach() , jika tidak melakukan apa yang dikatakannya?

constructor(private cdr: ChangeDetectorRef) {
  this.cdr.detach();
}

setTimeout() tidak perlu diretas, ini mungkin cara yang sangat valid untuk melakukan hal-hal seperti misalnya saat Anda bereaksi terhadap view untuk memperbaruinya, misalnya: mengubah binding template selama ngAfterViewInit .

Masalahnya adalah ketika Anda menggunakannya sebagai sihir tanpa benar-benar mengetahui alasannya. Tetapi ada skenario yang benar-benar valid di mana menggunakan setTimeout() adalah cara yang tepat.

@ Ghetolay Apakah tidak dievakuasi oleh sistem deteksi perubahan? Saya telah menggunakan rute ChangeDetectorRef.detectChanges (), tetapi itu bahkan terasa seperti hack bagi saya ... jika salah satu dari solusi ini "dapat diterima", saya kira kita harus menerimanya, tetapi itu benar-benar merusak penggunaan modifikasi ViewChild (ren) atau ContentChild (ren)

@bayu_joo

Apakah tidak hacky untuk keluar dari siklus hidup komponen yang dievakuasi oleh sistem deteksi perubahan?

Saya tidak mengerti pertanyaan itu. keluar dari siklus hidup?

Angular akan menjalankan putaran CD setiap kali terjadi peristiwa asinkron (acara dom, xhr, setTimeout dll ..), selama CD itu akan memperbarui tampilan. ngAfterViewInit hook dijalankan setelah itu sehingga tidak ada lagi CD yang direncanakan untuk dijalankan dan tidak akan dijalankan lagi sampai event async lain terjadi. Menggunakan setTimeout() adalah cara termudah untuk memicunya lagi. Anda pasti bisa melakukannya sendiri tapi menurut saya manfaatnya bisa diabaikan.

@ghetolay Saya mereferensikan komentar dari @JSMike dan beberapa utas lain yang telah saya baca:

@ matkokvesic sebenarnya bukan solusi, yang hanya menyebabkan perubahan terjadi di luar pengait siklus proses. Contoh kode yang diberikan tidak menyebabkan error saat menggunakan Angular v4.1.3

Jadi saya pikir menggunakan setTimeout () akan menyebabkan hook siklus hidup menjadi "dilewati" atau tidak dievaluasi seperti biasanya?

@ghetolay @fugwenna bagaimana dengan metode detach() dari ChangeDetectorRef ? Ada pemikiran? Bukankah itu seharusnya menonaktifkan deteksi perubahan sepenuhnya, sehingga juga menghilangkan kesalahan? Saya tidak mengerti mengapa itu tidak berhasil? Atau apakah kesalahan itu dianggap "independen" dari itu? Artinya Angular tidak terlalu peduli dengan fakta bahwa kami telah menonaktifkan deteksi perubahan, hanya peduli bahwa kami telah mengubah properti setelah disetel?
Jika orang masih memperdebatkan tentang "ExpressionChangedAfterItHasBeenChecked" sebagai bug atau bukan, mungkin yang ini pasti bug ... Menurut pendapat saya, Angular tidak lagi peduli tentang kita mengubah properti setelah disetel, jika kita sudah sepenuhnya deteksi perubahan dinonaktifkan. Terutama cara saya melakukannya, untuk komponen induk dan semua komponen anak lainnya, pada dasarnya di seluruh pohon.

Saya agak tersesat di sini, salah satu masalah yang saya hadapi adalah saya tidak yakin bagaimana saya bahkan akan melakukan beberapa hal yang sangat umum tanpa memicu pengecualian dalam mode pengembang.

Misalnya saya telah menulis komponen pengakses nilai kontrol yang merangkum datepicker. Saya memberi makan komponen ini dengan model kosong yang diteruskan ke datepicker secara internal yang menginisialisasi dan mengirimkannya kembali melalui fungsi terdaftar. Tentu saja anak-anak memodifikasi model selama initnya, tapi itulah yang saya inginkan! Saya ingin mendapatkan kembali model yang diinisialisasi tetapi tidak melakukan inisialisasi pada induknya karena datepicker-lah yang memahami bagaimana melakukan itu.

Masalahnya adalah bagaimana saya melakukannya sekarang? Panggil setTimeout dan picu deteksi perubahan lagi? Gunakan ChangeDetectorRef? Saya tidak suka karena itu benar-benar membuat kode sulit dibaca. Inisialisasi sesuatu dalam layanan terpisah? Tetapi layanan itu hanya melayani tujuan yang sangat teknis tanpa nilai bisnis apa pun dan saya bahkan tidak yakin ini akan menyelesaikan masalah.

@fugwenna Perhatian utama saya adalah berhasil di 4.1.3 dan sekarang rusak. Saya belum melihat siapa pun di tim sudut berpadu tentang apakah kita harus menggunakan setTimeout dalam fungsi init kita dan membiarkan zona mengambil alih, tetapi rasanya seperti anti-pola dibandingkan dengan cara kerjanya sebelumnya.

@ kavi87 Jadi pertanyaan stackoverflow ini benar-benar memberi saya solusi elegan yang saya butuhkan. Yang membuat event emitter di komponen anak saya async :
new EventEmitter(true)

Untuk masalah yang diberi label sebagai regresi dengan 109 komentar, masalah ini sangat mengecewakan dengan kata resmi apa pun.

Sementara semua orang di sini melakukan pekerjaan yang baik dengan teknik swadaya, saya ingin tahu apakah ini benar-benar bug, atau hanya beberapa perilaku baru yang diterapkan di 4.2.x dan seterusnya? Jika demikian, itu seharusnya terdaftar sebagai perubahan yang melanggar (dan dengan demikian bukan rilis kecil?)

@Tokopedia
Saya tidak yakin menonaktifkan deteksi perubahan sepenuhnya untuk membungkam pengecualian adalah jalan yang baik untuk turun, meskipun secara teori, saya melihat bagaimana itu seharusnya / bisa masuk akal.

Saya setuju dengan @JSMike dan @rosslavery sehubungan dengan sangat sedikit tanggapan atau penjelasan tentang mengapa pesan kesalahan ini muncul di posting 4.2x tanpa dokumentasi apa pun yang merusak perubahan ... namun bagian lain yang membingungkan dari ini adalah bagaimana tampaknya hanya dilemparkan mode pengembangan, yang hampir terasa seperti peringatan gangguan deteksi perubahan (?)

Im AppComponent saya, saya memiliki div.
Satu-satunya tujuan div itu adalah untuk menampilkan hamparan sibuk.
Status sibuk dipicu di dalam kelas layanan http:

app.component.html

<!-- Busy Overlay -->
<div *ngIf="busy$ | async"
     class="overlay">
    <i class="loading">
        <i><sk-wave></sk-wave></i>
    </i>
</div>

app.component.ts:

  get busy$(): Observable<boolean> {
    return this.catolService.busy$;
  }

Setelah memutakhirkan ke 4.3.4 saya selalu mendapatkan ExpressionChangedAfterItHasBeenCheckedError saat layanan memicu status sibuk.

Apakah ada perbaikan untuk skenario asinkron sederhana ini?

@ emazv72 ... apakah demo stackblitz / plunker ini sama dengan kasus Anda? https://stackblitz.com/edit/angular-2p2h9s?file=app%2Fapp.component.ts atau https://plnkr.co/edit/RPOxSVnXvM1TqKp0peSY?p=preview

@mukunku Terima kasih, saya akan mencobanya.

@ mlc-mlapis Saya sedikit mengubah plunker Anda, kode saya persis https://plnkr.co/edit/uUT7tiM5BKPIy2upJPhb?p=preview

tapi plunker tidak menunjukkan masalahnya. Saya akan mencoba meningkatkan ke 4.x terbaru untuk melihat apakah sudah diperbaiki.

Terima kasih

Halo semuanya - maaf atas keterlambatannya di sini.

Jadi - perilaku ini sebenarnya bekerja sebagaimana mestinya, yang saya sadari mungkin tidak terduga bagi sebagian orang, jadi izinkan saya mencoba menjelaskan mekanismenya.

@saveretts 'plunker adalah repro yang bagus untuk berjalan melalui (terima kasih!) - https://plnkr.co/edit/fr7rTNTCgEhHcbKnxAWN?p=preview

Tldrnya adalah Angular selalu bekerja dengan cara ini - deteksi perubahan sedang berjalan, dari atas ke bawah (atau induk -> anak), dalam satu lintasan. Jadi, jika kita memiliki:


@Component({ 
  selector: 'app-root',
  template: `
    <!-- initially falsey -->
    <div *ngIf="sharedService.someValue">NgIf Block</div>
    <child-comp></child-comp>
  `
})
class AppRoot {
  constructor(public sharedService:SharedService){}
}
@Component({ 
  selector: 'child-component',
  template: `child comp`
})
class ChildComponent {
  constructor(public sharedService:SharedService){}
  ngOnInit(){
    this.sharedService.someValue = true;
  }
}

Perubahan Detection akan dijalankan untuk aplikasi-akar pertama, periksa semua binding, dan kemudian berubah mendeteksi komponen anak - lagi, karena kita melakukan ini dalam single pass (bukan AngularJS-gaya keep checking sampai stabil), orang tua mengikat (* ngIf, dalam kasus ini) tidak diperiksa ulang - jadi sekarang aplikasi Anda dalam keadaan tidak konsisten (!)

Dalam mode pengembangan, kami benar-benar menjalankan deteksi perubahan itu dua kali - deteksi kedua kali dijalankan memverifikasi bahwa binding tidak berubah (yang akan menunjukkan status tidak konsisten), dan menampilkan error jika masalah seperti itu ditemukan. Anda dapat memverifikasi perilaku ini dengan memanggil enableProdMode() di repro: https://plnkr.co/edit/GGEzdxK1eHzHyGiAdp56?p=preview. Perhatikan bahwa kesalahan tidak muncul (karena kita tidak menjalankan CD x2), tetapi aplikasi berakhir dalam keadaan tidak konsisten (anak ditampilkan, tetapi bukan * ngIf), yang merupakan Hal Buruk.

Alasan hal ini muncul baru-baru ini adalah bahwa sebelumnya, router menjalankan sejumlah pemeriksaan deteksi perubahan tambahan saat memasukkan komponen - ini tidak efisien dan menyebabkan masalah dengan animasi dan perutean, terutama ketika outlet router bersarang di ngIfs dan hal-hal semacam itu.

Saya berpendapat bahwa biasanya, kode yang melanggar aliran satu arah ini (seperti yang dilakukan repro di atas) tidak benar . Namun - ada kasus di mana Anda mungkin menginginkan perilaku ini (pada kenyataannya, kami memiliki penyiapan serupa di perpustakaan formulir sudut). Artinya, Anda perlu secara eksplisit memicu proses deteksi perubahan lain (yang akan kembali ke komponen root dan memeriksa dari atas ke bawah). Memanggil setTimeout mencapai ini, meskipun biasanya lebih baik menggunakan Promise untuk memicu ini - lihat https://plnkr.co/edit/IoKMxEOlY9lY9LcWwFKv?p=preview

Semoga membantu!

@robwormald Kesalahan yang sama ini terjadi dengan skenario lain juga. Saya tidak yakin apakah ini mengatasi masalah ContentChildren . Mengapa Directive dapat mengubah nilai anak-anak selama AfterContentInit tanpa kesalahan tetapi melakukan hal yang sama dalam sebuah Komponen akan melempar kesalahan?

Juga, apakah diharapkan jika cdr.detectChanges() dipanggil selama satu proses init, sehingga fungsi init lainnya tidak dipanggil? Ini adalah sesuatu yang ditemukan di utas ini sehingga saya membuat masalah terpisah .

@bayu_joo

Saya yakin @JSMike benar dalam penilaian bahwa kesalahan ini masih terjadi selama skenario di mana aliran satu arah berasal dari Parent-> Child menggunakan ContentChild (ren) dan ViewChild (ren).

Apakah dapat diterima untuk mempertimbangkan apa yang telah saya katakan sebelumnya , bahwa kesalahan ini lebih merupakan "peringatan" yang menyatakan bahwa pengembang melanggar "praktik terbaik" aliran satu arah tetapi tidak menyebabkan kesalahan kerusakan aplikasi, karena deteksi perubahan hanya berjalan sekali selama mode produksi (dunia nyata)?

@fugwenna Tidak, ini sebenarnya adalah kesalahan, karena jika tidak, tampilan dalam keadaan tidak konsisten. Sampel yang dijalankan Rob menceritakan semuanya.

Juga, ada kutipan ini dari dokumen

Aturan aliran data searah Angular melarang pembaruan tampilan setelah dibuat. Kedua kait ini menyala setelah tampilan komponen dibuat.

Jika Anda benar-benar perlu mengubah apa pun di sana, gunakan salah satu metode asinkron untuk melakukannya. setTimeout mungkin cocok di sini, tetapi ketahuilah bahwa itu datang dengan (pada saat penulisan ini) penundaan 4ms sendiri. Cara lain adalah Promise.resolve().then(() => Assignment = here)

Yang lebih baik adalah mengambil langkah mundur, dan lihat mengapa Anda perlu melakukan perubahan di akhir proses ini dan lihat apakah ada cara yang lebih bersih untuk menyelesaikan masalah. Jika saya menemukan diri saya melakukan ini, saya memperlakukannya sebagai bau kode.

Terima kasih atas penjelasan detailnya @robwormald. Saya masih bingung mengapa seorang anak -> aliran data orang tua tampaknya masih berfungsi saat menggunakan pengikatan [hidden] daripada *ngIf (lihat https://plnkr.co/edit/L4QWK5pTgF1qZatFAN4u?p = pratinjau). Adakah ide mengapa ini masih berfungsi tanpa memberikan ExpressionChangedAfterItHasBeenCheckedError ?

Angular menggunakan fungsi checkAndUpdateView untuk melakukan deteksi perubahan.

Representasi singkat dari fungsi ini akan terlihat seperti:

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    // update DOM for the current view
    updateRenderer
    // perform checkAndUpdate for all child components
    execComponentViewsAction(view, ViewAction.CheckAndUpdate);
}

Ini dijalankan secara rekursif berkat metode execEmbeddedViewsAction dan execComponentViewsAction . Dan juga titik kunci di sini adalah pemeriksaan sudut tampilan yang disematkan setelah arahan diperiksa dan sebelum memperbarui DOM.

Untuk menyederhanakan contoh @saverett saya akan menggunakan template berikut

<div *ngIf="!sharedService.displayList">Some text</div>
<router-outlet></router-outlet>

https://plnkr.co/edit/OdkzHjEXbEd3efYAisFM?p=preview

Kita sudah tahu bahwa *ngIf hanyalah desugar. Begitu

<ng-template [ngIf]="sharedService.displayList">
  <div>Some text</div>
</ng-template>
<router-outlet></router-outlet>

Kita dapat membayangkan struktur berikut:

  my-app
     |
   NgIf directive
       NgIf EmbeddedView
   RouterOutlet directive

Apa yang dilakukan perintah RouterOutlet ?

Ini menyuntikkan komponen yang dirutekan ke ViewContainerRef

activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
    ...
    this.activated = this.location.createComponent(factory, this.location.length, injector);

Ini berarti bahwa router-outlet membuat EmbeddedView jadi setelah RouterOutlet direktif menginisialisasi kita memiliki dua tampilan tertanam dalam tampilan AppComponent .

  my-app
     |
   NgIf directive ([ngIf]="sharedService.displayList")
       NgIf EmbeddedView
   RouterOutlet directive 
      BComponent EmbeddedView

Mari kita ilustrasikan dalam gambar

haschanged

Sekarang mari kembali ke fungsi utama kita

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    ...
    // update DOM for the current view
    updateRenderer
    ...
}

Angular akan melakukan pemeriksaan dengan urutan sebagai berikut:

NgIf directive ([ngIf]="sharedService.displayList") updateDirectives(1)
      NgIf EmbeddedView   execEmbeddedViewsAction(2)
RouterOutlet directive 
      BComponent EmbeddedView  execEmbeddedViewsAction(3)

Direktif NgIf akan diperiksa terlebih dahulu (di sini sharedService.displayList=false diingat) dan kemudian metode NgOnInit di dalam BComponent akan dipanggil (di mana kami mengubah sharedService.displayList menjadi true ) Dan ketika angular melakukan checkNoChanges itu akan memunculkan kesalahan Expression has changed after it was checked

Kemudian mari kita ganti *ngIf dengan [hidden] . Sekarang kita hanya akan memiliki satu tampilan tertanam yang disisipkan oleh RouterOutlet dan [hidden]="sharedService.displayList" akan diperiksa dalam updateRenderer

  my-app
     |
   div{Some Text} just a node
   RouterOutlet directive 
      BComponent EmbeddedView

Angular akan melakukan pemeriksaan seperti ini:

function checkAndUpdateView(view) {
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);   BComponent.ngOnInit (1)
    ...
    // update DOM for the current view
    updateRenderer   update hidden binding (2)
    ...
}

Dengan cara ini pengikatan properti dom tidak akan menyebabkan kesalahan karena kami telah memperbarui setelah memanggil nilai ngOnInit .

Itulah perbedaan utama antara arahan struktural *ngIf dan properti dom mengikat [hidden]

Terima kasih untuk ikhtisar terperinci @alexzuza! Itu sangat membantu!

setTimeout () benar-benar berfungsi untuk saya :) Terima kasih @jingglang :)
.subscribe (data => setTimeout (() => this.requesting = data, 0))

@robwormald benar di sini. Saya memiliki masalah yang sama dan lebih sulit dideteksi karena saya menggunakan NGRX dan observable. Masalah yang saya hadapi adalah bahwa ada wadah komponen di berbagai tingkat dalam hierarki rute yang berlangganan status toko. Segera setelah saya memindahkan komponen penampung saya ke tingkat yang sama dalam modul fitur saya, masalahnya hilang. Saya tahu ini adalah jawaban yang berbelit-belit tetapi jika Anda kebetulan menggunakan pola wadah / komponen NGRX semoga ini mengarahkan penyelidikan Anda ke arah yang benar

Dengan setTimeOut berfungsi, terima kasih @jingglang
ngAfterViewInit () {
this.innerHeight = (window.innerHeight);
setTimeout (() => this.printPosition (), 0);
}

@alexzuza , ini tidak terjadi hanya untuk ngIf, ini terjadi (setidaknya untuk saya) juga dengan [ngClass]

@ Ninja-Coding-Git Saya baru saja menjelaskan kasus tertentu. Saya tahu itu bisa terjadi dengan kode apa pun.

Bagi saya, memanggil ChangeDetectorRef setelah mengubah elemen menyelesaikan masalah:

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

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

  constructor(private _cdr: ChangeDetectorRef) {}

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

}

Solusi ditemukan di sini

halo @thetylerb! bisakah Anda menjelaskan lebih lanjut tentang ini. Kami menggunakan ngrx dan saya tidak tahu bagaimana mengkompensasinya. Saya pikir menggunakan promise di atas observable cukup canggung.

@ piq9117 Anda juga dapat melihat komentar ini https://github.com/angular/angular/issues/18129#issuecomment -326801192 Saya membuat interaksi antara deteksi perubahan dan ngrx, ini dapat membantu saya kira

@victornoel terima kasih! Itu cukup menggambarkan situasi kita. Kami mendapatkan struktur data yang tidak dapat diubah (immutablejs), dan kami menggunakan pipa asinkron di mana-mana. Pikiran awal saya adalah, toko akan mengubah pohon komponen ketika toko telah terhidrasi, dan pohon komponen hanya akan mengkompensasi perubahan ini dan melakukan perubahan lagi

Kami mendapatkan struktur data yang tidak dapat diubah (immutablejs), dan kami menggunakan pipa asinkron di mana-mana. Pikiran awal saya adalah, toko akan mengubah pohon komponen ketika toko telah terhidrasi, dan pohon komponen hanya akan mengkompensasi perubahan ini dan melakukan changeDetection lagi.

Dalam proyek saya, kami memiliki beberapa jenis struktur data yang dinormalisasi (bukan struktur data bersarang), jadi itu mengurangi sedikit masalah semacam ini, sampai tidak! :HAI

Anda hanya berharap:

  • mengubah Anda menyimpan struktur data sehingga perubahan ke satu bagian tidak memicu pembaruan ke bagian lain
  • berhati-hatilah agar tidak memicu peristiwa dari turunan komponen yang mendengarkan penyimpanan melalui pipa asinkron
  • memicu deteksi perubahan manual saat Anda memicu peristiwa yang disebutkan di atas
  • memicu peristiwa tersebut di lain waktu atau di setTimeout

Saya menggunakan ngrx dan mendapat situasi seperti ini:

Dalam app.component.html:
<div *ngIf='(appState | async).show'>test</div>
Dan di app.component.ts
this.appState= this.store.select("app");

Tentu saja dalam situasi ini saya mendapatkan error, tetapi ketika saya menambahkan flag boolean di komponen seperti ini:
app.component.html -> <div *ngIf='show'>test</div>
app.component.ts ->
this.appState.subscribe((state: AppState) => { this.show= state.show; })

Errornya hilang, tentu bisa dimaklumi buat saya kenapa, tapi saya ingin tahu pendapat anda, apakah ini solusi yang baik? Apa pro dan kontra ??

Tidak menggunakan ngrx tetapi saya mengalami masalah dengan pemeriksaan sederhana dengan querySelectorAll let chips = [].map.call(document.querySelectorAll('li.chip'), this.chipVisible);

Solusi @bogdancar dengan ChangeDetectorRef.detectChanges() menekan kesalahan tetapi alangkah baiknya jika tidak harus mengimpor ChangeDetectorRef dengan memperbaiki apa pun yang menyebabkan kesalahan.

Saya mendapat masalah yang sama ketika saya menggunakan rxjs di sudut 4.4.4. Saya hanya berlangganan objek observasi yang disediakan oleh layanan DI. Pekerjaan saya di bawah ini:

this.eventBroadcastService.getCustomEvent({type: 'loginOpen'}).subscribe(e => { setTimeout(() => { this.isBlur = true; }, 0); });

Saya menggunakan setTimeout untuk bekerja. Sekarang berhasil, tetapi rumit. Saya merasa tidak enak.

ngOnChanges (perubahan: SimpleChanges | any) melakukan trik

Masalah telah diperbaiki hanya setelah memindahkan logika parameter perubahan dinamis di dalam metode ngDoCheck ().
ngDoCheck(): void { this.checkValid = this.sourceArr.includes((this.chanedVal).toUpperCase()); }

@ asrinivas61 Saya rasa ini adalah ide yang sangat buruk. ngDoCheck sering dipanggil oleh Angular dan jika sourceArr memiliki banyak data, hal itu dapat mengganggu kinerja Anda.

Lihat https://angular.io/guide/lifecycle-hooks#other -angular-lifecycle-hooks

Saya lebih suka merekomendasikan untuk saat ini menggunakan ngAfterViewInit dengan setTimeout :

setTimeout(_ => /* your code here */, 1000);

contoh:

  public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false, 1000);
  }

@dgroh Anda tidak perlu menambahkan waktu tunggu aktual, Anda hanya ingin js menjalankan kode Anda dalam tugas asinkron berikutnya yang berarti deteksi perubahan lain dijalankan.

@victornoel Saya barelly percaya ini akan bekerja dengan observable kecuali Anda secara eksplisit memanggil detectChanges dari ChangeDetectorRef . Saya berharap semua ini tidak diperlukan.

@dgroh Anda pasti salah paham, saya hanya menganjurkan untuk menulis:

public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false);
  }

Apakah tidak mungkin menambahkan langkah setelah ngAfterViewInit seperti setTimeout ? (misalnya: ngAfterViewLoad )

@ aks4it , ini tampaknya berhasil, tapi saya ingin detail lebih lanjut tentang cara kerjanya? Apakah kode pembuatan komponen dinamis sinkron?

Demo plunker resmi dari dokumen sudut memiliki kesalahan yang sama. Bisakah Anda memperbaikinya di sana? :) Terima kasih

@tokopedia

  ngAfterViewInit() {
    //this.loadComponent();
    this.getAds();
  }

ini bekerja?

saya tidak mengerti kebutuhan Anda tetapi Anda benar-benar ingin interval 3 detik? jika ya ini berhasil, dan bahkan jika Anda ingin langsung memanggil loadComponent saya berhasil dengan:

ngAfterViewInit() {
    setTimeout(()=>{this.loadComponent()},0)
  }

@istiti Saya dapat memperbaikinya dengan ChangeDetectorRef solusi, menempatkan detectChanges() setelah ... data = data

Tetapi karena ini berasal dari dokumen resmi, saya ingin tahu tentang solusi resmi dari masalah tersebut. Bukan timeout atau ChangeDetectorRef.

Maaf saya terlalu singkat di posting pertama .. Saya ingin menunjukkan tingkat keparahan masalah, karena bahkan dokumen sudut memiliki kode yang sama.

FYI, saya tidak yakin apa yang sebenarnya menyebabkan di sini, Tapi kesalahan hilang ketika saya mengaktifkan mode prod di main.ts
`enableProdMode ()

// if (environment.production) {

// enableProdMode ();

//} `

"@ angular / core": "^ 4.2.4"
"skrip ketikan": "~ 2.3.3"

@bakthaa ... itu seharusnya perilaku karena pemeriksaan kontrol (apakah komponen @Inputs() nilai adalah sama) yang dipanggil setelah setiap siklus CD dipanggil hanya dalam mode pengembangan dan bukan dalam mode produksi aplikasi Angular. Jadi masalahnya tersembunyi dalam mode produksi tetapi masih ada ... dan mungkin dapat menjadi sumber efek samping yang tidak diinginkan atau menyesatkan yang berbeda dalam mode produksi.

Saya menggunakan Angular 5, saya dapat memperbaiki masalah ini dengan menginjeksi komponen ChangeDetectorRef dan memaksanya untuk mendeteksi perubahan.

    constructor(private changeDetector: ChangeDetectorRef) {}

    ngAfterViewChecked(){
      this.changeDetector.detectChanges();
    }

@ Sabri-Bouchlema ... ya, ini satu cara ... tapi tetap saja Anda harus berpikir ulang sekali lagi ... mengapa komponen @Inputs diubah selama satu siklus CD ... dan putuskan apakah Logikanya benar ... dan memang benar bahwa dari 95% kasus, kode dapat diubah untuk menghindari masalah tanpa memanggil tambahan detectChanges() .

Menggunakan sudut 5.1.2. Saya mendapatkan kesalahan ini saat membuka dialog material di ngAfterViewChecked. Seperti yang dilaporkan @ aks4it , saya harus membungkus kode ke dalam setTimeout untuk membuatnya berfungsi.

Saya menghadapi masalah yang sama dengan versi 4.01
ng-version = "4.0.1

Sama di sini dengan sudut 4.4.6

Perbaiki menggunakan this.changeDetector.detectChanges();

Saya telah menulis plunker ini menggunakan variabel template dan saya tidak mengerti mengapa saya memiliki ExpressionChangedAfterItHasBeenCheckedError. Jika saya mengubah urutan komponen saya, kesalahan menghilang:

https://plnkr.co/edit/Mc0UkTR2loI7wgmLAWtX

Saya telah mencoba changeDetector.detectChanges tetapi tidak berhasil

@AlexCenteno ... karena logika evaluasinya adalah dari atas ke bawah ... dan value properti comp1 disetel @Input() public value:String=null; terlebih dahulu dan kemudian disetel ulang menggunakan referensi comp2.value ... <div comp1 [value]="comp2.value"></div> yang diset sementara ke Hello melalui @Input() ... semua dalam satu siklus CD ... jadi pemeriksaan berulang jika input pada komponen anak sama seperti pada awal siklus CD dalam mode dev mendeteksi perubahan pada properti value pada comp1 .

hai @ mlc-mlapis terima kasih atas waktunya. Jadi tidak ada cara untuk menghindari kesalahan? Saya rasa itu adalah sesuatu yang tidak perlu saya khawatirkan setelah saya memanggil enableProdMode?

@AlexCenteno ... ya, dalam mode prod, pemeriksaan setelah siklus CD tidak dipanggil ... tetapi itu tidak berarti Anda harus puas dengan itu. Anda harus menerapkan logika yang berbeda dengan meneruskan komponen anak silang value . Ada juga masalah bahwa <div> tidak memiliki properti value . Mengapa Anda menggunakan komponen atribut sama sekali? Mengapa tidak hanya:

<comp1 [value]="comp2.value"></comp1>
<comp2 #comp2 value="Hello"></comp2>

karena menggunakan @Output() pada <comp2> akan baik-baik saja dan memungkinkan Anda untuk memperbarui [value] pada comp1 tanpa kesalahan.

Menggunakan [email protected] dengan angular@5 standart store.select peringatan ExpressionChangedAfterItHasBeenCheckedError dalam mode pengembang dan salah bekerja pada mode prod. Pengiriman dan pemilihan berada pada level / wadah yang berbeda.

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': (isActiveSidenavMenu$ | async)
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  ...
}

Pendekatan ini menghapus pesan peringatan, tetapi tampaknya lebih rumit:

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': isActiveSidenavMenu
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  isActiveSidenavMenu;

  ngOnInit() {
    this.isActiveSidenavMenu$.subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
  }
}

Apakah ada ide bagaimana mengatasi masalah ini dengan lebih benar?

@rimlin sudahkah Anda mencoba langsung berlangganan ke toko?

ngOnInit() {
  this.store.select(fromHome.isActiveSidenavMenu)
    .subsbrice(res => {
      this.isActiveSidenavMenu = res;
    });
}

saya pikir saya punya masalah serupa. dan dengan menghindari async dalam template memecahkan masalah.

@ piq9117 terima kasih atas sarannya, mencoba solusi Anda, tampaknya lebih jelas dengan lebih sedikit kode, tetapi di sini juga perlu memanggil this.cd.detectChanges(); , jika tidak tampilan tidak akan diperbarui.

ngOnInit() {
    this.store.select(fromHome.isActiveSidenavMenu).subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
}

Saya menggunakan Angular 5.2, saya melakukan sesuatu seperti itu. Bekerja untuk saya

constructor(private changeDetector: ChangeDetectorRef) {}

ngAfterViewInit(){
  this.changeDetector.detectChanges();
}

Menggunakan Angular 5.2, diperbaiki menggunakan detectChanges ()

Ya, kami juga memperbaikinya menggunakan rute detectChanges () (Angular 5.1). Saya bersama banyak orang lain di sini meskipun yang berpikir ini masih hanya solusi dan tidak boleh diperlakukan sebagai jawaban dalam jangka panjang. Harus ada pengait siklus hidup lain yang bisa digunakan.

Memicu siklus deteksi perubahan yang sama sekali baru untuk memungkinkan perubahan seperti ini sepertinya kurang optimal.

Hal ini sepertinya terus berlangsung meskipun semua jawaban dan saran sudah diposting di atas.

  1. Cara yang tepat untuk memperbaikinya adalah dengan merancang ulang / merestrukturisasi komponen dan penanganan data Anda agar berfungsi dengan satu pass deteksi perubahan dari atas ke bawah. Ini berarti bahwa tidak ada komponen anak yang dapat mengubah nilai yang digunakan dalam template komponen induk. Evaluasi ulang apa yang telah Anda buat dan verifikasi bahwa data hanya mengalir ke satu arah, di bawah pohon komponen. Ini mungkin sesederhana memindahkan kode Anda dari ngAfterViewInit dan menjadi ngOnInit dalam beberapa kasus.
  2. this.changeDetectorRef.detectChanges(); adalah cara resmi dan dikenal untuk menangani ini dalam kurang dari 5% kasus di mana langkah pertama bermasalah.

Catatan lain:

  1. Berhati-hatilah saat melakukan apa pun di ngAfterViewInit karena mengubah nilai yang digunakan di DOM dapat menyebabkan kesalahan ini.
  2. Berhati-hatilah saat menggunakan salah satu dari hook siklus hidup Check atau Checked karena mereka mungkin dipanggil pada setiap siklus deteksi perubahan.
  3. setTimeout dapat mengatasi ini, tetapi dua solusi di atas lebih disukai.
  4. Mengabaikan kesalahan ini dalam mode pengembang dan merasa senang karena mode produksi "memperbaiki" kesalahan tidak valid seperti yang dijelaskan di atas. Pemeriksaan ini tidak dilakukan dalam mode prod, tetapi kode mode prod Anda masih membiarkan perubahan tidak terdeteksi dan menyebabkan tampilan dan data tidak sinkron. Hal ini tidak berbahaya dalam beberapa kasus, tetapi dapat menyebabkan korupsi data / tampilan dan masalah yang lebih buruk jika diabaikan.
  5. Aliran data satu arah tidak banyak berubah di Angular, jadi posting yang Anda tekan di Angular versi X tidak terlalu membantu.
  6. Menambahkan lebih banyak pengait atau status siklus hidup bukanlah solusi untuk ini. Pengembang hanya akan menambahkan kode yang melanggar aliran data satu arah ke kait tersebut dan memicu kesalahan yang sama ini.

Terakhir, ingatlah bahwa kesalahan ini tidak menunjukkan masalah dengan Angular. Kesalahan ini di sini untuk memberi tahu Anda bahwa Anda telah melakukan kesalahan dalam kode Anda dan bahwa Anda melanggar sistem aliran data satu arah yang membuat Angular cepat dan efisien.

Postingan di atas dan banyak postingan blog membahas hal ini lebih dalam dan layak dibaca karena Anda harus memahami model aliran data satu arah ini dan cara membuat komponen serta meneruskan data di dalamnya.

@Splaktar Apakah lebih baik mengunci masalah ini (dan menghapus saya) untuk membuat komentar di atas menonjol? Utasnya sudah terlalu panjang dan kebanyakan hanya berulang. 😃

@Splaktar Anda membuat poin ini "Ini berarti bahwa tidak ada komponen anak yang dapat mengubah nilai yang digunakan dalam template komponen induk".

Misalkan Anda memiliki komponen penampung yang mencakup navigasi, judul halaman, dll., Dan memuat kumpulan sub-komponen, masing-masing dapat memuat sub-komponennya sendiri. Komponen yang dimuat ke dalam penampung akan berubah berdasarkan data, dan sub-komponen yang dimuatnya akan berubah berdasarkan data. Masing-masing sub-komponen ini harus dapat berkomunikasi kembali ke informasi penampung induk tentang apa itu sehingga penampung induk dapat menampilkan Nav tingkat atas yang sesuai kepada pengguna (mungkin jejak runut tautan, atau yang serupa), juga sebagai informasi yang dapat digunakan dalam judul untuk mengidentifikasi di tingkat penampung apa yang dimuat ke dalam tampilan.

Dalam hal ini, yang merupakan arsitektur yang sangat umum dalam pengalaman saya, saya akan menggunakan layanan negara umum yang sub-komponen akan mendaftar sendiri, dan komponen induk berlangganan sehingga anak-anak dapat mendidik orang tua, dan orang tua. dapat menampilkan informasi yang sesuai.

Ini tidak mengikuti pendekatan bottom-up, karena induk akan mendapatkan semua data yang relevan dari layanan, dan mendorong informasi apa pun yang diperlukan oleh komponen turunan melalui komunikasi satu arah.

Ini tampaknya bertentangan dengan apa yang Anda sarankan di atas, dan bagi saya adalah sumber masalahnya. Ketika komponen anak mendaftar dengan layanan, komponen induk melontarkan kesalahan.

Apakah ini salah?

Dan jangan pernah lupa, bahwa ada bug yang disetujui:
https://github.com/angular/angular/issues/15634

@ Martin-Wegner Silakan lihat https://github.com/angular/angular/pull/18352#issuecomment -354170352

@alignsoft ... apakah Anda memiliki reproduksi sederhana dari kasus Anda dan masalahnya sebagai Stackblitz? Karena tampaknya ada hal lain yang memengaruhi kasus daripada sekadar logika yang Anda jelaskan.

@trotyl solusi dari @Splaktar tidak terkait dengan masalah sederhana saya di sini https://github.com/angular/angular/issues/17572#issuecomment -311589290 atau?

@ Martin-Wegner ... dalam kasus Anda, cukup mengubah ngAfterViewInit menjadi ngOnInit hook.

@alignsoft , seperti yang dikatakan @ mlc-mlapis, contoh Stackblitz akan sangat membantu dalam mendemonstrasikan arsitektur yang tepat untuk kasus ini. Ini sangat mungkin dilakukan dengan menggunakan set layanan yang tepat, observable, hook siklus hidup, dll. Kasus khusus ini juga akan menjadi topik yang baik untuk StackOverflow.

@Splaktar Setelah saya mendapatkan tenggat waktu saya saat ini dari meja saya, saya akan membuat mockup sebuah proyek yang menunjukkan kasus sederhana.

@ mlc-mlapis wow terima kasih, berhasil :)

@alignsoft , hari ini saya membuat aplikasi yang berperilaku persis seperti yang Anda jelaskan. @ mlc-mlapis, Anda dapat menemukan contoh yang dipreteli di ExpressionChangedAfterItHasBeenCheckedError akan ditampilkan.

Dalam contoh ini, admin.component bertindak sebagai komponen induk yang bertanggung jawab atas 3 hal:

  • menampilkan navigasi kiri
  • ticker teratas yang dapat digunakan sub komponen untuk menampilkan pesan tickernya sendiri
  • bagian konten utama yang menampilkan data spesifik sub komponen melalui <router-outlet></router-outlet> .

Komponen Induk

<div class="ticker-container" *ngIf="tickedMessage !== ''">
  <h1>{{tickedMessage}}</h1>
  <button type="button" (click)="tickedMessage = ''">
    close
  </button>
</div>

<div class="side-nav">
  <h2>side nav</h2>
  <ul>
    <li><a routerLink="/">dashboard</a></li>
    <li><a routerLink="/apps/thirdpartycms">CMS System</a></li>
  </ul>
</div>
<div class="main-content-container">
  <router-outlet></router-outlet>
</div>
export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

Dalam meneliti cara mengkomunikasikan data kembali ke komponen induk ketika menggunakan <router-outlet></router-outlet> , saya membaca bahwa jika saya menggunakan @Output untuk mengirim data kembali ke komponen induk, petunjuk router-outlet akan berakhir menangani acara tersebut. Karena ini adalah acara khusus, petunjuk outlet-router tidak akan tahu cara menanganinya. Jadi saya melewatkan metode ini. Setelah saya membaca tentang layanan bersama di angular.io, saya menulis sendiri:

Layanan bersama

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()
export class AdminService {

  private _tickedMessageAnnounced = new Subject<string>();

  tickedMessageAnnounced$ = this._tickedMessageAnnounced.asObservable();

  announceTickedMessage(tickedMessage: string) {
    this._tickedMessageAnnounced.next(tickedMessage);
  }
}

Setelah salah satu komponen anak memanggil this.adminService.announceTickedMessage(this._tickedMessage); :

Komponen Anak

import { Observable } from 'rxjs/Observable';
import { Component, ViewChild, OnInit, Inject } from '@angular/core';
import { AdminService } from '../../../core/admin/admin.service';

@Component({
  selector: 'cms',
  templateUrl: './thirdparty-cms.component.html',
  styleUrls: ['./thirdparty-cms.component.scss'],
})
export class ThirdPartyCmsComponent implements OnInit {
  private _tickedMessage: string;
  constructor(private adminService: AdminService) { }

  ngOnInit(): void {
    this._tickedMessage = 'Please do not create an entry for a page URL that does not exist in the CMS system';

    this.adminService.announceTickedMessage(this._tickedMessage);
  }
}

komponen induk melontarkan pengecualian ExpressionChangedAfterItHasBeenCheckedError karena mencoba memperbarui salah satu properti publiknya dengan nilai pesan yang dicentang yang dikirim oleh komponen anak:

Komponen Induk (melempar pengecualian)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

Apa yang memperbaiki ExpressionChangedAfterItHasBeenCheckedError adalah dengan menjalankan panggilan berlangganan kembali secara asinkron dan menunggu pesan yang dicentang:

Komponen Induk (dengan async await)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      This clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe(async (tickedMessage: string) => {
      this.tickedMessage = await tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

Berasal dari dunia WPF, sepertinya kita perlu memberi tahu utas UI untuk menunggu panggilan balik langganan yang dapat diamati karena yang dapat diamati dieksekusi secara asinkron?

Saya baru saja memulai dengan Angular jadi mohon perhatiannya terhadap skill Angular saya: P

@ktabarez Terima kasih, ini banyak membantu saya!

@ktabarez Terima kasih! Sepertinya itu bekerja dengan baik! Saya membungkus langganan di komponen induk dalam waktu tunggu, yang juga berfungsi, tetapi merasa kotor.

Saya melihat bahwa ketika menginterogasi status redux itu dijamin akan dikirimkan tetapi pada startup seperti itu ketika dimasukkan ke dalam ngOnInit sebagai lawan dari konstruktor

@ktabarez Terima kasih banyak!

Solusi async ... await in subscribe berfungsi.

@ktabarez Solusi Anda pada dasarnya melakukan hal yang sama seperti setTimeout ({}, 0) tetapi terasa lebih elegan. Sangat menyukainya! Itu juga membuat saya menyadari bahwa apa pun bisa ditunggu (hampir seperti C # menunggu Task.FromResult (tickedMessage)), jadi bersoraklah untuk itu!

Jadi, pertanyaan: mengapa kita harus melakukan async / await pada perhitungan properti pada observable yang mendapatkan data dari back end yang mungkin sering berubah? Bagaimana dengan ngClass membuat ini perlu? Saya mendapatkan ini saat menggunakan {observable | pipa async}. Jadi async / await bukanlah apprach terbaik untuk saya. (atau pipa asinkron bukanlah pendekatan terbaik)

Saya mengalami masalah ini di mana komponen induk saya memiliki beberapa komponen anak. Salah satu komponen anak memiliki Input yang membutuhkan data dari properti komponen induk dan properti mendapatkan datanya dari Observable / Promise (mencoba keduanya).

parent.component.html

<mat-tab-group>
   <mat-tab>
      <app-child-1></app-child-1>
   </mat-tab>
   <mat-tab>
      <app-child-2></app-child-2>
   </mat-tab>
   <mat-tab>
      <app-child-3 [datasource]="data"></app-child-3>
   </mat-tab>
</mat-tab-group>

parent.component.ts

// ... omitted...
data: any;

ngOnInit() {
   this.service1.getMethod(some_id).then(data => this.data = data);
}

Saya menemukan setiap solusi yang ditemukan di utas. Tapi tidak ada yang berhasil dalam kasus saya. Untungnya karena beberapa orang mengisyaratkan bahwa ini adalah masalah karena memanipulasi DOM, seperti meletakkan ekspresi ke _ (ngIf) _ sebagai contoh.

Jadi untuk menghindari hal ini saya mencoba bereksperimen dengan ng-template , ng-container dan ngTemplateOutlet .

<ng-container *ngTemplateOutlet="data ? content : loading"></ng-container>

<ng-template #content>
   <mat-tab-group>
      <mat-tab>
         <app-child-1></app-child-1>
      </mat-tab>
      <mat-tab>
         <app-child-2></app-child-2>
      </mat-tab>
      <mat-tab>
         <app-child-3 [datasource]="data"></app-child-3>
      </mat-tab>
   </mat-tab-group>
</ng-template>

<ng-template #loading>Loading your component. Please wait</ng-template>

Saya dapat menyelesaikan ini tanpa menggunakan ChangeDetectorRef atau setTimeout()

Bagi saya, kesalahan ini terjadi saat menampilkan / menyembunyikan bidang sesuai dengan kondisi, saya dapat menyelesaikannya setelah menerapkan nilai-nilai pengaturan bidang:

this.form.get ('field').updateValueAndValidity();

Saya telah mencoba melakukan ini dengan mat-dialog yang terbuka di init, dan saya benar-benar telah mencoba setiap solusi yang pernah saya lihat di utas ini (setel batas waktu, penyelesaian janji, pipa yang dapat diamati, setelah tampilan init, setelah konten init) dan kombinasi di dalamnya. Masih tidak berhasil. Kemudian saya melihat posting @Splaktar tentang bagaimana ini melanggar aliran data satu arah, jadi saya benar-benar membuat bendera di ngrx yang mengatakan untuk membuka dialog dan berlangganan itu, yang awalnya salah dan saya masih mendapatkan kesalahan ini. Apa yang harus saya lakukan? Apakah benar-benar tidak ada cara untuk membuka dialog modal tanpa melampirkannya ke acara yang benar-benar terpisah?

Di init:
this.store.pipe( select(getShouldLogin), ).subscribe((shouldLogin: boolean) => { if (shouldLogin) { this.openLoginDialog(); } }); this.checkUserId();

Fungsi tambahan:
checkUserId() { const id = this.cookies.getUserId(); if (!id || id === 'undefined') { this.connApi.setShouldLogin(true); } }
openLoginDialog() { const dialogRef = this.dialog.open(LoginDialogComponent, { width: '400px', height: '300px', }); dialogRef.afterClosed().subscribe(result => this.handleLogin(result)); }

Menggunakan angular 6.0.0-rc.1

Saya menggunakan angular dengan ngrx dan mendapatkan kesalahan ini setelah mengirimkan formulir.
diperbaiki dengan pengaturan
ChangeDetectionStrategy.OnPush
pada komponen formulir

masih mengalami masalah. mencoba segala macam solusi. setTimeout berfungsi pertama kali, tetapi pemicuan berikutnya dari dialog.open menghasilkan kesalahan yang sama. Ada status tentang ini?

Saya menghadapi masalah yang sama. Mencoba segalanya tetapi tampaknya masalah ini muncul pada aliran data satu arah.
Di sini saya mencoba memperbarui yang dipilih. di mana masalah terpecahkan tetapi kesalahan yang sama muncul.

        <p-row> <p-column class="text-left" footer="{{selectedWoids.length}} {{getWoidString(selectedWoids.length)}} selected out of {{getTotalCounts()}} {{getWoidString(getTotalCounts())}}" colspan="11"></p-column> </p-row>

private setSelectedWoidsCount () {
if (this.isSelectAllWoids) {
this.selectedWoids = this.allWoids;
}
}

Karena Anda memutus pipeline rendering Angular ...

Saya melihat kesalahan serupa "ExpressionChanged ..." setelah beralih dari 4.1.2 ke 4.2.3 .

Dalam kasus saya, saya memiliki dua komponen yang berinteraksi melalui layanan. Layanan ini disediakan melalui modul utama saya, dan ComponentA dibuat sebelum ComponentB .

Di 4.1.2 , sintaks berikut bekerja dengan baik tanpa kesalahan:

_ComponentA Template: _

<ul *ngIf="myService.showList">
...

_ComponentB Kelas: _

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

Di 4.2.3 , saya harus memodifikasi template ComponentA sebagai berikut untuk menghindari kesalahan "ExpressionChanged ...":

_ComponentA Template: _

<ul [hidden]="!myService.showList">
...

Saya masih mencoba mencari tahu mengapa ini baru saja menjadi masalah, dan mengapa peralihan dari *ngIf menjadi [hidden] berfungsi.

Apakah Anda mengetahui cara kerjanya saat beralih dari ngIf ke [tersembunyi]

Apakah ada solusi untuk masalah ini di Angular 6 ??

@alokkarma ... terlalu tua ... Anda harus memindahkannya ke Angular 6 atau setidaknya 5. Tetapi kode contoh Anda terlihat jelas ... Anda mengubah properti layanan di komponen anak B, dan ini memengaruhi beberapa perilaku di komponen anak A. Kedua komponen anak ditempatkan di induk yang sama, sehingga ketiganya diproses secara bersamaan di hubungan dalam satu siklus CD. Apa yang kamu harapkan?

Fakta bahwa kasus yang sama berfungsi di versi 4.1.2 terkait dengan beberapa bug yang dihilangkan di versi yang lebih baru, termasuk 4.2.3.

Saya menggunakan Angular 6. Saya menggunakan MessageService untuk berkomunikasi antara komponen otentikasi saya dan komponen aplikasi saya untuk menampilkan konten bilah navigasi yang berbeda jika pengguna masuk (atau tidak)

Ini berfungsi dengan baik terlepas dari kesalahan ini!
Saya sudah mencoba solusi lain seperti EventEmitter tetapi tidak berhasil! atau saya mendapatkan kesalahan yang sama.

@dotNetAthlete ... tunjukkan demo reproduksi sederhana di Stackblitz. Sulit membicarakannya tanpa kode yang sebenarnya.

@dotNetAthlete Saya menggunakan mekanisme dasar yang sama di mana saya memiliki layanan negara yang berisi judul halaman sebagai subjek perilaku (asObservable) di komponen penampung utama, dan komponen anak dimuat ke dalamnya yang menetapkan judul halaman yang sesuai di layanan negara untuk diamati dan ditampilkan oleh wadah induk.

Selalu ada kesalahan 'Ekspresi berubah' ketika penampung menginisialisasi nilai dan kemudian komponen anak segera memperbaruinya, jadi saya melakukan ini sekarang di induk / penampung:

    this.stateSvc.currentPageTitle$
      .subscribe(
        (async (pageTitle) => {
          this.pageTitle = await pageTitle;
        }))

Dimana layanan negara memiliki ini:

  // Store
  private currentPageTitleStore = new BehaviorSubject<string>("");

  // Getter (as Observable)
  public currentPageTitle$ = this.currentPageTitleStore.asObservable();

  // Setter
  public setCurrentPageTitle(pageTitle: string) {
    this.currentPageTitleStore.next(pageTitle);
  }

Ini menghilangkan masalah. Solusi ini didasarkan pada beberapa umpan balik bermanfaat dari orang lain di utas ini.

@alignsoft - solusi cantik - masalah diperbaiki di aplikasi Angular 6.

Saya melihat ini di halaman dimuat ulang di angular 7 sekarang ...
Saya menetapkan nilai komponen anak pada orang tua ngAfterViewInit
Harus melakukan ini pada komponen anak

  public activate() {
    setTimeout(() => this.active = true);
  }

Saya menemukan solusinya dalam utas masalah ini https://github.com/angular/angular/issues/10762
Setelah saya menggunakan hook siklus hidup AfterContentInit , kesalahan akan hilang.

@alignsoft , hanya menambahkan perubahan Anda, karena kami menggunakan BehavioralSubject alih-alih Subjek,

Saya menyempurnakannya sebagai berikut:

\\ Component
export class AppComponent implements OnInit {
  isLoading: Boolean;

  constructor(private loaderService: LoaderService) { }

  ngOnInit(){
    this.loaderService.isLoading.subscribe(async data => {
      this.isLoading = await data;
    });
  }
}
\\ Service
@Injectable({
  providedIn: 'root'
})
export class LoaderService {
  // A BehaviorSubject is an Observable with a default value
  public isLoading: BehaviorSubject<Boolean> = new BehaviorSubject(false);
  constructor() {}
}
\\ Any other component or in my case, an interceptor that has the LoaderService injected
this.loaderService.isLoading.next(true);

Tetapi saya setuju dengan @daddyschmack , saya ingin mendapatkan sumber daya apa pun yang menunjukkan info mengenai withotu salah satu perbaikan yang disebutkan di atas ( cdr.detectionChanges() atau async await atau aftercontentchecked ), mengapa [hidden] atribut berfungsi dan bukan *ngIf

@acidghost Terima kasih atas tautan itu, ini memecahkan masalah saya. Saya sebenarnya sudah menyerah untuk mencoba memperbaikinya di satu area aplikasi saya tetapi ketika itu mulai terjadi lagi di area baru ...

Masalah ini telah dikunci secara otomatis karena tidak ada aktivitas.
Ajukan masalah baru jika Anda mengalami masalah serupa atau terkait.

Baca lebih lanjut tentang kebijakan penguncian percakapan otomatis kami.

_Tindakan ini telah dilakukan secara otomatis oleh bot._

Apakah halaman ini membantu?
0 / 5 - 0 peringkat