如何重新触发Angular 2中所有组件树上的所有纯管道

问题描述 投票:12回答:5

我有纯管TranslatePipe,使用LocaleService翻译短语,locale$: Observable<string>具有ChangeDetectionStrategy.OnPush当前语言环境。我也为我的所有组件启用了AppComponent,包括locale$。现在,当有人更改语言时,如何重新加载整个应用程序? (在location.reload() observable中发出新值)。

目前,我在用户在语言之间切换后使用@Pipe({name: 'translate'}) export class TranslatePipe { transform(value:any, trigger:number) { ... } } 。这很烦人,因为整个页面都重新加载。如何使用纯管道和OnPush检测策略进行这种角度方式?

angular angular2-changedetection angular2-pipe
5个回答
19
投票

纯管道仅在输入值更改时触发。

您可以添加您修改的人工附加参数值

<div>{{label | translate:dummyCounter}}</div>

然后像使用它一样

dummyCounter

每当更新|async时,都会执行管道。

您还可以将区域设置作为附加参数而不是计数器传递。我不认为使用let i of someArray | groupBy:'someField':updated" <!--updated is updated after performing some function--> 作为单个管道参数会起作用,因此这可能有点麻烦(需要分配给一个可用作管道参数的字段)


9
投票

最佳性能解决方案:

我找到了解决方案。我讨厌称它为解决方案,但它确实有效。

我和orderBy管道有同样的问题。我在这里尝试了所有的解决方案,但性能影响很糟糕。

我只是在管道中添加了一个额外的参数

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.
}

然后我随时对数组执行更新

answer

这迫使我的orderBy管道再次进行转换。而且性能要好得多。


8
投票

感谢GünterZöchbauercd.detectChanges(); // Detects changes but doesn't update view. cd.markForCheck(); // Marks view for check but doesn't detect changes. (见评论),我得到了它的工作。

据我所知,角度变化检测的工作方式如下:

app.component.html

因此,您需要使用它们才能快速重建整个组件树。

1. Template changes

为了重新加载整个应用程序,我们需要隐藏并显示所有组件树,因此我们需要将ng-container中的所有内容包装到<ng-container *ngIf="!reloading"> <header></header> <main> <router-outlet></router-outlet> </main> <footer></footer> </ng-container> 中:

ng-container

<ng-container *ngIf="!(reloading$ | async)"> ... </ng-container> 比div好,因为它不会渲染任何元素。

对于异步支持,我们可以这样做:

reloading: boolean

reloading$: Observable<boolean>LocaleService此处表示该组件当前正在重新加载。

在组件我有language$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(); }); } } 可观察。我将听取更改的语言事件并执行应用程序重新加载操作。

2. Sync 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();
        });
    }
}

3. Aync example

cd.markForChanges()

我们现在没有router-outlet,但我们仍然必须告诉探测器检测变化。

4. Router

路由器无法按预期工作。当以这种方式重新加载应用程序时,Observable.of('en')内容将变为空。我还没有解决这个问题,并且走同一条路线可能会很痛苦,因为这意味着用户在表单中所做的任何更改都会被更改和丢失。

5. OnInit

你必须使用OnInit钩子。如果你试图在构造函数中调用cd.detectChanges(),你会收到一个错误,因为angular还没有构建组件,但你会尝试检测它的变化。

现在,您可能认为我在构造函数中订阅了另一个服务,而我的订阅只会在组件完全初始化后触发。但问题是 - 你不知道服务是如何运作的!例如,如果它只是发出一个值LocaleService - 你会得到一个错误,因为一旦你订阅 - 第一个元素立即发出而组件仍未初始化。

我的BehaviorSubject有同样的问题:可观察的背后的主题是BehaviorSubjectthis.locale.language$.subscribe(...)是rxjs主题,在您订阅后立即发出默认值。因此,一旦你写@Pipe({ name: 'callback', pure: false }) - 订阅立即触发至少一次,只有这样你才会等待语言变化。


3
投票

只需将属性pure设置为false即可

Async Pipe

2
投票

您还可以创建自己的不可用管道来跟踪外部更改。检查本地ChangeDetectorRef.markForCheck();的来源以获得主要想法。

你需要的是每次Observable返回新的语言环境字符串时,在你的unure管道内调用@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; } } 。我的解决方案

@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);
  }

}

或者您甚至可以在管道中封装AsyncPipe(例如,不是一个好的解决方案):

enter link description here

0
投票

阅读此角度文档以检测将要使用管道的阵列上的更改。

要解决此问题,请创建一个新阵列(使用新更改)并将其分配给要在管道中使用的阵列。这次Angular检测到数组引用已更改。它执行管道并使用新数组更新显示并进行新的更改

qazxswpoi

© www.soinside.com 2019 - 2024. All rights reserved.