ngx-rerender
is a small Angular library, that allows you to completely re-render a specific component/code block of your application.
In contrast to the regular Angular lifecycle change updates, this will completely render the given code block from scratch, so even hooks like OnInit
will be called again.
See npm documentation on how to get started with npm.
npm install --save ngx-rerender
Add the module import to your Angular module
import { NgxRerenderModule } from 'ngx-rerender';
@NgModule({
...
imports: [
...
NgxRerenderModule,
],
})
export class MyModule {}
Stackblitz Example
After you've added the module import you can attach the *mcRerender
directive to any element you like to rerender.
The directive accepts a parameter that you need to change if you want to trigger the rerender.
In my example I'm going to use a number with increment, but you can also use any data you want that triggers a regular Angular change detection.
class MyComponent {
public trigger: number = 0;
public rerender(): void {
this.trigger++;
}
}
<stuff-to-rerender *mcRerender="trigger">Some Content</stuff-to-rerender>
Every time the rerender
method is called the component that has the directive attached to it will be rerendered.
Keep in mind changing entries inside an array will not trigger an Angular change detection. If you use an array as trigger binding, you need to copy the array in order to get a new reference.
Stackblitz Example
If you prefer using a component in your template you can do the following approach. The TypeScript part can stay exactly the same.
However, if you like you can use a boolean flag and a two-way binding and the component will always change the value back to false.
This way you don't need to have a number as trigger, but you can use a boolean value
class MyComponent {
public trigger: boolean = false;
public rerender(): void {
this.trigger = true;
}
}
<mc-rerender [(trigger)]="trigger">
<ng-template mcRerenderContent>
<stuff-to-rerender *mcRerender="trigger">Some Content</stuff-to-rerender>
</ng-template>
</mc-rerender>
The important part in the component way is the additional <ng-template mcRerenderContent>
wrapper around your content.
Do not change this, otherwise it will not work properly.
Angular has a very robust change detection and is usually fully capable of dynamically updating components and bindings without the need to re-render entire code blocks. If you are not familiar with the basics of change detection or are just starting learning Angular, I highly recommend checking out their Documentation, because chances are that you don't even need this library.
Sometimes, especially when using 3rd-party-libraries and components, the Angular change detection is not enough. For example if no ngOnChanges
handling is implemented so specific bindings can only be set during ngOnInit
.
For those cases you can use this library to just re-render (and re-initialize) entire component trees.
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 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 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.
I explained the basics in a Blog Post
I'm using one of the workarounds (or some other workaround), does it make sense to start using this library?
Yes! While the workarounds all work to some extent they never feel nice to use and always cause confusion for other devs verifying your code.
Please create an issue with a Stackblitz reproduction and I'm sure we will find a solution
If you don't need it I'm really happy for your. Like I said nearly all my problems where I reach for this library myself are when dealing with 3rd party libraries.
The two ways are not entirely the same. The component approach allows you to use a boolean flag that automatically gets changed back to false - this feels very nice to use.
But in contrast the directive way has a smaller code footprint in your html template.
Ultimately it allows you to choose a way that fits your personal style and needs the best.
If you are using the directive way you have to use a value that will be picked up by the Angular change detection.
If you use a boolean value and change this to true
it just will stay true. For this to work you need to set it back to false by yourself
(It is sadly not possible for directives to update their binding value)
This project is licensed under the MIT license. See the LICENSE file for more info.