Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
Angular

La syntaxe de base d’Angular

Un composant Angular associe une classe TypeScript, un template HTML et des styles, reliés par une syntaxe propre à Angular. Ce chapitre présente l’anatomie d’un composant, puis les quatre liaisons de...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À propos de ce cours

Un composant Angular associe une classe TypeScript, un template HTML et des styles, reliés par une syntaxe propre à Angular. Ce chapitre présente l’anatomie d’un composant, puis les quatre liaisons de données : l’interpolation {{ }} pour afficher, la liaison de propriété [ ] pour régler un attribut, une classe ou un style, la liaison d’événement ( ) pour réagir aux actions, et [(ngModel)] pour synchroniser un formulaire. Vous découvrirez aussi les variables de référence et les blocs de contrôle @if, @for, @switch et @let. Enfin, vous formaterez l’affichage avec les pipes, en français. Cinq activités guidées (carte de profil, compteur, liste de tâches, convertisseur, statut de sessions) mettent toute cette syntaxe en pratique.
  1. Objectifs

    • Connaître l'anatomie d'un composant : classe, template, styles
    • Afficher des données avec l'interpolation {{ }}
    • Lier une propriété HTML avec [ ], une classe ou un style dynamiques
    • Réagir aux actions de l'utilisateur avec la liaison d'événement ( )
    • Utiliser les variables de référence #nom et la liaison bidirectionnelle [(ngModel)]
    • Afficher selon une condition ou répéter avec @if, @for, @switch et @let
    • Formater l'affichage avec les pipes, en français
  2. Présentation

    • Une application Angular est un assemblage de composants. Chaque composant associe :
      • une classe TypeScript, qui contient les données et les méthodes ;
      • un template HTML, qui décrit l'affichage ;
      • des styles CSS, limités à ce composant.
    • Le template est du HTML enrichi d'une syntaxe propre à Angular. Elle sert à faire circuler les informations entre la classe et la page : c'est la liaison de données (data binding). Quand une donnée change, Angular met la page à jour automatiquement.
    • Il existe quatre formes de liaison, à reconnaître à leurs symboles :

Les quatre liaisons de données d'Angular

  • Remarque : les exemples de ce chapitre ont été testés avec Angular 22. Ils utilisent les signals (signal(), computed()) pour les données qui changent : c'est la manière actuelle d'écrire un composant, et elle permet à Angular de savoir exactement ce qu'il doit mettre à jour.
  • L'anatomie d'un composant

    • Créer le projet du chapitre, puis le lancer :
    npx @angular/cli@latest new premiers-pas-angular --defaults --ssr=false --style=css
    cd premiers-pas-angular
    npx ng serve            # puis ouvrir http://localhost:4200
    • Le composant racine se trouve dans src/app/. Voici app.ts, tel que le génère Angular :
    import { Component, signal } from '@angular/core';
    import { RouterOutlet } from '@angular/router';
    
    @Component({
      imports: [RouterOutlet],
      selector: 'app-root',
      styleUrl: './app.css',
      templateUrl: './app.html',
    })
    export class App {
      protected readonly title = signal('premiers-pas-angular');
    }

    L'anatomie d'un composant Angular

    • @Component est le décorateur vu au chapitre précédent : il transforme la classe en composant.
    • selector est le nom de la balise qui affiche le composant : <app-root></app-root> se trouve dans src/index.html.
    • imports liste ce que le template utilise : autres composants, directives et pipes. Ce qui n'y figure pas est inconnu du template.
    • templateUrl / styleUrl désignent les fichiers du template et des styles. Pour un petit composant, on écrit directement template: `…` et styles: `…`: c'est ce que font les exemples de ce chapitre.
    • Les membres sont déclarés protected: ils sont accessibles depuis le template, mais pas depuis le code des autres classes.
    • Pour créer un nouveau composant : npx ng generate component activites/carte-profil (ou ng g c). Angular crée carte-profil.ts, carte-profil.html, carte-profil.css et la classe CarteProfil, de sélecteur app-carte-profil.
  • L'interpolation {{ }}

    • Les doubles accolades affichent la valeur d'une expression de la classe, convertie en texte. On peut y écrire une propriété, une opération, un ternaire, un appel de méthode ou les opérateurs ?. et ??.
    • Un signal se lit en l'appelant: {{ session() }}.
    import { Component, signal } from '@angular/core';
    
    @Component({
      selector: 'app-interpolation',
      template: `
        <h2>{{ titre }}</h2>
        <p>Formateur : {{ formateur.prenom }} {{ formateur.nom.toUpperCase() }}</p>
        <p>Places restantes : {{ places - inscrits }} / {{ places }}</p>
        <p>Statut : {{ inscrits >= places ? 'complet' : 'ouvert' }}</p>
        <p>Prix TTC : {{ calculerTTC(480) }} DT</p>
        <p>Session : {{ session() }}</p>
        <p>Salle : {{ salle ?? 'à définir' }}</p>
      `,
    })
    export class Interpolation {
      protected titre = 'Formation Angular';
      protected formateur = { prenom: 'Ali', nom: 'Ben Salah' };
      protected places = 15;
      protected inscrits = 12;
      protected salle: string | null = null;
      protected session = signal('Novembre 2026');
    
      protected calculerTTC(ht: number): number {
        return ht * 1.19;
      }
    }
    Formation Angular
    Formateur : Ali BEN SALAH
    Places restantes : 3 / 15
    Statut : ouvert
    Prix TTC : 571.1999999999999 DT
    Session : Novembre 2026
    Salle : à définir
    • Le prix affiché 571.1999999999999 montre qu'il ne faut pas afficher un calcul brut : la section « Les pipes » donne la solution.
    • Ce qui est interdit dans une interpolation : les affectations (=, +=, ++), new, les instructions (if, for), les expressions qui ont des effets de bord. Une interpolation lit des données, elle ne les modifie pas.
    • Attention : oublier les parenthèses d'un signal ({{ session }}) n'affiche pas sa valeur. Angular le signale par un avertissement : NG8109: session is a function and should be invoked: session().
  • La liaison de propriété [ ]

    • Les crochets lient une propriété d'un élément HTML à une expression : [src]="logo". La valeur entre guillemets est une expression TypeScript, pas un texte : pour un texte fixe, il faut des apostrophes ([alt]="'Logo'").
    • Pour une chaîne, l'interpolation dans un attribut est équivalente : src="{{ logo }}". Pour un booléen ou un nombre (disabled, value…), utilisez les crochets.
    • Pour un attribut HTML qui n'a pas de propriété correspondante (colspan, aria-*, data-*), on écrit [attr.nom].
    import { Component, signal } from '@angular/core';
    
    @Component({
      selector: 'app-proprietes',
      template: `
        <img [src]="logo" [alt]="'Logo ' + nom" width="64" />
        <img src="{{ logo }}" alt="Logo {{ nom }}" width="64" />
        <button [disabled]="complet()">Réserver</button>
        <input [value]="nom" [placeholder]="aide" />
        <td [attr.colspan]="colonnes">Total</td>
        <button [attr.aria-label]="'Supprimer ' + nom">✕</button>
      `,
    })
    export class Proprietes {
      protected logo = 'favicon.ico';
      protected nom = 'Angular';
      protected aide = 'Votre nom';
      protected colonnes = 3;
      protected complet = signal(false);
    }
    • Classes et styles dynamiques: [class.nom] ajoute une classe CSS si l'expression est vraie ; [style.propriete] règle un style, avec une unité si besoin ([style.width.%], [style.font-size.px]) ; [class] accepte aussi un objet.
    <p [class.alerte]="complet()" [style.color]="complet() ? 'crimson' : 'seagreen'">
      {{ complet() ? 'Complet' : 'Places disponibles' }}
    </p>
    <div class="barre" [style.width.%]="taux"></div>
    <p [class]="{ gras: true, barre: complet() }">Classes multiples</p>
    1. La liaison d'événement ( )

      • Les parenthèses écoutent un événement du DOM (click, input, change, submit, keyup…) et exécutent une instruction, le plus souvent un appel de méthode.
      • $event contient l'objet événement. Sur un <input>, Angular sait que $event.target est un HTMLInputElement: on peut lire directement $event.target.value.
      • Angular propose des filtres de touches: (keyup.enter), (keyup.escape), (keydown.control.s)…
    import { Component, signal } from '@angular/core';
    
    @Component({
      selector: 'app-evenements',
      template: `
        <button (click)="ajouter()">+1</button>
        <button (click)="compteur.set(0)">Remettre à zéro</button>
        <p>Compteur : {{ compteur() }}</p>
    
        <input (input)="prenom.set($event.target.value)" placeholder="Votre prénom" />
        <p>Bonjour {{ prenom() || 'inconnu' }}</p>
    
        <input #champ (keyup.enter)="valider(champ.value); champ.value = ''" placeholder="Ville puis Entrée" />
        <p>Dernière ville : {{ ville() }}</p>
      `,
    })
    export class Evenements {
      protected compteur = signal(0);
      protected prenom = signal('');
      protected ville = signal('—');
    
      protected ajouter(): void {
        this.compteur.update((n) => n + 1);
      }
    
      protected valider(valeur: string): void {
        this.ville.set(valeur.trim());
      }
    }
    • Après trois clics sur +1, la saisie de « Sami » et de « Sousse » suivie d'Entrée, la page affiche Compteur : 3, Bonjour Sami et Dernière ville : Sousse, et le champ de la ville est vidé.
    • Contrairement à l'interpolation, une liaison d'événement peut modifier des données, et enchaîner plusieurs instructions séparées par ;.
    • Pour modifier un signal, on appelle set() ou update(): écrire compteur = 5 est refusé (TS2322: Type 'number' is not assignable to type 'WritableSignal<number>').
  • Les variables de référence #

    • #nom placé sur un élément crée une variable utilisable partout dans le template, qui désigne cet élément. Dans l'exemple précédent, #champ donne accès au champ de saisie : champ.value lit son contenu, champ.value = '' le vide.
    • C'est la manière la plus simple de lire un champ au moment d'un événement, sans stocker chaque frappe dans la classe.
    <input #recherche placeholder="Rechercher" />
    <button (click)="chercher(recherche.value)">OK</button>
    <button (click)="recherche.focus()">Placer le curseur</button>
    1. La liaison bidirectionnelle [( )]

      • La liaison bidirectionnelle combine une liaison de propriété et une liaison d'événement : la donnée s'affiche dans le champ et chaque modification du champ met la donnée à jour. Sa syntaxe [( )] est surnommée la « banane dans une boîte ».
      • Pour les champs de formulaire, on utilise la directive ngModel, qui impose d'importer FormsModule dans le composant. Elle fonctionne directement avec un signal.
    import { Component, signal } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'app-bidirectionnelle',
      imports: [FormsModule],
      template: `
        <input [(ngModel)]="nom" placeholder="Nom" />
        <input type="number" [(ngModel)]="quantite" min="1" />
        <label><input type="checkbox" [(ngModel)]="livraison" /> Livraison</label>
        <p>{{ nom() }} commande {{ quantite() }} article(s) {{ livraison() ? 'avec' : 'sans' }} livraison.</p>
      `,
    })
    export class Bidirectionnelle {
      protected nom = signal('Amira');
      protected quantite = signal(1);
      protected livraison = signal(false);
    }
    Amira commande 1 article(s) sans livraison.
    (après saisie de Karim, 3 et cochage)
    Karim commande 3 article(s) avec livraison.
    • Attention : sans FormsModule dans imports, la compilation échoue : NG8002: Can't bind to 'ngModel' since it isn't a known property of 'input'.
  • Le contrôle de flux : @if, @for, @switch

    • Les blocs qui commencent par @ affichent une partie du template selon une condition, ou la répètent pour chaque élément d'une liste.
    • @if accepte des @else if et un @else. Avec as, on peut nommer la valeur testée.
    • @for répète un bloc ; track est obligatoire: il indique à Angular comment reconnaître chaque élément (en général son id), pour ne mettre à jour que ce qui a changé. @empty s'affiche si la liste est vide.
    • Dans @for, les variables $index, $first, $last, $even, $odd et $count sont disponibles, et renommables avec let.
    • @switch choisit un bloc selon une valeur, avec des @case et un @default.

    Le contrôle de flux dans les templates Angular

    import { Component, computed, signal } from '@angular/core';
    
    interface Formation {
      id: number;
      titre: string;
      niveau: 'debutant' | 'intermediaire' | 'avance';
      places: number;
    }
    
    @Component({
      selector: 'app-controle',
      template: `
        @if (formations().length === 0) {
          <p>Aucune formation pour le moment.</p>
        } @else if (formations().length === 1) {
          <p>Une seule formation disponible.</p>
        } @else {
          <p>{{ formations().length }} formations disponibles.</p>
        }
    
        <ul>
          @for (f of formations(); track f.id; let i = $index, premier = $first, dernier = $last) {
            <li [class.premier]="premier">
              {{ i + 1 }}. {{ f.titre }}
              @switch (f.niveau) {
                @case ('debutant') { <span>🟢 débutant</span> }
                @case ('intermediaire') { <span>🟠 intermédiaire</span> }
                @default { <span>🔴 avancé</span> }
              }
              @if (dernier) { <em>(dernière)</em> }
            </li>
          } @empty {
            <li>La liste est vide.</li>
          }
        </ul>
    
        @let total = totalPlaces();
        <p>Total : {{ total }} place{{ total > 1 ? 's' : '' }}</p>
    
        @if (selection(); as f) {
          <p>Sélection : {{ f.titre }}</p>
        }
      `,
    })
    export class Controle {
      protected formations = signal<Formation[]>([
        { id: 1, titre: 'HTML et CSS', niveau: 'debutant', places: 20 },
        { id: 2, titre: 'Angular', niveau: 'intermediaire', places: 15 },
        { id: 3, titre: 'Flutter', niveau: 'avance', places: 10 },
      ]);
      protected selection = signal<Formation | null>(this.formations()[1] ?? null);
      protected totalPlaces = computed(() => this.formations().reduce((t, f) => t + f.places, 0));
    }
    3 formations disponibles.
    1. HTML et CSS 🟢 débutant
    2. Angular 🟠 intermédiaire
    3. Flutter 🔴 avancé(dernière)
    Total : 45 places
    Sélection : Angular
    • @let déclare une variable locale au template, en lecture seule, pour éviter de répéter une expression : @let total = totalPlaces();.
    • À l'intérieur de @if (selection(); as f), TypeScript sait que f n'est pas null: le rétrécissement de type vu dans les chapitres TypeScript s'applique aussi aux templates.
    • Remarque : dans du code plus ancien, vous rencontrerez *ngIf, *ngFor et [ngSwitch]. Ce sont les anciennes directives de contrôle de flux : les blocs @if, @for et @switch les remplacent, sont plus lisibles et ne demandent aucun import.
  • Les pipes

    • Un pipe transforme une valeur pour l'affichage, sans modifier la donnée : {{ valeur | pipe }}. Il peut recevoir des paramètres après :, et plusieurs pipes peuvent s'enchaîner.
    • Chaque pipe doit être importé dans imports du composant (DatePipe, CurrencyPipe, DecimalPipe… depuis @angular/common). Sinon : NG8004: No pipe found with name 'date'.
    import { Component } from '@angular/core';
    import {
      CurrencyPipe, DatePipe, DecimalPipe, JsonPipe, LowerCasePipe,
      PercentPipe, SlicePipe, TitleCasePipe, UpperCasePipe,
    } from '@angular/common';
    
    @Component({
      selector: 'app-pipes',
      imports: [UpperCasePipe, LowerCasePipe, TitleCasePipe, DatePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, JsonPipe],
      template: `
        <p>{{ nom | uppercase }}</p>
        <p>{{ 'formation angular avancée' | titlecase }}</p>
        <p>{{ debut | date }}</p>
        <p>{{ debut | date: 'fullDate' }}</p>
        <p>{{ debut | date: 'dd/MM/yyyy HH:mm' }}</p>
        <p>{{ prix | currency: 'TND' }}</p>
        <p>{{ prix | currency: 'EUR' }}</p>
        <p>{{ moyenne | number: '1.2-2' }}</p>
        <p>{{ taux | percent }}</p>
        <p>{{ description | slice: 0 : 30 }}…</p>
        <p>{{ debut | date: 'EEEE d MMMM' | uppercase }}</p>
        <pre>{{ stagiaire | json }}</pre>
      `,
    })
    export class Pipes {
      protected nom = 'Amira Ben Ali';
      protected debut = new Date(2026, 10, 3, 9, 30);
      protected prix = 1480.5;
      protected moyenne = 14.6666;
      protected taux = 0.85;
      protected description = 'Apprenez à créer des applications web modernes avec Angular et TypeScript.';
      protected stagiaire = { nom: 'Amira', groupe: 'DEV1' };
    }

    Les pipes d'Angular

    • Le paramètre de number s'écrit 'chiffresEntiersMin.décimalesMin-décimalesMax': '1.2-2' donne toujours exactement deux décimales.
    • Afficher en français: par défaut, Angular formate en anglais (Nov 3, 2026, TND1,480.500). Pour obtenir 3 nov. 2026 et 1 480,500 TND, on enregistre les données de la langue française et on la déclare comme langue de l'application dans src/app/app.config.ts:
    import { ApplicationConfig, LOCALE_ID, provideBrowserGlobalErrorListeners } from '@angular/core';
    import { registerLocaleData } from '@angular/common';
    import localeFr from '@angular/common/locales/fr';
    import { provideRouter } from '@angular/router';
    import { routes } from './app.routes';
    
    registerLocaleData(localeFr);
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideBrowserGlobalErrorListeners(),
        provideRouter(routes),
        { provide: LOCALE_ID, useValue: 'fr' },
      ]
    };
    • Attention : déclarer LOCALE_ID sans appeler registerLocaleData provoque une erreur dans la console du navigateur : NG0701: Missing locale data for the locale "fr".
  • Aide-mémoire

  • Aide-mémoire de la syntaxe des templates Angular

    1. Activité 1 : une carte de profil

      • Dans le projet premiers-pas-angular, générer le composant activites/carte-profil et l'afficher dans app.html (penser à l'ajouter dans imports de App). Configurer la langue française.
      • Dans la classe, déclarer les données suivantes, puis écrire le template avec l'interpolation, des liaisons de propriété ([src], [alt], [href]) et les pipes uppercase, date: 'longDate' et number: '1.2-2'.
    protected stagiaire = {
      prenom: 'Amira',
      nom: 'Ben Ali',
      groupe: 'DEV1',
      photo: 'favicon.ico',
      inscription: new Date(2026, 8, 15),
      moyenne: 15.4567,
      email: 'amira@exemple.tn',
    };
    • Résultat attendu : le lien ouvre la messagerie (mailto:).

    Résultat attendu de l'activité 1 : carte de profil

    1. Activité 2 : un compteur de places

      • Générer le composant activites/compteur-places pour une session de 5 places:
        • un signal reservees et deux computed: complet et taux (pourcentage de remplissage) ;
        • les boutons Réserver et Annuler (liaisons d'événement), désactivés quand la session est complète ou vide ([disabled]) ;
        • une jauge dont la largeur suit [style.width.%] et qui devient rouge quand la session est complète ([class.plein]) ;
        • le message N place(s) disponible(s), ou Session complète en rouge.

    Résultat attendu de l'activité 2 après trois réservations

    • Cliquer cinq fois sur Réserver : le bouton se désactive-t-il ? La jauge change-t-elle de couleur ? Que se passerait-il sans [disabled] si l'on cliquait une sixième fois ?
  • Activité 3 : une liste de tâches

    • Générer le composant activites/liste-taches et déclarer l'interface Tache (id, texte, faite), puis un signal taches (tableau vide au départ). Le template doit :
      • ajouter une tâche quand on appuie sur Entrée dans le champ (#nouvelle et (keyup.enter)), puis vider le champ ; une saisie vide est ignorée ;
      • afficher les tâches avec @for (track t.id), numérotées avec $index, et Aucune tâche : profitez-en ! grâce à @empty;
      • barrer une tâche cochée ([class.faite], (change)) et la supprimer avec le bouton ✕ ;
      • afficher N tâche(s) restante(s) sur M uniquement s'il y a au moins une tâche (@if, computed).
    • Les méthodes ajouter, basculer et supprimer ne modifient jamais le tableau : elles en créent un nouveau avec update, le spread, map et filter (chapitre « Destructuration, spread et rest »).

    Résultat attendu de l'activité 3 : liste de tâches

    • Remplacer track t.id par track $index: la liste fonctionne-t-elle toujours ? Pourquoi t.id reste-t-il le meilleur choix ?
  • Activité 4 : un convertisseur de devises

    • Générer le composant activites/convertisseur, importer FormsModule et CurrencyPipe, et déclarer :
    type Devise = 'EUR' | 'USD' | 'GBP';
    
    private readonly taux: Record<Devise, number> = { EUR: 0.29, USD: 0.32, GBP: 0.25 };
    protected montant = signal(100);
    protected devise = signal<Devise>('EUR');
    protected resultat = computed(() => this.montant() * this.taux[this.devise()]);
    • Lier un champ numérique à montant et une liste déroulante (Euro, Dollar, Livre sterling) à devise avec [(ngModel)], puis afficher le montant en dinars et le résultat avec le pipe currency. Au départ, la page affiche 100,000 TND = 29,00 €; pour 250 en dollars :

    Résultat attendu de l'activité 4 : convertisseur de devises

    • Pourquoi le dinar s'affiche-t-il avec trois décimales et l'euro avec deux, alors que vous n'avez rien précisé ?
  • Activité 5 : le statut des sessions

    • Générer le composant activites/statut-session avec le type Statut = 'planifiee' | 'en-cours' | 'terminee' | 'annulee' et l'interface Session (id, titre, debut: Date, statut, inscrits). Déclarer trois sessions : Angular (3 novembre 2026, planifiée, 9 inscrits), TypeScript (20 octobre, en cours, 12) et HTML et CSS (1er septembre, terminée, 11).
    • Pour chaque session (@for), calculer les places libres sur 12 avec @let, afficher la date avec date: 'd MMM' et le statut avec @switch; seule une session planifiée affiche ses places libres.
    • Un bouton Annuler les sessions planifiées passe leur statut à 'annulee'.

    Résultat attendu de l'activité 5 : statut des sessions

    • Après un clic sur le bouton, la première ligne doit devenir Angular – 3 nov. – Annulée.
    • Remplacer le @default par @case ('annulee'), puis ajouter un statut 'reportee' au type sans le traiter dans le @switch: la compilation signale-t-elle l'oubli ? Qu'affiche la page pour une session reportée ?
    • Faire un commit après chaque activité, puis un push : git commit -m "Syntaxe Angular : activité N".
  • Erreurs fréquentes

  • Message ou symptôme Cause Solution
    NG8001: 'app-carte-profil' is not a known element Composant utilisé dans un template sans être importé L'ajouter dans imports du composant parent
    NG8113: CarteProfil is not used within the template of App (avertissement) Composant importé mais jamais utilisé dans le template Ajouter sa balise <app-carte-profil />, ou le retirer de imports
    NG8002: Can't bind to 'ngModel' since it isn't a known property of 'input'. FormsModule non importé imports: [FormsModule]
    NG8004: No pipe found with name 'uppercase'. Pipe non importé Importer UpperCasePipe (ou le pipe concerné) depuis @angular/common
    TS2339: Property 'titr' does not exist on type 'App'. Faute de frappe dans le template Corriger le nom : le template est vérifié comme du TypeScript
    NG8109: compteur is a function and should be invoked: compteur() (avertissement) Signal lu sans parenthèses Écrire {{ compteur() }}
    TS2322: Type 'number' is not assignable to type 'WritableSignal<number>'. Affectation directe d'un signal : (click)="compteur = 5" compteur.set(5) ou compteur.update(…)
    NG5002: Parser Error: Bindings cannot contain assignments Affectation dans une interpolation ou une liaison de propriété Déplacer la modification dans une méthode appelée par un événement
    NG5002: @for loop must have a "track" expression track oublié @for (x of liste; track x.id)
    NG5002: Unclosed block "if" Accolade fermante oubliée Fermer chaque bloc @if { … }, @for { … }
    TS2339: Property 'value' does not exist on type 'EventTarget'. + TS2531: Object is possibly 'null'. $event.target.value sur un élément qui n'est pas un champ (bouton, div…) Utiliser une variable de référence #champ et champ.value
    NG0701: Missing locale data for the locale "fr". (console) LOCALE_ID déclaré sans registerLocaleData Appeler registerLocaleData(localeFr) dans app.config.ts
    TS2339: Property 'Logo' does not exist on type 'Carte'. pour [alt]="Logo" Entre crochets, la valeur est une expression : Logo est lu comme une propriété de la classe [alt]="'Logo'" ou simplement alt="Logo"
    1. À retenir

      • Un composant = une classe décorée par @Component (selector, imports, template, styles) ; les membres protected sont accessibles au template.
      • {{ expr }} affiche, [prop]="expr" règle une propriété ([attr.], [class.], [style.]), (evenement)="instr" réagit, [(ngModel)] synchronise (avec FormsModule).
      • Un signal se lit avec () et se modifie avec set() / update().
      • #nom désigne un élément du template ; $event est l'événement.
      • @if / @else, @for (avec track et @empty), @switch / @case et @let structurent le template.
      • Les pipes (date, currency, number…) formatent l'affichage ; on les importe, et LOCALE_ID + registerLocaleData les passent en français.
    Accolades pour afficher, crochets pour envoyer, parenthèses pour écouter, banane dans une boîte pour les deux : avec ces quatre symboles et les blocs @, vous savez lire n'importe quel template Angular.