How to re-trigger all pure pipes on all component tree in Angular 2

Pure pipes are only triggered when the input value changes.

You could add an artificial additional parameter value that you modify

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

and then use it like

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

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)


Thanks to Günter Zöchbauer answer (see comments), I got it working.

As I understant, Angular's change detector works like this:

cd.detectChanges(); // Detects changes but doesn't update view.
cd.markForCheck();  // Marks view for check but doesn't detect changes.

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 into ng-container:

<ng-container *ngIf="!reloading">
  <header></header>
  <main>
    <router-outlet></router-outlet>
  </main>
  <footer></footer>
</ng-container>

ng-container is better than div because it doesn't render any elements.

For async support, we can do something like this:

<ng-container *ngIf="!(reloading$ | async)"> ... </ng-container>

reloading: boolean and reloading$: Observable<boolean> here indicates that the component is currently being reloaded.

In the component I have LocaleService which has language$ observable. I will listen to changed language event and perform application reload action.

2. Sync example

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

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 is BehaviorSubject. BehaviorSubject is rxjs subject that emits default value immediately right after you subscribe. So once you write this.locale.language$.subscribe(...) - subscription immediately fires at least once, and only then you will wait for language change.


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

let i of someArray | groupBy:'someField':updated" 
<!--updated is updated after performing some function-->

then anytime I perform an update to the array I simply to

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

This forces my orderBy pipe to do a transform again. And the performance is a lot better.