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

Les directives ngClass et ngStyle

Cette leçon présente les directives NgClass et NgStyle : chaîne, tableau, Set ou objet de classes, objet de styles avec suffixes d’unité et valeur null. Elle les compare aux liaisons [class], [class.x...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À propos de ce cours

Cette leçon présente les directives NgClass et NgStyle : chaîne, tableau, Set ou objet de classes, objet de styles avec suffixes d’unité et valeur null. Elle les compare aux liaisons [class], [class.x], [style] et [style.x.unité], qui ne demandent aucun import. Elle montre trois différences silencieuses entre les deux écritures, la combinaison de plusieurs sources et un thème piloté par des variables CSS. Elle couvre enfin les migrations ngclass-to-class et ngstyle-to-style, sept activités corrigées et les erreurs fréquentes.
  1. Objectifs

    • Ajouter et retirer des classes CSS avec [ngClass]: chaîne, tableau, Set et objet
    • Appliquer des styles calculés avec [ngStyle], avec ou sans unité
    • Obtenir les mêmes résultats avec les liaisons [class], [class.nom], [style] et [style.propriété.unité]
    • Connaître les différences silencieuses entre les directives et les liaisons
    • Piloter un thème avec des variables CSS liées sur l'hôte
    • Migrer un projet avec les commandes officielles
  1. Présentation

    • Une interface change d'apparence selon son état : un bouton actif, une note en échec, une barre de progression, un thème sombre… Angular propose deux outils pour cela : les directives d'attribut NgClass et NgStyle, présentes depuis la première version, et les liaisons [class] et [style], intégrées au langage des templates.
    • Contrairement à NgIf ou NgFor, NgClass et NgStyle ne sont pas dépréciées en Angular 22. Mais la documentation recommande les liaisons pour les cas simples, car elles ne demandent aucun import, et Angular fournit des commandes pour migrer. Vous rencontrerez donc les deux écritures : il faut savoir lire l'une et choisir l'autre en connaissance de cause.
  • Pour suivre ce chapitre, continuez dans le projet premiers-pas-angular et créez un dossier src/app/apparence/. Tous les exemples ont été compilés et testés avec Angular 22.2 (composants OnPush par défaut, sans Zone.js).
  1. La directive ngClass

    • [ngClass] ajoute des classes à l'élément, sans toucher à ses classes statiques (class="boite"). Elle accepte :
      • une chaîne: les classes séparées par des espaces ('arrondi ombre') ;
      • un tableau ou un Set de noms de classes ;
      • un objet { classe: condition }: la classe est présente quand la condition est vraie. Une clé peut contenir plusieurs classes ('gras souligne').
    • La directive s'importe depuis @angular/common:
import { Component, signal } from '@angular/core';
import { NgClass } from '@angular/common';

// [ngClass] accepte une chaîne, un tableau, un Set ou un objet { classe: condition }
@Component({
  selector: 'app-ngclass',
  imports: [NgClass],
  template: `
    <button (click)="actif.set(!actif())">Activer / désactiver</button>
    <button (click)="note.set(note() >= 10 ? 7 : 15)">Note : {{ note() }}</button>
    <p class="boite" [ngClass]="'arrondi ombre'">Chaîne : « arrondi ombre »</p>
    <p class="boite" [ngClass]="['arrondi', actif() ? 'actif' : 'inactif']">Tableau</p>
    <p class="boite" [ngClass]="{ actif: actif(), alerte: note() < 10, 'gras souligne': note() >= 10 }">
      Objet : actif, alerte, « gras souligne »
    </p>
  `,
  styles: `
    .boite { padding: 6px 10px; border: 1px solid #cbd5e1; margin: 6px 0; }
    .arrondi { border-radius: 12px; } .ombre { box-shadow: 0 3px 8px #0003; }
    .actif { border-color: #3178c6; background: #e8f1fb; } .inactif { color: #94a3b8; }
    .alerte { background: #fde8e8; color: #b91c1c; } .gras { font-weight: 700; } .souligne { text-decoration: underline; }
  `,
})
export class NgClassExemple {
  protected readonly actif = signal(true);
  protected readonly note = signal(15);
}

Résultat de ngClass : chaîne, tableau et objet

  • Cliquez sur les deux boutons : la deuxième carte passe de actif à inactif, la troisième perd gras souligne et prend alerte.

Les quatre formes de ngClass et les classes obtenues

  • Sans l'import de NgClass, la compilation échoue :

Erreur NG8002 : Can't bind to 'ngClass' since it isn't a known property of 'p'

  1. La directive ngStyle

    • [ngStyle] reçoit un objet { propriété: valeur }. Un suffixe d'unité dans la clé ('font-size.px', 'width.%', 'margin.em') évite de concaténer l'unité à la main.
    • Une valeur null ou undefined retire la propriété :
import { Component, signal } from '@angular/core';
import { NgStyle } from '@angular/common';

// [ngStyle] : un objet { propriété: valeur } ; un suffixe .px, .%, .em… ajoute l'unité ; null retire le style
@Component({
  selector: 'app-ngstyle',
  imports: [NgStyle],
  template: `
    <label>Taille : <input type="range" min="12" max="40" [value]="taille()" (input)="taille.set(+$any($event.target).value)" /> {{ taille() }} px</label>
    <label>Couleur : <input type="color" [value]="couleur()" (input)="couleur.set($any($event.target).value)" /></label>
    <label><input type="checkbox" [checked]="encadre()" (change)="encadre.set(!encadre())" /> encadré</label>
    <p [ngStyle]="{
      'font-size.px': taille(),
      color: couleur(),
      'width.%': 80,
      border: encadre() ? '2px dashed ' + couleur() : null
    }">Angular 22</p>
  `,
  styles: `label { margin-right: 12px; } p { margin: 8px 0; padding: 4px 8px; font-weight: 700; }`,
})
export class NgStyleExemple {
  protected readonly taille = signal(24);
  protected readonly couleur = signal('#dd0031');
  protected readonly encadre = signal(true);
}

Résultat de ngStyle : taille, couleur et bordure réglables

  • Décochez « encadré » : l'attribut style devient font-size: 32px; color: rgb(221, 0, 49); width: 80%;, sans border.

ngStyle : propriétés, suffixes d'unité et valeur null

  1. Les liaisons [class] et [style]

    • Les mêmes résultats s'obtiennent avec les liaisons de classe et de style, déjà vues dans la leçon sur le property binding, sans aucun import:
      • [class.actif]="condition": une classe ;
      • [class]="…": plusieurs classes, avec un objet, un tableau ou une chaîne ;
      • [style.color], [style.width.px], [style.width.%]: une propriété, l'unité dans le nom de la liaison ;
      • [style]="{ … }": plusieurs propriétés, chaque valeur avec son unité.
    • Un computed() qui renvoie l'objet de classes ou de styles garde le template court et se teste facilement :
import { Component, computed, signal } from '@angular/core';

// Les mêmes résultats avec les liaisons [class] et [style] : aucun import
@Component({
  selector: 'app-liaisons',
  template: `
    <button (click)="actif.set(!actif())">Activer / désactiver</button>
    <button (click)="taille.set(taille() === 24 ? 16 : 24)">Taille : {{ taille() }} px</button>
    <!-- une seule classe -->
    <p class="boite" [class.actif]="actif()">[class.actif]</p>
    <!-- plusieurs classes : objet, tableau ou chaîne -->
    <p class="boite" [class]="{ actif: actif(), arrondi: true }">[class] avec un objet</p>
    <p class="boite" [class]="['arrondi', actif() ? 'actif' : 'inactif']">[class] avec un tableau</p>
    <p class="boite" [class]="classes()">[class] avec un computed</p>
    <!-- un style, avec son unité -->
    <p [style.font-size.px]="taille()" [style.color]="actif() ? '#3178c6' : null">[style.font-size.px]</p>
    <!-- plusieurs styles : objet (sans suffixe d'unité) -->
    <p [style]="{ 'font-size': taille() + 'px', 'letter-spacing': '2px', color: '#16a34a' }">[style] avec un objet</p>
  `,
  styles: `
    .boite { padding: 6px 10px; border: 1px solid #cbd5e1; margin: 6px 0; }
    .arrondi { border-radius: 12px; } .actif { border-color: #3178c6; background: #e8f1fb; }
    .inactif { color: #94a3b8; } .alerte { background: #fde8e8; } p { margin: 6px 0; }
  `,
})
export class Liaisons {
  protected readonly actif = signal(true);
  protected readonly taille = signal(24);
  protected readonly classes = computed(() => ({ actif: this.actif(), arrondi: true, alerte: !this.actif() }));
}

Résultat des liaisons [class] et [style]

Correspondance entre ngClass et la liaison [class]

Correspondance entre ngStyle et la liaison [style]

  1. Trois différences silencieuses

    • Les directives et les liaisons ne se comportent pas exactement de la même façon. Ces différences ne provoquent aucune erreur, ni à la compilation ni dans la console : le style n'est simplement pas appliqué. L'exemple suivant les met côte à côte et affiche les classes et la largeur réellement obtenues :
import { Component, signal } from '@angular/core';
import { NgClass, NgStyle } from '@angular/common';

// Trois différences entre les directives et les liaisons, sans aucun message d'erreur
@Component({
  selector: 'app-pieges',
  imports: [NgClass, NgStyle],
  template: `
    <table>
      <tr><th></th><th>[ngClass] / [ngStyle]</th><th>[class] / [style]</th></tr>
      <tr><td>Clé « gras souligne »</td>
        <td><span #a [ngClass]="{ 'gras souligne': true }">texte</span> <code>{{ a.className || '∅' }}</code></td>
        <td><span #b [class]="{ 'gras souligne': true }">texte</span> <code>{{ b.className || '∅' }}</code></td></tr>
      <tr><td>Clé « width.px »</td>
        <td><span #c class="barre" [ngStyle]="{ 'width.px': 90 }"></span> <code>{{ c.style.width || '∅' }}</code></td>
        <td><span #d class="barre" [style]="{ 'width.px': 90 }"></span> <code>{{ d.style.width || '∅' }}</code></td></tr>
      <tr><td>Objet modifié sur place</td>
        <td><span #e [ngClass]="etats">texte</span> <code>{{ e.className || '∅' }}</code></td>
        <td><span #f [class]="etats">texte</span> <code>{{ f.className || '∅' }}</code></td></tr>
    </table>
    <button (click)="muter()">etats['alerte'] = true</button>
    <button (click)="remplacer()">etats = nouvel objet</button>
    <small>(tours d'affichage : {{ tours() }})</small>
  `,
  styles: `
    table { border-collapse: collapse; } td, th { border: 1px solid #e2e8f0; padding: 4px 8px; text-align: left; }
    .gras { font-weight: 700; } .souligne { text-decoration: underline; } .alerte { color: #dc2626; }
    .barre { display: inline-block; height: 10px; background: #3178c6; vertical-align: middle; min-width: 2px; }
    code { color: #64748b; font-size: 12px; margin-left: 6px; }
  `,
})
export class Pieges {
  protected etats: Record<string, boolean> = { gras: true, alerte: false };
  protected readonly tours = signal(0);
  protected muter() {
    this.etats['alerte'] = true; // même objet : seul NgClass (ngDoCheck) voit le changement
    this.tours.update((n) => n + 1);
  }
  protected remplacer() {
    this.etats = { ...this.etats }; // nouvel objet : la liaison [class] le voit aussi
    this.tours.update((n) => n + 1);
  }
}

Les trois différences entre ngClass/ngStyle et [class]/[style]

  • Clé à plusieurs classes: [ngClass] ajoute gras souligne, [class] n'ajoute rien. Avec la liaison, écrivez une clé par classe.
  • Suffixe d'unité: [ngStyle] donne width: 90px, [style] ignore la clé 'width.px'. Avec la liaison, écrivez [style.width.px], ou { width: '90px' }.
  • Objet modifié sur place: après etats['alerte'] = true, NgClass voit le changement (elle compare le contenu à chaque ngDoCheck), la liaison [class] non : elle ne réagit qu'à un nouvel objet. Cliquez sur « etats = nouvel objet » : les deux colonnes affichent alors gras alerte.
  • Avec les signals, on ne modifie jamais un objet sur place : set(), update() et computed() produisent toujours une nouvelle valeur, et cette troisième différence disparaît.

Trois différences silencieuses entre directives et liaisons

  • Autre piège : [style.width]="200" (un nombre sans unité) est ignoré par le navigateur. Écrivez [style.width.px]="200".
  1. Combiner plusieurs sources

    • Les classes s'additionnent: classe statique, [class], [class.x], [ngClass] et les liaisons d'hôte (host: { '[class.mode-urgent]': … }), qui s'appliquent à la balise du composant lui-même.
    • Pour une même propriété de style, deux sources se concurrencent. Ici [ngStyle] l'emporte sur [style.color], quel que soit l'ordre des attributs : ne donnez qu'une source à chaque propriété.
import { Component, signal } from '@angular/core';
import { NgStyle } from '@angular/common';

// Classe statique, [class], [class.x] et liaisons d'hôte se combinent ; [ngStyle] et [style.x] se concurrencent
@Component({
  selector: 'app-ensemble',
  imports: [NgStyle],
  template: `
    <button (click)="urgent.set(!urgent())">Urgent : {{ urgent() ? 'oui' : 'non' }}</button>
    <p #p class="boite" [class]="{ arrondi: true }" [class.urgent]="urgent()">Classes combinées</p>
    <code>class="{{ p.className }}"</code>
    <p #q [style.color]="'blue'" [ngStyle]="{ color: 'red' }">[style.color]="blue" + [ngStyle] color: red</p>
    <code>style="{{ q.getAttribute('style') }}"</code>
  `,
  host: { '[class.mode-urgent]': 'urgent()' },
  styles: `
    :host { display: block; padding: 4px; } :host(.mode-urgent) { background: #fff7ed; }
    .boite { padding: 6px 10px; border: 1px solid #cbd5e1; margin: 6px 0; } .arrondi { border-radius: 12px; }
    .urgent { border-color: #ea580c; font-weight: 700; } code { color: #64748b; font-size: 12px; }
  `,
})
export class Ensemble {
  protected readonly urgent = signal(true);
}

Classes combinées et conflit entre [style.color] et ngStyle

Ce qui s'additionne et ce qui se concurrence

  1. Des variables CSS pour le thème

    • Plutôt que de lier dix styles, on lie une variable CSS sur l'hôte du composant ('[style.--accent]') ; la feuille de styles l'utilise partout avec var(--accent), et tous les éléments enfants en héritent :
import { Component, signal } from '@angular/core';

// Une variable CSS liée par [style.--nom] : tout le composant suit la couleur choisie
@Component({
  selector: 'app-variables-css',
  template: `
    @for (c of couleurs; track c) {
      <button class="pastille" [style.background]="c" [class.choisie]="c === accent()" (click)="accent.set(c)" [attr.aria-label]="c"></button>
    }
    <div class="boite">
      <h4>Cours Angular</h4>
      <p>Les couleurs de cette carte viennent toutes de <code>--accent</code>.</p>
      <button class="principal">S'inscrire</button>
    </div>
  `,
  host: { '[style.--accent]': 'accent()' },
  styles: `
    .pastille { width: 26px; height: 26px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px #cbd5e1; margin-right: 6px; cursor: pointer; }
    .choisie { box-shadow: 0 0 0 3px var(--accent); }
    .boite { margin-top: 10px; border: 2px solid var(--accent); border-radius: 12px; padding: 8px 14px; }
    h4 { margin: 4px 0; color: var(--accent); }
    .principal { background: var(--accent); color: #fff; border: 0; border-radius: 8px; padding: 6px 14px; }
  `,
})
export class VariablesCss {
  protected readonly couleurs = ['#dd0031', '#3178c6', '#16a34a', '#9333ea'];
  protected readonly accent = signal('#3178c6');
}

Une variable CSS --accent choisie par un clic

Piloter le thème avec des variables CSS

  1. Migrer vers les liaisons

    • Angular fournit deux commandes de migration. Elles ne convertissent que les cas sans risque (un objet littéral sans clé à plusieurs classes, un objet de styles) et laissent le reste intact. Voici un composant avant migration :
import { Component, signal } from '@angular/core';
import { NgClass, NgStyle } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [NgClass, NgStyle],
  template: `
    <p [ngClass]="{ actif: actif(), alerte: note() < 10 }">Objet</p>
    <p [ngClass]="{ 'gras souligne': actif() }">Clé à deux classes</p>
    <p [ngClass]="['carte', theme()]">Tableau</p>
    <p [ngClass]="classes">Variable</p>
    <p [ngStyle]="{ color: couleur(), 'font-weight': 'bold' }">Style</p>
    <p [ngStyle]="{ 'width.px': largeur() }">Unité</p>
  `,
})
export class App {
  actif = signal(true);
  note = signal(8);
  theme = signal('sombre');
  couleur = signal('teal');
  largeur = signal(200);
  classes = { a: true };
}

Migration ngclass-to-class, avec et sans l'option des clés multiples

Migration ngstyle-to-style

  • Le résultat réel après les deux commandes :
import { Component, signal } from '@angular/core';
import { NgClass } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [NgClass],
  template: `
    <p [class]="{ actif: actif(), alerte: note() < 10 }">Objet</p>
    <p [ngClass]="{ 'gras souligne': actif() }">Clé à deux classes</p>
    <p [ngClass]="['carte', theme()]">Tableau</p>
    <p [ngClass]="classes">Variable</p>
    <p [style]="{ color: couleur(), 'font-weight': 'bold' }">Style</p>
    <p [style.width.px]="largeur()">Unité</p>
  `,
})
export class App {
  actif = signal(true);
  note = signal(8);
  theme = signal('sombre');
  couleur = signal('teal');
  largeur = signal(200);
  classes = { a: true };
}
  • Les [ngStyle] sont tous convertis : l'objet devient [style], la clé à suffixe devient [style.width.px], et NgStyle disparaît des imports.
  • Seul le premier [ngClass] est converti. La clé 'gras souligne' n'est migrée qu'avec l'option --migrate-space-separated-key, qui produit [class.gras]="actif()" [class.souligne]="actif()". Le tableau et la variable restent : c'est à vous de les convertir ([class] accepte aussi un tableau), puis de retirer NgClass des imports.

Ce que les migrations convertissent ou laissent

  1. Quelle écriture choisir ?

    • Préférez une classe CSS à un style en ligne : la mise en forme reste dans la feuille de styles. Réservez [style] aux valeurs réellement calculées (une largeur, une couleur choisie par l'utilisateur).

Quelle écriture choisir pour les classes et les styles

Critère [ngClass] / [ngStyle] [class] / [style]
Import NgClass, NgStyle Aucun
Statut en Angular 22 Non dépréciées, migration proposée Écriture recommandée
Clé à plusieurs classes Oui ('a b': cond) Non : une clé par classe
Suffixe d'unité Dans la clé de l'objet ('width.px') Dans le nom de la liaison ([style.width.px])
Objet modifié sur place Détecté (ngDoCheck) Non détecté : nouvel objet nécessaire
Coût Comparaison du contenu à chaque vérification Comparaison de la référence seulement

Aide-mémoire : classes et styles dynamiques

  1. Activité 1 : une barre de progression

    • Créer le composant Progression avec deux entrées : libelle et valeur (obligatoire). La valeur est arrondie et bornée entre 0 et 100 dans un computed.
    • La largeur de la barre est liée avec [style.width.%], et sa classe vaut faible (moins de 30), moyen (moins de 70) ou bon. Afficher trois compétences et un bouton « +10 % Angular ».

Résultat attendu de l'activité 1 : trois barres de progression

  • Vérifier : Angular passe de rouge à orange à 30 %, et une valeur de 130 donne une barre pleine à 100 %.
  • Pourquoi utiliser [style.width.%] plutôt qu'une classe par largeur ?
  1. Activité 2 : des badges de statut

    • Créer le composant Badge (entrées statut et nouveau). Le statut vaut 'brouillon' | 'publie' | 'archive' | 'refuse'.
    • Les classes sont posées sur l'hôte: une classe statique badge et une liaison '[class]': 'classes()', où classes est un computed qui renvoie { [statut]: true, nouveau }. Le style de chaque statut est défini avec :host(.publie)…

Résultat attendu de l'activité 2 : quatre badges de statut

  • Vérifier : l'hôte du premier badge porte class="badge nouveau publie".
  • La classe statique badge disparaît-elle quand [class] change ? Vérifiez dans les outils du navigateur.
  1. Activité 3 : réserver des places de cinéma

    • Afficher 18 sièges (rangées A à C, numéros 1 à 6) avec @for. Cinq sont occupés : classe occupe et bouton désactivé.
    • Un clic sur un siège libre le choisit ou le libère : les sièges choisis sont dans un signal Set<string>, et le siège porte [class.choisi]. Afficher le nombre de places et le total (8 DT la place) ; le total est grisé quand aucune place n'est choisie.

Résultat attendu de l'activité 3 : deux places choisies

  • Vérifier : B3 et B4 choisis donnent « 2 place(s) · 16 DT » ; un clic sur A3 (occupé) ne fait rien.
  • Pourquoi basculer() crée-t-il un nouveau Set au lieu d'appeler add() sur l'ensemble existant ?
  1. Activité 4 : un thème clair et sombre

    • Créer le composant ThemeCarte: un signal theme ('clair' | 'sombre') et deux palettes de variables CSS (--fond, --texte, --accent).
    • Lier la palette choisie sur l'hôte avec '[style]': 'variables()' (un computed), et ajouter la classe sombre. Toutes les règles CSS du composant utilisent var(--…).

Résultat attendu de l'activité 4 : le mode sombre

  • Vérifier : en mode sombre, l'hôte porte style="--accent: #38bdf8; --fond: #0f172a; --texte: #e2e8f0;".
  • Combien de liaisons faudrait-il sans variables CSS ? Et pour ajouter un troisième thème ?
  1. Activité 5 : migrer un tableau de notes

    • Réécrire ce template avec [class.x] et [style.width.px], sans NgClass ni NgStyle. Comparer ensuite avec le résultat des deux commandes de migration :
@for (n of notes(); track n.matiere; let pair = $even) {
  <tr [ngClass]="{ zebre: pair, echec: n.valeur < 10, excellent: n.valeur >= 16 }">
    <td>{{ n.matiere }}</td>
    <td><span class="jauge" [ngStyle]="{ 'width.px': n.valeur * 6 }"></span> {{ n.valeur }}</td>
  </tr>
}

Résultat attendu de l'activité 5 : le tableau de notes migré

  • Vérifier : l'affichage est identique avant et après, et le tableau imports est vide.
  • Que produit ngclass-to-class sur ce [ngClass]: trois [class.x] ou un [class] avec objet ?
  1. Activité 6 : un aperçu de texte

    • Trois cases à cocher (gras, italique, souligné), un curseur de taille (12 à 36), un sélecteur de couleur et une liste d'alignement pilotent des signals.
    • Un computed classes renvoie l'objet de classes, un autre styles renvoie { 'font-size', color, 'text-align' }. Le paragraphe d'aperçu utilise [class]="classes()" et [style]="styles()", et une ligne affiche les classes actives.

Résultat attendu de l'activité 6 : texte gras, italique, souligné et centré

  • Vérifier : en cochant « Italique » et en centrant, l'aperçu porte class="apercu gras souligne italique" et text-align: center.
  • Pourquoi la valeur de font-size doit-elle contenir px dans styles()?
  1. Activité 7 : tester classes et styles

    • Écrire apparence.spec.ts:
      • Progression: avec setInput('valeur', 25), la largeur vaut 25% et la classe faible; avec 130, 100% et bon;
      • Sieges: deux clics affichent « 2 place(s) · 16 DT », un troisième libère le siège, un siège occupé est désactivé ;
      • Apercu: après modification des signals, classList et style du paragraphe sont corrects.
$ ng test --include src/app/act-apparence/apparence.spec.ts
 ✓ Activités : classes et styles > Progression borne la valeur et choisit la classe du niveau
 ✓ Activités : classes et styles > Sieges : un clic choisit ou libère un siège, un siège occupé est désactivé
 ✓ Activités : classes et styles > Apercu applique les classes et les styles calculés

 Test Files  1 passed (1)
      Tests  3 passed (3)
  • Vérifier : 3 tests réussis.
  • Pourquoi le test compare-t-il style.color à rgb(147, 51, 234) et non à #9333ea?
  • Les corrigés des sept activités, testés avec Angular 22, sont fournis dans l'archive de la leçon (dossier corriges/).
  1. Erreurs fréquentes

Message ou symptôme Cause Solution
NG8002: Can't bind to 'ngClass' since it isn't a known property of 'p'. NgClass non importé Utiliser [class], ou importer NgClass
NG8002: Can't bind to 'ngStyle' since it isn't a known property of 'p'. NgStyle non importé Utiliser [style], ou importer NgStyle
[class]="{ 'gras souligne': ok }" n'ajoute aucune classe Clé à plusieurs classes non gérée par la liaison Une clé par classe, ou [class.gras] et [class.souligne]
[style]="{ 'width.px': 90 }" sans effet Suffixe d'unité non géré dans l'objet de [style] [style.width.px]="90" ou { width: '90px' }
[style.width]="200" sans effet Valeur CSS sans unité [style.width.px]="200"
La classe ne change pas après obj['x'] = true Objet modifié sur place avec [class] Créer un nouvel objet (computed, update)
L'affichage ne suit pas une valeur modifiée Champ ordinaire modifié hors événement dans un composant OnPush Utiliser un signal
[style.color] ignoré [ngStyle] définit aussi color et l'emporte Une seule source par propriété
La classe est ajoutée mais rien ne change Classe absente du CSS du composant (styles encapsulés) Définir la classe dans les styles du composant, ou utiliser :host(.classe) pour l'hôte
Une classe statique disparaît [attr.class] ou [className] remplace tout l'attribut Utiliser [class], qui conserve les classes statiques
  1. À retenir

    • [ngClass] accepte une chaîne, un tableau, un Set ou un objet { classe: condition }; [ngStyle] un objet { propriété: valeur } avec des suffixes d'unité.
    • Ces deux directives ne sont pas dépréciées, mais les liaisons [class], [class.x], [style] et [style.x.unité] font le même travail sans import.
    • Avec les liaisons : une clé par classe, l'unité dans le nom de la liaison (ou dans la valeur), et toujours un nouvel objet pour signaler un changement.
    • Un computed() qui renvoie l'objet de classes ou de styles garde le template lisible.
    • Les classes s'additionnent ; pour une propriété de style, une seule source.
    • Une variable CSS liée sur l'hôte ([style.--accent]) pilote tout un thème.
    • ngclass-to-class et ngstyle-to-style migrent les cas sûrs ; le reste se convertit à la main.
Le CSS décrit l'apparence, le template décide quand l'appliquer : liez des classes plutôt que des styles, et des valeurs calculées plutôt que des objets modifiés.