Developapa


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 :)

© 2024, Nicolas Gehlert
See Statistics for this blog