Съдържанието в ngOnInit или ngAfterViewInit не трябва да се зарежда, докато не се заредят всички изображения в таговете ‹IMG›

Използвам Angular 8 за базирано на блог уеб приложение. Засега данните се съхраняват в json файл, дори изображенията, които трябва да се заредят заедно с пътя.

JSON данни

[
    {
        "imgSrc": "./assets/images/dalai-hills-1.jpg",
        "destination": "Dalai Hills",
        "introTitle": "through happy valley, to a picturesque place",
        "place": "mussoorie, uttarakhand",
        "description": "Lorem ipsum dolor sit amet, no elitr tation delicata cum, mei in causae deseruisse.",
    }
]

imgSrc решава кое изображение да се зареди. Всички изображения вече са оптимизирани и се поставят в папката с активи.

Шаблон

<article class="blog-card" style="border-top: 0;" *ngFor="let blog of blogsList">
    <div class="blog-img-wrap" style="min-height: 200px;">
        <a href="/bg#"">
            <img loading="lazy" class="img-fluid blog-img" src="{{ blog.imgSrc }}" alt="blog-image-1">
        </a>
    </div>
</article>

Да кажем, че в страницата на блоговете при зареждане има 12 изображения, които се зареждат поради , искам да съм сигурен, че страницата ще се зареди едва след като всички изображения са заредени.

Не получавам конкретен отговор на stackoverflow. В момента има много малка част от секунди разлика между текста и изображенията, които се зареждат, но изглежда странно.

Някакви решения за същото?

P.S: Искам да избегна jQuery.


person Prashant Saxena    schedule 24.05.2020    source източник


Отговори (1)


Можеш;

  1. Създавайте програмно елементи на изображението. (използвайте HTMLImageElement)
  2. Проследете тяхното състояние на зареждане. (използвайте ReplaySubject и forkJoin)
  3. Когато всички изображения се заредят, покажете ги в страницата. (използвайте async pipe и Renderer2)

Ето примерна реализация (обясненията са в коментарите);

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
  data = [
    {
      imgSrc: "https://img.jakpost.net/c/2017/10/27/2017_10_27_34794_1509067747._large.jpg",
      destination: "destination-01",
      introTitle: "introTitle-01",
      place: "place-01",
      description: "description-01"
    },
    {
      imgSrc: "https://lakecomofoodtours.com/wp-content/uploads/gravedona-celiaa-img-0282_orig-800x600.jpg",
      destination: "destination-02",
      introTitle: "introTitle-02",
      place: "place-02",
      description: "description-02"
    },
    {
      imgSrc: "https://italicsmag.com/wp-content/uploads/2020/05/Lake-Como-5-770x550.jpg",
      destination: "destination-03",
      introTitle: "introTitle-03",
      place: "place-03",
      description: "description-03"
    }
  ];

  /* This array holds Observables for images' loading status */
  private tmp: ReplaySubject<Event>[] = [];

  blogData: BlogDataType[] = this.data.map(d => {
    const img = new Image();
    img.height = 200;
    img.width = 300;
    const evt = new ReplaySubject<Event>(1);
    img.onload = e => {
      evt.next(e);
      evt.complete();
    };
    this.tmp.push(evt);
    img.src = d.imgSrc 
    return { ...d, imgElem: img };
  });

  /* 
   * Convert images' loading status observables to a higher-order observable .
   * When all observables complete, forkJoin emits the last emitted value from each.
   * since we emit only one value and complete in img.load callback, forkJoin suits our needs.
   * 
   * when forkJoin emits (i.e. all images are loaded) we emit this.blogData so that we use it
   * in template with async pipe
   */
  blogsList: Observable<BlogDataType[]> = forkJoin(this.tmp).pipe(
    map(() => this.blogData)
  );

  constructor(private renderer: Renderer2) {}

  /* manually append image elements to DOM, used in template */
  appendImg(anchor: HTMLAnchorElement, img: HTMLImageElement) {
    this.renderer.appendChild(anchor, img);
    return "";
  }
}

interface BlogDataType {
  imgElem: HTMLImageElement;
  imgSrc: string;
  destination: string;
  introTitle: string;
  place: string;
  description: string;
}
<div *ngFor="let blog of blogsList | async">
  <div>{{blog.description}}</div>
  <a #aEl href="/bg#">
    {{ appendImg(aEl, blog.imgElem) }}
  </a>
</div>

Ето работеща демонстрация: https://stackblitz.com/edit/angular-ivy-ymmfz6

Моля, имайте предвид, че тази реализация не е устойчива на грешки. img.onerror също трябва да се обработва за използване в производството, пропуснах го за по-голяма простота.

person ysf    schedule 25.05.2020