Почему компонент этого простого plunk
@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 :)'
}
}
метательное:
ИСКЛЮЧЕНИЕ: выражение "Я {{сообщение}} в приложении @0: 5 'изменилось после его проверки. Предыдущее значение:" Я загружаю:( "Текущее значение:" Я все загрузил:)" в [Я {{сообщение}} в приложении @0: 5]
Когда все, что я делаю, обновляет простое связывание при инициировании моего представления?
Во-первых, обратите внимание, что это исключение будет генерироваться только при запуске вашего приложения в режиме dev (что по умолчанию имеет значение по умолчанию на бета-0): если вы вызываете enableProdMode()
при загрузке приложения, он не будет сброшен (см. обновленный список).
Во-вторых, не делают этого, потому что это исключение вызывается по уважительной причине: Короче говоря, когда в режиме dev каждый раунд обнаружения изменений сразу же сопровождается вторым раундом, который не проверяет привязки изменились с момента окончания первого, так как это указывает на то, что изменения вызваны самим обнаружением изменения.
В вашем плунге привязка {{message}}
изменяется по вашему вызову на setMessage()
, что происходит в hook ngAfterViewInit
, который происходит как часть начального поворота обнаружения изменений. Это само по себе не является проблематичным - проблема заключается в том, что setMessage()
изменяет привязку, но не вызывает новый раунд обнаружения изменений, а это означает, что это изменение не будет обнаружено до тех пор, пока какой-либо другой раунд обнаружения изменений не будет запущен где-то в другом месте,
Вынос: Все, что меняет привязку, должно запускать раунд обнаружения изменений, когда это происходит.
Обновление в ответ на все запросы для примера того, как это сделать: решение @Tycho работает, как и три метода в ответе @MarkRajcok отметил. Но, честно говоря, все они кажутся уродливыми и неправильными для меня, такими, как хаки, которые мы привыкли опираться на ng1.
Конечно, бывают случайные обстоятельства, когда эти хаки подходят, но если вы используете их на чем-то более чем случайном основании, это признак того, что вы сражаетесь с каркасом, а не полностью обнимаете его реактивный характер,
ИМХО, более идиоматический способ "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 :)')
}
}
Как указано в drewmoore, правильным решением в этом случае является ручное инициирование обнаружения изменений для текущего компонента. Это делается с помощью метода detectChanges()
объекта ChangeDetectorRef
(импортированного из angular2/core
) или его метода markForCheck()
, который также обновляет любые родительские компоненты. Релевантно пример:
import { Component, ChangeDetectorRef } from 'angular2/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();
}
}
Вот также Plunkers, демонстрирующие ngOnInit, setTimeout, и enableProdMode подходит на всякий случай.
cdr : any
или что-то подобное под объявлением message
. Просто беспокоишься, что я что-то пропустил?
Я исправил это, добавив ChangeDetectionStrategy из ядра angular.
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'page1',
templateUrl: 'page1.html',
})
CheckOnce
( документация )
Нельзя использовать ngOnInit
, потому что вы просто изменяете переменную-член message
?
Если вы хотите получить ссылку на дочерний компонент @ViewChild(ChildComponent)
, вам действительно нужно подождать его с помощью ngAfterViewInit
.
Грязное исправление - вызвать updateMessage()
в следующем цикле событий, например. SetTimeout.
ngAfterViewInit() {
setTimeout(() => {
this.updateMessage();
}, 1);
}
ngAfterViewChecked() работал у меня:
import { Component, ChangeDetectorRef } from '@angular/core'; //import ChangeDetectorRef
constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
//your code to update the model
this.cdr.detectChanges();
}
Статья Все, что вам нужно знать о ошибке 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.
Вам просто нужно обновить сообщение в правильном ключе жизненного цикла, в данном случае это 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.
@ViewChildren()
на @ViewChildren()
длины @ViewChildren()
который был заполнен Observable. Это было единственное решение, которое сработало для меня!
ngAfterContentChecked
сочетание ngAfterContentChecked
и ChangeDetectorRef
сверху. На ngAfterContentChecked
- this.cdr.detectChanges();
Вы также можете поместить свой вызов updateMessage() в метод ngOnInt(), по крайней мере, он работает для меня
ngOnInit() {
this.updateMessage();
}
В RC1 это не вызывает исключение
Для этого я попробовал выше ответы, многие из которых не работает в последней версии Angular (6 или более поздняя версия)
Я использую контроль материала, который требует изменений после первого связывания.
export class AbcClass implements OnInit, AfterContentChecked{
constructor(private ref: ChangeDetectorRef) {}
ngOnInit(){
// your tasks
}
ngAfterViewInit() {
this.ref.detectChanges();
}
}
Добавление моего ответа так, это помогает некоторым решить конкретную проблему.
Он выдает ошибку, потому что ваш код обновляется при вызове ngAfterViewInit(). Значит, ваше начальное значение изменилось, когда ngAfterViewInit имеет место. Если вы вызываете это в ngAfterContentInit(), то он не будет вызывать ошибку.
ngAfterContentInit() {
this.updateMessage();
}
Я не могу комментировать пост @Biranchi, так как у меня недостаточно репутации, но это исправило проблему для меня.
Стоит отметить одну вещь! Если добавление changeDetection: ChangeDetectionStrategy.OnPush на компонент не сработало, и его дочерний компонент (немой компонент), попробуйте добавить его и к родителю.
Это исправило ошибку, но мне интересно, каковы побочные эффекты этого.
Я уже пробовал это решение: (Он работает!)
export class BComponent {
name = 'I am B component';
@Input() text;
constructor(private parent: AppComponent) {}
ngOnInit() {
setTimeout(() => {
this.parent.text = 'updated text';
});
}
ngAfterViewInit() {
setTimeout(() => {
this.parent.name = 'updated name';
});
}
}
Подробнее вы можете прочитать здесь: https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
Вы также можете создать таймер с помощью функции rxjs Observable.timer
, а затем обновить сообщение в вашей подписке:
Observable.timer(1).subscribe(()=> this.updateMessage());
ExpressionChangedAfterItHasBeenCheckedError
объясняет поведение в деталях.