Accueil / Articles / Composants Angular de A à Z : sélecteurs, templates, entrées, sorties

Composants Angular de A à Z : sélecteurs, templates, entrées, sorties

Une présentation pratique des composants Angular indépendants modernes : organisation des fichiers, le décorateur, les sélecteurs, les templates, les styles ciblés, ainsi que le flux de données entre composants parents et enfants grâce à input() et output().

2504 mots

Lorsqu’un projet Angular a été créé et mis en marche, la question suivante est de savoir comment l’écran lui-même est construit. La réponse réside dans les composants : de petites unités autonomes qui chacune gèrent une partie de l’interface. Ce guide explique de quoi est composé un composant, en quoi les composants indépendants diffèrent des anciens codes basés sur NgModule, ainsi que la manière dont un composant parent et un composant enfant échangent des données et des événements à l’aide des API input() et output() actuelles. À la fin, vous devriez être capable de générer un composant, de l’intégrer à une page et d’envoyer des informations dans les deux sens.

Si vous avez encore besoin d’un projet pour expérimenter, le tutoriel sur la création d’un projet Angular 22 moderne décrit l’installation et la structure des dossiers que cet article suppose.

Pourquoi Angular divise l’UI en composants

Imaginez une page web ordinaire : une barre de navigation en haut, un panneau latéral, du contenu de produit au milieu et un pied de page en bas. Vous pourriez placer tout ce markup, ces styles et ce comportement dans un seul fichier énorme, mais il deviendrait rapidement difficile à lire et encore plus difficile à modifier. Angular vous encourage plutôt à diviser la page en parties, chacune ayant une seule fonction.

Angular Application
│
├── Navbar Component
├── Sidebar Component
├── Product Component
├── Login Component
└── Footer Component

Un composant regroupe généralement quatre éléments : une classe TypeScript qui gère l’état et le comportement, un modèle HTML décrivant la structure, des styles pour son apparence, ainsi que des métadonnées indiquant à Angular comment le traiter. Comme chaque élément a une responsabilité bien définie, il est possible d’analyser la barre de navigation sans se soucier du pied de page, de réutiliser la même carte produit en plusieurs endroits, et de tester des parties de l’interface de manière isolée. Un formulaire de connexion, un panneau de profil ou un widget de tableau de bord en sont tous des exemples appropriés.

Les composants autonomes sont la norme

De nombreux tutoriels Angular, en particulier les plus anciens, organisent les composants à l’intérieur d’un NgModule, qui déclare ces composants et indique de quels éléments ils dépendent. Angular moderne a abandonné ce modèle. Les composants sont désormais, par défaut, autonomes, ce qui permet à un composant de fonctionner indépendamment sans être enregistré dans un module. Un composant minimal ressemble à ceci :

import { Component } from '@angular/core';
@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.html',
  styleUrl: './welcome.css'
})
export class WelcomeComponent {
}

On pourrait s’attendre à voir un indicateur marquant le composant comme autonome, comme dans la ligne suivante, mais cela n’est plus requis :

standalone: true

Omettre cette étape a le même effet, car le mode autonome est la configuration par défaut. Lorsqu’un composant autonome a besoin d’un autre composant, d’une directive ou d’un pipe, il indique cette dépendance dans le tableau imports de sa propre configuration @Component. L’avantage réside dans la localisation : on peut ouvrir un seul fichier pour voir exactement de quoi dépend ce composant, au lieu de chercher dans une définition de module ailleurs dans le projet.

Comment un composant est organisé sur disque

Un composant typique se trouve dans son propre dossier, avec trois fichiers côte à côte :

welcome/
│
├── welcome.ts
├── welcome.html
└── welcome.css

Le fichier .ts contient la logique, le fichier .html contient le markup et le fichier .css contient les styles. Voici ce que chacun de ces fichiers pourrait contenir pour un composant de bienvenue simple. Tout d’abord la classe, qui expose une propriété title au template :

import { Component } from '@angular/core';
@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.html',
  styleUrl: './welcome.css'
})
export class WelcomeComponent {
  title = 'Welcome to Angular 22';
}

Le template lit cette propriété entre des accolades doubles :

<h1>{{ title }}</h1>
<p>
  This is my first Angular component.
</p>

Et le fichier de style cible uniquement les éléments présents dans ce template :

h1 {
  color: #dd0031;
}
p {
  font-size: 18px;
}

Le décorateur @Component() sert de lien. Il indique à Angular le template et le fichier de style afin que ces trois fichiers fonctionnent comme une seule unité, comme l’illustre le schéma suivant :

welcome.ts
    │
    ├── Logic
    │
    ├── welcome.html
    │      ↓
    │    UI
    │
    └── welcome.css
           ↓
         Style

Le guide de style d’Angular recommande de placer des fichiers étroitement liés comme ceux-ci dans le même répertoire, ce que propose précisément cette organisation. Lorsqu’une composante s’agrandit, on sait exactement où se trouvent son markup, ses styles et sa logique.

Ce que le décorateur @Component() indique à Angular

Le décorateur ajoute des métadonnées à la classe. Ce sont ces métadonnées qui permettent à Angular de comprendre l’usage de la classe et la manière de la rendre affichable :

@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.html',
  styleUrl: './welcome.css'
})

Trois propriétés assurent ici la majeure partie du travail :

  • selector définit le nom de la balise que vous utilisez pour insérer le composant dans d’autres templates.
  • templateUrl fait référence au fichier HTML contenant le markup du composant.
  • styleUrl fait référence au fichier de style du composant.

Il existe d’autres options, telles que imports pour intégrer d’autres composants ou des fonctionnalités d’Angular, ainsi que des alternatives en ligne pour templateUrl et styleUrl qui seront abordées plus tard dans cet article. Il n’est pas nécessaire d’en apprendre toutes dès le début ; vous les connaîtrez mieux à mesure que vous créerez davantage de composants.

Les sélecteurs transforment une classe en élément personnalisé

Le sélecteur est l’identifiant que Angular cherche à l’intérieur des templates. Dans cette configuration, il est défini sur app-welcome:

@Component({
  selector: 'app-welcome',
  ...
})

Ainsi, la valeur du sélecteur est simplement :

app-welcome

Où que vous souhaitiez que le composant apparaisse, vous écrivez son nom comme s’il s’agissait d’une balise HTML :

<app-welcome></app-welcome>

Lorsque Angular rencontre <app-welcome> lors du rendu d’un template, il crée une instance du composant correspondant et rend son template à l’intérieur de cet élément. L’élément qui correspond au sélecteur est appelé l’élément hôte du composant, et il reste dans le DOM, ce qui est important par la suite lorsque vous souhaitez styliser ou ajouter des comportements au composant dans son ensemble.

Angular CLI génère des sélecteurs avec le préfixe configuré pour votre application, généralement app-, bien que vous puissiez choisir un sélecteur différent lors de la création d’un composant. Le préfixe n’a pas seulement une fonction esthétique : les noms d’éléments personnalisés doivent contenir un trait d’union, et un préfixe spécifique au projet empêche vos composants de entrer en conflit avec les éléments natifs ou avec des balises provenant de bibliothèques tierces.

Les templates relient le markup aux données du composant

Le template définit ce que l’utilisateur voit réellement. Dans son format le plus simple, il s’agit de HTML pur :

<h1>Welcome!</h1>
<p>We are learning Angular components.</p>

Ce qui rend les templates d’Angular plus puissants que l’HTML statique, c’est leur capacité à lire des données et à appeler des comportements définis dans la classe du composant. Supposons que cette classe declare une propriété :

name = 'Mubbassir';

Le template peut alors la afficher grâce à l’interpolation :

<h1>Hello {{ name }}!</h1>

Angular maintient la sortie affichée en synchronisation avec ces données : lorsque la valeur liée change, la page se met à jour sans que vous ayez besoin de modifier manuellement le DOM. Ce lien entre la classe TypeScript et son template est la base sur laquelle repose tout le reste d’Angular, des traitements d’événements aux formulaires.

Les styles des composants restent par défaut limités à leur propre scope

Chaque composant peut disposer de ses propres styles :

.card {
  padding: 20px;
  border-radius: 10px;
}
h2 {
  color: #dd0031;
}

Par défaut, Angular applique une encapsulation de vue émulée à ces règles. Lors de la compilation, il réécrit les sélecteurs et ajoute des attributs générés aux éléments du composant, de sorte qu’une règle telle que h2 { ... } ne correspond qu’aux éléments h2 du template de ce composant et non à tous les titres de l’application. Dans une application comptant des centaines de composants, cette protection empêche qu’une modification de style en un endroit ne vienne altérer discrètement l’apparence d’un autre composant.

L’encapsulation n’exclut pas l’utilisation de styles partagés. Les règles qui doivent s’appliquer partout, telles que la typographie, les réinitialisations ou les outils de mise en page, doivent figurer dans le feuille de style globale de l’application, tandis que les fichiers des composants contiennent les styles spécifiques à ce dernier.

Générer un composant avec Angular CLI

Créer les fichiers manuellement fonctionne, mais la CLI le fait plus rapidement et avec une nomenclature cohérente. Depuis la racine du projet, exécutez :

ng generate component user-card

ou la forme abrégée :

ng g c user-card

Le générateur crée un dossier comme celui-ci (le = inutile après le nom du fichier de spécification est une faute de frappe dans l’exemple, et non faisant partie du nom) :

user-card/
├── user-card.ts
├── user-card.html
├── user-card.css
└── user-card.spec.ts=

Le nouveau style de nommage des fichiers est plus court que la convention ancienne user-card.component.ts. La création d’un fichier de test .spec.ts dépend de la configuration de test du projet. Les versions récentes de la CLI, suivant le guide de style mis à jour, ont également tendance à générer des noms de classe sans le suffixe Component (par exemple UserCard) ; les exemples ci-dessous conservent ce suffixe, et n’importe quel nom fonctionne tant que les imports correspondent.

Comment les composants parents et enfants communiquent-ils entre eux

Les écrans réels sont des arbres de composants qui doivent partager des informations. Prenons un composant parent qui connaît les détails d’un utilisateur et un composant enfant dont la fonction est d’afficher cet utilisateur. Le parent a besoin d’un moyen de transmettre des données vers le bas, tandis que l’enfant a besoin d’un moyen de signaler ce qui se passe, comme un clic sur un bouton.

Angular gère la direction vers le bas avec les inputs et la direction vers le haut avec les outputs :

Parent Component
      │
      │  Data
      │
      ▼
Child Component
      │
      │  Event
      │
      ▼
Parent Component

Pour du nouveau code, Angular recommande la fonction input() basée sur les signaux ainsi que la fonction output(). Les API basées sur des décorateurs @Input() et @Output() restent pleinement prises en charge, vous les verrez donc encore dans des bases de code existantes.

Transfert de données vers le bas avec input()

Commencez par la classe du composant enfant :

import { Component, input } from '@angular/core';
@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.html',
  styleUrl: './user-card.css'
})
export class UserCardComponent {
  name = input.required<string>();
}

La ligne importante est la déclaration input :

name = input.required<string>();

Cela indique que UserCardComponent attend une chaîne de caractères name de la part de celui qui l’utilise. L’utilisation de input.required() rend cette exigence stricte : si un parent oublie de lier la propriété name, Angular signale une erreur au lieu d’afficher silencieusement une valeur vide. Pour les champs facultatifs, input() suffit et permet d’utiliser une valeur par défaut.

Puisque name est un signal plutôt qu’une simple propriété, on le lit en l’appelant. Dans la classe, vous écrivez :

this.name()

et dans le template :

{{ name() }}

Oublier les parenthèses est une erreur fréquente au début ; sans elles, le template affiche la fonction signal elle-même plutôt que sa valeur. Le template du composant enfant utilise l’entrée de cette manière :

<div class="card">
  <h2>{{ name() }}</h2>
  <p>Welcome to Angular!</p>
</div>

Du côté du parent, on lie une valeur à l’entrée lors de l’inclusion du composant enfant :

<app-user-card [name]="userName" />

Si la classe parente contient :

userName = 'Yuvaraj';

l’enfant reçoit cette chaîne de caractères et la rend :

Yuvaraj

Les crochets carrés sont ce qui fait de cela une liaison de propriété :

[name]="userName"

Ils indiquent à Angular d’évaluer userName comme une expression dans la classe parente et de transmettre le résultat à l’entrée name de l’enfant. Sans ces crochets, Angular transmettrait plutôt le texte littéral userName. Comme l’entrée est un signal, l’enfant se rérend automatiquement chaque fois que la valeur de la classe parente change.

Envoi d’événements vers le haut avec output()

Donnons maintenant à l’enfant un bouton, afin que la classe parente puisse savoir quand un utilisateur est sélectionné. L’enfant déclare une sortie et une méthode qui émet par son intermédiaire :

import { Component, input, output } from '@angular/core';
@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.html',
  styleUrl: './user-card.css'
})
export class UserCardComponent {
  name = input.required<string>();
  selected = output<string>();
  selectUser() {
    this.selected.emit(this.name());
  }
}

La déclaration de sortie définit un événement personnalisé qui transporte une chaîne de caractères :

selected = output<string>();

L’appel emit déclenche cet événement et transmet le nom actuel en tant que charge utile :

this.selected.emit(this.name());

Le template enfant appelle selectUser() lorsque le bouton est cliqué. Notez la syntaxe (click), qui correspond au liage d’événements d’Angular :

<div class="card">
 <h2>{{ name() }}</h2>
  <button (click)="selectUser()">
    Select User
  </button>
</div>

Le parent écoute l’événement personnalisé en utilisant la même syntaxe entre parenthèses, cette fois avec le nom de la sortie :

<app-user-card
  [name]="userName"
  (selected)="userSelected($event)"
/>

Les parenthèses rondes signifient « écouter cet événement », et $event contient la valeur émise par l’enfant, ici le nom de l’utilisateur. Le parent gère cette valeur dans une méthode ordinaire :

userSelected(name: string) {
  console.log('Selected user:', name);
}

Cette répartition des tâches permet à l’enfant d’être réutilisable. Il ne sait pas ni ne se soucie de ce que fait le parent avec une sélection ; il se contente d’annoncer qu’une sélection a eu lieu. C’est le parent qui décide s’il faut en enregistrer les données, naviguer ou mettre à jour son propre état.

Insérer un composant sur la page

L’exemple final relie tous les éléments entre eux. Cette version du composant de bienvenue utilise des template et styles en ligne dans le décorateur au lieu de fichiers séparés, ce qui est pratique pour des composants très petits :

import { Component } from '@angular/core';

@Component({
  selector: 'app-welcome',
  template: `
    <div class="welcome-card">
      <h2>Welcome Component</h2>
      <p>This component was created using the <strong>app-welcome</strong> selector.</p>
    </div>
  `,
  styles: `
    .welcome-card {
      padding: 25px;
      margin-top: 20px;
      border-radius: 12px;
      background: #f5f5f5;
      text-align: center;
      font-family: Arial, sans-serif;
    }
    h2 {
      color: #dd0031;
    }
    p {
      font-size: 18px;
    }
  `
})
export class WelcomeComponent {}

Le composant racine importe WelcomeComponent dans son tableau imports, ce qui permet à l’élément sélecteur app-welcome d’être utilisé dans son template. Oublier cette importation est l’une des causes les plus fréquentes d’erreur « n’est pas un élément connu » dans les applications autonomes :

import { Component } from '@angular/core';
import { WelcomeComponent } from './welcome';

@Component({
  selector: 'app-root',
  imports: [WelcomeComponent],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App {}

Finalement, le template racine insère le composant en utilisant son élément sélecteur :

<div class="container">
<h1>Angular Components</h1>
  <p>
    Below is our custom Angular component:
  </p>
  <app-welcome></app-welcome>
</div>

Les templates en ligne conservent tout dans un seul fichier, mais ils deviennent gênants lorsque le markup dépasse quelques lignes. Une règle raisonnable consiste à commencer avec des éléments en ligne pour de petites parties présentatives et à passer à des fichiers séparés .html et .css dès que le template nécessite une véritable structure.

Points clés

  • Un composant combine une classe TypeScript, un template et des styles optionnels, reliés entre eux par le décorateur @Component().
  • Le mode standalone est la norme dans Angular moderne ; les dépendances doivent être déclarées dans l’array imports de chaque composant plutôt que dans un NgModule.
  • Le sélecteur est la balise personnalisée utilisée pour placer un composant, et l’élément correspondant en devient l’hôte.
  • Les templates se lient aux données de la classe, et Angular met à jour l’affichage lorsque ces données changent.
  • Les styles des composants sont délimités grâce à une encapsulation simulée, tandis que les règles applicables à l’ensemble de l’application se trouvent dans le feuille de style globale.
  • Utilisez input() (ou input.required()) pour transmettre des données vers le bas et output() pour envoyer des événements vers le haut ; lisez les entrées en les appelant comme des signaux.
  • Vu de cette manière, une application Angular est un arbre de composants, chacun étant responsable de sa propre partie de l’interface et communiquant par des entrées et sorties explicites. C’est cette structure qui permet aux grandes applications Angular de rester compréhensibles à mesure qu’elles grandissent.