現在のロケールが_locale$: Observable<string>
_であるTranslatePipe
を使用してフレーズを翻訳する純粋なパイプLocaleService
があります。また、AppComponent
を含むすべてのコンポーネントに対して_ChangeDetectionStrategy.OnPush
_を有効にしています。今、誰かが言語を変更したときにアプリケーション全体をリロードするにはどうすればよいですか? (_locale$
_ observableに新しい値を出力します)。
現在、ユーザーが言語を切り替えた後、location.reload()
を使用しています。また、ページ全体がリロードされるため、面倒です。 pure pipeおよびOnPush検出戦略を使用して、この角度の方法を実行するにはどうすればよいですか?
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.
_
したがって、コンポーネントツリー全体をすばやく再構築するには、両方を使用する必要があります。
アプリケーション全体をリロードするには、すべてのコンポーネントツリーを非表示にして表示する必要があるため、_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
があります。変更された言語イベントをリッスンし、アプリケーションのリロードアクションを実行します。
_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();
});
}
}
_
_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変更を伝える必要があります。
ルーターは機能しません期待どおり。このような方法でアプリケーションをリロードすると、_router-outlet
_コンテンツはemptyになります。私はまだこの問題を解決していませんが、同じルートに行くのは苦痛になります。これは、たとえば、ユーザーがフォームで行った変更が変更されて失われることを意味するためです。
OnInitフックを使用する必要があります。コンストラクタ内でcd.detectChanges()を呼び出そうとすると、angularはコンポーネントをまだビルドしませんが、変更を検出しようとするため、エラーが発生します。
今、あなたは私がコンストラクタで別のサービスにサブスクライブし、私のサブスクリプションがコンポーネントが完全に初期化された後にのみ起動すると考えるかもしれません。しかし問題なのは、サービスがどのように機能するかわからないということです!たとえば、値がObservable.of('en')
を出力するだけの場合-一度サブスクライブすると、コンポーネントがまだ初期化されていない間に最初の要素がすぐに出力されるため、エラーが発生します。
私のLocaleService
にも同じ問題があります:observableの背後にある主題はBehaviorSubject
です。 BehaviorSubject
は、サブスクライブした直後にdefault値を出力するrxjsサブジェクトです。したがって、一度this.locale.language$.subscribe(...)
を記述すると、サブスクリプションが少なくとも1回すぐに起動し、言語の変更を待つことになります。
純粋なパイプは、入力値が変更されたときにのみトリガーされます。
変更する人為的な追加パラメータ値を追加できます
@Pipe({name: 'translate'})
export class TranslatePipe {
transform(value:any, trigger:number) {
...
}
}
そして、それを次のように使用します
<div>{{label | translate:dummyCounter}}</div>
dummyCounter
が更新されるたびに、パイプが実行されます。
カウンターの代わりにロケールを追加パラメーターとして渡すこともできます。単一のパイプパラメーターに|async
を使用しても機能しないと思うため、これは少し面倒かもしれません(パイプパラメーターとして使用するにはフィールドに割り当てる必要があります)
最高のパフォーマンスソリューション:
これに対する解決策を見つけました。私はそれを解決策と呼ぶのは嫌いですが、うまくいきます。
と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パイプは再び変換を実行します。そして、パフォーマンスははるかに優れています。
プロパティを純粋にfalseに設定するだけです
@Pipe({
name: 'callback',
pure: false
})
また、独自の不純なパイプを作成して、外部の変更を追跡することもできます。ネイティブ 非同期パイプ のソースを確認して、主なアイデアを取得します。
必要なのは、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);
}
}
パイプを使用する配列の変更を検出するためのドキュメントangularのドキュメントをお読みください。
これを修正するには、新しい配列を(新しい変更を加えて)作成し、パイプで使用する配列に割り当てます。今回はAngularは配列参照が変更されたことを検出します。パイプを実行し、新しい変更で新しい配列で表示を更新します