Ontwikkel een Angular Component met Geavanceerde Lazy Loading van Afbeeldingen
description
Met deze prompt kun je een Angular component ontwikkelen die laadtijden van pagina's aanzienlijk verbetert door afbeeldingen pas te laden wanneer ze zichtbaar zijn. Dit bespaart bandbreedte en verhoogt de gebruikerservaring, vooral bij pagina's met veel media. De uitleg is gericht op moderne technieken zoals Intersection Observer, wat beter is dan standaard image lazy loading en geen extra bibliotheken vereist.
prompt
Help me een Angular component te maken dat geavanceerde lazy loading van afbeeldingen ondersteunt om laadtijden te verbeteren en bandbreedte te besparen. Leg uit hoe ik Intersection O ... more ›
try_prompt
generate_helper
disclaimerOnPageApi
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Natuurlijk! Hier is een stapsgewijze uitleg over hoe je een Angular component kunt maken die geavanceerd lazy loading van afbeeldingen ondersteunt, gebruikmakend van de Intersection Observer API.
### Stap 1: Maak een nieuwe Angular component
Bijvoorbeeld `product-gallery`.
```bash
ng generate component product-gallery
```
### Stap 2: HTML template
Gebruik een *ngFor* om door je afbeeldingen te itereren. Voeg een placeholder of lege `src` toe en een attribuut dat de daadwerkelijke URL bevat, bijvoorbeeld `data-src`.
```html
<div class="gallery">
  <div *ngFor="let image of images" class="image-container">
    <img [attr.data-src]="image.url" #lazyImage class="lazy-image" alt="{{image.alt}}">
  </div>
</div>
```
### Stap 3: CSS
Je kunt basis CSS toevoegen om de afbeeldingen netjes te stylen.
```css
.gallery {
  display: flex;
  flex-wrap: wrap;
}
.image-container {
  width: 200px;
  height: 200px;
  margin: 5px;
  background-color: #f0f0f0;
}
.lazy-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
```
### Stap 4: TypeScript - Lazy loading met Intersection Observer
In de component class ga je het observeren van de afbeeldingen instellen.
```typescript
import { Component, AfterViewInit, ViewChildren, QueryList, ElementRef } from '@angular/core';
@Component({
  selector: 'app-product-gallery',
  templateUrl: './product-gallery.component.html',
  styleUrls: ['./product-gallery.component.css']
})
export class ProductGalleryComponent implements AfterViewInit {
  // Voorbeeld lijst met afbeeldingen
  images = [
    { url: 'path/to/image1.jpg', alt: 'Afbeelding 1' },
    { url: 'path/to/image2.jpg', alt: 'Afbeelding 2' },
    // voeg meer afbeeldingen toe
  ];
  @ViewChildren('lazyImage') lazyImages!: QueryList<ElementRef<HTMLImageElement>>;
  private observer!: IntersectionObserver;
  ngAfterViewInit() {
    this.setupObserver();
  }
  setupObserver() {
    const options = {
      root: null,
      rootMargin: '0px',
      threshold: 0.1
    };
    this.observer = new IntersectionObserver(this.onIntersection.bind(this), options);
    this.lazyImages.forEach((imgRef) => {
      this.observer.observe(imgRef.nativeElement);
    });
  }
  onIntersection(entries: IntersectionObserverEntry[]) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img: HTMLImageElement = entry.target as HTMLImageElement;
        const dataSrc = img.getAttribute('data-src');
        if (dataSrc) {
          img.src = dataSrc;
          img.removeAttribute('data-src');
        }
        this.observer.unobserve(entry.target);
      }
    });
  }
}
```
### Uitleg:
- We gebruiken `@ViewChildren` om alle `<img>` elementen te selecteren.
- We initialiseren een `IntersectionObserver` die elke afbeelding observeert.
- Wanneer een afbeelding in beeld komt (`isIntersecting`), wordt de daadwerkelijke `src` geladen door de waarde uit `data-src` te halen.
- Daarna wordt de afbeelding niet meer geobserveerd.
### Alternatief: Angular bibliotheken
Je kunt ook gebruik maken van bestaande Angular modules zoals `ng-lazyload-image` die dit proces nog eenvoudiger maken.
---
### Samenvatting:
- Gebruik `IntersectionObserver` om te detecteren wanneer afbeeldingen in beeld komen.
- Laad de afbeelding pas door de `src` te zetten wanneer nodig.
- Verwijder de observer na laden voor betere prestaties.
Deze aanpak vermindert laadtijden en bespaart bandbreedte doordat alleen de benodigde afbeeldingen worden geladen.
provider: deepseek
model: deepseek-chat
Zeker! Ik help je een Angular component te maken voor geavanceerde lazy loading van afbeeldingen. Hier is een complete implementatie:
## 1. Eerst de Lazy Image Component
Maak een bestand `lazy-image.component.ts`:
```typescript
import { Component, Input, ElementRef, AfterViewInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
  selector: 'app-lazy-image',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="image-container">
      <img 
        [src]="imageSrc" 
        [alt]="alt" 
        [class.loaded]="isLoaded"
        [class.loading]="!isLoaded && !hasError"
        [class.error]="hasError"
        (load)="onImageLoad()"
        (error)="onImageError()"
      />
      <div *ngIf="!isLoaded && !hasError" class="loading-placeholder">
        <div class="spinner"></div>
      </div>
      <div *ngIf="hasError" class="error-placeholder">
        Afbeelding niet geladen
      </div>
    </div>
  `,
  styles: [`
    .image-container {
      position: relative;
      width: 100%;
      height: 300px;
      overflow: hidden;
    }
    
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      opacity: 0;
      transition: opacity 0.3s ease-in-out;
    }
    
    img.loaded {
      opacity: 1;
    }
    
    .loading-placeholder, .error-placeholder {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      background: #f5f5f5;
    }
    
    .spinner {
      width: 40px;
      height: 40px;
      border: 4px solid #e0e0e0;
      border-top: 4px solid #007bff;
      border-radius: 50%;
      animation: spin 1s linear infinite;
    }
    
    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
  `]
})
export class LazyImageComponent implements AfterViewInit, OnDestroy {
  @Input() imageUrl!: string;
  @Input() alt: string = '';
  @Input() placeholderUrl: string = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PC9zdmc+';
  
  isLoaded = false;
  hasError = false;
  imageSrc = this.placeholderUrl;
  
  private observer: IntersectionObserver | null = null;
  constructor(private elementRef: ElementRef) {}
  ngAfterViewInit(): void {
    this.setupIntersectionObserver();
  }
  ngOnDestroy(): void {
    if (this.observer) {
      this.observer.disconnect();
    }
  }
  private setupIntersectionObserver(): void {
    // Controleer of Intersection Observer ondersteund wordt
    if (!('IntersectionObserver' in window)) {
      // Fallback: laad afbeelding direct als Intersection Observer niet beschikbaar is
      this.loadImage();
      return;
    }
    const options = {
      root: null, // viewport
      rootMargin: '50px 0px', // 50px boven en onder de viewport
      threshold: 0.1 // 10% van de afbeelding moet zichtbaar zijn
    };
    this.observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          this.loadImage();
          this.observer?.unobserve(entry.target);
        }
      });
    }, options);
    this.observer.observe(this.elementRef.nativeElement);
  }
  private loadImage(): void {
    const img = new Image();
    img.onload = () => {
      this.imageSrc = this.imageUrl;
    };
    img.onerror = () => {
      this.hasError = true;
    };
    img.src = this.imageUrl;
  }
  onImageLoad(): void {
    this.isLoaded = true;
    this.hasError = false;
  }
  onImageError(): void {
    this.isLoaded = false;
    this.hasError = true;
  }
}
```
## 2. Product Galerij Component
Maak een bestand `product-gallery.component.ts`:
```typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LazyImageComponent } from './lazy-image.component';
interface ProductImage {
  id: number;
  url: string;
  alt: string;
  thumbnail: string;
}
@Component({
  selector: 'app-product-gallery',
  standalone: true,
  imports: [CommonModule, LazyImageComponent],
  template: `
    <div class="gallery-container">
      <h2>Product Galerij</h2>
      <div class="gallery-grid">
        <app-lazy-image
          *ngFor="let image of productImages"
          [imageUrl]="image.url"
          [alt]="image.alt"
          [placeholderUrl]="image.thumbnail"
        ></app-lazy-image>
      </div>
      
      <!-- Load More knop voor oneindig scrollen -->
      <div class="load-more-container" *ngIf="hasMoreImages">
        <button (click)="loadMoreImages()" class="load-more-btn">
          Meer afbeeldingen laden
        </button>
      </div>
    </div>
  `,
  styles: [`
    .gallery-container {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }
    
    .gallery-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      gap: 20px;
      margin-bottom: 30px;
    }
    
    .load-more-container {
      text-align: center;
      margin-top: 30px;
    }
    
    .load-more-btn {
      padding: 12px 24px;
      background: #007bff;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
      transition: background 0.3s;
    }
    
    .load-more-btn:hover {
      background: #0056b3;
    }
    
    .load-more-btn:disabled {
      background: #6c757d;
      cursor: not-allowed;
    }
  `]
})
export class ProductGalleryComponent {
  productImages: ProductImage[] = [];
  private currentPage = 0;
  private imagesPerPage = 12;
  hasMoreImages = true;
  ngOnInit(): void {
    this.loadMoreImages();
  }
  loadMoreImages(): void {
    const newImages = this.generateMockImages(this.currentPage * this.imagesPerPage, this.imagesPerPage);
    this.productImages = [...this.productImages, ...newImages];
    this.currentPage++;
    
    // Simuleer eind van beschikbare afbeeldingen
    if (this.currentPage >= 3) {
      this.hasMoreImages = false;
    }
  }
  private generateMockImages(startIndex: number, count: number): ProductImage[] {
    const images: ProductImage[] = [];
    
    for (let i = 0; i < count; i++) {
      const id = startIndex + i + 1;
      images.push({
        id: id,
        url: `https://picsum.photos/600/400?random=${id}`,
        alt: `Product afbeelding ${id}`,
        thumbnail: `https://picsum.photos/60/40?random=${id}`
      });
    }
    
    return images;
  }
}
```
## 3. Gebruik in je App Module
Voeg de componenten toe aan je `app.component.ts`:
```typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductGalleryComponent } from './product-gallery/product-gallery.component';
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, ProductGalleryComponent],
  template: `
    <app-product-gallery></app-product-gallery>
  `
})
export class AppComponent {
  title = 'Lazy Loading Gallery';
}
```
## 4. Geavanceerde Optimalisaties
### Preloading Strategie
```typescript
// Voeg toe aan LazyImageComponent
@Input() preload: boolean = false;
private setupIntersectionObserver(): void {
  if (this.preload) {
    this.loadImage();
    return;
  }
  // ... bestaande Intersection Observer code
}
```
### Responsive Images
```typescript
// Uitgebreide image interface
interface ProductImage {
  id: number;
  url: string;
  alt: string;
  thumbnail: string;
  sizes: {
    small: string;
    medium: string;
    large: string;
  };
}
```
## Hoe Intersection Observer Werkt:
1. **Observatie**: Elke afbeelding wordt geobserveerd door de Intersection Observer
2. **Threshold**: Afbeelding wordt geladen wanneer 10% zichtbaar is in viewport
3. **Root Margin**: Voegt 50px buffer toe boven en onder de viewport
4. **Efficiëntie**: Observer stopt met observeren na het eerste laden
## Belangrijkste Voordelen:
- ✅ **Betere Performance**: Alleen zichtbare afbeeldingen worden geladen
- ✅ **Bandbreedte Besparing**: Onnodige downloads worden voorkomen
- ✅ **Smooth Scrolling**: Geen blokkering van de main thread
- ✅ **Fallback Mechanismen**: Werkt ook in oudere browsers
- ✅ **Accessibility**: Goede foutafhandeling en loading states
Deze implementatie zorgt voor een optimale gebruikerservaring terwijl de laadtijden en bandbreedte significant verbeterd worden!

