Angular est-il vraiment plus compliqué que React ?

Quand on débute avec Angular, il est facile de se sentir découragé face à la multitude de concepts à assimiler. Cette complexité peut inciter à se tourner vers des frameworks comme React, qui semblent plus simples à première vue. Mais est-ce vraiment le cas ?

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.

@defer : Le chargement différé des vues

La directive @defer est une des nouveautés majeures d'Angular 17. Elle révolutionne la façon dont nous gérons le chargement paresseux (lazy loading) de nos composants. Voyons ensemble comment l'utiliser efficacement pour optimiser vos applications.

Comprendre le Concept

Imaginez un restaurant qui prépare tous ses plats à l'avance, avant même l'arrivée des clients. Ce serait inefficace et potentiellement du gaspillage ! De la même manière, charger tous les composants d'une application dès le démarrage n'est pas optimal.

Avec @defer, Angular nous permet de "préparer les plats à la demande", c'est-à-dire de charger les composants uniquement quand ils sont nécessaires.

AVANTAGE CLÉ

@defer permet de réduire significativement le temps de chargement initial de votre application en ne chargeant que ce qui est strictement nécessaire au départ.

ATTENTION

@defer ne peut être utilisé que sur les composants standalone. De plus, les dépendances ne peuvent pas être référencées en dehors des blocs @defer dans le même fichier. Si elles sont référencées en dehors du bloc @defer ou référencées dans des requêtes ViewChild, les dépendances seront chargées immédiatement.

Cas d'Usage

Cas d'UsageDescriptionExemple de Fonctionnalité
Composants ModauxCharger les composants de dialogue uniquement lors du clicModal de confirmation de suppression d'un utilisateur
Widgets ComplexesDifférer le chargement des composants avec beaucoup de logique ou de donnéesTableau de bord avec graphiques et statistiques utilisateur
Formulaires DynamiquesCharger les formulaires complexes à la demandeFormulaire d'édition de profil utilisateur avec validation avancée
Composants TiersRetarder le chargement des bibliothèques externes volumineusesÉditeur de texte riche ou carte interactive
Sections ConditionnellesCharger du contenu basé sur le rôle utilisateurInterface d'administration accessible uniquement aux administrateurs

Syntaxe de Base

Voici la structure de base d'un bloc @defer :

ts
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { UserListComponent } from './user-list/user-list.component';
import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, UserListComponent, LoadingSpinnerComponent],
  template: `
    @defer {
      <app-user-list [users]="users" />
    } @placeholder {
      <div>Chargement de la liste...</div>
    } @loading {
      <app-loading-spinner />
    } @error {
      <div>Erreur de chargement</div>
    }
  `
})
export class AppComponent {
  users: User[] = [];
}

Explication des Blocs

  1. @defer

    • C'est le bloc principal qui contient le contenu à charger de manière différée
    • Dans notre exemple, il contient le composant UserListComponent qui sera chargé uniquement lorsque nécessaire
    • Sans condition de déclenchement spécifiée, le chargement commence dès que possible
  2. @placeholder (minimum)

    • Affiché immédiatement à la place du contenu différé
    • Reste visible jusqu'à ce que le contenu différé soit prêt
    • Accepte un paramètre minimum pour définir la durée minimale d'affichage
    • Idéal pour afficher une version simplifiée ou un "squelette" de votre contenu
ts
@defer {
  <app-user-list [users]="users" />
} @placeholder (minimum 500ms) {
  <div>Chargement de la liste...</div>
}
  1. @loading (after, minimum)
    • S'affiche pendant le chargement actif du contenu
    • Accepte deux paramètres optionnels :
      • after: délai avant l'affichage du loading (évite le clignotement)
      • minimum: durée minimale d'affichage du loading
    • Particulièrement utile pour les chargements qui prennent du temps
ts
@defer {
  <app-user-list [users]="users" />
} @loading (after 100ms; minimum 1s) {
  <app-loading-spinner />
}

ANTI-CLIGNOTEMENT

Les paramètres after et minimum du bloc @loading sont essentiels pour éviter le "flickering" (clignotement) de l'interface lorsque le chargement est très rapide. Par exemple :

  • after 100ms : le loading ne s'affichera que si le chargement dure plus de 100ms
  • minimum 1s : une fois affiché, le loading restera visible au moins 1 seconde
  1. @error
    • S'affiche si le chargement du contenu échoue
    • Permet de gérer gracieusement les erreurs
    • Idéal pour afficher un message d'erreur explicite à l'utilisateur

ORDRE D'AFFICHAGE

  1. Le @placeholder s'affiche en premier
  2. Lors du déclenchement du chargement, le @loading remplace le placeholder
  3. Une fois le chargement terminé, le contenu du bloc @defer s'affiche
  4. En cas d'erreur, le bloc @error s'affiche

Déclencheurs Déclaratifs "on"

1. immediate

Le déclencheur immediate est particulièrement intéressant car il permet de charger le composant dès que possible tout en bénéficiant des avantages du chargement différé.

En effet, même si le composant est chargé immédiatement, il est importé dynamiquement dans un chunk séparé. Cela signifie que le bundle principal de l'application reste léger. Par exemple, si votre application a une page de connexion qui n'utilise pas le profil utilisateur, le code du composant de profil ne sera pas chargé inutilement.

ts
import { Component } from '@angular/core';
import { UserDetailsComponent } from './user-details.component';

@Component({
  selector: 'app-user-profile',
  standalone: true,
  template: `
    @defer (on immediate) {
      <app-user-details [user]="user" />
    } @placeholder {
      <div>Chargement du profil...</div>
    }
  `,
  imports: [UserDetailsComponent],
})
export class UserProfileComponent {
  // ...
}

2. idle (par défaut)

Ce déclencheur attend que le navigateur soit en état d'inactivité pour charger le composant.

ts
@defer (on idle) {
  <app-analytics-dashboard />
} @placeholder {
  <div>Le tableau de bord se charge...</div>
}

CONSEIL

Parfait pour les composants non critiques qui peuvent être chargés en arrière-plan sans impacter l'expérience utilisateur.

3. timer(delay)

Permet de retarder le chargement d'un composant d'une durée spécifique.

ts
@defer (on timer(2000)) { // Délai de 2 secondes
  <app-notification-panel />
} @placeholder {
  <div>Les notifications arrivent...</div>
}

ATTENTION

Utilisez ce déclencheur avec parcimonie. Un délai trop long peut frustrer les utilisateurs.

4. viewport

Déclenche le chargement lorsque l'élément entre dans la zone visible de l'écran.

ts
import { Component } from '@angular/core';
import { UserCardComponent } from './user-card.component';
import { UserCardSkeletonComponent } from './user-card-skeleton.component';

@Component({
  selector: 'app-user-list',
  standalone: true,
  imports: [UserCardComponent, UserCardSkeletonComponent],
  template: `
    <div class="user-list">
      @for (user of users; track user.id) {
        @defer (on viewport) {
          <app-user-card [user]="user" />
        } @placeholder {
          <app-user-card-skeleton />
        }
      }
    </div>
  `
})
export class UserListComponent {
  // ...
}

OPTIMISATION

Particulièrement utile pour les listes longues ou les images qui ne doivent être chargées que lorsqu'elles sont visibles.

5. hover

Déclenche le chargement lorsque l'utilisateur survole un élément.

ts
@defer (on hover) {
  <app-user-details [userId]="user.id" />
} @placeholder {
  <div>Survolez pour voir les détails</div>
}

ASTUCE

Idéal pour les info-bulles complexes ou les menus détaillés.

6. interaction

Charge le composant lors de la première interaction utilisateur (clic, toucher, etc.).

ts
@defer (on interaction) {
  <app-comments-section [postId]="post.id" />
} @placeholder {
  <button>Afficher les commentaires</button>
}

Combinaison de Déclencheurs

Vous pouvez combiner plusieurs déclencheurs pour plus de flexibilité :

ts
@defer (on viewport; on hover) {
  <app-complex-widget />
} @placeholder {
  <div>Widget en attente</div>
}

Préchargement Intelligent

Le préchargement intelligent (prefetch) permet d'optimiser l'expérience utilisateur en chargeant le composant en arrière-plan avant qu'il ne soit réellement nécessaire. Par exemple, si vous avez un composant qui se charge au scroll, vous pouvez commencer à le précharger pendant les moments d'inactivité du navigateur :

EXEMPLE CONCRET

Imaginez un catalogue de produits. Quand l'utilisateur fait défiler la page, les produits se chargent au fur et à mesure qu'ils entrent dans le viewport. Avec le préchargement, pendant que l'utilisateur regarde les premiers produits, Angular charge déjà en arrière-plan les produits suivants. Ainsi, quand l'utilisateur continue à scroller, les produits apparaissent instantanément car ils sont déjà chargés !

ts
@defer (on viewport; prefetch on idle) {
  <app-user-statistics [userId]="user.id" />
} @placeholder {
  <div>Statistiques en cours de chargement...</div>
}

Déclencheurs Impératifs "when"

Les déclencheurs impératifs "when" offrent une flexibilité maximale en vous permettant de définir vos propres conditions de chargement. Contrairement aux déclencheurs "on" qui sont prédéfinis, les déclencheurs "when" vous permettent d'écrire une logique personnalisée.

Cas d'Utilisation Courants

1. Conditions Basées sur l'État

ts
import { Component, signal } from '@angular/core';
import { ProfileDetailsComponent } from './profile-details.component';
import { ProfileSetupComponent } from './profile-setup.component';

@Component({
  template: `
    @defer (when userProfile.isComplete) {
      <app-profile-details [user]="userProfile" />
    } @placeholder {
      <app-profile-setup />
    }
  `,
  standalone: true,
  imports: [ProfileDetailsComponent, ProfileSetupComponent],
})
export class UserProfileComponent {
  userProfile = signal<UserProfile>({ isComplete: false });
}

2. Conditions Multiples

ts
@defer (when isAuthenticated() && hasPermission('VIEW_STATS')) {
  <app-analytics-dashboard />
} @placeholder {
  <app-unauthorized-message />
}

3. Conditions Asynchrones

ts
import { Component, inject } from '@angular/core';
// on part du principe que nous avons un service FeatureService qui nous permet de vérifier si une fonctionnalité est activée
import { FeatureService } from './feature.service';
import { AsyncPipe } from '@angular/common';
import { FeatureUnavailableComponent } from './feature-unavailable.component';
import { NewFeatureComponent } from './new-feature.component';

@Component({
  template: `
    @defer (when featureFlag$ | async) {
      <app-new-feature />
    } @placeholder {
      <app-feature-unavailable />
    }
  `,
  standalone: true,
  imports: [FeatureUnavailableComponent, NewFeatureComponent, AsyncPipe],
})
export class FeatureComponent {
  featureFlag$ = this.featureService.isEnabled('NEW_FEATURE');
}

Patterns Avancés

1. Combinaison avec des Observables

ts
import { Component, inject } from '@angular/core';
// on part du principe que nous avons un service DataService qui nous permet de récupérer les données
import { DataService } from './data.service';
import { DataVisualizationComponent } from './data-visualization.component';
import { LoadingSpinnerComponent } from './loading-spinner.component';
import { ErrorMessageComponent } from './error-message.component';
import { AsyncPipe } from '@angular/common';

@Component({
  template: `
    @defer (when data$ | async; when !isLoading()) {
      <app-data-visualization [data]="data$ | async" />
    } @loading {
      <app-loading-spinner />
    } @error {
      <app-error-message />
    }
  `,
  standalone: true,
  imports: [DataVisualizationComponent, LoadingSpinnerComponent, ErrorMessageComponent, AsyncPipe],
})
export class DataViewComponent {
  data$ = this.dataService.getData();
  isLoading = signal(false);
}

2. Gestion des Étapes

ts
import { Component, signal } from '@angular/core';
import { ProfileStepComponent } from './profile-step.component';
import { PreferencesStepComponent } from './preferences-step.component';
import { ConfirmationStepComponent } from './confirmation-step.component';

@Component({
  template: `
    @defer (when currentStep() === 'PROFILE') {
      <app-profile-step />
    }
    @defer (when currentStep() === 'PREFERENCES') {
      <app-preferences-step />
    }
    @defer (when currentStep() === 'CONFIRMATION') {
      <app-confirmation-step />
    }
  `,
  standalone: true,
  imports: [ProfileStepComponent, PreferencesStepComponent, ConfirmationStepComponent],
})
export class WizardComponent {
  currentStep = signal<'PROFILE' | 'PREFERENCES' | 'CONFIRMATION'>('PROFILE');
}

Bonnes Pratiques

PERFORMANCE

  1. Évitez les Calculs Complexes
  • Gardez vos conditions simples et efficaces
  • Évitez les opérations coûteuses dans les conditions
ts
// ❌ À éviter
@defer (when users.filter(u => u.isActive).length > 0) {
  <app-user-list />
}

// ✅ Préférez
@defer (when hasActiveUsers()) {
  <app-user-list />
}

LISIBILITÉ

  1. Extrayez les Conditions Complexes
  • Créez des méthodes dédiées pour les conditions complexes
  • Améliorez la maintenabilité du code
ts
// ❌ À éviter
@defer (when user && user.roles.includes('ADMIN') && !isLoading) {
  <app-admin-panel />
}

// ✅ Préférez
@defer (when canAccessAdminPanel()) {
  <app-admin-panel />
}

ATTENTION

  1. Gestion des États
  • Gérez correctement les états initiaux
  • Prévoyez les cas d'erreur
ts
@defer (when userState() !== 'LOADING') {
  <app-user-content />
} @loading {
  <app-loading-spinner />
} @error {
  <app-error-state />
}

Hydratation Incrémentielle (Angular 19+)

L'hydratation incrémentielle est une nouvelle fonctionnalité introduite dans Angular 19 qui améliore significativement les performances du rendu côté serveur (SSR).

Pour activer l'hydratation incrémentielle, vous devez d'abord configurer votre application :

ts
import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser';

bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(withIncrementalHydration())
  ]
});

L'hydratation incrémentielle s'intègre parfaitement avec la directive @defer en utilisant le déclencheur hydrate :

ts
import { Component } from '@angular/core';
import { UserStatisticsComponent } from './user-statistics.component';
@Component({
  template: `
    <header>Dashboard Utilisateur</header>
    
    @defer (hydrate on viewport) {
      <app-user-statistics [userId]="user.id" />
    } @placeholder {
      <div>Chargement des statistiques...</div>
    }
  `,
  standalone: true,
  imports: [UserStatisticsComponent],
})
export class UserDashboardComponent {
  // ...
}

Il faut activer le SSR pour que l'hydratation incrémentielle fonctionne. Voir l'article Server-side rendering pour plus d'informations.

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