Facing "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked" to show/hide loader
Facing "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked" to show/hide loader
我在父组件 (AppComponent) 中有一个通用加载器和 router-outlet
(用于加载子组件)。在子组件中,我们更改值以分别在调用 API 之前和之后显示和隐藏加载程序。因此发生此错误。
例如这是我在 app.cpmponent.html
:
中的加载程序
<p [ngClass]="(overlappingProgress | async) ? 'display-block' : 'display-none'">holaaaaaaaaaaaaaaa</p>
这里overlappingProgress
是一个observable
类型的变量,通过async
管道解析。
在子组件中,我有一个需要来自服务器的一些数据的表单。因此,在子组件的 ngOnInit
中,我按以下顺序发送了一些操作(因为我们正在使用 ngrx
)-
- showOverlappingProgress() // 它使 reducer
中的加载 属性 为真
- 获取数据()
- hideOverlappingProgress() // 它使得在 reducer
中加载 属性 false
因此,在 app.component.ts
中检测该更改,我监听更改以更新 show/hide 加载程序。
this.overlappingProgress = this.store.select(s => s.app.overlappingProgress);
我有一些想法,它将 previous/old 值与第一个摘要周期的值进行比较,但是该值在摘要周期内发生变化,因此,当它运行第二个摘要周期时,值不匹配,因此发生此错误。
我在 stackblitz.
中使用更简单的版本来模拟这种情况
我找到了一些建议使用 setTimeout()
或 this.cdr.detectChanges()
的解决方案。
个人感觉setTimeout()
不太行。此外,手动触发 detectchanges
可能会做很多额外的工作,因为所有的子组件都在使用这个加载器,并且每次它都会触发和额外的变化检测。
所以,我想就这种情况下最好的解决方案提出一些建议。
有父子关系。子组件更改父组件表达式中使用的父组件的值。
To solve the issue, I changed the parent-child structure a bit. I have
created a new module called FfLoadingIndicatorComponentModule
and it
has a component called FfLoadingIndicatorComponent
. Inside that
component I subscribed the changes it show hide the loader
respectively. Finally I import that module in AppModule
to use the
newly created loading indicator component from app component.
这里最主要的是独立组件及其与应用组件的关系。让我们看看代码。
代码示例:
ff-加载-indicator.component.ts
@Component({
selector: 'ff-loading-indicator',
templateUrl: './ff-loading-indicator.component.html',
styleUrls: ['./ff-loading-indicator.component.scss']
})
export class FfLoadingIndicatorComponent implements OnInit {
progress$: Observable<boolean>;
overlappingProgress$: Observable<boolean>;
constructor(private store: Store<State>) { }
ngOnInit(): void {
this.progress$ = this.store.select(s => s.app.progress);
this.overlappingProgress$ = this.store.select(s => s.app.overlappingProgress);
}
}
ff-加载-indicator.component.html
<div [ngClass]="(progress$ | async) ? 'display-block' : 'display-none'">
<mat-progress-bar class="ff-z-index-above-toolbar ff-general-loading-indicator" mode="indeterminate" color="accent"></mat-progress-bar>
</div>
并最终在 app.component.html
中使用此组件。
app.component.html
<ff-loading-indicator></ff-loading-indicator>
您可以在 stackblitz 中找到更改。
Note: FfLoadingIndicatorComponent
can be imported directly in
AppModule
. As I preferred the module approach, I created a separate
module and import that in AppModule
我在父组件 (AppComponent) 中有一个通用加载器和 router-outlet
(用于加载子组件)。在子组件中,我们更改值以分别在调用 API 之前和之后显示和隐藏加载程序。因此发生此错误。
例如这是我在 app.cpmponent.html
:
<p [ngClass]="(overlappingProgress | async) ? 'display-block' : 'display-none'">holaaaaaaaaaaaaaaa</p>
这里overlappingProgress
是一个observable
类型的变量,通过async
管道解析。
在子组件中,我有一个需要来自服务器的一些数据的表单。因此,在子组件的 ngOnInit
中,我按以下顺序发送了一些操作(因为我们正在使用 ngrx
)-
- showOverlappingProgress() // 它使 reducer 中的加载 属性 为真
- 获取数据()
- hideOverlappingProgress() // 它使得在 reducer 中加载 属性 false
因此,在 app.component.ts
中检测该更改,我监听更改以更新 show/hide 加载程序。
this.overlappingProgress = this.store.select(s => s.app.overlappingProgress);
我有一些想法,它将 previous/old 值与第一个摘要周期的值进行比较,但是该值在摘要周期内发生变化,因此,当它运行第二个摘要周期时,值不匹配,因此发生此错误。
我在 stackblitz.
中使用更简单的版本来模拟这种情况我找到了一些建议使用 setTimeout()
或 this.cdr.detectChanges()
的解决方案。
个人感觉setTimeout()
不太行。此外,手动触发 detectchanges
可能会做很多额外的工作,因为所有的子组件都在使用这个加载器,并且每次它都会触发和额外的变化检测。
所以,我想就这种情况下最好的解决方案提出一些建议。
有父子关系。子组件更改父组件表达式中使用的父组件的值。
To solve the issue, I changed the parent-child structure a bit. I have created a new module called
FfLoadingIndicatorComponentModule
and it has a component calledFfLoadingIndicatorComponent
. Inside that component I subscribed the changes it show hide the loader respectively. Finally I import that module inAppModule
to use the newly created loading indicator component from app component.
这里最主要的是独立组件及其与应用组件的关系。让我们看看代码。 代码示例:
ff-加载-indicator.component.ts
@Component({
selector: 'ff-loading-indicator',
templateUrl: './ff-loading-indicator.component.html',
styleUrls: ['./ff-loading-indicator.component.scss']
})
export class FfLoadingIndicatorComponent implements OnInit {
progress$: Observable<boolean>;
overlappingProgress$: Observable<boolean>;
constructor(private store: Store<State>) { }
ngOnInit(): void {
this.progress$ = this.store.select(s => s.app.progress);
this.overlappingProgress$ = this.store.select(s => s.app.overlappingProgress);
}
}
ff-加载-indicator.component.html
<div [ngClass]="(progress$ | async) ? 'display-block' : 'display-none'">
<mat-progress-bar class="ff-z-index-above-toolbar ff-general-loading-indicator" mode="indeterminate" color="accent"></mat-progress-bar>
</div>
并最终在 app.component.html
中使用此组件。
app.component.html
<ff-loading-indicator></ff-loading-indicator>
您可以在 stackblitz 中找到更改。
Note:
FfLoadingIndicatorComponent
can be imported directly inAppModule
. As I preferred the module approach, I created a separate module and import that inAppModule