Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieve elements created by ngFor in ngAfterViewInit

I'm implementing a lazy image loader in my Angular (5) app, and am curious how I can avoid having to call setTimeout() in my ngAfterViewInit(), if possible.

The relevant portions of the code are:

# component
ngOnInit(): void {
  this.workService.getCategories().then(workCategories => {
    this.workCategories = workCategories;
  });
}

ngAfterViewInit(): void {
  setTimeout(() => {
    const images = Array.from(document.querySelectorAll('.lazy-image'));
  }, 100);
}

# component template
<div *ngFor="let workCategory of workCategories">
  <h3>{{ workCategory.fields.name }}</h3>
  <div *ngFor="let workSample of workCategory.fields.workSamples">
    <img width="294" height="294" class="lazy-image" src="..." data-src="..." />
  </div>
</div>

If I remove setTimeout() the images array is always empty. AfterViewInit should run after all of the child components have been created. I've also tried AfterContentInit, which behaves the same and AfterContentChecked, which crashed Chrome.

Is it possible to avoid setTimeout in this case?

like image 300
Brandon Avatar asked Oct 16 '22 21:10

Brandon


1 Answers

This stackblitz shows one method to get notified when the elements have been created with the ngFor directive. In the template, you assign a template reference variable #lazyImage to the img element:

<div *ngFor="let workCategory of workCategories">
  ...
  <div *ngFor="let workSample of workCategory.fields.workSamples">
    <img #lazyImage width="294" height="294" class="lazy-image" src="..." data-src="..." />
  </div>
</div>

In the code, @ViewChildren("lazyImage") is used to declare a QueryList<ElementRef> associated to these images. By subscribing to the changes event of the Querylist in ngAfterViewInit, you get notified when the elements are available. The HTML elements can then be retrieved from the QueryList:

import { Component, ViewChildren, AfterViewInit, QueryList } from '@angular/core';

@Component({
  ...
})
export class AppComponent {

  @ViewChildren("lazyImage") lazyImages: QueryList<ElementRef>;

  ngAfterViewInit() {
    this.lazyImages.changes.subscribe(() => {
      let images = this.lazyImages.toArray().map(x => x.nativeElement);
    });
  }
}

In cases where only the last created item is to be processed, the QueryList.last can be used:

    this.lazyImages.changes.subscribe(() => {
      this.doSomethingOnLastImage(this.lazyImages.last);
    });
like image 97
ConnorsFan Avatar answered Nov 03 '22 21:11

ConnorsFan