3

Wrote an Angular 13 component that draws something in a div whenever the window resizes.

The problem is getting the rendered width of the div. If I do not use setTimeout() with a 170ms delay, this.treemap.nativeElement.offsetWidth is zero.

As the treemap has to re-render each time the window resizes, I also tried dispatching a window resize event from ngAfterViewInit() but it also seems to happen too fast (so again, this.treemap.nativeElement.offsetWidth is zero).

Relevent part of the template...

  <div #treemap class="treemap col-12"></div>

and abbreviated code...

// Details omitted for brevity
export class TreemapComponent implements AfterViewInit {
  @ViewChild('treemap') treemap!: ElementRef

  @HostListener('window:resize', ['$event'])
  private render() {
    const width = this.treemap.nativeElement.offsetWidth
    // code that successfully renders a treemap in the div as long as width != 0
  }

  callHack(): void {
    setTimeout(() => this.render(), 170) // testing shows 170ms is lowest possible
  }

  ngAfterViewInit(): void {
    this.callHack()
    // window.dispatchEvent(new Event('resize')) // happens too fast (offsetWidth = 0)
  }

Is AfterViewInit the wrong lifecycle event hook (this post suggested ngAfterViewInit)?

Is there a less-hacky approach to making this work in Angular 13?

nstuyvesant
  • 1,392
  • 2
  • 18
  • 43
  • There are couple of `hacks` (along with their explanation) mentioned in this post, see if any one works for your use case - https://stackoverflow.com/questions/34947154/angular-2-viewchild-annotation-returns-undefined – Aakash Goplani Mar 28 '22 at 14:32
  • What I wrote above works but it uses a hack (setTimeout). The examples on the other question are pretty much what I've already done to get it working. When I wrote the same component in Svelte, I didn't have to do any hacks because it uses the actual rather than a virtual DOM. It left me feeling like I'm missing some knowledge in Angular to make it work more elegantly. – nstuyvesant Mar 28 '22 at 14:49
  • Make an example on stackblitz with your problem. – Anton Marinenko Mar 28 '22 at 16:13
  • @nstuyvesant I'm unable to get a zero width with just a regular div, could you please provide some way to reproduce this? What library are you using for the treemap? – Chris Hamilton Mar 28 '22 at 17:37

2 Answers2

5

You can use a ResizeObserver to observe the element itself rather than the window.

export class TreemapComponent implements AfterViewInit, OnDestroy {
  @ViewChild('treemap') treemap!: ElementRef;
  treemapObserver = new ResizeObserver(() => this.render());

  private render() {
    const width = this.treemap.nativeElement.offsetWidth;
    // code that successfully renders a treemap in the div as long as width != 0
  }

  ngAfterViewInit(): void {
    this.treemapObserver.observe(this.treemap.nativeElement);
  }

  ngOnDestroy() {
    this.treemapObserver.unobserve(this.treemap.nativeElement);
  }
}

Credit to this answer: https://stackoverflow.com/a/39312522/12914833

Chris Hamilton
  • 9,252
  • 1
  • 9
  • 26
0

Try calling ngAfterViewChecked() instead of ngAfterViewInit()

Lok C
  • 393
  • 1
  • 4
  • 13