📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

~8 min de lecture

ngComponentOutlet vs ngTemplateOutlet : l'arbre de décision que personne ne t'a donné

Les deux directives ont un nom qui finit par Outlet, les deux "projettent quelque chose dynamiquement", les deux vivent dans @angular/common. Alors on les mélange. On prend ngTemplateOutlet là où il fallait un composant, on se retrouve à passer des callbacks partout ; ou on prend ngComponentOutlet là où il fallait un template, et on se bat pour styler un truc qu'on ne contrôle plus.

Le problème n'est pas technique, il est conceptuel. Tant que tu ne sais pas quelle question chaque directive répond, tu choisis à l'instinct, et une fois sur deux tu te trompes. Cet article te donne la distinction nette et un arbre de décision utilisable en 30 secondes.

Valide Angular 17+, exemples en Angular 20. Cet article suppose que tu connais déjà chaque directive prise séparément. Si ce n'est pas le cas, commence par ngTemplateOutlet et les composants génériques typés et ngComponentOutlet piloté par la donnée, puis reviens ici pour arbitrer.


TL;DR

Question Réponse -> directive
Qui écrit le markup rendu ? Le consumer -> ngTemplateOutlet. L'auteur du composant / la donnée -> ngComponentOutlet
Le rendu a-t-il sa propre logique, son cycle de vie, sa DI ? Oui -> ngComponentOutlet. Non, c'est juste du visuel -> ngTemplateOutlet
Le rendu doit-il émettre vers l'hôte (outputs) ? Oui et c'est central -> ViewContainerRef.createComponent
L'ensemble des variantes vient d'une donnée / config ? Oui -> ngComponentOutlet + registry
Le consumer doit-il customiser un bout visuel avec des données de l'hôte ? Oui -> ngTemplateOutlet

La phrase à retenir : ngTemplateOutlet inverse le contrôle du rendu vers le consumer ; ngComponentOutlet délègue le rendu à un composant que l'hôte choisit. Ce n'est pas la même direction de dépendance.


La vraie distinction : qui possède quoi

Oublie la mécanique deux minutes et regarde qui possède quoi.

ngTemplateOutlet : le consumer possède le markup, l'hôte possède la donnée.

Tu écris un composant List<T>. Le composant sait boucler, gérer l'état vide, la pagination. Mais il ne sait pas à quoi ressemble une row, et il ne veut pas le savoir : c'est le job de celui qui l'utilise. Alors le consumer fournit un <ng-template>, l'hôte lui passe l'item courant via un context, et le consumer décide du HTML. C'est une inversion de contrôle : l'hôte pilote la logique, le consumer pilote l'apparence.

<app-list [items]="users()">
  <ng-template [listRow]="users()" let-user let-i="index">
    <span>{{ i }} - {{ user.username }}</span>
  </ng-template>
</app-list>

Le composant List ne connaît pas User. Il projette un trou, le consumer le remplit. C'est le pattern des composants génériques réutilisables : DataTable, Select, Autocomplete, Virtual scroll.

ngComponentOutlet : l'hôte possède le choix du composant, le composant se possède lui-même.

Tu as un dashboard. La donnée dit type: 'weather'. L'hôte résout la classe WeatherWidget et la rend. Mais l'hôte ne décide rien du contenu du widget : le WeatherWidget a son propre template, sa propre logique, ses propres services injectés, son propre cycle de vie. L'hôte ne fait que choisir quel composant, et lui passer des inputs.

<ng-container
  [ngComponentOutlet]="WIDGET_REGISTRY[widget.type]"
  [ngComponentOutletInputs]="widget.inputs"
/>

Le composant rendu est une boîte noire autonome. C'est le pattern du rendu polymorphe piloté par la donnée : dashboards configurables, feeds hétérogènes, formulaires dynamiques, systèmes de plugins.

Résume la différence en une image : avec ngTemplateOutlet tu prêtes un slot à quelqu'un qui le remplit avec ton matériel (la donnée) ; avec ngComponentOutlet tu postes une adresse et un composant complet vient s'installer.


Trois scénarios, trois choix

Scénario 1 : une row de tableau customisable

Tu construis un DataTable réutilisable dans toute l'app. Chaque écran affiche des colonnes différentes, un style de row différent. Le tableau, lui, gère le tri, la sélection, la pagination.

Choix : ngTemplateOutlet. Le markup de la row appartient à chaque consumer, la logique de tableau appartient à l'hôte. Un composant par style de row serait absurde : tu aurais 40 mini-composants quasi identiques juste pour changer trois <td>. Le template projeté avec un context typé (via une directive ngTemplateContextGuard, cf. l'article dédié) est exactement fait pour ça.

Signe que tu t'es trompé : si tu te retrouves à créer un UserRow, un ProductRow, un OrderRow qui ne font qu'afficher des champs, sans logique propre, tu as pris un composant là où un template suffisait.

Scénario 2 : un dashboard de widgets hétérogènes

La composition vient d'une config serveur. Un graphe, un compteur, une carte météo, chacun avec sa logique, ses appels HTTP, ses services.

Choix : ngComponentOutlet + registry. Chaque widget est une unité autonome avec son comportement : ce n'est pas "du markup à remplir", c'est un composant à part entière. Le registry mappe type -> classe, l'hôte résout et rend. Un @switch à 12 cas serait un annuaire ingérable, et un template projeté ne pourrait pas porter la logique interne de chaque widget.

Signe que tu t'es trompé : si tu passes des dizaines de callbacks en input pour recréer le comportement de chaque widget depuis l'hôte, c'est que la logique voulait vivre dans le composant, pas être pilotée de l'extérieur.

Scénario 3 : un feed avec des items polymorphes qui émettent des événements

Un feed social : posts, sondages, annonces. Chaque type a son rendu, sa logique, et surtout émet des événements (vote, partage, signalement) que le feed doit intercepter pour mettre à jour l'état global.

Choix : ni l'un ni l'autre en direct, ViewContainerRef.createComponent. Dès que le canal remontant (les outputs) est central, ngComponentOutlet te lâche : il câble les inputs, jamais les outputs. createComponent te rend un ComponentRef complet, tu souscris aux outputs de l'instance et tu gardes la référence.

const ref = this.slot().createComponent(PollItem);
ref.setInput('poll', poll);
ref.instance.voted.subscribe((choice) => this.store.registerVote(poll.id, choice));

Signe que tu t'es trompé : si tu inventes un service partagé juste pour faire remonter un événement depuis un composant rendu par ngComponentOutlet, arrête. C'est le symptôme qu'il fallait createComponent dès le départ.


L'arbre de décision

Pose-toi les questions dans cet ordre, tu tombes sur la bonne réponse à tous les coups :

  1. Le rendu doit-il émettre des événements vers l'hôte, exposer une méthode, ou être référencé impérativement ? Oui -> ViewContainerRef.createComponent. Fin.

  2. Le contenu rendu a-t-il sa propre logique, son cycle de vie, des services injectés ? Oui -> ngComponentOutlet. C'est un composant autonome, traite-le comme tel.

  3. Le contenu est-il purement visuel, et son markup doit-il être décidé par celui qui utilise ton composant ? Oui -> ngTemplateOutlet. Tu prêtes un slot, le consumer le remplit avec la donnée que tu lui passes en context.

  4. L'ensemble des variantes est-il connu à l'écriture mais choisi au runtime depuis une donnée ? Oui -> ngComponentOutlet + registry (type -> classe).

Un raccourci mental qui marche presque toujours : si c'est "mon composant, ton apparence" -> template. Si c'est "ta donnée, ton composant" -> componentOutlet. Si c'est "et ça me répond" -> impératif.


Le cas où tu combines les deux

Il existe une zone où les deux se rencontrent : rendre un composant dynamique et lui projeter du contenu que le consumer a écrit. ngComponentOutlet accepte pour ça un [ngComponentOutletContent], qui prend des noeuds projetables (Node[][]).

En pratique, tu récupères ces noeuds depuis un <ng-template> du consumer via createEmbeddedView().rootNodes, et tu les passes au composant dynamique pour qu'ils atterrissent dans ses <ng-content>. C'est puissant mais verbeux, et 90% des besoins n'en ont pas l'usage. Retiens juste que ça existe : le jour où tu dois rendre un composant choisi au runtime tout en laissant le consumer injecter du markup dedans, ce n'est pas un mur, c'est ngComponentOutletContent.

La règle : n'y va que si tu as réellement les deux besoins en même temps (composant dynamique + slot rempli par le consumer). Si tu n'as qu'un des deux, reste sur la directive simple correspondante.


Récap actionnable

  1. ngTemplateOutlet = "mon composant, ton apparence". Le consumer possède le markup, l'hôte passe la donnée via context. Pour les composants génériques réutilisables (DataTable, Select).
  2. ngComponentOutlet = "ta donnée, ton composant". L'hôte choisit une classe depuis la donnée, le composant est autonome. Pour le rendu polymorphe piloté par config (dashboards, feeds, formulaires dynamiques).
  3. createComponent = "et ça me répond". Dès que les outputs, une méthode ou une ref sont centraux, quitte les deux directives pour l'impératif.
  4. Le test qui tranche vite : le contenu a-t-il une logique propre ? Oui -> composant. Non, juste du visuel à customiser -> template.
  5. Ne force jamais une directive hors de son rôle. 40 mini-composants pour styler des rows, ou un service partagé pour récupérer un output d'un ngComponentOutlet : les deux sont le symptôme d'un mauvais choix de départ.
  6. Besoin des deux à la fois (composant dynamique + slot consumer) -> ngComponentOutletContent, mais seulement si tu as vraiment les deux besoins.

Les deux directives ne sont pas interchangeables, elles répondent à deux questions opposées : "qui écrit le markup ?" et "qui possède le comportement ?". Une fois que tu poses ces deux questions avant de coder, tu ne prends plus jamais la mauvaise, et tu arrêtes de te battre contre le framework pour lui faire faire ce qu'une autre directive faisait naturellement.

Si tu veux approfondir Angular moderne avec ce genre de patterns, jette un oeil à EasyAngularKit.

📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

AngularKit

Suite d'outils pour développeurs Angular francophones. Apprends, modernise tes réflexes, audite ta codebase.

Produits

Contact

Légal

© 2026 AngularKit. Tous droits réservés.