How to force a component's re-rendering in Angular 2?
Asked Answered
P

6

226

How to force a component's re-rendering in Angular 2? For debug purposes working with Redux i'd like to force a component to re-render it's view, is that possible?

Permeability answered 30/1, 2016 at 18:22 Comment(3)
What do you mean by "re-rendering". Update the bindings?Alexander
Just a quick question why you need to force re-rendering?Gasbag
Possible duplicate of Triggering Angular2 change detection manuallyGlavin
J
257

Rendering happens after change detection. To force change detection, so that component property values that have changed get propagated to the DOM (and then the browser will render those changes in the view), here are some options:

  • ApplicationRef.tick() - similar to Angular 1's $rootScope.$digest() -- i.e., check the full component tree
  • NgZone.run(callback) - similar to $rootScope.$apply(callback) -- i.e., evaluate the callback function inside the Angular 2 zone. I think, but I'm not sure, that this ends up checking the full component tree after executing the callback function.
  • ChangeDetectorRef.detectChanges() - similar to $scope.$digest() -- i.e., check only this component and its children

You will need to import and then inject ApplicationRef, NgZone, or ChangeDetectorRef into your component.

For your particular scenario, I would recommend the last option if only a single component has changed.

Jab answered 30/1, 2016 at 19:25 Comment(6)
Any working code on ChangeDetectorRef for final version of angular2? I am now facing a situation where view is not updated after a http's post request to create a new user and then on success pushing the new object to the existing old userlist (used to iterate in view). Quite strange that this is the first time I am facing an update not working in ng2. The change detection strategy is default so i know i have not messed up with the change detection strategy.Virilism
@Gary, you should post a new question and include your component and your service code (ideally, include a minimal plunker demonstrating the issue). A common problem I've seen is not using the proper this context in the POST callback.Jab
Do you know if I can manually trigger the pipes whenever I perform a change? I've tried to trigger the change detection but the pipe doesn't update... I've also tried with pure:false in the pipe. It works but it is way too expensive (inefficient) for my use case.Possing
@ncohen, I'm not aware of any way to manually trigger a pipe update. You could use a pure pipe and change the object reference whenever you want to trigger an update. This is discussed under the "Pure Pipes" section of the Pipes doc. Depending on your use case, you might want to use a component property instead of a pipe. This technique is briefly discussed at the end of the Pipes doc.Jab
@N-ate, all of the links are fixed.Jab
ChangeDetectorRef.detectChanges() does not work for me. I think, it's because there are no changes. I just want to tigger redrawing / computing. Update pipe values. I want to trigger it every x seconds by setInterval. I thinks re-assigning the variable is currently a easy way to do this. But maybe not the best.Mesothorax
P
53

tx, found the workaround I needed:

  constructor(private zone:NgZone) {
    // enable to for time travel
    this.appStore.subscribe((state) => {
        this.zone.run(() => {
            console.log('enabled time travel');
        });
    });

running zone.run will force the component to re-render

Permeability answered 30/1, 2016 at 18:42 Comment(1)
what is appStore in this context - which kind of variable and its type ? seems to be observable ... but my observable is inside the component which I want to refresh on a click of a button ... and I do not know how to access a child component method/variable from parent/current locationAker
E
52

ChangeDetectorRef approach

import { Component, OnInit, ChangeDetectorRef } from '@angular/core';

export class MyComponent {

    constructor(private cdr: ChangeDetectorRef) { }

    selected(item: any) {
        if (item == 'Department')
            this.isDepartment = true;
        else
            this.isDepartment = false;
        this.cdr.detectChanges();
    }

}
Entanglement answered 22/5, 2018 at 16:56 Comment(0)
T
37

I force reload my component using *ngIf.

All the components inside my container goes back to the full lifecycle hooks .

In the template :

<ng-container *ngIf="_reload">
    components here 
</ng-container>

Then in the ts file :

public _reload = true;

private reload() {
    setTimeout(() => this._reload = false);
    setTimeout(() => this._reload = true);
}
Tanta answered 11/6, 2019 at 9:48 Comment(5)
Thanks for this, @loonis! I felt like this should work, and I had everything except for the setTimeout(). Now mine is working with a simple and lightweight solution!Atheism
1 thing to note - the container disappearing and appearing again can can cause size changes and the page could flickerAiling
This is the best solution for me. This solved my translation issue using my custom translator pipe.Tauto
Thanks, a little bit hacky, but that is the only solution that works for my case (slider of videos)Pardo
Except that if you are using @ViewChild() on this component, to get it, then it will be undefined.Eupatrid
A
17

Other answers here provide solutions for triggering change detection cycles that will update component's view (which is not same as full re-render).

Full re-render, which would destroy and reinitialize component (calling all lifecycle hooks and rebuilding view) can be done by using ng-template, ng-container and ViewContainerRef in following way:

<div>
  <ng-container #outlet >
  </ng-container>
</div>

<ng-template #content>
  <child></child>
</ng-template>

Then in component having reference to both #outlet and #content we can clear outlets' content and insert another instance of child component:

@ViewChild("outlet", {read: ViewContainerRef}) outletRef: ViewContainerRef;
@ViewChild("content", {read: TemplateRef}) contentRef: TemplateRef<any>;

private rerender() {
    this.outletRef.clear();
    this.outletRef.createEmbeddedView(this.contentRef);
}

Additionally initial content should be inserted on AfterContentInit hook:

ngAfterContentInit() {
    this.outletRef.createEmbeddedView(this.contentRef);
}

Full working solution can be found here https://stackblitz.com/edit/angular-component-rerender .

Audiphone answered 8/11, 2018 at 16:59 Comment(1)
I guess that in previous versions of angular this hook may be correct (not sure). But with newer one (v13 for me) it does not work and anybody interested in this solution should use ngAfterViewInit. BTW, it interestingly is the only solution that worked for me when input's held the previous values despite form model was correct. This occurred when I was switching between routes differentiated by the :id route. In the end I had to call the rerender function every time that navigation ended.Modify
A
13

ChangeDetectorRef.detectChanges() is usually the most focused way of doing this. ApplicationRef.tick() is usually too much of a sledgehammer approach.

To use ChangeDetectorRef.detectChanges(), you'll need this at the top of your component:

import {  ChangeDetectorRef } from '@angular/core';

... then, usually you alias that when you inject it in your constructor like this:

constructor( private cdr: ChangeDetectorRef ) { ... }

Then, in the appropriate place, you call it like this:

this.cdr.detectChanges();

Where you call ChangeDetectorRef.detectChanges() can be highly significant. You need to completely understand the life cycle and exactly how your application is functioning and rendering its components. There's no substitute here for completely doing your homework and making sure you understand the Angular lifecycle inside out. Then, once you understand that, you can use ChangeDetectorRef.detectChanges() appropriately (sometimes it's very easy to understand where you should use it, other times it can be very complex).

Avulsion answered 29/8, 2019 at 2:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.