Les nouveautés d'Angular 19 en 4 minutes

Angular 19 vient de sortir, et il y a beaucoup de nouveautés intéressantes : hydratation incrémentale, linkedSignal, l'API des ressources, et plein d'autres choses. Venez découvrir tout ça en moins de 4 minutes !

Abonnez-vous à notre chaîne

Pour profiter des prochaines vidéos sur Angular, abonnez-vous à la nouvelle chaîne YouTube !

Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

afterNextRender : Le hook de cycle de vie pour manipuler le DOM après le rendu

WARNING

afterNextRender est disponible depuis Angular 17.

Imaginons que vous développiez une application de galerie photos où vous devez initialiser un carousel d'images. Avant d'initialiser le carousel, vous devez être sûr que toutes les images sont chargées et que le DOM est prêt. C'est exactement le type de scénario où ces hooks sont précieux !

DIFFÉRENCES CLÉS

  • afterNextRender : s'exécute une seule fois après le prochain cycle de rendu
  • afterRender : s'exécute après chaque cycle de rendu

Exemple avec afterNextRender

On a installé la librairie Swiper pour gérer le carousel.

ts
import {
  Component,
  afterNextRender,
  ElementRef,
  ViewChild,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import Swiper from 'swiper';

interface Image {
  id: number;
  url: string;
  alt: string;
}

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <div class="swiper" #carouselContainer>
      <div class="swiper-wrapper">
      @for (image of images; track image.id) {
        <img class="swiper-slide" [src]="image.url" [alt]="image.alt" />
      }
      </div>
    </div>
  `,
})
export class CarouselComponent {
  @ViewChild('carouselContainer')
  carouselContainer!: ElementRef<HTMLDivElement>;

  images: Image[] = [
    {
      id: 1,
      url: 'https://via.placeholder.com/150',
      alt: 'Image 1',
    },
    {
      id: 2,
      url: 'https://via.placeholder.com/150',
      alt: 'Image 1',
    },
  ];

  constructor() {
    afterNextRender(() => {
      this.initializeCarousel();
    });
  }

  private initializeCarousel() {
    new Swiper(this.carouselContainer.nativeElement);
  }
}

Comprendre afterNextRender en détail

Pourquoi utiliser afterNextRender ?

Dans notre exemple du carousel, nous avons besoin d'initialiser Swiper uniquement après que le DOM soit complètement rendu. Voici pourquoi afterNextRender est parfait pour ce cas d'usage :

  1. Il garantit que tous les éléments du DOM sont disponibles
  2. Il s'exécute une seule fois, ce qui est idéal pour les initialisations
  3. Il évite les erreurs courantes liées à l'accès précoce au DOM
  4. Ne fonctionne pas côté serveur

ATTENTION

N'utilisez pas afterNextRender pour :

  • Des opérations qui doivent se répéter à chaque cycle de rendu (utilisez afterRender à la place)
  • Des opérations qui ne nécessitent pas l'accès au DOM

Comparaison avec ngAfterViewInit

Voici pourquoi afterNextRender est souvent préférable à ngAfterViewInit :

ts
// ❌ Ancien style avec ngAfterViewInit
ngAfterViewInit() {
  this.initializeCarousel(); // Peut causer des erreurs ExpressionChangedAfterItHasBeenChecked
}

// ✅ Nouveau style avec afterNextRender
constructor() {
  afterNextRender(() => {
    this.initializeCarousel(); // Plus sûr et prévisible
  });
}

BONNE PRATIQUE

afterNextRender est plus sûr car il évite les erreurs ExpressionChangedAfterItHasBeenChecked courantes avec ngAfterViewInit.

Les phases de afterNextRender

Vue d'ensemble des phases

afterNextRender propose 4 phases distinctes qui s'exécutent dans un ordre précis :

  1. earlyRead → Lecture précoce du DOM
  2. write → Écriture dans le DOM
  3. mixedReadWrite → Lecture et écriture simultanées
  4. read → Lecture du DOM

BONNE PRATIQUE

Privilégiez toujours les phases read et write pour de meilleures performances.

Exemple détaillé avec les phases

Voici un exemple qui illustre l'utilisation des différentes phases :

ts
import { afterNextRender, AfterRenderPhase } from '@angular/core';

@Component({
  // ... configuration du composant
})
export class CarouselComponent {
  constructor() {
    // Phase 1: Lecture précoce
    afterNextRender(() => {
      const initialDimensions = this.getContainerDimensions();
      return initialDimensions;
    }, { phase: AfterRenderPhase.earlyRead });

    // Phase 2: Écriture
    afterNextRender((dimensions) => {
      this.updateCarouselStyles(dimensions);
      return dimensions;
    }, { phase: AfterRenderPhase.write });

    // Phase 3: Lecture finale
    afterNextRender((dimensions) => {
      this.validateCarouselSetup(dimensions);
    }, { phase: AfterRenderPhase.read });
  }

  private getContainerDimensions() {
    const element = this.carouselContainer.nativeElement;
    return {
      width: element.clientWidth,
      height: element.clientHeight
    };
  }

  private updateCarouselStyles(dimensions: any) {
    const element = this.carouselContainer.nativeElement;
    element.style.width = `${dimensions.width}px`;
  }

  private validateCarouselSetup(dimensions: any) {
    console.log('Carousel initialized with dimensions:', dimensions);
  }
}

Chaque mois, recevez en avant-première notre newsletter avec les dernières actualités, tutoriels, astuces et ressources Angular directement par email !