48

I am new to Angular2 and Angular in general and am trying to get some jQuery to fire after the dom is updated when the data of a component is changed. The jQuery needs to calculate heights of elements so I can't exactly just use the data. Unfortunately it looks like onAllChangesDone only fires after data changes, not the dom.

ComputerWolf
  • 1,201
  • 2
  • 10
  • 14
  • Related to this ["Angular 2 with Jquery"](http://stackoverflow.com/questions/30623825/how-to-use-jquery-with-angular2) issue? Try `setTimeout`. – shmck Jul 01 '15 at 23:16
  • @shmck, that seems to only work on construction, I need something that will allow the function to run each time the data is changed and the dom is updated. – ComputerWolf Jul 02 '15 at 01:12
  • 1
    new callbacks have been added in alpha37 https://github.com/angular/angular/blob/2.0.0-alpha.37/CHANGELOG.md core: added afterContentInit, afterViewInit, and afterViewChecked hooks (d49bc43), closes #3897 – Mourad Zouabi Sep 10 '15 at 11:55

1 Answers1

67

The only solution I've found with the lifecycle hook ngAfterViewChecked.

Example of a chat where you have to scroll down the messages list after adding and rendering a new message:

import {Component, AfterViewChecked, ElementRef} from 'angular2/core';

@Component({
    selector: 'chat',
    template: `
        <div style="max-height:200px; overflow-y:auto;" class="chat-list">
            <ul>
                <li *ngFor="#message of messages;">
                    {{ message }}
                </li>
            </ul>
        </div>
        <textarea #txt></textarea>
        <button (click)="messages.push(txt.value); txt.value = '';">Send</button>
    `
})

export class ChatComponent implements AfterViewChecked {
    public messages: any[] = [];        
    private _prevChatHeight: number = 0;

    constructor (public element: ElementRef) {
        this.messages = ['message 3', 'message 2', 'message 1'];

        this.elChatList = this.element.nativeElement.querySelector('.chat-list');
    }       

    public ngAfterViewChecked(): void {
        /* need _canScrollDown because it triggers even if you enter text in the textarea */

        if ( this._canScrollDown() ) {
            this.scrollDown();
        }       
    }       

    private _canScrollDown(): boolean {
        /* compares prev and current scrollHeight */

        var can = (this._prevChatHeight !== this.elChatList.scrollHeight);

        this._prevChatHeight = this.elChatList.scrollHeight;

        return can;
    }

    public scrollDown(): void {
        this.elChatList.scrollTop = this.elChatList.scrollHeight;
    }
}
Artod
  • 891
  • 7
  • 12
  • 7
    Don't use AfterViewChecked, use AfterViewInit (called only once, after the first AfterViewChecked) instead. https://angular.io/guide/lifecycle-hooks – Nico Toub Jul 17 '17 at 09:20
  • 24
    @NicoToub AfterViewInit sometimes gets called before the DOM is constructed. I found using AfterViewChecked with manually handled flag that will allow code running only once the only option. It's not elegant but it works. – George Knap Aug 16 '17 at 12:03
  • 1
    @GeorgeKnap, sorry, may i ask you to share the code which uses AfterViewChecked with the manually handled flag? Thanks in advance! – lucifer63 Jul 06 '18 at 16:40
  • 4
    @lucifer63 You can have a conditional variable, like `initialViewCheck = false`, and in `ngAfterViewChecked` function, you need to write an `if` condition evaluating the boolean value of `initialViewCheck`. If it is `false` (`if (!this.initialViewCheck)`), set the variable to `true` inside the condition and also write the code inside the condition that you want to be executed only once. So, whenever `ngAfterViewChecked` is called then, the condition fails because `initialViewCheck` is set `true`. – Wolverine Jul 07 '18 at 18:16
  • 1
    Dont use AfterViewChecked . It caused performance issues to us since it loads gets fired multiple times ! – Shravan Hebbar Aug 30 '19 at 14:05
  • @ShraavanHebbar it causes serious performance issues. Do you have any alternative? – siddharth shah Mar 18 '20 at 08:45