Biểu thức ___ đã thay đổi sau khi được kiểm tra


286

Tại sao thành phần trong plunk đơn giản này

@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 :)'
  }
}

ném:

NGOẠI TRỪ: Biểu thức 'Tôi {{message}} trong Ứng dụng @ 0: 5' đã thay đổi sau khi được kiểm tra. Giá trị trước: 'Tôi đang tải :('. Giá trị hiện tại: 'Tôi đã tải xong :)' trong [Tôi {{message}} trong Ứng dụng @ 0: 5]

khi tất cả những gì tôi đang làm là cập nhật một ràng buộc đơn giản khi chế độ xem của tôi được bắt đầu?



Cân nhắc sửa đổi ChangeDetectionStrategykhi sử dụng detectChanges() stackoverflow.com/questions/39787038/ từ
Luis Limas

Chỉ cần nghĩ về việc có một điều khiển đầu vào và bạn đang điền dữ liệu vào nó trong một phương thức và trong cùng một phương thức bạn đang gán một số giá trị cho nó. Trình biên dịch sẽ bị nhầm lẫn với giá trị mới / trước đó. Vì vậy, ràng buộc và dân cư nên xảy ra trong các phương pháp khác nhau.
MAC

Câu trả lời:


292

Như đã nêu của drewmoore, giải pháp thích hợp trong trường hợp này là kích hoạt thủ công phát hiện thay đổi cho thành phần hiện tại. Điều này được thực hiện bằng cách sử dụng detectChanges()phương thức của ChangeDetectorRefđối tượng (được nhập từ angular2/core) hoặc markForCheck()phương thức của nó , cũng làm cho bất kỳ thành phần cha mẹ nào được cập nhật. Ví dụ liên quan :

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

}

Đây cũng là các Plunker thể hiện các cách tiếp cận ngOnInit , setTimeoutenableProdMode chỉ trong trường hợp.


7
Trong trường hợp của tôi, tôi đã mở một phương thức. Sau khi mở phương thức, nó hiển thị thông báo "Biểu thức ___ đã thay đổi sau khi được kiểm tra", vì vậy giải pháp của tôi đã được thêm this.cdr.detectChanges (); sau khi mở phương thức của tôi Cảm ơn!
Jorge Casariego

Tuyên bố của bạn cho tài sản cdr ở đâu? Tôi sẽ mong đợi để xem một dòng như cdr : anyhoặc một cái gì đó như thế dưới messagekhai báo. Chỉ lo là tôi đã bỏ lỡ điều gì?
CodeCabbie

1
@CodeCabbie đó là trong các đối số của hàm tạo.
lả lơi

1
Giải pháp này khắc phục vấn đề của tôi! Cảm ơn rất nhiều! Cách rất rõ ràng và đơn giản.
lwozniak

3
Điều này đã giúp tôi - với một vài sửa đổi. Tôi đã định kiểu các phần tử li được tạo thông qua vòng lặp ngFor. Tôi cần thay đổi màu của các nhịp trong các mục danh sách dựa trên InternalText khi nhấp vào 'sort' đã cập nhật một bool đang được sử dụng làm tham số của một ống sắp xếp (kết quả được sắp xếp là một bản sao của dữ liệu, vì vậy các kiểu không nhận được cập nhật với ngStyle một mình). - Thay vì sử dụng 'AfterViewInit', tôi đã sử dụng 'AfterViewChecked' - Tôi cũng đảm bảo nhậptriển khai AfterViewChecked. lưu ý: đặt đường ống thành 'thuần: sai' không hoạt động, tôi phải thêm bước bổ sung này (:
Chloe Corrigan

170

Đầu tiên, lưu ý rằng ngoại lệ này sẽ chỉ được ném khi bạn đang chạy ứng dụng của mình ở chế độ dev (theo mặc định là beta-0): Nếu bạn gọi enableProdMode()khi bootstrapping ứng dụng, nó sẽ không bị ném ( xem cập nhật đầy đủ ).

Thứ hai, đừng làm điều đó bởi vì ngoại lệ này bị ném vì lý do chính đáng: Tóm lại, khi ở chế độ dev, mọi vòng phát hiện thay đổi được theo dõi ngay sau vòng thứ hai để xác minh không có ràng buộc nào thay đổi kể từ khi kết thúc vòng đầu tiên, vì điều này sẽ chỉ ra rằng những thay đổi đang được gây ra bởi chính phát hiện thay đổi.

Trong phần mở rộng của bạn, ràng buộc {{message}}được thay đổi bởi lệnh gọi của bạn setMessage(), xảy ra trong ngAfterViewInithook, xảy ra như một phần của lượt phát hiện thay đổi ban đầu. Mặc dù bản thân nó không có vấn đề gì - vấn đề là setMessage()thay đổi liên kết nhưng không kích hoạt vòng phát hiện thay đổi mới, nghĩa là thay đổi này sẽ không được phát hiện cho đến khi một vòng phát hiện thay đổi trong tương lai được kích hoạt ở một nơi khác.

Việc thực hiện: Bất cứ điều gì thay đổi ràng buộc đều cần để kích hoạt vòng phát hiện thay đổi khi thực hiện.

Cập nhật để đáp ứng với tất cả các yêu cầu cho một ví dụ về cách thực hiện điều đó : Giải pháp của @ Tycho hoạt động, cũng như ba phương pháp trong câu trả lời @MarkRajcok đã chỉ ra. Nhưng thành thật mà nói, tất cả họ đều cảm thấy xấu xí và sai đối với tôi, giống như kiểu hack mà chúng ta đã quen với việc dựa vào ng1.

Để chắc chắn, thỉnh thoảng có trường hợp hack này là phù hợp, nhưng nếu bạn đang sử dụng chúng trên bất cứ điều gì ngoài một cơ sở rất thường xuyên, đó là một dấu hiệu cho thấy bạn đang chiến đấu với khuôn khổ thay vì chấp nhận hoàn toàn bản chất phản ứng của nó.

IMHO, một cách thành ngữ hơn, "cách Angular2" để tiếp cận điều này là một cái gì đó dọc theo dòng: ( plunk )

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

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

13
Tại sao setMessage () không kích hoạt vòng phát hiện thay đổi mới? Tôi nghĩ Angular 2 tự động kích hoạt phát hiện thay đổi khi bạn thay đổi giá trị của thứ gì đó trong UI.
Danny Libin

4
@drewmoore "Bất cứ điều gì thay đổi một ràng buộc cần phải kích hoạt một vòng phát hiện thay đổi khi nó xảy ra". Làm sao? Đó có phải là một thực hành tốt? Không phải mọi thứ nên được hoàn thành trong một lần chạy sao?
Daniel Birowsky Popeski

2
@Tycho, thực sự. Vì tôi đã viết nhận xét đó, tôi đã trả lời một câu hỏi khác trong đó tôi mô tả 3 cách để chạy phát hiện thay đổi , bao gồm detectChanges().
Mark Rajcok 6/2/2016

4
chỉ cần lưu ý rằng, trong phần thân câu hỏi hiện tại, phương thức được gọi được đặt tên updateMessage, không phảisetMessage
superjos

3
@Daynil, tôi cũng có cảm giác tương tự, cho đến khi tôi đọc blog được đưa ra trong nhận xét dưới câu hỏi: blog.angularindepth.com/. Nó giải thích lý do tại sao điều này cần phải được thực hiện thủ công. Trong trường hợp này, phát hiện thay đổi của góc có vòng đời. Trong trường hợp có thứ gì đó thay đổi giá trị ở giữa các vòng đời này, thì cần phải phát hiện thay đổi mạnh mẽ (hoặc giải quyết - thực hiện trong vòng lặp sự kiện tiếp theo, kích hoạt lại phát hiện thay đổi).
Mahesh

50

ngAfterViewChecked() đã làm cho tôi:

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

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

Như đã đề cập, chu kỳ phát hiện thay đổi của góc có hai pha, phải phát hiện các thay đổi sau khi chế độ xem của trẻ bị thay đổi và tôi cảm thấy cách tốt nhất để làm điều đó là sử dụng móc vòng đời do chính góc cung cấp và yêu cầu góc phải tự làm phát hiện sự thay đổi và ràng buộc nó. Ý kiến ​​cá nhân của tôi là đây có vẻ là một câu trả lời thích hợp.
Joey587

1
Công việc này đối với tôi, để tải thành phần động phân cấp bên trong cùng nhau.
Mehdi Daustany

47

Tôi đã sửa lỗi này bằng cách thêm ChangeDetectionStrargety từ lõi góc.

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

Điều này làm việc cho tôi. Tôi tự hỏi sự khác biệt giữa cái này và sử dụng ChangeDetectorRef
Ari

1
Hmm ... Sau đó, chế độ của trình phát hiện thay đổi ban đầu sẽ được đặt thành CheckOnce( tài liệu )
Alex Klaus

Có, lỗi / cảnh báo đã biến mất. Nhưng nó đang chiếm nhiều thời gian tải hơn trước đây như chênh lệch 5-7 giây, rất lớn.
Kapil Raghuwanshi

1
@KapilRaghuwanshi Chạy this.cdr.detectChanges();theo bất cứ thứ gì bạn đang cố tải. Bởi vì có thể là do phát hiện thay đổi không được kích hoạt
Harshal Carpenter

36

Bạn không thể sử dụng ngOnInitvì bạn chỉ thay đổi biến thành viên message?

Nếu bạn muốn truy cập một tham chiếu đến một thành phần con @ViewChild(ChildComponent), bạn thực sự cần phải chờ nó với ngAfterViewInit.

Một sửa chữa bẩn là gọi updateMessage()vòng lặp sự kiện tiếp theo với ví dụ setTimeout.

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

4
Thay đổi mã của tôi thành phương thức ngOnInit làm việc cho tôi.
Faliorn

29

Vì điều này tôi đã thử các câu trả lời ở trên, nhiều câu trả lời không hoạt động trong phiên bản Angular mới nhất (6 trở lên)

Tôi đang sử dụng Kiểm soát vật liệu yêu cầu thay đổi sau khi thực hiện ràng buộc đầu tiên.

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

Thêm câu trả lời của tôi như vậy, điều này giúp một số giải quyết vấn đề cụ thể.


Điều này thực sự hiệu quả với trường hợp của tôi, nhưng bạn có một lỗi đánh máy, sau khi triển khai AfterContentChecked, bạn nên gọi ngAfterContentChecked, không phải ngAfterViewInit.
Tomas Lukac

Tôi hiện đang ở phiên bản 8.2.0 :)
Tomas Lukac

1
@ TomášLukáč Cảm ơn bạn đã trả lời, tôi đã cập nhật câu trả lời của mình (y)
MarmiK

1
Tôi đề nghị câu trả lời này nếu không có cách nào ở trên không hoạt động.
Amir Choubani

afterContentChecked được gọi mỗi khi DOM được tính toán lại hoặc kiểm tra lại. Bắt đầu từ phiên bản Angular 9
Imran Faruqi

24

Bài viết Mọi thứ bạn cần biết về ExpressionChangedAfterItHasBeenCheckedErrorlỗi đều giải thích hành vi rất chi tiết.

Vấn đề với bạn thiết lập là ngAfterViewInithook vòng đời được thực thi sau khi phát hiện thay đổi các bản cập nhật DOM được xử lý. Và bạn đang thay đổi một cách hiệu quả thuộc tính được sử dụng trong mẫu trong hook này, điều đó có nghĩa là DOM cần được kết xuất lại:

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

và điều này sẽ yêu cầu một chu kỳ phát hiện thay đổi khác và Angular theo thiết kế chỉ chạy một chu kỳ tiêu hóa.

Về cơ bản, bạn có hai cách để khắc phục:

  • cập nhật tài sản không đồng bộ hoặc sử dụng setTimeout, Promise.thenhoặc không đồng bộ quan sát được tham chiếu trong mẫu

  • thực hiện cập nhật thuộc tính trong một hook trước khi cập nhật DOM - ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.


Đọc bài viết của bạn: blog.angularindepth.com/ , Sẽ sớm đọc một blog khác.angularindepth.com . Vẫn không thể tìm ra giải pháp cho vấn đề này. bạn có thể cho tôi biết điều gì xảy ra nếu tôi thêm ngDoCheck hoặc ngAfterContentChecked móc vòng đời và thêm cái này.cdr.markForCheck (); (cdr cho ChangeDetectorRef) bên trong nó. Đây không phải là một cách đúng đắn để kiểm tra các thay đổi sau khi móc vòng đời và kiểm tra tiếp theo hoàn tất.
Harsimer

9

Bạn chỉ cần cập nhật tin nhắn của mình trong móc vòng đời bên phải, trong trường hợp này là ngAfterContentCheckedthay ngAfterViewInitvì bởi vì trong ngAfterViewInit, kiểm tra thông báo biến đã được bắt đầu nhưng chưa kết thúc.

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

vì vậy mã sẽ chỉ là:

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

xem bản demo làm việc trên Plunker.


Tôi đang sử dụng kết hợp một @ViewChildren()bộ đếm dựa trên chiều dài được đặt bởi một Đài quan sát. Đây là giải pháp duy nhất hiệu quả với tôi!
msanford

2
Kết hợp ngAfterContentCheckedChangeDetectorReftừ trên làm việc cho tôi. Khi ngAfterContentCheckedđược gọi -this.cdr.detectChanges();
Kunal Dethe

7

Lỗi này đang đến vì giá trị hiện tại đang được cập nhật ngay lập tức sau khi được khởi tạo. Vì vậy, nếu bạn sẽ cập nhật giá trị mới sau khi giá trị hiện tại được hiển thị trong DOM, thì nó sẽ hoạt động tốt. Giống như đã đề cập trong bài viết này Gỡ lỗi góc "Biểu thức đã thay đổi sau khi được kiểm tra"

ví dụ bạn có thể sử dụng

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

}

hoặc là

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

Như bạn có thể thấy tôi đã không đề cập đến thời gian trong phương thức setTimeout. Vì nó là API do trình duyệt cung cấp, không phải API JavaScript, vì vậy, nó sẽ chạy riêng biệt trong ngăn xếp trình duyệt và sẽ đợi cho đến khi các mục ngăn xếp cuộc gọi kết thúc.

Làm thế nào khái niệm envokes API trình duyệt được Philip Roberts giải thích trong một trong các video Youtube (Vòng lặp sự kiện là gì?).


4

Bạn cũng có thể thực hiện cuộc gọi của mình đến updateMessage () trong phương thức ngOnInt () - ít nhất là nó hoạt động với tôi

ngOnInit() {
    this.updateMessage();
}

Trong RC1, điều này không kích hoạt ngoại lệ


3

Bạn cũng có thể tạo bộ hẹn giờ bằng Observable.timerchức năng rxjs , sau đó cập nhật tin nhắn trong đăng ký của bạn:                    

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

2

Nó báo lỗi vì mã của bạn được cập nhật khi ngAfterViewInit () được gọi. Có nghĩa là giá trị ban đầu của bạn đã thay đổi khi ngAfterViewInit diễn ra, Nếu bạn gọi nó trong ngAfterContentInit () thì nó sẽ không gây ra lỗi.

ngAfterContentInit() {
    this.updateMessage();
}

0

Tôi không thể nhận xét về bài đăng của @Biranchi vì tôi không có đủ danh tiếng, nhưng nó đã khắc phục vấn đề cho tôi.

Một điều cần lưu ý! Nếu thêm changeDetection: ChangeDetectionStrargety.OnPush trên thành phần không hoạt động và thành phần con của nó (thành phần câm) cũng thử thêm nó vào cha mẹ.

Điều này đã sửa lỗi, nhưng tôi tự hỏi những tác dụng phụ của việc này là gì.


0

Tôi đã gặp lỗi tương tự trong khi làm việc với dữ liệu. Điều gì xảy ra là khi bạn sử dụng * ngFor bên trong một * ngFor datable khác có thể gây ra lỗi này khi nó xen vào chu kỳ thay đổi góc. SO thay vì sử dụng datable bên trong datable sử dụng một bảng thông thường hoặc thay thế mf.data bằng tên mảng. Điều này hoạt động tốt.


0

Tôi nghĩ rằng một giải pháp đơn giản nhất sẽ như sau:

  1. Thực hiện một việc thực hiện gán giá trị cho một số biến tức là thông qua hàm hoặc setter.
  2. Tạo một biến lớp (static working: boolean)trong lớp nơi hàm này tồn tại và mỗi khi bạn gọi hàm, chỉ cần làm cho nó đúng bất cứ khi nào bạn muốn. Trong hàm, nếu giá trị làm việc là đúng, thì chỉ cần trả về ngay mà không làm gì cả. khác, thực hiện các nhiệm vụ bạn muốn. Đảm bảo thay đổi biến này thành false sau khi tác vụ hoàn thành, tức là ở cuối dòng mã hoặc trong phương thức đăng ký khi bạn hoàn thành việc gán giá trị!
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.