24

我有一个纯管道,可以使用具有当前语言环境的TranslatePipe短语来翻译。我还启用了我的所有组件,包括. 现在,当有人更改语言时,我如何重新加载整个应用程序?(在observable中发出新值)。LocaleServicelocale$: Observable<string>ChangeDetectionStrategy.OnPushAppComponentlocale$

目前,我location.reload()在用户切换语言后使用。这很烦人,因为整个页面都被重新加载了。我怎样才能用管道和OnPush检测策略做到这一点?

4

6 回答 6

28

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

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

@Pipe({name: 'translate'})
export class TranslatePipe {
  transform(value:any, trigger:number) {
    ...
  }
}

然后像这样使用它

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

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

您还可以将语言环境作为附加参数而不是计数器传递。我不认为使用|async单个管道参数会起作用,因此这可能有点麻烦(需要分配给一个字段才能用作管道参数)

于 2017-01-26T08:26:02.367 回答
20

最佳性能解决方案:

我想出了一个解决方案。我讨厌将其称为解决方案,但它确实有效。

我在 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 管道再次进行转换。而且性能要好很多。

于 2018-02-14T00:52:27.760 回答
13

感谢 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.htmlng-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>表示该组件当前正在重新加载。

在我有可观察的组件LocaleServicelanguage$。我将监听更改的语言事件并执行应用程序重新加载操作。

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. 异步示例

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 主题,在您订阅后立即发出默认值。因此,一旦您编写this.locale.language$.subscribe(...)- 订阅立即触发至少一次,然后您将等待语言更改。

于 2017-01-26T12:03:06.557 回答
11

只需将属性 pure 设置为 false

@Pipe({
  name: 'callback',
  pure: false
})
于 2018-02-09T01:28:11.990 回答
3

您还可以创建自己的非纯管道来跟踪外部更改。检查本机Async Pipe的来源以了解主要思想。

ChangeDetectorRef.markForCheck();每次您的 Observable 返回新的语言环境字符串时,您所需要的只是在不纯管道内调用。我的解决方案:

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

}
于 2018-07-11T05:32:21.260 回答
-1

阅读此角度文档以检测将使用管道的数组的更改。

要解决此问题,请创建一个新数组(具有新更改)并将其分配给您要在管道中使用的数组。这次 Angular 检测到数组引用发生了变化。它执行管道并使用带有新更改的新数组更新显示

在此处输入链接描述

于 2019-07-13T10:33:46.613 回答