问题描述
我有一个纯管道 TranslatePipe
,该管道使用具有 locale $的
当前语言环境。我还为所有组件(包括 LocaleService
来翻译短语:可观察的< string> AppComponent
)启用了 ChangeDetectionStrategy.OnPush
。现在,当有人更改语言时如何重新加载整个应用程序? (在 locale $
中观察到新值)。
当前,我正在使用用户在语言之间切换后,location.reload()
。这很烦人,因为整个页面都已重新加载。我该如何使用 pure 管道和 OnPush 检测策略来做到这一点?
感谢GünterZöchbauer(请参阅评论),我明白了
在我不太了解的情况下,Angular的变更检测器的工作原理如下:
cd.detectChanges(); //检测更改,但不更新视图。
cd.markForCheck(); //将视图标记为检查,但未检测到更改。
因此您需要同时使用两者才能快速重建整个组件树。
1。模板更改
为了重新加载整个应用程序,我们需要隐藏并显示所有组件树,因此需要将所有内容包装在 app.component中。 html
放入 ng-容器
:
< ng容器* ngIf =!reloading>
< header>< / header>
< main>
< router-outlet>< / router-outlet>
< / main>
< footer>< / footer>
< / ng-container>
ng容器
比div好,因为
对于异步支持,我们可以执行以下操作:
< ng-container * ngIf =!(reloading $ | async)> ...< / ng-container>
重装:布尔值
和 reloading $:这里的Observable< boolean>
表示当前正在重新加载组件。
在组件中,我有 LocaleService
具有可观察的语言$
。我将收听更改语言的事件并执行应用程序重新加载操作。
2。同步示例
导出类AppComponent实现了OnInit {
reloading:boolean;
构造函数(
私有cd:ChangeDetectorRef,
私有语言环境:LocaleService){
this.reloading = false;
}
ngOnInit(){
this.locale.language $ .subscribe(_ => {
this.reloading = true;
this .cd.detectChanges();
this.reloading = false;
this.cd.detectChanges();
this.cd.markForCheck();
});
}
}
3。 Aync示例
export类AppComponent实现OnInit {
reloading:BehaviorSubject< boolean> ;;
获得重新加载$():Observable< boolean> {
返回this.reloading.asObservable();
}
构造函数(
private cd:ChangeDetectorRef,//我们仍然必须使用它。
private locale:LocaleService){
this.reloading = new BehaviorSubject< boolean>(false);
}
ngOnInit(){
this.locale.language $ .subscribe(_ => {
this.reloading.next(true);
this.cd.detectChanges();
this.reloading.next(false);
this.cd.detectChanges();
});
}
}
我们不必 cd.markForChanges()
,但是我们仍然必须告诉检测器检测更改。
4。路由器
路由器无效。以这种方式重新加载应用程序时,路由器出口
内容将变为空。我还没有解决这个问题,所以走同一条路线可能会很痛苦,因为这意味着用户对表单所做的任何更改都会被更改或丢失。
5。 OnInit
您必须使用OnInit挂钩。如果尝试在构造函数内部调用cd.detectChanges(),则会出现错误,因为angular尚未构建组件,但您将尝试检测其更改。
Observable.of('en')-您将收到错误消息,因为一旦订阅-当component为
我的 LocaleService
也有同样的问题:observable后面的主题是 BehaviorSubject
。 BehaviorSubject
是rxjs主题,在您订阅后立即发出 default 值。因此,一旦您编写 this.locale.language $ .subscribe(...)
-订阅会立即触发至少一次,只有这样您才能等待语言更改。
I have pure pipe TranslatePipe
that translates phrases using LocaleService
that has locale$: Observable<string>
current locale. I also have ChangeDetectionStrategy.OnPush
enabled for all my components including AppComponent
. Now, how can I reload whole application when someone changes language? (emits new value in locale$
observable).
Currently, I'm using location.reload()
after user switches between languages. And that's annoying, because whole page is reloaded. How can I do this angular-way with pure pipe and OnPush detection strategy?
Thanks to Günter Zöchbauer answer (see comments), I got it working.
As I understant, Angular's change detector works like this:
cd.detectChanges(); // Detects changes but doesn't update view.
cd.markForCheck(); // Marks view for check but doesn't detect changes.
So you need to use both in order to quickly rebuild whole component tree.
1. Template changes
In order to reload whole application we need to hide and show all component tree, therefore we need to wrap everything in app.component.html
into ng-container
:
<ng-container *ngIf="!reloading">
<header></header>
<main>
<router-outlet></router-outlet>
</main>
<footer></footer>
</ng-container>
ng-container
is better than div because it doesn't render any elements.
For async support, we can do something like this:
<ng-container *ngIf="!(reloading$ | async)"> ... </ng-container>
reloading: boolean
and reloading$: Observable<boolean>
here indicates that the component is currently being reloaded.
In the component I have LocaleService
which has language$
observable. I will listen to changed language event and perform application reload action.
2. Sync example
export class AppComponent implements OnInit {
reloading: boolean;
constructor(
private cd: ChangeDetectorRef,
private locale: LocaleService) {
this.reloading = false;
}
ngOnInit() {
this.locale.language$.subscribe(_ => {
this.reloading = true;
this.cd.detectChanges();
this.reloading = false;
this.cd.detectChanges();
this.cd.markForCheck();
});
}
}
3. Aync example
export class AppComponent implements OnInit {
reloading: BehaviorSubject<boolean>;
get reloading$(): Observable<boolean> {
return this.reloading.asObservable();
}
constructor(
private cd: ChangeDetectorRef, // We still have to use it.
private locale: LocaleService) {
this.reloading = new BehaviorSubject<boolean>(false);
}
ngOnInit() {
this.locale.language$.subscribe(_ => {
this.reloading.next(true);
this.cd.detectChanges();
this.reloading.next(false);
this.cd.detectChanges();
});
}
}
We don't have to cd.markForChanges()
now but we still have to tell the detector to detect changes.
4. Router
Router doesn't work as expected. When reloading application in such fashion, router-outlet
content will become empty. I did not resolve this problem yet, and going to the same route can be painful because this means that any changes user has made in forms, for example, will be altered and lost.
5. OnInit
You have to use the OnInit hook. If you try to call cd.detectChanges() inside of constructor, you will get an error because angular will not build component yet, but you will try to detect changes on it.
Now, you may think that I subscribe to another service in constructor, and my subscription will only fire after component is fully initialized. But the thing is - you don't know how the service works! If, for example, it just emits a value Observable.of('en')
- you'll get an error because once you subscribe - first element emitted immediately while component is still not initialized.
My LocaleService
has the very same issue: the subject behind observable is BehaviorSubject
. BehaviorSubject
is rxjs subject that emits default value immediately right after you subscribe. So once you write this.locale.language$.subscribe(...)
- subscription immediately fires at least once, and only then you will wait for language change.
这篇关于如何在Angular 2中重新触发所有组件树上的所有纯管道的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!