I have pure pipe TranslatePipe
that translates phrases using LocaleService
that has locale$: Observable<string>
current locale. I also have ChangeDetectionStrategy.OnPush
enabled for all my components including AppComponent
. Now, how can I reload whole application when someone changes language? (emits new value in locale$
observable).
Currently, I'm using location.reload()
after user switches between languages. And that's annoying, because whole page is reloaded. How can I do this angular-way with pure pipe and OnPush detection strategy?
BEST PERFORMANCE SOLUTION:
I figured out a solution for this. I hate to call it a solution, but it works.
I was having the same issue with and orderBy pipe. I tried all the solutions here but the performance impact was terrible.
I simply added an addtional argument to my pipe
then anytime I perform an update to the array I simply to
This forces my orderBy pipe to do a transform again. And the performance is a lot better.
You can also create your own unpure pipe to track external changes. Check the sources of native Async Pipe to get the main idea.
All you need is to call
ChangeDetectorRef.markForCheck();
inside of your unpure pipe every time your Observable return new locale string. My solution:Or you even can incapsulate AsyncPipe inside your pipe (not a good solution, just for example):
Just set the property pure to false
Thanks to Günter Zöchbauer answer (see comments), I got it working.
As I understant, Angular's change detector works like this:
So you need to use both in order to quickly rebuild whole component tree.
1. Template changes
In order to reload whole application we need to hide and show all component tree, therefore we need to wrap everything in
app.component.html
intong-container
:ng-container
is better than div because it doesn't render any elements.For async support, we can do something like this:
reloading: boolean
andreloading$: Observable<boolean>
here indicates that the component is currently being reloaded.In the component I have
LocaleService
which haslanguage$
observable. I will listen to changed language event and perform application reload action.2. Sync example
3. Aync example
We don't have to
cd.markForChanges()
now but we still have to tell the detector to detect changes.4. Router
Router doesn't work as expected. When reloading application in such fashion,
router-outlet
content will become empty. I did not resolve this problem yet, and going to the same route can be painful because this means that any changes user has made in forms, for example, will be altered and lost.5. OnInit
You have to use the OnInit hook. If you try to call cd.detectChanges() inside of constructor, you will get an error because angular will not build component yet, but you will try to detect changes on it.
Now, you may think that I subscribe to another service in constructor, and my subscription will only fire after component is fully initialized. But the thing is - you don't know how the service works! If, for example, it just emits a value
Observable.of('en')
- you'll get an error because once you subscribe - first element emitted immediately while component is still not initialized.My
LocaleService
has the very same issue: the subject behind observable isBehaviorSubject
.BehaviorSubject
is rxjs subject that emits default value immediately right after you subscribe. So once you writethis.locale.language$.subscribe(...)
- subscription immediately fires at least once, and only then you will wait for language change.Pure pipes are only triggered when the input value changes.
You could add an artificial additional parameter value that you modify
and then use it like
Whenever
dummyCounter
is updated, the pipe is executed.You can also pass the locale as additional parameter instead of the counter. I don't think using
|async
for a single pipe parameter will work, therefore this might a bit cumbersome (would need to be assigned to a field to be usable as pipe parameter)