___ 확인 후 표현식이 변경되었습니다.


286

이 단순한 플 런크 의 구성 요소가 왜

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

던지기 :

예외 : App @ 0 : 5의 'I 'm {{message}}식이 확인 된 후 변경되었습니다. 이전 값 : 'I 'm loading :('. 현재 값 : 'I 'm all loading loading :)'[I 'm {{message}} in App @ 0 : 5]

내보기가 시작될 때 내가하고있는 모든 것이 간단한 바인딩을 업데이트하는 것입니까?


7
오류에 대해 알아야 할 모든ExpressionChangedAfterItHasBeenCheckedError 기사 는 동작을 자세하게 설명합니다.
Max Koretskyi

당신의 수정을 고려 ChangeDetectionStrategy하여 detectChanges() stackoverflow.com/questions/39787038/...을
루이스 마스에게

입력 컨트롤이 있다고 생각하면 메소드에서 데이터를 채우는 것과 동일한 방법으로 값을 지정하는 것입니다. 컴파일러는 새로운 / 이전 값과 혼동됩니다. 따라서 바인딩과 채우기는 다른 방법으로 발생해야합니다.
MAC

답변:


293

drewmoore에 의해 언급 된 바와 같이,이 경우 적절한 해결책은 현재 구성 요소에 대한 변경 감지를 수동으로 트리거하는 것입니다. 이는 객체 의 detectChanges()메소드 ChangeDetectorRef(에서 가져옴 angular2/core) 또는 해당 markForCheck()메소드를 사용하여 수행되며 , 이로 인해 상위 구성 요소도 업데이트됩니다. 관련 :

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

}

다음은 ngOnInit , setTimeoutenableProdMode 접근 방식을 보여주는 Plunkers 입니다.


7
제 경우에는 모달을 열었습니다. 모달을 연 후 "표현식 ___이 확인 된 후 변경되었습니다"라는 메시지가 표시되었으므로 솔루션에 this.cdr.detectChanges (); 내 모달을 연 후. 감사!
Jorge Casariego

cdr 속성에 대한 귀하의 선언은 어디에 있습니까? 선언 cdr : any에서 이와 비슷한 줄을 기대합니다 message. 내가 뭔가를 놓쳤다 고 걱정 했습니까?
CodeCabbie

1
@CodeCabbie 그것은 생성자 인수에 있습니다.
slasky 2016 년

1
이 해결책은 내 문제를 해결합니다! 고마워요! 매우 명확하고 간단한 방법.
lwozniak

3
이것은 약간의 수정으로 도움이되었습니다. ngFor 루프를 통해 생성 된 li 요소의 스타일을 지정해야했습니다. 정렬 파이프의 매개 변수로 사용되는 부울을 업데이트 한 '정렬'을 클릭하면 innerText를 기준으로 목록 항목의 범위 색상을 변경해야했습니다 (정렬 결과는 데이터의 사본이므로 스타일이 얻지 못했습니다) ngStyle만으로 업데이트 됨). - 대신 'AfterViewInit'를 사용하는, 내가 사용하는 'AfterViewChecked는' - 나는 또한에 확인했다 수입구현 AfterViewChecked을. 참고 : 파이프를 '순수 : 거짓'으로 설정하지 않으면이 추가 단계를 추가해야했습니다 (:
Chloe Corrigan

170

먼저,이 예외는 개발자 모드에서 앱을 실행할 때만 발생합니다 (기본적으로 베타 0의 경우). enableProdMode()앱을 부트 스트랩 할 때 호출 하면 발생하지 않습니다 ( 참조). 업데이트 된 plunk ).

둘째, 하지마 이 예외가 정당한 이유로 발생하기 때문에 . 즉, dev 모드에서는 모든 변경 라운드 감지 다음에 첫 번째 종료 이후 바인딩이 변경되지 않았 음을 확인하는 두 번째 라운드가 즉시 발생합니다. 변경 감지 자체로 인해 변경이 발생했음을 나타냅니다.

plunk에서을 {{message}}호출 하면 바인딩 이 변경됩니다.이 변경 setMessage()ngAfterViewInit후크에서 발생하며 초기 변경 감지 회전의 일부로 발생합니다. 그 자체로는 문제가되지 않습니다. 문제는 setMessage()바인딩 을 변경하지만 새로운 변경 라운드 감지를 트리거하지 않는다는 것입니다. 이는 향후 변경 라운드 감지가 다른 곳에서 트리거 될 때까지이 변경이 감지되지 않음을 의미합니다.

테이크 아웃 : 바인딩을 변경하는 것은 변경 감지를 트리거해야합니다 .

@MarkRajcok이 지적한 답변 의 세 가지 방법과 같이 @Tycho 의 솔루션이 작동하는 것처럼 모든 방법에 대한 예제 요청에 대한 응답으로 업데이트 하십시오 . 그러나 솔직히, 그들은 우리가 ng1에 기대 던 일종의 해킹과 같이 나에게 추악하고 잘못된 느낌을 느낍니다.

있다, 확실히 가끔 당신이보다 더 아무것도를 사용하는 경우 이러한 해킹이 적절한 상황,하지만 아주 가끔으로, 당신이 완전히의 반응 특성을 수용하기보다는 프레임 워크를 싸우고 있다는 신호입니다.

이것에 접근하는 더 관용적 인 "Angular2 방식"인 IMHO는 다음과 같은 라인입니다 : ( plunk )

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

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

13
setMessage ()가 새로운 라운드 변경 감지를 트리거하지 않는 이유는 무엇입니까? UI에서 무언가의 값을 변경할 때 Angular 2가 자동으로 변경 감지를 트리거한다고 생각했습니다.
Danny Libin

4
@drewmoore "바인딩을 변경하는 것은 변경 감지를 트리거해야합니다." 어떻게? 좋은 습관입니까? 한 번의 실행으로 모든 것이 완료되어서는 안됩니까?
Daniel Birowsky Popeski

2
실제로 @Tycho. 그 의견을 썼기 때문에을 포함 하여 변경 감지를 실행 하는 세 가지 방법을 설명하는 또 다른 질문에 대답했습니다 detectChanges().
Mark Rajcok

4
현재 질문 본문에서 호출 된 메소드의 이름은 다음 updateMessage과 같습니다.setMessage
superjos

3
@Daynil, 질문에서 코멘트로 주어진 블로그를 읽을 때까지 같은 느낌이 들었습니다. blog.angularindepth.com/… 이것이 수동으로 수행되어야하는 이유를 설명합니다. 이 경우 각도 변화 감지에는 수명주기가 있습니다. 이러한 수명주기 사이에 값이 변경되는 경우, 강력한 변경 감지 (또는 다음 이벤트 루프에서 실행되어 변경 감지를 다시 트리거하는 settimeout)를 실행해야합니다.
Mahesh

51

ngAfterViewChecked() 나를 위해 일했다 :

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

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

언급 한 바와 같이, 2 단계의 각도 변화 감지주기는 어린이의 시야가 수정 된 후 변화를 감지해야하며 각도 자체에서 제공하는 수명주기 후크를 사용하고 각도를 수동으로 요청하는 것이 가장 좋은 방법이라고 생각합니다 변경 사항을 감지하고 바인드하십시오. 내 개인적인 의견은 이것이 적절한 답변 인 것입니다.
Joey587

1
이것은 계층 동적 구성 요소를 함께로드하는 데 도움이됩니다.
Mehdi Daustany

47

각도 코어에서 ChangeDetectionStrategy를 추가하여이 문제를 해결했습니다.

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

이것은 나를 위해 일했습니다. 이것과 ChangeDetectorRef 사용의 차이점이 무엇인지 궁금합니다.
Ari

1
흠 ... 그러면 변경 감지기의 모드는 처음에 CheckOnce( documentation )로 설정됩니다.
Alex Klaus

예, 오류 / 경고가 사라졌습니다. 그러나 5-7 초의 차이와 같이 이전보다 많은 로딩 시간이 걸리고 있습니다.
Kapil Raghuwanshi

1
@KapilRaghuwanshi this.cdr.detectChanges();로드하려고 시도한 후에 실행하십시오 . 변경 감지가 트리거되지 않기 때문일 수 있습니다
Harshal Carpenter

36

ngOnInit멤버 변수를 변경했기 때문에 사용할 수 없습니까 message?

자식 구성 요소에 대한 참조에 액세스 @ViewChild(ChildComponent)하려면와 함께 기다려야합니다 ngAfterViewInit.

더티 픽스는 updateMessage()setTimeout과 같이 다음 이벤트 루프에서 in 을 호출하는 것 입니다.

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

4
내 코드를 ngOnInit 메소드로 변경하면 나에게 도움이되었습니다.
Faliorn

29

이를 위해 위의 답변을 시도했지만 많은 버전의 Angular (6 이상)에서 작동하지 않습니다.

첫 바인딩 후 변경이 필요한 머티리얼 컨트롤을 사용하고 있습니다.

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

내 대답을 추가하면 특정 문제를 해결하는 데 도움이됩니다.


이것은 실제로 제 경우에는 효과가 있었지만, 오타가 있습니다. AfterContentChecked를 구현 한 후에는 ngAfterViewInit가 아닌 ngAfterContentChecked를 호출해야합니다.
Tomas Lukac

나는 현재 버전 8.2.0에있다 :)
Tomas Lukac

1
회신 TomášLukáč 감사 @, 나는 내 대답 (y)를 업데이트 한
MarmiK

1
위의 어느 것도 작동하지 않으면이 답변을 권장합니다.
Amir Choubani

afterContentChecked는 DOM을 다시 계산하거나 다시 확인할 때마다 호출됩니다. Angular 버전 9에서 암시
Imran Faruqi

24

기사 오류 에 대해 알아야 할 모든 것ExpressionChangedAfterItHasBeenCheckedError 대해 는 동작을 자세하게 설명합니다.

설정 문제 ngAfterViewInit는 변경 감지 처리 된 DOM 업데이트 후에 수명주기 후크가 실행 된다는 것 입니다. 이 후크에서 템플릿에 사용되는 속성을 효과적으로 변경하면 DOM을 다시 렌더링해야합니다.

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

그리고 이것은 또 다른 변화 감지주기를 필요로하며 Angular는 설계 상 단 하나의 다이제스트 주기만 실행합니다.

기본적으로 두 가지 대안이 있습니다.

  • 사용 중 비동기 적 속성을 업데이트 setTimeout, Promise.then또는 비동기 관찰 템플릿에서 참조

  • DOM 업데이트 전 ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked에서 속성 업데이트를 수행합니다.


당신의 기사를 읽으십시오 : blog.angularindepth.com/… , 곧 또 하나의 blog.angularindepth.com/…을 읽을 것 입니다. 여전히이 문제에 대한 해결책을 찾을 수 없습니다. ngDoCheck 또는 ngAfterContentChecked 라이프 사이클 후크를 추가하고이 항목을 추가하면 어떻게되는지 알려주십시오 .cdr.markForCheck (); (ChangeDetectorRef의 경우 cdr). 수명주기 후크 및 후속 점검이 완료된 후 변경 사항을 점검하는 올바른 방법이 아닙니까?
Harsimer

9

올바른 라이프 사이클 후크에서 메시지를 업데이트하면됩니다.이 경우 ngAfterContentChecked에는ngAfterViewInit ngAfterViewInit에서 변수 메시지에 대한 검사가 시작 되었기 때문에, 아직 종료되지 않습니다.

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

코드는 다음과 같습니다.

import { Component } from 'angular2/core'

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

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

Plunker 의 실무 데모 를 참조하십시오 .


Observable에 의해 채워진 @ViewChildren()길이 기반 카운터 의 조합을 사용하고 있었습니다. 이것은 나를 위해 일한 유일한 솔루션이었습니다!
msanford

2
조합 ngAfterContentCheckedChangeDetectorRef에서 위의 나를 위해 일했다. 에 ngAfterContentChecked이라 -this.cdr.detectChanges();
쿠날 Dethe

7

이 오류는 기존 값이 초기화 된 직후 업데이트되기 때문에 발생합니다. 따라서 기존 값이 DOM에서 렌더링 된 후 새로운 값을 업데이트하면 정상적으로 작동합니다. 각도 디버깅 "이 확인 된 후 표현이 변경"

예를 들어 사용할 수 있습니다

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

}

또는

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

보시다시피 setTimeout 메서드에서 시간을 언급하지 않았습니다. JavaScript API가 아닌 브라우저 제공 API이므로 브라우저 스택에서 별도로 실행되며 호출 스택 항목이 완료 될 때까지 기다립니다.

필립 로버츠 (Philip Roberts)는 브라우저 API가 개념을 불러 일으키는 방법을 유튜브 비디오 (이벤트 루프 란 무엇인가)에서 설명합니다.


4

ngOnInt ()-Method에서 updateMessage ()를 호출 할 수도 있습니다. 적어도 그것은 나를 위해 작동합니다

ngOnInit() {
    this.updateMessage();
}

RC1에서는 예외가 발생하지 않습니다.


3

rxjs Observable.timer함수를 사용하여 타이머를 만든 다음 구독에서 메시지를 업데이트 할 수도 있습니다 .                    

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

2

ngAfterViewInit () 가 호출 되면 코드가 업데이트되므로 오류가 발생합니다 . ngAfterViewInit가 발생할 때 초기 값이 변경되었음을 의미합니다. ngAfterContentInit () 에서 해당 값을 호출 하면 오류가 발생하지 않습니다.

ngAfterContentInit() {
    this.updateMessage();
}

0

나는 평판이 충분하지 않기 때문에 @Biranchi의 게시물에 대해 언급 할 수 없었지만 문제가 해결되었습니다.

한 가지 유의할 사항! 구성 요소에 changeDetection : ChangeDetectionStrategy.OnPush 를 추가 해도 작동하지 않고 하위 구성 요소 (멍청한 구성 요소)도 부모에 추가해보십시오.

이것은 버그를 수정했지만 이것의 부작용이 무엇인지 궁금합니다.


0

datatable로 작업하는 동안 비슷한 오류가 발생했습니다. 다른 * ngFor datatable 내에서 * ngFor를 사용하면 각도 변경주기를 방해하는이 오류가 발생합니다. 따라서 datatable 내부에서 datatable을 사용하는 대신 하나의 일반 테이블을 사용하거나 mf.data를 배열 이름으로 바꾸십시오. 이것은 잘 작동합니다.


0

가장 간단한 해결책은 다음과 같습니다.

  1. 함수 또는 설정자를 통해 변수에 값을 할당하는 한 가지 구현을하십시오.
  2. (static working: boolean)이 함수가 존재하는 클래스에서 클래스 변수 를 작성하고 함수를 호출 할 때마다 원하는대로 변수 를 작성하십시오 . 함수 내에서 working의 가치가 사실이라면, 아무 것도하지 않고 바로 돌아갑니다. 그렇지 않으면 원하는 작업을 수행하십시오. 작업이 완료된 후 (예 : 코드 줄 끝 또는 값 할당이 완료되면 subscribe 메소드 내에서)이 변수를 false로 변경하십시오.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.