web-dev-qa-db-ja.com

Angular 2のすべてのコンポーネントツリーですべての純粋なパイプを再トリガーする方法

現在のロケールが_locale$: Observable<string>_であるTranslatePipeを使用してフレーズを翻訳する純粋なパイプLocaleServiceがあります。また、AppComponentを含むすべてのコンポーネントに対して_ChangeDetectionStrategy.OnPush_を有効にしています。今、誰かが言語を変更したときにアプリケーション全体をリロードするにはどうすればよいですか? (_locale$_ observableに新しい値を出力します)。

現在、ユーザーが言語を切り替えた後、location.reload()を使用しています。また、ページ全体がリロードされるため、面倒です。 pure pipeおよびOnPush検出戦略を使用して、この角度の方法を実行するにはどうすればよいですか?

16
EwanCoder

GünterZöchbauer answer (コメントを参照)のおかげで、うまくいきました。

私が理解したように、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>_は、コンポーネントが現在リロードされていることを示します。

コンポーネントには、language$_オブザーバブルを持つLocaleServiceがあります。変更された言語イベントをリッスンし、アプリケーションのリロードアクションを実行します。

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()する必要はありませんが、検出器にdetect変更を伝える必要があります。

4.ルーター

ルーターは機能しません期待どおり。このような方法でアプリケーションをリロードすると、_router-outlet_コンテンツはemptyになります。私はまだこの問題を解決していませんが、同じルートに行くのは苦痛になります。これは、たとえば、ユーザーがフォームで行った変更が変更されて失われることを意味するためです。

5. OnInit

OnInitフックを使用する必要があります。コンストラクタ内でcd.detectChanges()を呼び出そうとすると、angularはコンポーネントをまだビルドしませんが、変更を検出しようとするため、エラーが発生します。

今、あなたは私がコンストラクタで別のサービスにサブスクライブし、私のサブスクリプションがコンポーネントが完全に初期化された後にのみ起動すると考えるかもしれません。しかし問題なのは、サービスがどのように機能するかわからないということです!たとえば、値がObservable.of('en')を出力するだけの場合-一度サブスクライブすると、コンポーネントがまだ初期化されていない間に最初の要素がすぐに出力されるため、エラーが発生します。

私のLocaleServiceにも同じ問題があります:observableの背後にある主題はBehaviorSubjectです。 BehaviorSubjectは、サブスクライブした直後にdefault値を出力するrxjsサブジェクトです。したがって、一度this.locale.language$.subscribe(...)を記述すると、サブスクリプションが少なくとも1回すぐに起動し、言語の変更を待つことになります。

8
EwanCoder

純粋なパイプは、入力値が変更されたときにのみトリガーされます。

変更する人為的な追加パラメータ値を追加できます

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

そして、それを次のように使用します

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

dummyCounterが更新されるたびに、パイプが実行されます。

カウンターの代わりにロケールを追加パラメーターとして渡すこともできます。単一のパイプパラメーターに|asyncを使用しても機能しないと思うため、これは少し面倒かもしれません(パイプパラメーターとして使用するにはフィールドに割り当てる必要があります)

19

最高のパフォーマンスソリューション:

これに対する解決策を見つけました。私はそれを解決策と呼ぶのは嫌いですが、うまくいきます。

と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パイプは再び変換を実行します。そして、パフォーマンスははるかに優れています。

10
Judson Terrell

プロパティを純粋にfalseに設定するだけです

@Pipe({
  name: 'callback',
  pure: false
})
4
lohiarahul

また、独自の不純なパイプを作成して、外部の変更を追跡することもできます。ネイティブ 非同期パイプ のソースを確認して、主なアイデアを取得します。

必要なのは、Observableが新しいロケール文字列を返すたびに、不純なパイプ内で 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);
  }

}
2
Harry Burns

パイプを使用する配列の変更を検出するためのドキュメントangularのドキュメントをお読みください。

これを修正するには、新しい配列を(新しい変更を加えて)作成し、パイプで使用する配列に割り当てます。今回はAngularは配列参照が変更されたことを検出します。パイプを実行し、新しい変更で新しい配列で表示を更新します

リンクの説明をここに入力

0
Kazem