Force Angular to rerender
May 11, 2022
Sometimes the regular Angular change detection is just not enough and you need to rerender a specific component completely.
Easy way
Since I was reaching for the same code snippets in couple of projects I extracted it into a npm-library
ngx-rerender.
After installing, you can just put the *mcRerender
directive on any element and rerender it if you update the value in the binding.
<stuff-to-rerender *mcRerender="trigger">Some Content</stuff-to-rerender>
class MyComponent {
public trigger: number = 0;
public rerender(): void {
this.trigger++;
}
}
Do it manually
Stackblitz example
The very basic approach works by wrapping the element you want to rerender inside a ng-template
element that gets rendered
into a ng-container
. On rerender you can just clear the content of the ng-container
and create a new component from the ng-element
.
Your template looks something like this
<div>
<ng-container #outlet [ngTemplateOutlet]="content">
</ng-container>
</div>
<ng-template #content>
<child-component></child-component>
</ng-template>
And in your TypeScript you can access the content
and outlet
element with @ViewChild
for the rerender
export class AppComponent {
@ViewChild('outlet', { read: ViewContainerRef }) outletRef: ViewContainerRef;
@ViewChild('content', { read: TemplateRef }) contentRef: TemplateRef<any>;
public rerender() {
this.outletRef.clear();
this.outletRef.createEmbeddedView(this.contentRef);
}
}
And my library is more or less a wrapper around this functionality, so you don’t need to copy-paste this around in your project. There’s also a component included that lets you use boolean values which always looks so nice. Check out the documentation ;)
What not to do
I’m just quoting from my lib documentation in this place.
In this small section I will show some workarounds that I’ve seen in the past on StackOverflow or other projects and try to explain why they are not a good idea.
The ngIf “Have you tried turning it off and on again?”
The idea is to completely remove the specific component from the DOM, manually trigger a change detection and then re-add it. A basic solution looks something like this
import { ChangeDetectorRef } from '@angular/core';
class MyComponent {
public isVisible: boolean = true;
constructor(private changeDetectorRef: ChangeDetectorRef) {}
public rerender(): void {
this.isVisible = false;
this.changeDetectorRef.detectChanges();
this.isVisible = true;
}
}
<stuff-to-rerender *ngIf="isVisible">Some Content</stuff-to-rerender>
This is a very “straight forward” and often suggested solution. However, it’s not ideal for two reasons. First you will notice a “blink” in your page, because there is one entire lifecycle where your component is not visible.
And secondly you trigger a change detection for the entire application. Every other binding and lifecycle hook gets also triggered. This can become an issue in big applications where the ChangeDetectionStrategy.OnPush
is not being used.
The ngFor “smart” workaround
The idea is to “trick” Angular into thinking my current element is actually a new one. For this we make use of ngFor
which will initialize each entry from scratch once and then only update bindings based on the reference in the array.
If we update the reference in the array it will effectively re-rerender the given code part.
class MyComponent {
public rerenderProps: Array<number> = [1];
public rerender(): void {
this.rerenderProps[0]++;
}
}
<div *ngFor="let i of rerenderProps">
<stuff-to-rerender>Some Content</stuff-to-rerender>
</div>
While this solves the two issues of the ngIf
workaround (content blink and app-wide change detection) this is still not a nice solution.
It is very hard to understand for others looking at your code, and also you always need to implement additional logic like index checks ngIf="index === 0"
in order to prevent accidentally showing the component multiple times.
Personal Blog written by Nicolas Gehlert, software developer from Freiburg im Breisgau. Developer & Papa. Github | Twitter
Add a comment
Comments
Someone
November 07, 2022
Thanks a lot your second solution with “outletref” saved me :)