typescript - Ekspresi ___ telah berubah setelah diperiksa
Mengapa komponen ada di plunk sederhana ini
@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 :)'
}
}
melempar:
PENGECUALIAN: Ekspresi 'Saya {{message}} di App@0:5' telah berubah setelah diperiksa. Nilai sebelumnya: 'Saya sedang memuat :( '. Nilai saat ini: 'Saya sudah selesai memuat :) ' di [Saya {{message}} di App@0:5]
ketika yang saya lakukan hanyalah memperbarui binding sederhana saat tampilan saya diinisialisasi?
Jawaban #1
Seperti yang dinyatakan oleh drewmoore, solusi yang tepat dalam kasus ini adalah memicu deteksi perubahan secara manual untuk komponen saat ini. Ini dilakukan menggunakan metode detectChanges() dari objek ChangeDetectorRef (diimpor dari angular2/core), atau metode markForCheck()-nya, yang juga membuat komponen induk diperbarui. Contoh yang relevan:
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();
}
}
Berikut ini juga ada Plunkers yang mendemonstrasikan pendekatan ngOnInit, setTimeout, dan enableProdMode untuk berjaga-jaga.
Jawaban #2
Pertama, perhatikan bahwa pengecualian ini hanya akan dilemparkan ketika Anda menjalankan aplikasi Anda dalam mode pengembangan (yang merupakan kasus secara default sejak beta-0): Jika Anda memanggil enableProdMode() saat memulai aplikasi, pengecualian ini tidak akan dilemparkan (lihat plunk yang diperbarui).
Kedua, jangan lakukan itu karena pengecualian ini dilemparkan untuk alasan yang baik: Singkatnya, ketika dalam mode pengembangan, setiap putaran deteksi perubahan segera diikuti oleh putaran kedua yang memverifikasi bahwa tidak ada binding yang berubah sejak akhir putaran pertama, karena ini akan menunjukkan bahwa perubahan disebabkan oleh deteksi perubahan itu sendiri.
Dalam plunk Anda, binding {{message}} diubah oleh panggilan Anda ke setMessage(), yang terjadi dalam hook ngAfterViewInit, yang terjadi sebagai bagian dari putaran deteksi perubahan awal. Itu sendiri tidak menjadi masalah - masalahnya adalah bahwa setMessage() mengubah binding tetapi tidak memicu putaran baru deteksi perubahan, yang berarti perubahan ini tidak akan terdeteksi sampai putaran deteksi perubahan berikutnya dipicu di tempat lain.
Yang perlu diingat: Apa pun yang mengubah sebuah binding harus memicu satu putaran deteksi perubahan ketika hal itu terjadi.
Pembaruan sebagai tanggapan atas semua permintaan untuk contoh cara melakukannya: Solusi @Tycho berhasil, begitu juga dengan tiga metode dalam jawaban yang disebutkan @MarkRajcok. Tapi sejujurnya, semuanya terasa jelek dan salah bagi saya, seperti jenis trik yang biasa kita gunakan di ng1.
Untuk memastikan, ada keadaan sesekali di mana trik-trik ini tepat, tetapi jika Anda menggunakannya lebih dari sangat sesekali, itu adalah tanda bahwa Anda sedang melawan kerangka kerja daripada sepenuhnya menerima sifat reaktifnya.
Menurut pendapat saya, cara yang lebih idiomatik dan 'cara Angular2' untuk mendekati ini adalah sesuatu yang seperti ini: (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 :)')
}
}
Jawaban #3
ngAfterViewChecked() berhasil untuk saya:
import { Component, ChangeDetectorRef } from '@angular/core'; //import ChangeDetectorRef
constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
//your code to update the model
this.cdr.detectChanges();
}
Jawaban #4
Saya memperbaiki ini dengan menambahkan ChangeDetectionStrategy dari angular core.
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'page1',
templateUrl: 'page1.html',
})
Jawaban #5
Apakah Anda tidak bisa menggunakan ngOnInit karena Anda hanya mengubah variabel anggota message?
Jika Anda ingin mengakses referensi ke komponen anak @ViewChild(ChildComponent), Anda memang perlu menunggunya dengan ngAfterViewInit.
Solusi kotor adalah memanggil updateMessage() di loop event berikutnya misalnya dengan setTimeout.
ngAfterViewInit() {
setTimeout(() => {
this.updateMessage();
}, 1);
}
Jawaban #6
Untuk ini saya telah mencoba jawaban-jawaban di atas, banyak yang tidak bekerja di versi terbaru Angular (6 atau lebih baru)
Saya menggunakan kontrol Material yang memerlukan perubahan setelah binding pertama selesai.
export class AbcClass implements OnInit, AfterContentChecked{
constructor(private ref: ChangeDetectorRef) {}
ngOnInit(){
// your tasks
}
ngAfterContentChecked() {
this.ref.detectChanges();
}
}
Menambahkan jawaban saya, jadi ini membantu beberapa orang menyelesaikan masalah tertentu.
Jawaban #7
Saya beralih dari AfterViewInit ke AfterContentChecked dan itu berhasil untuk saya.
Berikut adalah prosesnya
Tambahkan dependency di konstruktor Anda:
constructor (private cdr: ChangeDetectorRef) {}dan panggil login Anda di kode metode yang diimplementasikan di sini:
ngAfterContentChecked() { this.cdr.detectChanges(); // call or add here your code }
Jawaban #8
Artikel Semua yang perlu Anda ketahui tentang kesalahan ExpressionChangedAfterItHasBeenCheckedError menjelaskan perilakunya secara rinci.
Masalah dengan pengaturan Anda adalah bahwa hook siklus hidup ngAfterViewInit dijalankan setelah deteksi perubahan memproses pembaruan DOM. Dan Anda secara efektif mengubah properti yang digunakan dalam template di hook ini yang berarti DOM perlu dirender ulang:
ngAfterViewInit() {
this.message = 'all done loading :)'; // needs to be rendered the DOM
}
dan ini akan membutuhkan satu siklus deteksi perubahan lagi dan Angular menurut desain hanya menjalankan satu siklus digest.
Pada dasarnya Anda memiliki dua alternatif untuk memperbaikinya:
perbarui properti secara asynchronous baik menggunakan
setTimeout,Promise.thenatau observable asynchronous yang direferensikan dalam templatelakukan pembaruan properti di hook sebelum pembaruan DOM - ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.
Jawaban #9
Kesalahan ini terjadi karena nilai yang ada diperbarui segera setelah diinisialisasi. Jadi jika Anda memperbarui nilai baru setelah nilai yang ada dirender di DOM, maka itu akan bekerja dengan baik. Seperti yang disebutkan dalam artikel ini Angular Debugging "Expression has changed after it was checked"
misalnya Anda dapat menggunakan
ngOnInit() {
setTimeout(() => {
//code for your new value.
});
}
atau
ngAfterViewInit() {
this.paginator.page
.pipe(
startWith(null),
delay(0),
tap(() => this.dataSource.loadLessons(...))
).subscribe();
}
Seperti yang Anda lihat, saya belum menyebutkan waktu dalam metode setTimeout. Karena ini adalah API yang disediakan oleh browser, bukan API JavaScript, maka ini akan berjalan terpisah di tumpukan browser dan akan menunggu hingga item di call stack selesai.
Bagaimana API browser memanggil konsep dijelaskan oleh Philip Roberts dalam salah satu video Youtube (What the hack is event loop?).
Jawaban #10
Anda hanya perlu memperbarui pesan Anda di hook lifecycle yang tepat, dalam hal ini adalah ngAfterContentChecked bukan ngAfterViewInit, karena di ngAfterViewInit pemeriksaan untuk variabel message telah dimulai tetapi belum selesai.
lihat: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#afterview
jadi kodenya akan seperti ini:
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 :)'
}
}
lihat demo yang berfungsi di Plunker.
