Découvrez les nouveautés d'Angular 20 en quelques minutes

Angular 20 arrive avec plusieurs nouveautés et stabilisation des API: Zoneless, les APIs resource() et httpResource(), un nouveau runner de tests, etc. La vidéo vous donne un aperçu de ces nouveautés.

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.

Créer des composants dynamiquement avec createComponent

Dans ce tutoriel, nous allons explorer comment créer des composants de manière dynamique en Angular en utilisant la fonction createComponent. Cette fonctionnalité est particulièrement utile pour créer des modales, des notifications ou tout composant qui doit apparaître conditionnellement dans votre application.

Analogie du monde réel

Imaginez que vous êtes un chef cuisinier dans un restaurant. Normalement, vous préparez les plats selon les commandes qui arrivent en cuisine (composants statiques dans le template). Mais parfois, vous devez créer un plat spécial à la volée selon les demandes particulières des clients - c'est exactement ce que fait createComponent : il vous permet de "cuisiner" des composants dynamiquement selon les besoins de votre application.

Angular a considérablement amélioré l'API createComponent avec l'introduction des signaux dans la version 20. Voyons les deux approches.

L'approche moderne avec les signaux (Angular 20+)

Angular 20 introduit une API révolutionnaire basée sur les signaux qui simplifie drastiquement la création de composants dynamiques.

Mise à jour du composant Alert avec les signaux

D'abord, modernisons notre composant pour utiliser les signaux :

typescript
import { Component, input, output } from '@angular/core';

@Component({
  selector: 'app-alert',
  standalone: true,
  template: `
    <article class="alert">
      <h3>{{ title() }}</h3>
      <p>{{ message() }}</p>
      <button (click)="close.emit()">OK</button>
    </article>
  `,
  styles: [`.alert { 
    border: 1px solid #c00;
    padding: 1rem;
    border-radius: 0.4rem;
    background: #ffe6e6;
    position: fixed;
    top: 20px;
    right: 20px;
    z-index: 1000;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  }`]
})
export class AlertComponent {
  // Utilisation des signaux pour les inputs et outputs
  title = input<string>('');
  message = input<string>('');
  close = output<void>();
}

Utilisation avec la nouvelle API

typescript
import {
  Component, EnvironmentInjector, inject, signal,
  createComponent, inputBinding, outputBinding
} from '@angular/core';
import { AlertComponent } from './alert.component';

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <div class="container">
      <h1>Démonstration createComponent</h1>
      <button (click)="openModernAlert()" class="btn btn-primary">
        Afficher alerte
      </button>
      
      <!-- Conteneur pour les composants dynamiques -->
      <ng-container #dialog></ng-container>
    </div>
  `,
  styles: `
    .container {
      padding: 2rem;
      max-width: 800px;
      margin: 0 auto;
    }
    .btn {
      padding: 0.75rem 1.5rem;
      border: none;
      border-radius: 0.375rem;
      cursor: pointer;
      font-weight: 500;
      transition: all 0.2s;
    }
    .btn:hover {
      transform: translateY(-1px);
      box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    }
    .btn-primary { 
      background: #3b82f6; 
      color: white; 
    }
  `
})
export class AppComponent {
  // Référence au conteneur pour les composants dynamiques
  readonly dialogRef = viewChild.required('dialog', { read: ViewContainerRef });

  /**
   * Crée une alerte moderne en utilisant ViewContainerRef
   * Cette approche est plus sûre et recommandée pour Angular 20
   */
  openModernAlert(): void {
    // Création des signaux réactifs pour les données
    const titleSignal = signal('Succès !');
    const messageSignal = signal('Utilisateur créé avec succès');

    let componentRef: ComponentRef<AlertComponent>;

    // Création du composant avec bindings déclaratifs
    componentRef = this.dialogRef().createComponent(AlertComponent, {
      bindings: [
        // Liaison des inputs via des signaux (typé et réactif)
        inputBinding('title', titleSignal),
        inputBinding('message', messageSignal),
        // Gestion de l'output de manière déclarative
        outputBinding('close', () => componentRef.destroy())
      ]
    });
  }
}

Avantages de l'API moderne

  • Type-safe : Vérification des types à la compilation
  • Réactif : Les signaux mettent automatiquement à jour la vue
  • Déclaratif : Configuration claire et concise des bindings
  • Performance : Optimisations automatiques grâce aux signaux

L'approche classique (Angular 14+)

Création du composant Alert

Commençons par créer un composant simple que nous utiliserons dans nos exemples :

typescript
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-alert',
  standalone: true,
  template: `
    <article class="alert">
      <h3>{{ title }}</h3>
      <p>{{ message }}</p>
      <button (click)="close.emit()">OK</button>
    </article>
  `,
  styles: [`.alert { 
    border: 1px solid #c00;
    padding: 1rem;
    border-radius: 0.4rem;
    background: #ffe6e6;
    position: fixed;
    top: 20px;
    right: 20px;
    z-index: 1000;
  }`]
})
export class AlertComponent {
  @Input() title = '';
  @Input() message = '';
  @Output() close = new EventEmitter<void>();
}

Utilisation avec l'ancienne API

Avec l'API classique, la création d'un composant dynamique nécessite plusieurs étapes manuelles :

typescript
import { Component, EnvironmentInjector, inject, createComponent } from '@angular/core';
import { AlertComponent } from './alert.component';

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <div class="container">
      <h1>Gestion des utilisateurs</h1>
      <button (click)="openLegacyAlert()" class="btn-primary">
        Afficher alerte (API classique)
      </button>
    </div>
  `,
  styles: [`
    .container { padding: 2rem; }
    .btn-primary { 
      background: #007bff; 
      color: white; 
      padding: 0.5rem 1rem; 
      border: none; 
      border-radius: 0.25rem; 
      cursor: pointer; 
    }
  `]
})
export class AppComponent {
  // Injection de l'environnement nécessaire pour créer le composant
  private environmentInjector = inject(EnvironmentInjector);

  /**
   * Crée et affiche une alerte en utilisant l'API classique de createComponent
   * Cette méthode illustre l'approche manuelle nécessaire avant Angular 20
   */
  openLegacyAlert(): void {
    // 1. Création du composant avec l'environnement d'injection
    const componentRef = createComponent(AlertComponent, {
      environmentInjector: this.environmentInjector,
      hostElement: document.body // Le composant sera inséré dans le body
    });

    // 2. Configuration des inputs de manière impérative
    // Attention : aucune vérification de type à la compilation !
    componentRef.instance.title = 'Attention !';
    componentRef.instance.message = 'Utilisateur supprimé avec succès (API classique)';
    
    // 3. Déclenchement manuel de la détection de changements
    // Crucial pour que les modifications soient reflétées dans la vue
    componentRef.changeDetectorRef.detectChanges();

    // 4. Gestion de l'événement de fermeture
    const subscription = componentRef.instance.close.subscribe(() => {
      // Nettoyage : destruction du composant et désabonnement
      componentRef.destroy();
      subscription.unsubscribe();
    });
  }
}

Limitations de l'API classique

  • Pas de typage : Les erreurs de propriétés ne sont détectées qu'au runtime
  • Gestion manuelle : Nécessité d'appeler detectChanges() manuellement
  • Boilerplate : Code répétitif pour la gestion des événements et du nettoyage

Comparaison des approches

AspectcreateComponent directViewContainerRef (Recommandé)
Gestion du DOMInsertion dans document.bodyInsertion dans un conteneur défini
NettoyageDestruction manuelle complexeDestruction automatique propre
IsolationRisque de conflits DOMIsolation complète dans le conteneur
PerformancePlus lourd (manipulation DOM directe)Plus léger (gestion par Angular)
SécuritéRisque d'éléments orphelinsNettoyage garanti
LisibilitéCode verbeuxCode concis et expressif

Pourquoi utiliser ViewContainerRef ?

  • Gestion automatique : Angular gère automatiquement l'insertion et la suppression
  • Pas d'éléments orphelins : La destruction est propre et complète
  • Meilleure performance : Moins de manipulations DOM manuelles
  • Code plus simple : Moins de code boilerplate nécessaire

Cas d'usage avancés

Création de modales dynamiques

typescript
/**
 * Crée une modale de confirmation dynamique avec ViewContainerRef
 * Utilise les signaux pour une gestion réactive des données
 */
createConfirmationModal(action: string): void {
  const titleSignal = signal('Confirmation requise');
  const messageSignal = signal(`Êtes-vous sûr de vouloir ${action} ?`);

  let componentRef: ComponentRef<ConfirmationModalComponent>;

  componentRef = this.dialogRef().createComponent(ConfirmationModalComponent, {
    bindings: [
      inputBinding('title', titleSignal),
      inputBinding('message', messageSignal),
      outputBinding('confirm', () => {
        this.executeAction(action);
        componentRef.destroy();
      }),
      outputBinding('cancel', () => componentRef.destroy())
    ]
  });
}

Notifications toast

typescript
/**
 * Affiche une notification toast temporaire
 * Démontre l'utilisation de setTimeout avec les signaux
 */
showToast(type: 'success' | 'error' | 'info', message: string): void {
  const messageSignal = signal(message);
  const typeSignal = signal(type);

  let componentRef: ComponentRef<ToastComponent>;

  componentRef = this.dialogRef().createComponent(ToastComponent, {
    bindings: [
      inputBinding('message', messageSignal),
      inputBinding('type', typeSignal),
      outputBinding('close', () => componentRef.destroy())
    ]
  });

  // Auto-destruction après 3 secondes
  setTimeout(() => componentRef.destroy(), 3000);
}

Bonnes pratiques

  • Toujours nettoyer : Appelez componentRef.destroy() pour éviter les fuites mémoire
  • Utilisez les signaux : Préférez l'API moderne pour une meilleure réactivité
  • Gérez les erreurs : Encapsulez la création dans des try-catch
  • Testez la responsivité : Vérifiez le comportement sur différentes tailles d'écran