如何在 Angular 2 中重新触发所有组件树上的所有纯管道
How to re-trigger all pure pipes on all component tree in Angular 2
我有纯管道 TranslatePipe
,它使用具有 locale$: Observable<string>
当前语言环境的 LocaleService
翻译短语。我还为我的所有组件启用了 ChangeDetectionStrategy.OnPush
,包括 AppComponent
。现在,当有人更改语言时,我如何重新加载整个应用程序? (在 locale$
observable 中发出新值)。
目前,我在用户切换语言后使用 location.reload()
。这很烦人,因为整个页面都被重新加载了。我如何使用 pure 管道和 OnPush 检测策略来执行此 angular 方式?
纯管道仅在输入值改变时触发。
您可以添加一个您修改的人工附加参数值
@Pipe({name: 'translate'})
export class TranslatePipe {
transform(value:any, trigger:number) {
...
}
}
然后像
一样使用它
<div>{{label | translate:dummyCounter}}</div>
每当 dummyCounter
更新时,都会执行管道。
您也可以将区域设置作为附加参数而不是计数器传递。
我不认为对单个管道参数使用 |async
会起作用,因此这可能有点麻烦(需要分配给一个字段才能用作管道参数)
多亏了 Günter Zöchbauer (见评论),我让它工作了。
据我了解,Angular 的变化检测器是这样工作的:
cd.detectChanges(); // Detects changes but doesn't update view.
cd.markForCheck(); // Marks view for check but doesn't detect changes.
所以你需要同时使用这两者才能快速重建整个组件树。
1。模板更改
为了重新加载整个应用程序,我们需要隐藏和显示所有组件树,因此我们需要将 app.component.html
中的所有内容包装到 ng-container
:
<ng-container *ngIf="!reloading">
<header></header>
<main>
<router-outlet></router-outlet>
</main>
<footer></footer>
</ng-container>
ng-container
比 div 好,因为它不渲染任何元素。
对于异步支持,我们可以这样做:
<ng-container *ngIf="!(reloading$ | async)"> ... </ng-container>
reloading: boolean
和reloading$: Observable<boolean>
表示当前正在重新加载该组件。
在我有 LocaleService
的组件中,它有 language$
可观察的。我将监听更改语言事件并执行应用程序重新加载操作。
2。同步示例
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 示例
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();
});
}
}
我们现在不必 cd.markForChanges()
但我们仍然必须告诉检测器 检测 变化。
4。路由器
路由器无法正常工作。以这种方式重新加载应用程序时,router-outlet
内容将变为 空 。我还没有解决这个问题,走同样的路可能会很痛苦,因为这意味着用户在表单中所做的任何更改都会被更改和丢失。
5。 OnInit
您必须使用 OnInit 挂钩。如果您尝试在构造函数内部调用 cd.detectChanges(),您将收到一个错误,因为 angular 还不会构建组件,但您将尝试检测其上的更改。
现在,您可能认为我在构造函数中订阅了另一个服务,我的订阅只会在组件完全初始化后触发。但问题是——您不知道该服务是如何运作的!例如,如果它只发出一个值 Observable.of('en')
- 你会得到一个错误,因为一旦你订阅 - 第一个元素立即发出,而组件仍未初始化。
我的 LocaleService
也有同样的问题:observable 背后的主题是 BehaviorSubject
。 BehaviorSubject
是 rxjs 主题,它会在您订阅后立即发出 default 值。所以一旦你写 this.locale.language$.subscribe(...)
- 订阅会立即触发至少一次,然后你才会等待语言更改。
只需将 属性 pure 设置为 false
@Pipe({
name: 'callback',
pure: false
})
最佳性能解决方案:
我想出了一个解决方案。我讨厌称其为解决方案,但它确实有效。
我在使用 orderBy 管道时遇到了同样的问题。我尝试了这里的所有解决方案,但性能影响很糟糕。
我只是在管道中添加了一个附加参数
let i of someArray | groupBy:'someField':updated"
<!--updated is updated after performing some function-->
然后,每当我对数组执行更新时,我只需
updateArray(){
//this can be a service call or add, update or delete item in the array
.then.....put this is in the callback:
this.updated = new Date(); //this will update the pipe forcing it to re-render.
}
这会强制我的 orderBy 管道再次进行转换。而且性能也好很多。
您还可以创建自己的非纯管道来跟踪外部变化。检查原生 Async Pipe 的来源以了解主要思想。
你需要做的就是每次 Observable return 新的语言环境字符串时在你的非纯管道中调用 ChangeDetectorRef.markForCheck();
。我的解决方案:
@Pipe({
name: 'translate',
pure: false
})
export class TranslatePipe implements OnDestroy, PipeTransform {
private subscription: Subscription;
private lastInput: string;
private lastOutput: string;
constructor(private readonly globalizationService: GlobalizationService,
private readonly changeDetectorRef: ChangeDetectorRef) {
this.subscription = this.globalizationService.currentLocale // <- Observable of your current locale
.subscribe(() => {
this.lastOutput = this.globalizationService.translateSync(this.lastInput); // sync translate function, will return string
this.changeDetectorRef.markForCheck();
});
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
this.subscription = void 0;
this.lastInput = void 0;
this.lastOutput = void 0;
}
transform(id: string): string { // this function will be called VERY VERY often for unpure pipe. Be careful.
if (this.lastInput !== id) {
this.lastOutput = this.globalizationService.translateSync(id);
}
this.lastInput = id;
return this.lastOutput;
}
}
或者您甚至可以将 AsyncPipe 封装在您的管道中(这不是一个好的解决方案,仅作为示例):
@Pipe({
name: 'translate',
pure: false
})
export class TranslatePipe implements OnDestroy, PipeTransform {
private asyncPipe: AsyncPipe;
private observable: Observable<string>;
private lastValue: string;
constructor(private readonly globalizationService: GlobalizationService,
private readonly changeDetectorRef: ChangeDetectorRef) {
this.asyncPipe = new AsyncPipe(changeDetectorRef);
}
ngOnDestroy(): void {
this.asyncPipe.ngOnDestroy();
this.lastValue = void 0;
if (this.observable) {
this.observable.unsubscribe();
}
this.observable = void 0;
this.asyncPipe = void 0;
}
transform(id: string): string {
if (this.lastValue !== id || !this.observable) {
this.observable = this.globalizationService.translateObservable(id); // this function returns Observable
}
this.lastValue = id;
return this.asyncPipe.transform(this.observable);
}
}
阅读此 angular 文档以检测将要使用管道的阵列上的更改。
要解决这个问题,请创建一个新数组(进行新更改)并将其分配给您要在管道中使用的数组。这次 Angular 检测到数组引用已更改。它执行管道并使用具有新更改的新数组更新显示
我有纯管道 TranslatePipe
,它使用具有 locale$: Observable<string>
当前语言环境的 LocaleService
翻译短语。我还为我的所有组件启用了 ChangeDetectionStrategy.OnPush
,包括 AppComponent
。现在,当有人更改语言时,我如何重新加载整个应用程序? (在 locale$
observable 中发出新值)。
目前,我在用户切换语言后使用 location.reload()
。这很烦人,因为整个页面都被重新加载了。我如何使用 pure 管道和 OnPush 检测策略来执行此 angular 方式?
纯管道仅在输入值改变时触发。
您可以添加一个您修改的人工附加参数值
@Pipe({name: 'translate'})
export class TranslatePipe {
transform(value:any, trigger:number) {
...
}
}
然后像
一样使用它<div>{{label | translate:dummyCounter}}</div>
每当 dummyCounter
更新时,都会执行管道。
您也可以将区域设置作为附加参数而不是计数器传递。
我不认为对单个管道参数使用 |async
会起作用,因此这可能有点麻烦(需要分配给一个字段才能用作管道参数)
多亏了 Günter Zöchbauer
据我了解,Angular 的变化检测器是这样工作的:
cd.detectChanges(); // Detects changes but doesn't update view.
cd.markForCheck(); // Marks view for check but doesn't detect changes.
所以你需要同时使用这两者才能快速重建整个组件树。
1。模板更改
为了重新加载整个应用程序,我们需要隐藏和显示所有组件树,因此我们需要将 app.component.html
中的所有内容包装到 ng-container
:
<ng-container *ngIf="!reloading">
<header></header>
<main>
<router-outlet></router-outlet>
</main>
<footer></footer>
</ng-container>
ng-container
比 div 好,因为它不渲染任何元素。
对于异步支持,我们可以这样做:
<ng-container *ngIf="!(reloading$ | async)"> ... </ng-container>
reloading: boolean
和reloading$: Observable<boolean>
表示当前正在重新加载该组件。
在我有 LocaleService
的组件中,它有 language$
可观察的。我将监听更改语言事件并执行应用程序重新加载操作。
2。同步示例
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 示例
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();
});
}
}
我们现在不必 cd.markForChanges()
但我们仍然必须告诉检测器 检测 变化。
4。路由器
路由器无法正常工作。以这种方式重新加载应用程序时,router-outlet
内容将变为 空 。我还没有解决这个问题,走同样的路可能会很痛苦,因为这意味着用户在表单中所做的任何更改都会被更改和丢失。
5。 OnInit
您必须使用 OnInit 挂钩。如果您尝试在构造函数内部调用 cd.detectChanges(),您将收到一个错误,因为 angular 还不会构建组件,但您将尝试检测其上的更改。
现在,您可能认为我在构造函数中订阅了另一个服务,我的订阅只会在组件完全初始化后触发。但问题是——您不知道该服务是如何运作的!例如,如果它只发出一个值 Observable.of('en')
- 你会得到一个错误,因为一旦你订阅 - 第一个元素立即发出,而组件仍未初始化。
我的 LocaleService
也有同样的问题:observable 背后的主题是 BehaviorSubject
。 BehaviorSubject
是 rxjs 主题,它会在您订阅后立即发出 default 值。所以一旦你写 this.locale.language$.subscribe(...)
- 订阅会立即触发至少一次,然后你才会等待语言更改。
只需将 属性 pure 设置为 false
@Pipe({
name: 'callback',
pure: false
})
最佳性能解决方案:
我想出了一个解决方案。我讨厌称其为解决方案,但它确实有效。
我在使用 orderBy 管道时遇到了同样的问题。我尝试了这里的所有解决方案,但性能影响很糟糕。
我只是在管道中添加了一个附加参数
let i of someArray | groupBy:'someField':updated"
<!--updated is updated after performing some function-->
然后,每当我对数组执行更新时,我只需
updateArray(){
//this can be a service call or add, update or delete item in the array
.then.....put this is in the callback:
this.updated = new Date(); //this will update the pipe forcing it to re-render.
}
这会强制我的 orderBy 管道再次进行转换。而且性能也好很多。
您还可以创建自己的非纯管道来跟踪外部变化。检查原生 Async Pipe 的来源以了解主要思想。
你需要做的就是每次 Observable return 新的语言环境字符串时在你的非纯管道中调用 ChangeDetectorRef.markForCheck();
。我的解决方案:
@Pipe({
name: 'translate',
pure: false
})
export class TranslatePipe implements OnDestroy, PipeTransform {
private subscription: Subscription;
private lastInput: string;
private lastOutput: string;
constructor(private readonly globalizationService: GlobalizationService,
private readonly changeDetectorRef: ChangeDetectorRef) {
this.subscription = this.globalizationService.currentLocale // <- Observable of your current locale
.subscribe(() => {
this.lastOutput = this.globalizationService.translateSync(this.lastInput); // sync translate function, will return string
this.changeDetectorRef.markForCheck();
});
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
this.subscription = void 0;
this.lastInput = void 0;
this.lastOutput = void 0;
}
transform(id: string): string { // this function will be called VERY VERY often for unpure pipe. Be careful.
if (this.lastInput !== id) {
this.lastOutput = this.globalizationService.translateSync(id);
}
this.lastInput = id;
return this.lastOutput;
}
}
或者您甚至可以将 AsyncPipe 封装在您的管道中(这不是一个好的解决方案,仅作为示例):
@Pipe({
name: 'translate',
pure: false
})
export class TranslatePipe implements OnDestroy, PipeTransform {
private asyncPipe: AsyncPipe;
private observable: Observable<string>;
private lastValue: string;
constructor(private readonly globalizationService: GlobalizationService,
private readonly changeDetectorRef: ChangeDetectorRef) {
this.asyncPipe = new AsyncPipe(changeDetectorRef);
}
ngOnDestroy(): void {
this.asyncPipe.ngOnDestroy();
this.lastValue = void 0;
if (this.observable) {
this.observable.unsubscribe();
}
this.observable = void 0;
this.asyncPipe = void 0;
}
transform(id: string): string {
if (this.lastValue !== id || !this.observable) {
this.observable = this.globalizationService.translateObservable(id); // this function returns Observable
}
this.lastValue = id;
return this.asyncPipe.transform(this.observable);
}
}
阅读此 angular 文档以检测将要使用管道的阵列上的更改。
要解决这个问题,请创建一个新数组(进行新更改)并将其分配给您要在管道中使用的数组。这次 Angular 检测到数组引用已更改。它执行管道并使用具有新更改的新数组更新显示