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.

Gérer les dépendances circulaires avec forwardRef

Découvrons comment implémenter des composants qui s'appellent mutuellement, particulièrement utile pour afficher des données hiérarchiques comme des commentaires imbriqués.

Un exemple concret: Un système de commentaires

Imaginez un système de commentaires comme sur Reddit :

  • Chaque commentaire peut avoir des réponses
  • Chaque réponse est elle-même un commentaire
  • L'imbrication peut être infinie
  • Chaque niveau peut être replié/déplié

Voici comment créer cette structure avec des composants récursifs :

ts
export interface Comment {
  id: number;
  content: string;
  author: string;
  replies: Comment[];
  level: number;
}
ts
import { Component, Input, forwardRef } from '@angular/core';
import { Comment } from './comment.interface';
import { CommentListComponent } from './comment-list.component';

@Component({
  selector: 'app-comment',
  standalone: true,
  imports: [forwardRef(() => CommentListComponent)],
  template: `
    <div class="comment" [style.margin-left.px]="level * 20">
      <div class="comment-header">
        <strong>{{ comment.author }}</strong>
      </div>
      
      <div class="comment-content">
        {{ comment.content }}
      </div>

      @if (comment.replies.length > 0) {
        <button (click)="toggleReplies()">
          {{ showReplies ? 'Masquer' : 'Afficher' }} les réponses
        </button>

        @if (showReplies) {
          <app-comment-list 
            [comments]="comment.replies" 
            [level]="level + 1">
          </app-comment-list>
        }
      }
    </div>
  `
})
export class CommentComponent {
  @Input() comment!: Comment;
  @Input() level: number = 0;
  
  showReplies = false;

  toggleReplies() {
    this.showReplies = !this.showReplies;
  }
}
ts
import { Component, Input, forwardRef } from '@angular/core';
import { Comment } from './comment.interface';
import { CommentComponent } from './comment.component';

@Component({
  selector: 'app-comment-list',
  standalone: true,
  imports: [forwardRef(() => CommentComponent)],
  template: `
    @for (comment of comments; track comment.id) {
      <app-comment 
        [comment]="comment"
        [level]="level">
      </app-comment>
    }
  `
})
export class CommentListComponent {
  @Input() comments: Comment[] = [];
  @Input() level: number = 0;
}
ts
import { Component } from '@angular/core';
import { CommentListComponent } from './comment-list.component';
import { Comment } from './comment.interface';

@Component({
  selector: 'app-home',
  standalone: true,
  imports: [CommentListComponent],
  template: `
    <h2>Commentaires</h2>
    <app-comment-list [comments]="comments"></app-comment-list>
  `
})
export class HomeComponent {
  comments: Comment[] = [
    {
      id: 1,
      author: 'Alice',
      content: 'Super article !',
      level: 0,
      replies: [
        {
          id: 2,
          author: 'Bob',
          content: 'Je suis d\'accord !',
          level: 1,
          replies: [
            {
              id: 3,
              author: 'Alice',
              content: 'Merci Bob !',
              level: 2,
              replies: []
            }
          ]
        }
      ]
    }
  ];
}

Pourquoi utiliser forwardRef ?

Dans notre exemple, nous avons deux composants qui dépendent l'un de l'autre :

  • CommentComponent a besoin de CommentListComponent pour afficher les réponses
  • CommentListComponent a besoin de CommentComponent pour afficher chaque commentaire

Sans forwardRef, nous obtiendrions une erreur :

ERREUR DE DÉPENDANCE CIRCULAIRE

ERROR TypeError: Cannot read properties of undefined (reading 'ɵcmp')

Cette erreur se produit car Angular ne peut pas résoudre les dépendances circulaires lors de la compilation.

Comment forwardRef résout le problème

forwardRef permet de "reporter" la résolution de la dépendance après l'initialisation des composants. Voici comment :

ts
// ❌ Sans forwardRef - Provoque une erreur
@Component({
  imports: [CommentListComponent], // Erreur: CommentListComponent n'est pas encore défini
})

// ✅ Avec forwardRef - Fonctionne correctement
@Component({
  imports: [forwardRef(() => CommentListComponent)], // La résolution est reportée
})
ts
// ❌ Sans forwardRef - Provoque une erreur
@Component({
  imports: [CommentComponent], // Erreur: Dépendance circulaire
})

// ✅ Avec forwardRef - Fonctionne correctement
@Component({
  imports: [forwardRef(() => CommentComponent)], // La résolution est reportée
})
  1. forwardRef crée une fonction qui retournera la référence au composant
  2. Cette fonction n'est évaluée qu'après l'initialisation des deux composants
  3. Cela brise le cycle de dépendance en reportant la résolution

CONSEIL

Utilisez forwardRef uniquement quand c'est nécessaire, c'est-à-dire dans les cas de dépendances circulaires. Pour les imports normaux, utilisez la syntaxe standard.

Autres cas d'utilisation

Les dépendances circulaires peuvent apparaître dans d'autres situations :

  1. Services qui se référencent mutuellement
  2. Composants parent/enfant récursifs
  3. Modules qui dépendent l'un de l'autre

Dans tous ces cas, forwardRef est la solution recommandée par Angular pour résoudre les dépendances circulaires.

ATTENTION À LA PROFONDEUR

Dans un cas réel, il est recommandé de limiter la profondeur d'imbrication pour des raisons de performance et d'expérience utilisateur. Vous pouvez ajouter une vérification :

ts
@if (comment.replies.length > 0 && level < 5) {
  <app-comment-list [comments]="comment.replies" [level]="level + 1">
  </app-comment-list>
}

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