Ekspresi ___ telah berubah setelah diperiksa

286

Mengapa komponen dalam 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 :)'
  }
}

pelemparan:

PENGECUALIAN: Ekspresi 'Saya {{message}} di App @ 0: 5' telah berubah setelah diperiksa. Nilai sebelumnya: 'Saya memuat :('. Nilai saat ini: 'Saya sudah selesai memuat :)' di [Saya {{message}} di App @ 0: 5]

ketika semua yang saya lakukan adalah memperbarui ikatan sederhana ketika pandangan saya dimulai?

menggambar moore
sumber
7
Artikel Semua yang perlu Anda ketahui tentang ExpressionChangedAfterItHasBeenCheckedErrorkesalahan menjelaskan perilakunya dengan sangat rinci.
Max Koretskyi
Pertimbangkan untuk memodifikasi Anda ChangeDetectionStrategysaat menggunakan detectChanges() stackoverflow.com/questions/39787038/…
Luis Limas
Bayangkan saja ada kontrol input dan Anda mengisi data untuk itu dalam suatu metode dan dalam metode yang sama Anda memberikan beberapa nilai untuk itu. Compiler akan bingung pasti dengan nilai baru / sebelumnya. Jadi mengikat dan mengisi harus terjadi dengan metode yang berbeda.
MAC

Jawaban:

292

Seperti yang dinyatakan oleh drewmoore, solusi yang tepat dalam hal ini adalah secara manual memicu deteksi perubahan untuk komponen saat ini. Ini dilakukan dengan menggunakan detectChanges()metode ChangeDetectorRefobjek (diimpor dari angular2/core), atau markForCheck()metode, yang juga membuat setiap 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 juga Plunker yang menunjukkan pendekatan ngOnInit , setTimeout , dan enableProdMode untuk berjaga-jaga.

Kiara Grouwstra
sumber
7
Dalam kasus saya, saya membuka modal. Setelah membuka modal itu menunjukkan pesan "Ekspresi ___ telah berubah setelah diperiksa", jadi solusi saya ditambahkan this.cdr.detectChanges (); setelah membuka modal saya. Terima kasih!
Jorge Casariego
Di mana deklarasi Anda untuk properti cdr? Saya berharap melihat garis seperti cdr : anyatau sesuatu seperti itu di bawah messagedeklarasi. Hanya khawatir saya melewatkan sesuatu?
CodeCabbie
1
@CodeCabbie ada dalam argumen konstruktor.
slasky
1
Resolusi ini memperbaiki masalah saya! Terima kasih banyak! Cara yang sangat jelas dan sederhana.
lwozniak
3
Ini membantu saya - dengan beberapa modifikasi. Saya harus menata elemen li yang dihasilkan melalui ngFor loop. Saya perlu mengubah warna bentang dalam item daftar berdasarkan innerText setelah mengklik 'sort' yang memperbarui bool yang digunakan sebagai parameter dari pipa sortir (hasil yang diurutkan adalah salinan data, sehingga gaya tidak mendapatkan diperbarui dengan ngStyle saja). - Alih-alih menggunakan 'AfterViewInit', saya menggunakan 'AfterViewChecked' - Saya juga memastikan untuk mengimpor dan mengimplementasikan AfterViewChecked. note: mengatur pipa ke 'pure: false' tidak berfungsi, saya harus menambahkan langkah ekstra ini (:
Chloe Corrigan
170

Pertama, perhatikan bahwa pengecualian ini hanya akan dibuang ketika Anda menjalankan aplikasi Anda dalam mode dev (yang merupakan kasus default pada beta-0): Jika Anda menelepon enableProdMode()saat bootstrap aplikasi, itu tidak akan dibuang ( lihat plunk yang diperbarui ).

Kedua, jangan lakukan itu karena pengecualian ini dilempar karena alasan yang baik: Singkatnya, ketika dalam mode dev, setiap putaran deteksi perubahan segera diikuti oleh putaran kedua yang memverifikasi bahwa tidak ada binding yang berubah sejak akhir yang pertama, karena ini akan menunjukkan bahwa perubahan disebabkan oleh perubahan deteksi itu sendiri.

Dalam plunk Anda, pengikatan {{message}}diubah oleh panggilan Anda ke setMessage(), yang terjadi di ngAfterViewInithook, yang terjadi sebagai bagian dari giliran deteksi perubahan awal. Itu sendiri tidak bermasalah - masalahnya adalah bahwa setMessage()perubahan mengikat tetapi tidak memicu putaran baru deteksi perubahan, yang berarti bahwa perubahan ini tidak akan terdeteksi sampai beberapa putaran deteksi perubahan berikutnya dipicu di tempat lain.

The takeaway: Apa pun yang mengubah kebutuhan mengikat untuk memicu putaran deteksi perubahan saat itu terjadi.

Perbarui sebagai respons terhadap semua permintaan untuk contoh cara melakukan itu : Solusi @ Tycho berfungsi, seperti yang ditunjukkan tiga metode dalam jawaban yang ditunjukkan @MarkRajcok. Tapi sejujurnya, mereka semua merasa jelek dan salah bagi saya, seperti jenis peretasan yang biasa kami gunakan di ng1.

Yang pasti, ada sesekali keadaan di mana hacks ini tepat, tetapi jika Anda menggunakan mereka pada sesuatu yang lebih dari yang sangat dasar sesekali, itu pertanda bahwa Anda berjuang kerangka bukan sepenuhnya merangkul alam reaktif.

IMHO, yang lebih idiomatik, "Angular2 way" untuk mendekati ini adalah sesuatu di sepanjang baris: ( 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 :)')
  }
}
menggambar moore
sumber
13
Mengapa setMessage () tidak memicu putaran baru deteksi perubahan? Saya pikir Angular 2 secara otomatis memicu deteksi perubahan ketika Anda mengubah nilai sesuatu di UI.
Danny Libin
4
@drewmoore "Apa pun yang mengubah kebutuhan mengikat untuk memicu putaran deteksi perubahan ketika itu terjadi". Bagaimana? Apakah ini praktik yang baik? Bukankah semuanya harus diselesaikan dalam satu kali?
Daniel Birowsky Popeski
2
@ Oto, memang. Sejak saya menulis komentar itu, saya telah menjawab pertanyaan lain di mana saya menjelaskan 3 cara untuk menjalankan deteksi perubahan , yang termasuk detectChanges().
Mark Rajcok
4
hanya untuk mencatat bahwa, di badan pertanyaan saat ini, metode yang dipanggil bernama updateMessage, tidaksetMessage
superjos
3
@ Daynil, saya memiliki perasaan yang sama, sampai saya membaca blog yang diberikan dalam komentar di bawah pertanyaan: blog.angularindepth.com/… Ini menjelaskan mengapa ini perlu dilakukan secara manual. Dalam hal ini, deteksi perubahan sudut memiliki siklus hidup. Jika ada sesuatu yang mengubah nilai di antara siklus hidup ini, maka deteksi perubahan yang kuat perlu dijalankan (atau settimeout - yang dijalankan di loop acara berikutnya, memicu deteksi perubahan lagi).
Mahesh
50

ngAfterViewChecked() bekerja untuk saya:

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

constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
   //your code to update the model
   this.cdr.detectChanges();
}
Jaswanth Kumar
sumber
Seperti yang digambarkan, siklus deteksi perubahan sudut yang terdiri dari dua fase, harus mendeteksi perubahan setelah pandangan anak diubah, dan saya merasakan cara terbaik untuk melakukannya adalah menggunakan kait siklus hidup yang disediakan oleh sudut itu sendiri, dan meminta sudut untuk secara manual mendeteksi perubahan dan mengikatnya. Pendapat pribadi saya adalah ini sepertinya jawaban yang tepat.
Joey587
1
Ini bekerja untuk saya, untuk memuat komponen dinamis hierarki di dalam bersama-sama.
Mehdi Daustany
47

Saya memperbaikinya dengan menambahkan ChangeDetectionStrategy dari inti sudut.

import {  Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'page1',
  templateUrl: 'page1.html',
})
Biranchi
sumber
Ini berhasil untuk saya. Saya ingin tahu apa perbedaan antara ini dan menggunakan ChangeDetectorRef
Ari
1
Hmm ... Kemudian mode detektor perubahan awalnya akan diatur ke CheckOnce( dokumentasi )
Alex Klaus
Ya, kesalahan / peringatan hilang. Tapi itu mengambil banyak waktu pemuatan daripada sebelumnya seperti perbedaan 5-7 detik yang sangat besar.
Kapil Raghuwanshi
1
@KapilRaghuwanshi Jalankan this.cdr.detectChanges();setelah apa pun yang Anda coba muat. Karena itu mungkin karena deteksi perubahan tidak memicu
Harshal Carpenter
36

Tidak bisakah Anda menggunakan ngOnInitkarena Anda hanya mengubah variabel anggota message?

Jika Anda ingin mengakses referensi ke komponen anak @ViewChild(ChildComponent), Anda memang harus menunggu ngAfterViewInit.

Perbaikan kotor adalah memanggil updateMessage()loop acara berikutnya dengan misalnya setTimeout.

ngAfterViewInit() {
  setTimeout(() => {
    this.updateMessage();
  }, 1);
}
Jo VdB
sumber
4
Mengubah kode saya menjadi metode ngOnInit bekerja untuk saya.
Faliorn
29

Untuk ini saya sudah mencoba jawaban di atas banyak yang tidak berfungsi dalam versi terbaru Angular (6 atau lebih baru)

Saya menggunakan kontrol Material yang memerlukan perubahan setelah pengikatan pertama dilakukan.

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

Menambahkan jawaban saya, ini membantu beberapa orang memecahkan masalah tertentu.

MarmiK
sumber
Ini benar-benar bekerja untuk kasus saya, tetapi Anda memiliki kesalahan ketik, setelah menerapkan AfterContentChecked Anda harus memanggil ngAfterContentChecked, bukan ngAfterViewInit.
Tomas Lukac
Saya saat ini pada versi 8.2.0 :)
Tomas Lukac
1
@ TomášLukáč Terima kasih atas balasan, saya telah memperbarui jawaban saya (y)
MarmiK
1
Saya merekomendasikan jawaban ini jika tidak ada yang di atas tidak berfungsi.
Amir Choubani
afterContentChecked dipanggil setiap kali DOM dihitung ulang atau diperiksa ulang. Intimating from Angular versi 9
Imran Faruqi
24

Artikel Semua yang perlu Anda ketahui tentang ExpressionChangedAfterItHasBeenCheckedErrorkesalahan menjelaskan perilakunya dengan sangat rinci.

Masalah dengan pengaturan Anda adalah bahwa ngAfterViewInitkait siklus hidup dijalankan setelah deteksi perubahan diproses pembaruan DOM. Dan Anda secara efektif mengubah properti yang digunakan dalam templat di kait ini yang berarti bahwa DOM perlu dirender ulang:

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

dan ini akan membutuhkan siklus deteksi perubahan lain dan Angular by design hanya menjalankan satu siklus digest.

Anda pada dasarnya memiliki dua alternatif cara memperbaikinya:

  • perbarui properti secara sinkron baik menggunakan setTimeout, Promise.thenatau asynchronous yang dapat diamati yang direferensikan dalam template

  • lakukan pembaruan properti di sebuah kail sebelum pembaruan DOM - ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.

Max Koretskyi
sumber
Baca artikel Anda: blog.angularindepth.com/… , akan segera membaca satu lagi blog.angularindepth.com/… . Masih tidak dapat menemukan solusi untuk masalah ini. dapatkah Anda memberi tahu saya apa yang terjadi jika saya menambahkan kait siklus hidup ngDoCheck atau ngAfterContentChecked dan menambahkan this.cdr.markForCheck (); (cdr untuk ChangeDetectorRef) di dalamnya. Bukankah ini cara yang tepat untuk memeriksa perubahan setelah hook siklus hidup dan cek berikutnya selesai.
Harsimer
9

Anda hanya perlu memperbarui pesan Anda di hook siklus hidup yang benar, dalam hal ini ngAfterContentCheckedbukanngAfterViewInit , karena di ngAfterViewInit cek untuk pesan variabel telah dimulai tetapi belum berakhir.

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

jadi kodenya akan adil:

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 kerja di Plunker.

RedPelle
sumber
Saya menggunakan kombinasi @ViewChildren()penghitung berbasis panjang yang dihuni oleh Observable. Ini adalah satu-satunya solusi yang berhasil untuk saya!
msanford
2
Kombinasi dari ngAfterContentCheckeddan ChangeDetectorRefdari atas berhasil untuk saya. Saat ngAfterContentCheckeddipanggil -this.cdr.detectChanges();
Kunal Dethe
7

Kesalahan ini terjadi karena nilai yang ada diperbarui segera setelah diinisialisasi. Jadi jika Anda akan memperbarui nilai baru setelah nilai yang ada diberikan di DOM, Maka itu akan berfungsi dengan baik. Seperti disebutkan dalam artikel ini Angular Debugging "Ekspresi telah berubah setelah diperiksa"

misalnya bisa kamu gunakan

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 itu adalah API yang disediakan browser, bukan JavaScript API, Jadi ini akan berjalan secara terpisah di tumpukan browser dan akan menunggu sampai item tumpukan panggilan selesai.

Bagaimana konsep browser API envokes dijelaskan oleh Philip Roberts di salah satu video Youtube (What the hack is event loop?).

sharad jain
sumber
4

Anda juga dapat melakukan panggilan ke updateMessage () di Metode ngOnInt () - setidaknya berfungsi untuk saya

ngOnInit() {
    this.updateMessage();
}

Dalam RC1 ini tidak memicu pengecualian

Tobias Gassmann
sumber
3

Anda juga dapat membuat timer menggunakan Observable.timerfungsi rxjs , dan kemudian memperbarui pesan di langganan Anda:                    

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

Itu melempar kesalahan karena kode Anda diperbarui ketika ngAfterViewInit () dipanggil. Berarti nilai awal Anda berubah ketika ngAfterViewInit terjadi, Jika Anda memanggilnya di ngAfterContentInit () maka itu tidak akan menimbulkan kesalahan.

ngAfterContentInit() {
    this.updateMessage();
}
Sandip - Pengembang Stack Lengkap
sumber
0

Saya tidak bisa mengomentari posting @Biranchi karena saya tidak punya reputasi yang cukup, tetapi itu memperbaiki masalah bagi saya.

Satu hal yang perlu diperhatikan! Jika menambahkan changeDetection: ChangeDetectionStrategy.OnPush pada komponen tidak berfungsi, dan komponen turunannya (komponen bodoh) coba menambahkannya ke induk juga.

Ini memperbaiki bug, tapi saya ingin tahu apa efek sampingnya.

TKDev
sumber
0

Saya mendapat kesalahan serupa saat bekerja dengan datatable. Apa yang terjadi adalah ketika Anda menggunakan * ngFor di dalam yang lain * ngFor datatable membuang kesalahan ini karena ia mengganggu siklus perubahan sudut. SO daripada menggunakan datatable di dalam datatable, gunakan satu tabel biasa atau ganti mf.data dengan nama array. Ini berfungsi dengan baik.

Priyanka Arora
sumber
0

Saya pikir solusi paling sederhana adalah sebagai berikut:

  1. Buat satu implementasi untuk menetapkan nilai ke beberapa variabel yaitu melalui fungsi atau setter.
  2. Buat variabel kelas (static working: boolean)di kelas di mana fungsi ini ada dan setiap kali Anda memanggil fungsi, cukup buat yang mana pun yang Anda suka. Dalam fungsi tersebut, jika nilai bekerja itu benar, maka langsung kembali tanpa melakukan apa-apa. lain, lakukan tugas yang Anda inginkan. Pastikan untuk mengubah variabel ini menjadi false setelah tugas selesai yaitu pada akhir baris kode atau dalam metode berlangganan ketika Anda selesai menetapkan nilai!
Imran Faruqi
sumber