Angular2 ExpressionChangedAfterItHasBeenCheckedError
Angular2 ExpressionChangedAfterItHasBeenCheckedError
我已经阅读了有关此错误的文档,我想我明白了为什么会出现这个问题。我在尝试找出适合我的替代架构时遇到问题。
我网站的根组件 (app.component) 上有一个 "loading" 组件。它是一个第三方组件(ngx-loading),它基本上会抛出一个加载指示器来向用户显示应用程序中正在发生的事情,这需要一些时间。加载组件接受一个参数,告诉它是否显示:
<ngx-loading [show]="loading" [config]="{ backdropBorderRadius: '14px' }"></ngx-loading>
我有一个保存数据的站点服务,我的应用程序在初始化时获取服务数据:
this.siteService.getLoading().takeUntil(this.ngUnsubscribe).subscribe(res => {this.loading = res})
这一切的妙处在于,我可以从应用程序的几乎任何位置更改站点服务值,并控制是否弹出此加载指示器。糟糕的是,现在我看到了这个 ExpressionChangedAfterItHasBeenCheckedError 错误。
同样,我想我明白为什么会发生错误,但到目前为止我看到的解决方案不允许我继续使用这个简单的组件来处理我所有的 "loading" 调用。还是我遗漏了什么?
站点服务:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class SiteService {
private currentRoute = new Subject<string>();
private currentAction = new Subject<string>();
private loading = new BehaviorSubject<boolean>(false);
constructor() {
}
public menuState:string = 'in';
toggleMenuPicker() {
this.menuState = this.menuState === 'out' ? 'in' : 'out';
}
getCurrentRoute(): Observable<string> {
return this.currentRoute.asObservable();
}
setCurrentRoute(route: string) {
this.currentRoute.next(route);
}
getCurrentAction(): Observable<string> {
return this.currentAction.asObservable();
}
setCurrentAction(action: string) {
this.currentAction.next(action);
}
getLoading(): Observable<boolean> {
return this.loading.asObservable();
}
setLoading(show: boolean) {
this.loading.next(show);
}
}
app.component
import { Component, OnDestroy, AfterContentInit } from '@angular/core';
import { trigger, state, style, transition, animate} from '@angular/animations';
import { SiteService } from './site/site.service';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
animations: [
trigger('menuState', [
state('in', style({
transform: 'translate3d(-100%, 0, 0)'
})),
state('out', style({
transform: 'translate3d(0, 0, 0)'
})),
transition('in => out', animate('200ms ease-in-out')),
transition('out => in', animate('200ms ease-in-out'))
])
]
})
export class AppComponent implements OnDestroy, AfterContentInit {
private ngUnsubscribe: Subject<void> = new Subject<void>();
public loading;
constructor(public siteService: SiteService) {
}
ngAfterContentInit() {
this.siteService.getLoading().takeUntil(this.ngUnsubscribe).subscribe(res => {this.loading = res})
}
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
尝试强制change detection。
this.siteService.getLoading()
.takeUntil(this.ngUnsubscribe)
.subscribe(res => {
this.loading = res;
this.ref.detectChanges(); // <-- force change detection
});
查看我的了解更多详情
您可以使用 ChangeDetectionStrategy.OnPush 或 ChangeDetectorRef.detach() 来保持您的单一组件,同时避免错误。
这是一种可能的解决方案:
@Component({
selector: 'loader',
template: `<ngx-loading [show]="loading"></ngx-loading>`
})
export class LoaderComponent {
public loading;
constructor(public siteService: SiteService, private changeDetectorRef: ChangeDetectorRef) {
this.changeDetectorRef.detach();
this.siteService.getLoading()
.takeUntil(this.ngUnsubscribe)
.subscribe(res => {
this.loading = res;
this.changeDetectorRef.detectChanges();
});
}
}
您可以创建此组件并在 AppComponent 模板中使用它。
我已经阅读了有关此错误的文档,我想我明白了为什么会出现这个问题。我在尝试找出适合我的替代架构时遇到问题。
我网站的根组件 (app.component) 上有一个 "loading" 组件。它是一个第三方组件(ngx-loading),它基本上会抛出一个加载指示器来向用户显示应用程序中正在发生的事情,这需要一些时间。加载组件接受一个参数,告诉它是否显示:
<ngx-loading [show]="loading" [config]="{ backdropBorderRadius: '14px' }"></ngx-loading>
我有一个保存数据的站点服务,我的应用程序在初始化时获取服务数据:
this.siteService.getLoading().takeUntil(this.ngUnsubscribe).subscribe(res => {this.loading = res})
这一切的妙处在于,我可以从应用程序的几乎任何位置更改站点服务值,并控制是否弹出此加载指示器。糟糕的是,现在我看到了这个 ExpressionChangedAfterItHasBeenCheckedError 错误。
同样,我想我明白为什么会发生错误,但到目前为止我看到的解决方案不允许我继续使用这个简单的组件来处理我所有的 "loading" 调用。还是我遗漏了什么?
站点服务:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class SiteService {
private currentRoute = new Subject<string>();
private currentAction = new Subject<string>();
private loading = new BehaviorSubject<boolean>(false);
constructor() {
}
public menuState:string = 'in';
toggleMenuPicker() {
this.menuState = this.menuState === 'out' ? 'in' : 'out';
}
getCurrentRoute(): Observable<string> {
return this.currentRoute.asObservable();
}
setCurrentRoute(route: string) {
this.currentRoute.next(route);
}
getCurrentAction(): Observable<string> {
return this.currentAction.asObservable();
}
setCurrentAction(action: string) {
this.currentAction.next(action);
}
getLoading(): Observable<boolean> {
return this.loading.asObservable();
}
setLoading(show: boolean) {
this.loading.next(show);
}
}
app.component
import { Component, OnDestroy, AfterContentInit } from '@angular/core';
import { trigger, state, style, transition, animate} from '@angular/animations';
import { SiteService } from './site/site.service';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
animations: [
trigger('menuState', [
state('in', style({
transform: 'translate3d(-100%, 0, 0)'
})),
state('out', style({
transform: 'translate3d(0, 0, 0)'
})),
transition('in => out', animate('200ms ease-in-out')),
transition('out => in', animate('200ms ease-in-out'))
])
]
})
export class AppComponent implements OnDestroy, AfterContentInit {
private ngUnsubscribe: Subject<void> = new Subject<void>();
public loading;
constructor(public siteService: SiteService) {
}
ngAfterContentInit() {
this.siteService.getLoading().takeUntil(this.ngUnsubscribe).subscribe(res => {this.loading = res})
}
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
尝试强制change detection。
this.siteService.getLoading()
.takeUntil(this.ngUnsubscribe)
.subscribe(res => {
this.loading = res;
this.ref.detectChanges(); // <-- force change detection
});
查看我的
您可以使用 ChangeDetectionStrategy.OnPush 或 ChangeDetectorRef.detach() 来保持您的单一组件,同时避免错误。
这是一种可能的解决方案:
@Component({
selector: 'loader',
template: `<ngx-loading [show]="loading"></ngx-loading>`
})
export class LoaderComponent {
public loading;
constructor(public siteService: SiteService, private changeDetectorRef: ChangeDetectorRef) {
this.changeDetectorRef.detach();
this.siteService.getLoading()
.takeUntil(this.ngUnsubscribe)
.subscribe(res => {
this.loading = res;
this.changeDetectorRef.detectChanges();
});
}
}
您可以创建此组件并在 AppComponent 模板中使用它。