r3plica
r3plica

Reputation: 13387

Angular4 ngAfterViewChecked invoked multiple times when using setTimeout

I have a service that I want to call once a view has been fully rendered. I did some digging around and found the Angular lifecycle. It seemed that the best method for me to use was AfterViewChecked, so I updated my AppComponent and added:

import { Component, OnInit, AfterViewChecked } from "@angular/core";
import { ActivatedRoute, Params } from "@angular/router";

import { ResultsService } from "./shared/results.service";
import { HeightService } from "./shared/height.service";

@Component({
  selector: 'bzRoot',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, AfterViewChecked {

  constructor(
    private _activatedRoute: ActivatedRoute,
    private _resultsService: ResultsService,
    private _heightService: HeightService
  ) { }

  ngOnInit() {
    this._activatedRoute.queryParams.subscribe((params: Params) => this._resultsService.elq = params['elq']);
  }

  ngAfterViewChecked() {
    console.log('hi');
  }
}

When I run my application, I can see that the console has 25 instances of "hi" in it. I have no idea why it is so many, but that isn't the issue. My method is calling a service I created which uses postMessage to send a message to the parent window. It looks like this:

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

@Injectable()
export class HeightService {

  constructor() { }

  public resize() {
    console.log('resizing');

    var body = document.body,
        html = document.documentElement;

    var height = Math.max( 
      body.scrollHeight, 
      body.offsetHeight, 
      body.clientHeight, 
      html.offsetHeight
    );

    setTimeout(function () {
      parent.postMessage(height, '*');
    }, 0);
  }
}

And my ngAfterViewChecked method now looks like this:

ngAfterViewChecked() {
  this._heightService.resize();
}

If I comment out the setTimeout line, it goes back down to 25 invocations. Does anyone know why and how I can stop it from happening?

Upvotes: 3

Views: 7568

Answers (1)

Pierre Mallet
Pierre Mallet

Reputation: 7221

It is by design. The docs says :

ngAfterViewChecked()
Respond after Angular checks the component's views and child views. Called after the ngAfterViewInit and every subsequent ngAfterContentChecked(). A component-only hook.

You are looking for ngAfterViewInit() hooks

EDIT: It seems that angular hooks are not the solution to your needs, you should then save the last height value sent and trigger resize only if the value changed

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

@Injectable()
export class HeightService {

  lastHeightPosted = null;
  constructor() { }

  public resize() {
    console.log('resizing');

    var body = document.body,
        html = document.documentElement;

    var height = Math.max( 
      body.scrollHeight, 
      body.offsetHeight, 
      body.clientHeight, 
      html.offsetHeight
    );

    if ( lastHeightPosted !== height) {
        parent.postMessage(height, '*');
        lastHeightPosted = height;
    }
  }

  ngAfterViewChecked() {
     this._heightService.resize();
  }
}

Upvotes: 1

Related Questions