AngularAtlaséd. 22.2

Chap. 01Départ04 / 75

Questions d’entretien Angular

60 vraies questions d’entretien pour profils junior, confirmé et senior, avec la réponse à donner, la relance et les signaux d’alerte. Plus un entretien blanc.

v21v22 ~0 min de lecture

60 questions qui reviennent vraiment dans les entretiens Angular en 2026, classées par niveau : ce qu’un junior doit savoir, ce que l’on attend d’un développeur confirmé qui construit seul des fonctionnalités, et ce sur quoi l’on interroge un senior : les rouages internes, l’architecture et les compromis. Chaque réponse commence par ce qu’il faut dire en premier, puis ce qu’il faut ajouter pour montrer que vous comprenez, la question qui suit généralement et la réponse qui fait échouer l’entretien.

Comment utiliser cette page

  1. Répondez à voix haute avant d’ouvrir la réponse. Lire donne l’impression de savoir ; le dire, c’est ce que vous ferez en entretien.
  2. Commencez par la réponse courte, puis ajoutez une ou deux idées de « Puis allez plus loin ». Les recruteurs préfèrent une position claire à une longue liste.
  3. Marquez ce que vous savez avec « Je maîtrise ». C’est enregistré dans ce navigateur, et l’entretien simulé vous pose le reste en premier.
  4. Chaque réponse renvoie vers la leçon qui explique correctement le sujet.

Entretien simulé

Quelques questions aléatoires, une à la fois, avec la réponse masquée jusqu’à ce que vous la demandiez. À la fin, vous obtenez la liste des questions à revoir.

LiveSimulateur d’entretien

Répondez à voix haute, comme si quelqu’un vous écoutait. Révélez ensuite la réponse type et soyez honnête avec vous-même.

Niveau
Questions
Les questions que vous n’avez pas marquées comme maîtrisées passent en premier.

Junior : les fondamentaux

Pour un premier poste Angular, on n’attend pas que vous connaissiez les rouages internes, mais on attend que vous maîtrisiez sans hésiter les bases modernes : composants standalone, nouveau Control Flow, signaux, inputs et outputs, services avec inject(), routing et formulaires. Montrez que vous savez pourquoi, pas seulement comment.

0 sur 20 marquées comme maîtrisées
  1. J-01Qu’est-ce qu’un composant, et que signifie « standalone » ? Comment une application démarre-t-elle sans NgModule ? Composants

    À dire en premier

    Un composant est une classe TypeScript décorée avec @Component qui associe un template à un état et à un comportement, et s’utilise via son sélecteur. Standalone (le défaut) signifie qu’il déclare lui-même ses dépendances dans imports, donc aucun NgModule n’est nécessaire : l’application démarre avec bootstrapApplication(App, appConfig).

    Puis allez plus loin

    • Vous n’écrivez plus standalone: true — c’est le défaut. Chaque composant importe exactement ce que son template utilise (d’autres composants, des pipes, RouterLink), si bien que les dépendances sont explicites et compatibles avec le tree-shaking.
    • La configuration globale de l’application vit dans app.config.ts, sous forme de providers : provideRouter(routes), provideHttpClient(...) seulement si vous le configurez, etc.
    • Une ancienne bibliothèque qui n’exporte qu’un NgModule peut quand même aller directement dans les imports d’un composant ; les providers du module passent par importProvidersFrom().
    import { bootstrapApplication } from '@angular/platform-browser';
    import { App } from './app/app';
    import { appConfig } from './app/app.config';
    
    bootstrapApplication(App, appConfig).catch((err) => console.error(err));

    Signal d’alerte

    Dit que chaque composant doit être déclaré dans un AppModule, ou importe CommonModule « au cas où ».

  2. J-02Quels types de data binding existe-t-il dans un template ? Templates

    À dire en premier

    L’interpolation {{ value }} et le property binding [prop]="expr" vont du composant vers le DOM ; l’event binding (event)="handler($event)" va du DOM vers le composant ; le two-way binding [(value)]="sig" combine les deux.

    Puis allez plus loin

    • [disabled] lie une propriété du DOM ; pour ce qui n’existe que comme attribut (ARIA, colspan), on utilise [attr.aria-label].
    • [(x)] (« banana in a box ») est du sucre syntaxique pour [x]="v()" + (xChange)="v.set($event)". Avec un signal, on passe le signal lui-même, sans parenthèses.
    • Les expressions de template doivent être légères et sans effet de bord ; la lecture d’un signal comme count() est acceptable, mais l’appel d’une méthode coûteuse s’exécute à chaque vérification.
    <h2>{{ user().name }}</h2>
    <img [src]="user().avatarUrl" [attr.aria-label]="user().name" />
    <button [disabled]="saving()" (click)="save()">Save</button>
    <input (input)="query.set($any($event.target).value)" />
    <app-toggle [(checked)]="notifications" />

    Signal d’alerte

    Pense que [value]="x" et value="x" sont équivalents (le second est une chaîne statique), ou écrit [(checked)]="notifications()".

  3. J-03Comment écrire des conditions et des boucles dans un template, et pourquoi @for exige-t-il track ? Templates

    À dire en premier

    Avec le Control Flow intégré : @if / @else, @for avec @empty, et @switch / @case. track est obligatoire : il indique à Angular comment identifier chaque élément, afin de réutiliser les nœuds DOM au lieu de recréer toute la liste.

    Puis allez plus loin

    • Trackez par un identifiant unique et stable (track user.id) ; $index seulement pour les listes statiques ; tracker l’objet lui-même signifie que chaque nouvelle requête détruit et recrée le DOM.
    • Il fait partie du langage : aucun import de NgIf/NgFor. @for expose $index, $first, $last, $even, $odd, $count.
    • @if (user(); as u) fournit un alias typé et non nul. Depuis la v21.1, plusieurs @case peuvent partager un bloc, et @default never; (v21.2) transforme un cas d’union oublié en erreur de compilation.
    @if (tasks().length) {
      <ul>
        @for (task of tasks(); track task.id; let i = $index) {
          <li>{{ i + 1 }}. {{ task.title }}</li>
        }
      </ul>
    } @else {
      <p>You have no tasks.</p>
    }

    Signal d’alerte

    Utilise encore *ngIf/*ngFor dans du nouveau code, ou ne sait pas expliquer à quoi sert track.

  4. J-04Qu’est-ce que @let et quand l’utiliseriez-vous ? Templates

    À dire en premier

    @let déclare une variable locale en lecture seule dans le template, visible dans le bloc courant et ses enfants. Je l’utilise pour nommer une longue expression employée plusieurs fois, ou pour déballer une valeur une seule fois, p. ex. @let user = user$ | async;.

    Puis allez plus loin

    • Elle ne peut pas être réaffectée depuis le template ; elle est recalculée quand ce qu’elle lit change.
    • Elle est pratique dans @for pour une valeur propre à chaque élément (p. ex. un prix final après remise).
    • La vraie logique appartient toujours à la classe, sous forme de computed() ; @let sert la lisibilité, pas les règles métier.

    Signal d’alerte

    Utilise @let comme variable mutable, ou y déplace de la logique métier complexe dans le template.

  5. J-05Comment ajouter une classe CSS ou un style de façon conditionnelle ? Templates

    À dire en premier

    Avec les bindings natifs : [class.active]="isActive()" pour une classe, [class]="{ ... }" pour plusieurs, et [style.width.px]="width()" pour les styles. Aucun import n’est nécessaire, et les classes statiques sont combinées aux classes liées, pas écrasées.

    Puis allez plus loin

    • [class] accepte une chaîne, un tableau ou un objet ; [style] accepte une chaîne ou un objet, et les unités se placent dans le binding (.px, .%, .rem).
    • [ngClass]/[ngStyle] sont l’ancienne méthode ; la v21 propose les migrations ngclass-to-class et ngstyle-to-style.
    • Pour l’élément hôte du composant lui-même, on utilise les métadonnées host ('[class.is-loading]': 'loading()'), pas @HostBinding.

    Signal d’alerte

    Construit les chaînes de classes à la main dans le composant, ou manipule directement classList sur l’élément.

  6. J-06Qu’est-ce qu’une pipe, et que signifie « pure » ? Templates

    À dire en premier

    Une pipe transforme une valeur pour l’affichage, directement dans le template, comme {{ price | currency }}. Par défaut elle est pure : Angular ne la réexécute que lorsque la valeur d’entrée ou ses arguments changent (par référence), elle est donc mémoïsée.

    Puis allez plus loin

    • Les pipes sont standalone et s’importent comme n’importe quelle dépendance (imports: [DatePipe]) ; une pipe personnalisée est une classe avec @Pipe et transform().
    • Comme la vérification se fait par référence, une pipe pure ne se réexécute pas si vous mutez un tableau sur place.
    • Une méthode appelée depuis le template s’exécute à chaque vérification ; pour les calculs propres à un composant, préférez computed(), et réservez les pipes au formatage d’affichage réutilisable. Évitez pure: false.

    Signal d’alerte

    Rend une pipe de filtrage impure pour « la faire marcher » sur une grande liste.

  7. J-07Comment un parent transmet-il des données à un composant enfant, et comment l’enfant répond-il ? Composants

    À dire en premier

    L’enfant déclare input() pour les données entrantes et output() pour les événements sortants. Le parent lie [value]="..." et (rate)="onRate($event)" ; l’enfant appelle rate.emit(4).

    Puis allez plus loin

    • Un input() est un signal en lecture seule : on le lit avec value() et on en dérive avec computed() — pas besoin de ngOnChanges.
    • input.required<T>() transforme un binding manquant en erreur de compilation ; transform: numberAttribute / booleanAttribute convertissent max="10" ou un simple attribut readonly vers le bon type.
    • L’enfant ne peut pas faire set() sur son propre input. S’il doit changer la valeur, il émet un événement et laisse le parent décider, ou utilise model().
    export class StarRating {
      readonly value = input.required<number>();
      readonly max = input(5, { transform: numberAttribute });
      readonly rate = output<number>();
    
      protected readonly stars = computed(() =>
        Array.from({ length: this.max() }, (_, i) => i + 1),
      );
    }

    Signal d’alerte

    Dit qu’il faut ngOnChanges pour réagir à un input, ou utilise @Input/EventEmitter dans du nouveau code.

  8. J-08Qu’est-ce que model() et en quoi diffère-t-il de input() + output() ? Composants

    À dire en premier

    model() est un signal modifiable qui fait office d’input plus un output xChange automatique, de sorte que le parent peut utiliser le two-way binding : [(checked)]="notifications". Quand l’enfant appelle set() ou update(), le signal du parent est mis à jour.

    Puis allez plus loin

    • Utilisez model() quand la valeur appartient vraiment aux deux côtés, comme dans un toggle, un stepper ou un champ personnalisé.
    • Utilisez input() + output() quand l’enfant ne fait que proposer et que le parent décide (valide, confirme, enregistre côté serveur).
    • Dans un two-way binding, on passe le signal lui-même (notifications), pas sa valeur (notifications()).

    Signal d’alerte

    Ne sait pas expliquer en quoi se développe [( )], ou utilise model() pour tout.

  9. J-09Qu’est-ce que la projection de contenu ? Composants

    À dire en premier

    Elle permet à un parent de placer son propre markup entre les balises d’un composant, et le composant décide où l’afficher avec <ng-content />. Avec select, on peut avoir plusieurs emplacements, par exemple un titre et des actions dans une carte.

    Puis allez plus loin

    • select accepte un sélecteur de balise, d’attribut ou de classe ([card-title]) ; le <ng-content /> sans select récupère tout le reste, et vous pouvez y mettre un contenu de repli.
    • Piège : le contenu projeté est créé par le parent même si l’enfant le masque avec @if. Pour un contenu vraiment lazy, passez un <ng-template>.
    • La projection convient au markup flexible (layouts, cartes, boîtes de dialogue) ; les inputs servent aux données que le composant met lui-même en forme.

    Signal d’alerte

    Passe du HTML dans un input de type chaîne et l’affiche avec innerHTML au lieu de le projeter.

  10. J-10Que sont signal() et computed() ? Signals

    À dire en premier

    Un signal() est une valeur réactive : on la lit en l’appelant, et on la modifie avec set() ou update(). Un computed() est une valeur en lecture seule dérivée d’autres signaux ; il est lazy et mémoïsé, recalculé uniquement quand ses dépendances changent.

    Puis allez plus loin

    • Les dépendances sont suivies automatiquement : tous les signaux lus par la fonction deviennent des dépendances, sans abonnement à gérer.
    • Les signaux se comparent avec === : items().push(x) ne notifie personne ; utilisez update((list) => [...list, x]).
    • Règle empirique : tout ce qui peut être calculé à partir d’un autre état est un computed(), pas un second signal à garder synchronisé. Exposez l’état avec asReadonly().
    export class Counter {
      protected readonly count = signal(0);
      protected readonly double = computed(() => this.count() * 2);
    
      protected increment() {
        this.count.update((c) => c + 1);
      }
    }
    // template: {{ count() }} · {{ double() }}

    Signal d’alerte

    Tente d’appeler set() sur un computed(), ou oublie les parenthèses et lie la fonction du signal au lieu de sa valeur.

  11. J-11Qu’est-ce que effect(), et quand ne faut-il pas l’utiliser ? Signals

    À dire en premier

    effect() exécute une fonction une première fois, puis à nouveau chaque fois que les signaux qu’elle lit changent. Il sert aux effets de bord vers le monde extérieur — localStorage, journalisation, bibliothèque tierce — pas à calculer de l’état.

    Puis allez plus loin

    • N’utilisez pas un effect pour copier un signal dans un autre : c’est un computed() (ou un linkedSignal() s’il doit aussi être modifiable).
    • Ne récupérez pas de données depuis un effect : il n’y a pas d’annulation et d’anciennes réponses peuvent gagner la course. Utilisez httpResource() / resource().
    • Il doit être créé dans un contexte d’injection (constructeur ou champ), il est détruit avec le composant, et onCleanup nettoie les timers avant l’exécution suivante.

    Signal d’alerte

    Utilise effect() pour garder fullName synchronisé avec firstName et lastName.

  12. J-12Quelle est la différence entre le constructeur et ngOnInit ? Comment nettoyer quand un composant est détruit ? Composants

    À dire en premier

    Le constructeur s’exécute à la création de la classe et constitue le contexte d’injection : inject(), les signaux et les effects y vont, ou dans les initialiseurs de champs. ngOnInit s’exécute une fois, après la définition des premiers inputs. Pour le nettoyage, j’utilise inject(DestroyRef).onDestroy() ou takeUntilDestroyed().

    Puis allez plus loin

    • Lire un input.required() dans le constructeur lève une erreur, car le parent ne l’a pas encore lié. Avec les signal inputs, ngOnInit est rarement nécessaire : un computed() ou un httpResource() lit l’input quand il est prêt.
    • inject() ne fonctionne pas dans ngOnInit ni dans un gestionnaire de clic — c’est hors du contexte d’injection (erreur NG0203).
    • DestroyRef fonctionne aussi dans les services, les directives et les fonctions réutilisables, ce que ngOnDestroy ne peut pas faire. Pour accéder au DOM après le rendu, utilisez afterNextRender().
    export class Clock {
      readonly now = signal(new Date());
    
      constructor() {
        const id = setInterval(() => this.now.set(new Date()), 1000);
        inject(DestroyRef).onDestroy(() => clearInterval(id));
      }
    }

    Signal d’alerte

    Dit que le constructeur sert « uniquement à la DI » et met toute la logique dans ngOnInit, ou ne nettoie jamais les intervalles et les abonnements.

  13. J-13Qu’est-ce qu’un service et comment l’utilise-t-on dans un composant ? DI

    À dire en premier

    Un service est une classe qui porte de la logique ou un état partagés entre composants — appels d’API, panier, utilisateur connecté. En v22, je le marque avec @Service(), qui est fourni en root comme singleton, et je l’obtiens avec inject(CartService).

    Puis allez plus loin

    • @Service() équivaut à @Injectable({ providedIn: 'root' }) : créé à la première injection et compatible avec le tree-shaking. Il impose inject() — la DI par constructeur est une erreur de compilation.
    • @Injectable reste valide (code existant, bibliothèques pour des versions plus anciennes). Placer le service dans les providers d’un composant donne une instance par composant au lieu d’une par application.
    • inject() fonctionne aussi dans les guards fonctionnels, les resolvers et les interceptors, et évite les longs appels super(...) avec l’héritage.
    @Service()
    export class CartService {
      private readonly _items = signal<CartItem[]>([]);
      readonly items = this._items.asReadonly();
      readonly count = computed(() => this._items().length);
    
      add(item: CartItem) {
        this._items.update((list) => [...list, item]);
      }
    }

    Signal d’alerte

    Crée le service avec new CartService(), ou pense qu’un service dans providers est toujours le même singleton global.

  14. J-14Comment configurer le routing et naviguer entre les pages ? Routing

    À dire en premier

    On définit un tableau Routes, on l’enregistre avec provideRouter(routes), et on place <router-outlet /> là où les pages s’affichent. On navigue avec routerLink dans les templates et router.navigate() dans le code ; les pages sont généralement chargées en lazy loading avec loadComponent.

    Puis allez plus loin

    • loadComponent: () => import('./home-page') place la page dans un chunk séparé, téléchargé seulement quand la route est visitée ; loadChildren fait de même pour toute une fonctionnalité.
    • L’ordre compte — la première correspondance l’emporte — donc le wildcard '**' va en dernier. routerLinkActive ajoute une classe au lien actif.
    • Utilisez routerLink, pas href : href recharge toute l’application.

    Signal d’alerte

    Navigue avec <a href> ou window.location, ou place la route wildcard en premier.

  15. J-15Comment une page lit-elle un paramètre de route comme :id ? Routing

    À dire en premier

    Avec withComponentInputBinding() dans provideRouter, les paramètres de chemin, les query params et les données de route arrivent comme inputs du composant : readonly id = input.required({ transform: numberAttribute }). Comme c’est un signal, tout ce qui en dérive se met à jour quand l’id change.

    Puis allez plus loin

    • Piège : naviguer de /products/1 à /products/2 réutilise la même instance de composant ; le code qui lit l’id une seule fois (p. ex. un snapshot dans ngOnInit) affiche des données périmées.
    • Les paramètres sont des chaînes dans l’URL, donc un transform comme numberAttribute vous donne un vrai nombre.
    • Depuis la v22, les routes enfants héritent par défaut des paramètres et des données de tous leurs parents (paramsInheritanceStrategy: 'always').
    @Component({ templateUrl: './product-page.html' })
    export default class ProductPage {
      readonly id = input.required({ transform: numberAttribute }); // :id
      readonly tab = input<'specs' | 'reviews'>('specs');           // ?tab=
    
      readonly product = httpResource<Product>(() => '/api/products/' + this.id());
    }

    Signal d’alerte

    Ne connaît que route.snapshot.paramMap.get('id') et ignore que le composant peut être réutilisé.

  16. J-16Comment appeler une API REST dans Angular ? HTTP

    À dire en premier

    Avec HttpClient, injecté dans un service : http.get<Product[]>('/api/products') renvoie un Observable. Depuis la v21, il est disponible en root sans configuration ; provideHttpClient() ne sert qu’à configurer, par exemple des interceptors.

    Puis allez plus loin

    • L’Observable est cold : aucune requête n’est envoyée tant que personne ne s’abonne (subscribe(), firstValueFrom(), la pipe async).
    • Pour des lectures réactives dans un composant, httpResource() fournit value(), isLoading() et error() sous forme de signaux et annule les anciennes requêtes ; pour les écritures (POST, PUT), on appelle HttpClient.
    • Le générique get<T> n’est qu’une assertion de type, pas une validation. En v22, Fetch est le backend par défaut ; les préoccupations transverses (jeton d’authentification, erreurs) vont dans des interceptors fonctionnels.

    Signal d’alerte

    Appelle http.get() sans s’abonner et se demande pourquoi rien ne se passe, ou appelle fetch directement depuis les composants.

  17. J-17Comment construire un formulaire avec validation dans Angular 22 ? Formulaires

    À dire en premier

    Pour les nouveaux formulaires, j’utilise les Signal Forms, stables en v22 : un signal porte le modèle, form(model, schema) construit un arbre de champs avec des règles comme required(p.email), et [formField] lie chaque input. Les Reactive Forms restent le bon choix pour les applications existantes qui en sont pleines, ou les bibliothèques qui ne supportent que ControlValueAccessor.

    Puis allez plus loin

    • Le signal du modèle est la source de vérité — pas de copie interne, pas de patchValue : model.set(data) remplit le formulaire, et le formulaire écrit directement dans le modèle.
    • L’état de chaque champ est fait de signaux : f.email().invalid(), touched(), errors() ; le formulaire entier est aussi un champ (f().valid(), f().submitting()).
    • Affichez les erreurs seulement après touched(), et validez toujours aussi côté serveur — la validation côté client relève de l’UX, pas de la sécurité.
    export class LoginForm {
      readonly model = signal({ email: '', password: '' });
    
      readonly loginForm = form(this.model, (p) => {
        required(p.email);
        email(p.email);
        minLength(p.password, 8);
      });
    }
    // template: <input [formField]="loginForm.email" />

    Signal d’alerte

    Valide à la main dans le gestionnaire de soumission avec des if, ou dit que les Signal Forms sont encore seulement expérimentaux en v22.

  18. J-18Que fait @defer ? Performance

    À dire en premier

    @defer charge en lazy une partie d’un template : les composants qu’il contient vont dans un chunk séparé, téléchargé seulement quand un déclencheur se produit — on idle par défaut, ou on viewport, on interaction, when cond(). Il réduit le bundle initial sans toucher aux routes.

    Puis allez plus loin

    • @placeholder s’affiche avant le déclencheur (requis pour on viewport / interaction / hover), @loading pendant le téléchargement, @error si le téléchargement échoue.
    • Piège : si un composant différé est aussi référencé ailleurs dans le même fichier (hors du bloc), il est chargé immédiatement et le chunk est perdu.
    • prefetch on hover télécharge tôt et affiche plus tard — idéal pour les actions « à un clic ».
    @defer (on viewport) {
      <app-comments [postId]="post().id" />
    } @placeholder {
      <div class="comments-skeleton"></div>
    } @loading (after 100ms) {
      <app-spinner />
    }

    Signal d’alerte

    Pense que @defer n’est qu’un @if retardé et ne mentionne pas le chunk JavaScript séparé.

  19. J-19Comment Angular sait-il quand mettre à jour l’écran ? Qu’a changé la v21, et la v22 ? Change Detection

    À dire en premier

    La Change Detection réaffiche les templates quand quelque chose notifie Angular : un signal lu dans le template change, un événement de template se déclenche, la pipe async émet, ou markForCheck() est appelé. Depuis la v21, les nouvelles applications sont zoneless (sans zone.js), et depuis la v22 les composants sont OnPush par défaut.

    Puis allez plus loin

    • Sans zone.js, un setTimeout ou un subscribe() qui modifie un simple champ ne met pas l’interface à jour. La solution est un signal (ou toSignal() / la pipe async).
    • Un composant OnPush n’est vérifié que lorsqu’un input reçoit une nouvelle référence, qu’un événement s’y produit, qu’un signal qu’il lit change, ou qu’il est marqué pour vérification — sinon son sous-arbre est ignoré.
    • ChangeDetectionStrategy.Eager est l’ancien comportement « toujours vérifier » (Default est déprécié) ; ng update l’ajoute là où c’est nécessaire.

    Signal d’alerte

    Dit que « zone.js détecte automatiquement chaque changement » pour une application v21+, ou corrige les mises à jour manquantes en semant des detectChanges() partout.

  20. J-20Comment tester un composant dans un nouveau projet Angular ? Tests

    À dire en premier

    Depuis la v21, ng test exécute Vitest (Karma est déprécié). Je crée le composant avec TestBed.createComponent(), je définis les inputs, j’attends await fixture.whenStable() pour que le rendu zoneless se termine, puis je vérifie sur fixture.nativeElement.

    Puis allez plus loin

    • Testez le comportement que voit l’utilisateur (texte, clics, outputs émis), pas les détails d’implémentation privés.
    • Les inputs peuvent être passés comme signaux avec inputBinding() ; les outputs sont espionnés avec outputBinding() et vi.fn().
    • Pour le HTTP, utilisez provideHttpClientTesting() et HttpTestingController plutôt qu’un vrai serveur ; les services et les signal stores se testent avec TestBed.inject() et sans DOM.
    it('shows the right number of stars', async () => {
      const fixture = TestBed.createComponent(StarRating, {
        bindings: [inputBinding('value', () => 3)],
      });
      await fixture.whenStable();
    
      const stars = fixture.nativeElement.querySelectorAll('button');
      expect(stars).toHaveLength(5);
    });

    Signal d’alerte

    N’a jamais écrit de test, ou s’appuie sur fakeAsync/tick de zone.js dans un projet Vitest zoneless.

Confirmé : construire des fonctionnalités en autonomie

À ce niveau, les questions portent sur des choix : computed ou effect, quel opérateur RxJS, où vit un provider, comment un composant est découpé, comment vous le testez. Une bonne réponse de niveau confirmé nomme l’option, la raison, et le cas où l’autre option l’emporte.

0 sur 20 marquées comme maîtrisées
  1. M-01Quand utilise-t-on computed() et quand effect() ? Pourquoi ne faut-il pas définir de signaux dans un effect ? Signals

    À dire en premier

    computed() sert à l’état dérivé : une valeur pure, lazy et mémoïsée. effect() sert aux effets de bord qui poussent des valeurs de signaux vers le monde non réactif (DOM, localStorage, journalisation). Passer de signal à signal via un effect, c’est presque toujours un computed() ou un linkedSignal() déguisé.

    Puis allez plus loin

    • Définir un signal dans un effect, c’est une exécution supplémentaire, un moment où la cible est périmée (un rendu avec une valeur erronée) et un graphe de dépendances difficile à suivre.
    • Les effects ne sont pas synchrones : définissez un signal trois fois et l’effect s’exécute une seule fois, avec la valeur finale. Les effects de composant s’exécutent pendant la Change Detection ; ceux des services root s’exécutent comme microtâches.
    • Les dépendances sont dynamiques : seuls les signaux lus lors de la dernière exécution sont suivis. Utilisez untracked() pour les lectures qui ne doivent pas redéclencher l’effect.
    • Un effect a besoin d’un contexte d’injection (constructeur ou initialiseur de champ) ou d’un injector explicite ; sinon vous obtenez NG0203. Utilisez onCleanup pour les timers et les listeners.
    • Du HTTP depuis un effect est un mauvais signe : pas d’annulation, donc les réponses peuvent arriver dans le désordre. C’est précisément le rôle de resource / httpResource.
    readonly firstName = signal('Ana');
    readonly lastName = signal('Pop');
    
    // ✗ effect used to synchronise state
    // effect(() => this.fullName.set(this.firstName() + ' ' + this.lastName()));
    
    // ✓ derived state
    readonly fullName = computed(() => this.firstName() + ' ' + this.lastName());
    
    // ✓ effect: reactive world → outside world
    constructor() {
      effect(() => localStorage.setItem('name', this.fullName()));
    }

    Signal d’alerte

    « J’utilise effect pour garder deux signaux synchronisés » ou « computed et effect, c’est pareil, effect s’exécute juste plus souvent ».

  2. M-02Quel problème linkedSignal() résout-il ? Donnez un exemple concret. Signals

    À dire en premier

    C’est un signal modifiable qui se réinitialise à partir d’une source : dérivé comme computed(), mais on peut aussi le set(). Cas typiques : une sélection qui prend par défaut la première option d’une liste, un numéro de page qui revient à 1 quand les filtres changent, une copie modifiable d’un input.

    Puis allez plus loin

    • Il remplace le schéma fragile signal + effect : pas d’exécution supplémentaire, pas de rendu avec une valeur périmée.
    • La forme longue (source + computation) vous donne la valeur previous, ce qui permet de conserver le choix de l’utilisateur s’il existe encore dans la nouvelle liste.
    • Un set() manuel n’est valable que jusqu’au prochain changement de la source ; ensuite, c’est la computation qui l’emporte.
    • Depuis la v22.1, l’option set intercepte les écritures, p. ex. pour plafonner une quantité au stock disponible.

    Signal d’alerte

    Copier l’input dans un signal dans ngOnInit (il ne se réinitialise jamais) ou synchroniser avec un effect.

  3. M-03Comment fonctionnent resource / httpResource, et pourquoi sont-ils préférables à un abonnement HTTP dans un effect ? HTTP

    À dire en premier

    Un resource transforme le chargement asynchrone en signaux : une fonction params (ou URL) réactive, et quand elle change, l’ancienne requête est annulée et une nouvelle démarre. Vous obtenez gratuitement value(), status(), isLoading(), error() et reload(), sans race condition.

    Puis allez plus loin

    • resource reçoit un abortSignal dans le loader ; httpResource passe par HttpClient, donc les interceptors s’appliquent ; rxResource prend un flux Observable et se comporte comme switchMap.
    • Renvoyer undefined depuis params donne le statut idle : le loader ne s’exécute pas. En v22, vous pouvez aussi lever ResourceParamsStatus.IDLE / LOADING, et chaîner des resources avec ctx.chain().
    • hasValue() est un type guard ; defaultValue supprime undefined ; parse valide ou transforme la réponse (p. ex. DTO → modèle, Zod).
    • Les resources servent à la lecture. Les écritures (POST/PUT/DELETE) passent par HttpClient, puis vous faites un update() local du resource (optimiste) ou un reload().
    readonly userId = input.required<number>();
    
    // a new id → the old request is cancelled, a new one starts
    readonly user = httpResource<User>(() => '/api/users/' + this.userId());
    
    // template:
    // @if (user.isLoading()) { <app-spinner /> }
    // @if (user.error()) { <button (click)="user.reload()">Retry</button> }
    // @else if (user.hasValue()) { <h2>{{ user.value().name }}</h2> }

    Signal d’alerte

    Utiliser httpResource pour un POST au clic sur un bouton, ou ne pas savoir ce qu’il advient d’une requête en cours quand les params changent.

  4. M-04Comment implémenter une recherche à la frappe sans lancer une requête à chaque touche ? Signals

    À dire en premier

    En v22, j’enveloppe le signal de la requête avec debounced(query, 300) et j’injecte sa value() dans un httpResource. Le debounce supprime le bruit ; le resource annule les requêtes périmées, donc la dernière requête gagne toujours.

    Puis allez plus loin

    • debounced() renvoie un Resource : value() se met à jour après le dernier changement, et isLoading() vaut true tant que l’utilisateur tape encore.
    • Il est expérimental en v22. L’alternative classique est toSignal(toObservable(query).pipe(debounceTime(300), distinctUntilChanged())) : trois conversions pour un seul debounce.
    • wait peut être une fonction, p. ex. pas de délai quand le champ est vidé.
    • Dans les Signal Forms, il existe une règle dédiée debounce(path.field, 300), et validateHttp a sa propre option debounce.
    import { httpResource } from '@angular/common/http';
    import { debounced, signal } from '@angular/core';
    
    export class UserSearch {
      readonly query = signal('');
      readonly search = debounced(this.query, 300);
    
      readonly users = httpResource<UserDto[]>(
        () => '/api/users?q=' + encodeURIComponent(this.search.value()),
        { defaultValue: [] },
      );
    }

    Signal d’alerte

    Un setTimeout dans le gestionnaire d’input avec un subscribe() manuel et sans annulation de la requête précédente.

  5. M-05Comment combiner signaux et RxJS dans un composant sans fuite d’abonnements ? Quand garder RxJS ? RxJS

    À dire en premier

    Des signaux pour l’état affiché, RxJS aux frontières (événements, flux, asynchrone complexe). toSignal() s’abonne et se désabonne automatiquement ; pour un subscribe() manuel, j’ajoute takeUntilDestroyed(), qui le lie au DestroyRef du composant.

    Puis allez plus loin

    • toSignal() sans initialValue est typé T | undefined ; requireSync: true ne fonctionne que si la source émet de façon synchrone (p. ex. un BehaviorSubject).
    • toObservable() émet de façon asynchrone (via un effect) et uniquement la dernière valeur si le signal est modifié plusieurs fois dans le même tick — ce n’est pas un flux d’événements.
    • takeUntilDestroyed() sans argument ne fonctionne que dans un contexte d’injection ; ailleurs, passez explicitement inject(DestroyRef). Pour un nettoyage hors RxJS, utilisez DestroyRef.onDestroy().
    • Gardez RxJS pour les websockets, la combinaison de flux d’événements, le retry/backoff et les politiques d’aplatissement (switchMap, exhaustMap…). Les signaux n’ont aucune notion du temps.

    Signal d’alerte

    « Les signaux remplacent complètement RxJS » — ou l’inverse : s’abonner partout et copier les valeurs dans des champs sans jamais se désabonner.

  6. M-06Expliquez switchMap vs mergeMap vs concatMap vs exhaustMap, avec un cas d’usage pour chacun. RxJS

    À dire en premier

    Ils diffèrent par ce qui se passe quand une nouvelle valeur arrive alors que l’Observable interne précédent tourne encore : switchMap l’annule (recherche), mergeMap exécute en parallèle (uploads), concatMap met en file dans l’ordre (sauvegardes ordonnées), exhaustMap ignore la nouvelle valeur (soumission, connexion).

    Puis allez plus loin

    • Choisir l’opérateur, c’est choisir une politique de concurrence ; un mauvais choix cause de vrais bugs : des commandes en double avec mergeMap sur un bouton de paiement, des sauvegardes perdues avec switchMap sur une sauvegarde automatique.
    • mergeMap accepte une limite de concurrence, p. ex. mergeMap(upload, 3).
    • Mettez catchError dans le pipe interne : après l’opérateur d’aplatissement, la première erreur termine le flux externe et la recherche « meurt ».
    • Avec les signaux, httpResource et rxResource vous donnent déjà la sémantique de switchMap pour les lectures.
    // the last one wins; errors handled INSIDE, so the stream survives
    query$.pipe(
      debounceTime(300),
      switchMap((q) => this.api.search(q).pipe(catchError(() => of([])))),
    );
    
    files$.pipe(mergeMap((file) => this.api.upload(file), 3)); // parallel, max 3
    patches$.pipe(concatMap((patch) => this.api.save(patch))); // in order
    submit$.pipe(exhaustMap(() => this.api.login(this.credentials()))); // ignore double clicks

    Signal d’alerte

    « J’utilise toujours switchMap » ou des appels subscribe() imbriqués au lieu d’un opérateur d’aplatissement.

  7. M-07Dans une application Angular 22 zoneless, qu’est-ce qui déclenche la Change Detection, et pourquoi un champ modifié dans setTimeout peut-il ne pas s’afficher ? Change Detection

    À dire en premier

    Sans zone.js, rien ne patche les timers ni les promises. La Change Detection est planifiée par le changement d’un signal lu dans le template, un événement de template/host, la pipe async, markForCheck(), setInput() ou une navigation. Un simple champ modifié dans setTimeout ou subscribe() ne notifie personne — faites-en un signal.

    Puis allez plus loin

    • Depuis la v22, les composants sans changeDetection sont OnPush ; l’ancien comportement est ChangeDetectionStrategy.Eager (Default est déprécié). ng update ajoute Eager là où c’est nécessaire.
    • Un composant OnPush n’est vérifié que pour une nouvelle référence d’input (===), un événement en lui ou dans ses enfants, un signal lu dans son template, la pipe async, ou markForCheck(). Sinon, tout le sous-arbre est ignoré.
    • Muter un tableau passé en input (push) ne change pas la référence : faites des mises à jour immuables.
    • markForCheck() est la rustine d’urgence pour les callbacks hérités ; detectChanges() est synchrone et rarement nécessaire.
    // ✗ works with zone.js, NOT zoneless + OnPush
    // now = new Date();
    // setInterval(() => (this.now = new Date()), 1000);
    
    // ✓ a signal schedules change detection by itself
    export class Clock {
      readonly now = signal(new Date());
    
      constructor() {
        const id = setInterval(() => this.now.set(new Date()), 1000);
        inject(DestroyRef).onDestroy(() => clearInterval(id));
      }
    }

    Signal d’alerte

    Appeler detectChanges() partout, ou croire qu’OnPush signifie « le composant ne se met jamais à jour sauf si les inputs changent ».

  8. M-08En quoi viewChild() et contentChildren() diffèrent-ils des anciens décorateurs, et quand peut-on les lire en toute sécurité ? Composants

    À dire en premier

    Les signal queries renvoient des signaux, donc les résultats sont réactifs : vous en dérivez avec computed() au lieu de ngAfterViewInit et de QueryList.changes. La vue n’existe pas dans le constructeur, donc le travail sur le DOM va dans afterNextRender() ; viewChild.required() lève une erreur s’il est lu trop tôt.

    Puis allez plus loin

    • viewChild cherche dans le template propre du composant ; contentChild cherche dans ce que le parent a projeté (p. ex. un conteneur Tabs qui découvre ses enfants Tab).
    • contentChildren ne regarde que les enfants directs par défaut ; utilisez descendants: true pour des correspondances plus profondes.
    • read choisit ce que vous obtenez : ElementRef, TemplateRef, ViewContainerRef, une directive ou (v22.2) l’Injector de l’enfant.
    • Piège : interroger un composant par son type dans la classe le rend eager, donc il ne sera pas séparé par un bloc @defer.

    Signal d’alerte

    Lire une view query dans le constructeur ou ngOnInit et s’étonner qu’elle soit undefined.

  9. M-09Projection de contenu avec ng-content vs passage d’un ng-template rendu avec ngTemplateOutlet : quand choisir l’un ou l’autre ? Templates

    À dire en premier

    ng-content sert aux emplacements statiques : le parent crée le contenu une fois, que l’enfant l’affiche ou non. Un ng-template est une recette que l’enfant instancie quand et aussi souvent qu’il le veut, avec un contexte — nécessaire pour le contenu lazy et pour les API du type « à quoi doit ressembler une ligne ».

    Puis allez plus loin

    • Envelopper ng-content dans @if masque le DOM, mais les composants projetés existent déjà et leur lifecycle a déjà tourné.
    • Les emplacements nommés utilisent select (balise, attribut, classe) ; ngProjectAs projette plusieurs éléments dans un même emplacement sans wrapper.
    • Le contexte renvoie des données : $implicit correspond à let-item. Sans directive avec ngTemplateContextGuard, les variables let- sont de type any.
    • Un template voit l’injector de l’endroit où il est déclaré ; depuis la v21.2, [ngTemplateOutletInjector]="'outlet'" lui fait utiliser l’injector de l’endroit où il est rendu.
    @Component({
      selector: 'app-collapsible',
      imports: [NgTemplateOutlet],
      template: `
        <button (click)="open.set(!open())">Toggle</button>
        @if (open()) { <ng-container [ngTemplateOutlet]="content() ?? null" /> }
      `,
    })
    export class Collapsible {
      readonly content = contentChild(TemplateRef); // created only when opened
      readonly open = signal(false);
    }
    // <app-collapsible><ng-template><app-heavy-report /></ng-template></app-collapsible>

    Signal d’alerte

    Penser que @if autour de ng-content rend le contenu projeté lazy.

  10. M-10Comment construiriez-vous un composant de tableau de données réutilisable et typé, sans en faire un « composant fourre-tout » ? Composants

    À dire en premier

    Rendez le composant générique (DataTable<T>) : T est inféré depuis rows, les clés de colonnes sont restreintes à keyof T, les outputs sont typés. La personnalisation passe par des templates de cellule, pas par des inputs booléens, et les wrappers propres au domaine vivent dans la feature.

    Puis allez plus loin

    • Le signe du composant fourre-tout : des données any[], des flags isProductTable, 30 inputs et de la connaissance métier dans shared/.
    • Les templates de cellule sont trouvés avec contentChildren() sur une directive ; ngTemplateContextGuard type let-row en T au lieu de any.
    • Passez le comportement sous forme de fonctions (trackBy, displayWith, compareWith) pour que le composant n’ait pas à connaître la forme des données.
    • Une faute de frappe dans une clé de colonne devient une erreur de compilation, pas une colonne vide en production.

    Signal d’alerte

    Ajouter un nouvel input booléen chaque fois qu’une page a besoin de quelque chose de différent.

  11. M-11Comment afficher un composant choisi à l’exécution, p. ex. des widgets de tableau de bord issus d’une config ? Composants

    À dire en premier

    Montez une échelle : un petit ensemble connu → @switch ; le composant est une valeur → NgComponentOutlet avec inputs ; contrôle total (outputs, directives) → ViewContainerRef.createComponent() avec bindings (inputBinding, outputBinding, twoWayBinding).

    Puis allez plus loin

    • Un registre de loaders () => import(…) fait de chaque widget un chunk lazy distinct.
    • inputBinding('title', () => …) est réactif, contrairement à l’ancien setInput() qu’il fallait rappeler à chaque changement, avec en plus des abonnements manuels aux outputs.
    • ComponentFactoryResolver a été supprimé en v22 : on passe directement la classe à createComponent.
    • Pour les toasts et boîtes de dialogue hors de l’arbre, utilisez le createComponent() standalone avec un EnvironmentInjector, puis appRef.attachView() pour qu’il rejoigne la Change Detection, et détruisez-le vous-même.
    private readonly vcr = inject(ViewContainerRef);
    
    private async render(type: WidgetType) {
      const component = await WIDGET_REGISTRY[type](); // lazy chunk
      this.vcr.clear();
      this.vcr.createComponent(component, {
        bindings: [
          inputBinding('title', () => this.config().title), // stays in sync
          outputBinding('remove', () => this.removed.emit(this.config().id)),
        ],
      });
    }

    Signal d’alerte

    Recourir à ComponentFactoryResolver ou à innerHTML avec des balises de composants.

  12. M-12Comment gérer l’état d’une page de fonctionnalité sans bibliothèque d’état ? Où se placent les composants smart et dumb ? Architecture

    À dire en premier

    Un service store basé sur les signaux : des signaux modifiables privés, des signaux publics en lecture seule (asReadonly()) et des sélecteurs computed(), plus des méthodes aux noms métier qui font des mises à jour immuables. La page routée (smart) fournit et injecte le store ; les composants dumb reçoivent les données par des inputs et émettent des intentions par des outputs.

    Puis allez plus loin

    • Délimitez par durée de vie : l’état d’une fonctionnalité dans les providers de la page ou de la route (il meurt avec elle), l’état global (utilisateur, panier, thème) dans un @Service() root.
    • Les données serveur vont dans un httpResource à l’intérieur du store ; l’état qui doit survivre à un rafraîchissement ou être partageable va dans l’URL (query params + input()).
    • Un composant dumb peut avoir un état d’UI local et injecter des éléments d’UI (ElementRef, DestroyRef), mais jamais de services de données ni le routeur — sinon il n’est ni réutilisable ni facile à tester, et une liste de 50 cartes fait 50 requêtes.
    • NgRx SignalStore devient intéressant quand l’équipe veut des conventions, des plugins et des devtools, pas par défaut.

    Signal d’alerte

    Mettre chaque morceau d’état dans un unique service root global, ou exposer des signaux modifiables que n’importe quel composant mute sur place.

  13. M-13Expliquez la hiérarchie de la DI : quand fournir en root, sur une route ou sur un composant, et quand faut-il un InjectionToken ? DI

    À dire en premier

    inject() cherche dans les injectors d’éléments (l’élément courant, puis ses parents DOM), puis l’injector de route, root et platform, et lève NG0201 si rien ne correspond. Root (@Service()) signifie un singleton pour toute l’application ; les providers d’une route donnent une instance par fonctionnalité ; les providers d’un composant, une instance par composant et ses enfants. Les tokens servent aux valeurs qui ne sont pas des classes : config, URLs, fonctions.

    Puis allez plus loin

    • @Service() (v22) équivaut à providedIn: 'root', est compatible avec le tree-shaking et n’autorise que inject(). autoProvided: false vous oblige à le lister dans providers.
    • inject() l’emporte sur la DI par constructeur : pas de chaînes super(…), il fonctionne dans les guards, les interceptors et les factories, et permet des fonctions injectX() réutilisables. Il exige un contexte d’injection (sinon NG0203).
    • useClass, useValue, useFactory, useExisting et multi: true pour les tableaux de type plugin ; optional, self, skipSelf, host modifient la recherche.
    • Les viewProviders sont invisibles pour le contenu projeté ; les EnvironmentProviders (provideX()) ne peuvent aller qu’au niveau de l’application ou de la route, pas dans un composant.

    Signal d’alerte

    Lister un service root dans les providers d’un composant « par sécurité » — ce qui crée une seconde instance et des bugs d’état difficiles à trouver.

  14. M-14Guards et resolvers fonctionnels : quelle différence entre canMatch et canActivate, et quand éviteriez-vous un resolver ? Routing

    À dire en premier

    Les guards sont de simples fonctions qui utilisent inject(). canMatch décide si la route « existe » pour l’utilisateur — sinon le routeur essaie la route suivante et le chunk lazy n’est pas téléchargé. canActivate bloque l’entrée dans une route qui a correspondu. Un resolver retarde la navigation jusqu’à l’arrivée des données ; pour les listes et les tableaux de bord, je préfère httpResource dans la page.

    Puis allez plus loin

    • Renvoyez true/false, un UrlTree ou un RedirectCommand (redirection + options de navigation comme replaceUrl) ; depuis la v22.2, vous pouvez aussi lever un RedirectCommand.
    • Resolver : la page apparaît avec ses données prêtes, mais la navigation attend et une erreur l’annule. httpResource : navigation instantanée, chargement et erreur sont de l’état d’UI.
    • Avec withComponentInputBinding(), les données résolues, les params et les query params arrivent sous forme d’input().
    • Les guards relèvent de l’UX, pas de la sécurité : l’API doit aussi faire respecter l’autorisation. canDeactivate protège les modifications non enregistrées.

    Signal d’alerte

    Des guards à base de classes avec injection par constructeur comme choix par défaut, ou traiter un guard comme seul contrôle d’accès.

  15. M-15Écrivez un interceptor HTTP fonctionnel qui ajoute un jeton d’authentification. Quels sont les pièges ? HTTP

    À dire en premier

    Un HttpInterceptorFn est une fonction (req, next) qui renvoie next(req) — ou un clone de la requête, car les requêtes sont immuables. Il s’enregistre avec provideHttpClient(withInterceptors([…])) et peut utiliser inject().

    Puis allez plus loin

    • N’attachez le jeton qu’à votre propre API (vérifiez l’URL), jamais à des hôtes tiers.
    • L’ordre compte : les requêtes traversent les interceptors dans l’ordre indiqué, les réponses reviennent dans l’ordre inverse.
    • Appelez inject() en haut de la fonction, pas dans des callbacks RxJS comme catchError qui s’exécutent plus tard, hors du contexte d’injection.
    • Utilisez HttpContextToken pour les métadonnées par requête (p. ex. ignorer la barre de chargement) plutôt que de faux en-têtes. httpResource et validateHttp passent aussi par les interceptors.
    export const authInterceptor: HttpInterceptorFn = (req, next) => {
      const token = inject(AuthStore).token();
      if (!token || !req.url.startsWith('/api')) return next(req);
    
      return next(req.clone({ setHeaders: { Authorization: 'Bearer ' + token } }));
    };
    
    // app.config.ts
    provideHttpClient(withInterceptors([authInterceptor, errorInterceptor]));

    Signal d’alerte

    Muter directement req.headers, ou envoyer le jeton à toutes les URL.

  16. M-16Comment structurer la gestion des erreurs dans une application Angular : erreurs HTTP, erreurs de rendu et tout le reste ? Architecture

    À dire en premier

    En couches : les erreurs HTTP prévisibles (404, 422) sont de l’état d’UI dans le store ou le composant (resource.error()) ; les erreurs transverses (401, 5xx) vont dans un interceptor ; une zone défaillante de la page est isolée avec @boundary ; tout ce qui n’est pas intercepté finit dans un ErrorHandler global personnalisé pour la journalisation et la télémétrie.

    Puis allez plus loin

    • @boundary (v22.2, developer preview) possède des blocs @error (let e = $error; let reset = $reset), éventuellement avec des conditions when — la première correspondance l’emporte.
    • @boundary n’intercepte que les erreurs de rendu, pas celles des gestionnaires d’événements ni du code asynchrone — celles-ci atteignent toujours l’ErrorHandler.
    • provideBrowserGlobalErrorListeners() transmet window.onerror et les rejets non gérés à l’ErrorHandler.
    • Un interceptor doit relancer l’erreur (throwError(() => err)) après sa réaction transverse, afin que l’appelant puisse encore réagir. Les erreurs de validation ne sont pas des exceptions : elles vivent dans les errors() du formulaire.
    @boundary {
      <app-price-widget [price]="price()" />
      <app-recommendations />
    } @error (let e = $error; when e instanceof PermissionError) {
      <p>You don't have access to this widget.</p>
    } @error (let e = $error; let retry = $reset) {
      <app-error-card [message]="e.message" (retry)="retry()" />
    }

    Signal d’alerte

    Un try/catch avec console.log dans chaque composant, ou avaler les erreurs dans un interceptor pour que personne d’autre ne les voie.

  17. M-17Comment fonctionne la validation dans les Signal Forms, y compris asynchrone, et en quoi diffère-t-elle des Reactive Forms typés ? Formulaires

    À dire en premier

    Dans les Signal Forms, le modèle est un simple signal, et form(model, schema) construit dessus un arbre de champs avec des règles déclarées dans une fonction de schéma (required, email, validate, validateHttp…). Chaque état de champ est un signal : valid(), errors(), pending(), touched(). Les Reactive Forms gardent leur propre copie de la valeur dans des FormControl et exposent les changements sous forme d’Observables.

    Puis allez plus loin

    • Pas de copie interne : model.set() met à jour les inputs et la saisie met à jour le modèle — ni patchValue ni valueChanges.
    • Les règles entre champs utilisent valueOf() dans validate ; les règles conditionnelles utilisent when, hidden, disabled (les champs masqués et désactivés ne sont pas validés) ; les règles réutilisables utilisent schema() + apply().
    • validateHttp passe par HttpClient (donc les interceptors s’appliquent) et possède une option debounce en v22 ; validateAsync fonctionne avec n’importe quel resource. pending() vaut true pendant son exécution.
    • Les erreurs serveur issues de submission.action peuvent être renvoyées avec un fieldTree afin d’atterrir sur le bon champ.
    • La migration peut être progressive avec SignalFormControl et compatForm ; les Reactive Forms typés (nonNullable, getRawValue()) restent valides pour le code existant.
    readonly model = signal({ username: '', password: '' });
    
    readonly signup = form(this.model, (p) => {
      required(p.username);
      minLength(p.password, 8, { message: 'At least 8 characters' });
      validateHttp(p.username, {
        request: ({ value }) => '/api/username-available?username=' + value(),
        debounce: 400,
        onSuccess: (res: { available: boolean }) =>
          res.available ? undefined : { kind: 'taken', message: 'Username taken' },
        onError: () => ({ kind: 'network', message: 'Could not verify' }),
      });
    });

    Signal d’alerte

    Écrire un validateur asynchrone qui lance une requête à chaque frappe sans debounce, ou ignorer que le champ est pending pendant ce temps.

  18. M-18Comment construire un contrôle de formulaire personnalisé (p. ex. une notation par étoiles) compatible avec les Signal Forms ? Et avec les Reactive Forms ? Formulaires

    À dire en premier

    Pour les Signal Forms, vous implémentez FormValueControl<T> : le seul membre obligatoire est value = model() ; ensuite [formField] fonctionne dessus exactement comme sur un input. Pour les Reactive Forms, la méthode classique est un ControlValueAccessor enregistré avec NG_VALUE_ACCESSOR.

    Puis allez plus loin

    • Les inputs facultatifs comme disabled, invalid, touched sont renseignés automatiquement par le formulaire — ne déclarez que ceux que vous utilisez. En v22, un output touch signale que l’utilisateur a touché le contrôle.
    • Les contrôles booléens implémentent FormCheckboxControl avec checked à la place de value.
    • Un CVA nécessite quatre méthodes (writeValue, registerOnChange, registerOnTouched, setDisabledState) et vous devez appeler onChange vous-même ; oublier onTouched casse « afficher les erreurs après le touch ».
    • Interopérabilité en v22 : les Signal Forms peuvent lier des CVA, et les formulaires template/reactive supportent FormValueControl, donc un même contrôle peut servir aux deux pendant la migration.

    Signal d’alerte

    Passer un FormControl en input au composant enfant et le muter là-bas.

  19. M-19Comment fonctionne @defer, quels déclencheurs utiliseriez-vous, et qu’est-ce qui rend un bloc différé réellement non lazy ? Performance

    À dire en premier

    @defer déplace les composants du bloc dans un chunk séparé chargé sur un déclencheur : on idle (défaut), viewport, interaction, hover, timer, immediate ou when une condition. prefetch télécharge plus tôt qu’il n’affiche. Il ne reste lazy que si ces dépendances sont standalone et utilisées nulle part ailleurs dans le fichier.

    Puis allez plus loin

    • Référencer un composant différé dans la classe (p. ex. viewChild(HeavyChart)) ou ailleurs dans le template le rend eager.
    • @placeholder s’affiche avant le déclencheur (et c’est l’élément observé pour viewport / interaction / hover sans cible) ; @loading (after 100ms; minimum 500ms) évite le scintillement ; @error gère l’échec du téléchargement d’un chunk.
    • when ne se déclenche qu’une fois ; plusieurs déclencheurs se combinent avec un OU.
    • Nouveau : on viewport({ rootMargin: '200px' }) (v21), on idle(500) (v22). Avec le SSR, les déclencheurs hydrate on … donnent l’incremental hydration, activée par défaut en v22.
    @defer (on viewport; prefetch on idle) {
      <app-comments [postId]="post().id" />
    } @placeholder (minimum 300ms) {
      <div class="comments-skeleton"></div>
    } @loading (after 100ms; minimum 500ms) {
      <app-spinner />
    } @error {
      <p>We couldn't load the comments.</p>
    }
    
    @defer (on interaction; prefetch on hover) {
      <app-share-dialog />
    } @placeholder { <button>Share</button> }

    Signal d’alerte

    Envelopper le contenu above-the-fold dans @defer, ou penser que @defer retarde une requête HTTP plutôt que le chargement de code.

  20. M-20Comment tester un composant avec des signal inputs et un service qui appelle du HTTP, avec Vitest dans Angular 21+ ? Tests

    À dire en premier

    Composants : TestBed.createComponent(Cmp, { bindings: [inputBinding('value', signal)] }), puis await fixture.whenStable(), puisqu’il n’y a pas de zone à vider. HTTP : provideHttpClient() + provideHttpClientTesting(), puis HttpTestingController.expectOne(url).flush(data) et verify() dans afterEach.

    Puis allez plus loin

    • Vitest est le runner par défaut depuis la v21 (@angular/build:unit-test) ; Karma est déprécié. Utilisez vi.fn() pour les espions d’outputs et vi.useFakeTimers() à la place de fakeAsync.
    • Modifier le signal lié met à jour l’input comme dans un vrai parent — pas besoin de setInput().
    • Les signal stores sont les plus faciles à tester : TestBed.inject(), appelez les méthodes, vérifiez les computed(). Les effects s’exécutent de façon asynchrone, donc videz-les avec TestBed.tick().
    • Testez le comportement via le DOM ou des harnesses, pas via des champs privés. TestBed.createDirective() (v22.2) teste une directive sans hôte écrit à la main.
    beforeEach(() => {
      TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] });
      api = TestBed.inject(ProductsApi);
      http = TestBed.inject(HttpTestingController);
    });
    
    afterEach(() => http.verify()); // no unexpected requests
    
    it('maps DTO → Product', async () => {
      const result = firstValueFrom(api.get(1));
      http.expectOne('/api/products/1').flush({ id: 1, name: 'Mouse', price: 99, stock: 0 });
      expect(await result).toEqual({ id: 1, name: 'Mouse', price: 99, inStock: false });
    });

    Signal d’alerte

    Interroger le vrai backend dans les tests unitaires, ou appeler detectChanges() à répétition et vérifier des champs privés.

Senior : rouages internes, architecture, compromis

Les entretiens senior vérifient si vous savez décider pour une équipe : comment le framework fonctionne en dessous, comment une application reste maintenable à 50 développeurs, ce que coûtent le SSR, la sécurité et la performance. Répondez toujours avec une position, le compromis, et la manière dont vous l’introduiriez dans un vrai projet.

0 sur 20 marquées comme maîtrisées
  1. S-01Comment les signaux fonctionnent-ils en interne ? Expliquez le graphe producteur/consommateur, le push-pull et pourquoi ils sont sans glitch. Signals

    À dire en premier

    Les signaux forment un graphe réactif construit à partir des lectures : signal() est un producteur, computed() est les deux, les effects et les templates sont des consommateurs actifs. Une écriture ne fait qu’incrémenter une version et pousser une notification « peut-être sale » ; les valeurs sont tirées et recalculées paresseusement quand quelqu’un les lit, si bien qu’un computed voit toujours des entrées cohérentes et ne s’exécute qu’une fois par lot d’écritures.

    Puis allez plus loin

    • Les dépendances sont dynamiques : à chaque exécution, la liste des producteurs est reconstruite à partir de ce qui a réellement été lu, donc un signal lu uniquement dans une branche if inactive ne déclenche rien.
    • L’égalité arrête la propagation : la version n’augmente que si la nouvelle valeur n’est pas égale (Object.is par défaut). Un computed dont le résultat ne change pas ne réveille pas ses consommateurs ; { equal } personnalise cela pour les valeurs recréées depuis le serveur.
    • Conséquence : la mutation (items().push()) ne notifie personne, car la référence et la version restent identiques. L’immuabilité n’est pas une question de style, c’est ainsi que le graphe détecte le changement.
    • Les effects sont asynchrones et planifiés avec la Change Detection (effects de vue avant la vérification de leur composant, effects root avant tous les composants). Les signaux ne sont pas un mécanisme d’événements et n’ordonnent pas les effets de bord dans le temps — c’est le territoire de RxJS.
    const a = signal(1);
    const b = signal(2);
    const sum = computed(() => a() + b());
    const product = computed(() => a() * b());
    const result = computed(() => sum() + product()); // diamond
    
    a.set(5);
    b.set(3);  // push: versions bump, nothing is recalculated yet
    result();  // pull: sum, product, result run once each, with consistent values
    
    // equality cutoff: a new array with the same content does not propagate
    const ids = signal<number[]>([], {
      equal: (x, y) => x.length === y.length && x.every((v, i) => v === y[i]),
    });

    Signal d’alerte

    « Un signal, c’est juste un BehaviorSubject avec une meilleure API » ou « set() réaffiche le composant de façon synchrone » — cela montre qu’on n’a compris ni la paresse, ni les versions, ni le push-pull.

  2. S-02Dans une application zoneless avec des composants OnPush, qu’est-ce qui planifie réellement la Change Detection, et qu’est-ce qu’Angular ignore ? Change Detection

    À dire en premier

    Sans zone.js, personne ne patche setTimeout ni les promises, donc Angular n’affiche que lorsqu’on le lui dit : un signal lu dans un template change, un listener de template/host se déclenche, la pipe async émet, markForCheck(), setInput() ou une navigation. Un sous-arbre OnPush est ignoré sauf si l’un d’eux le marque, donc le travail est proportionnel à ce qui a changé, pas à la taille de l’arbre.

    Puis allez plus loin

    • Le template est un consommateur réactif : quand un signal qu’il a lu change, Angular marque cette vue pour rafraîchissement (comme markForCheck) et la Change Detection n’atteint que les composants concernés.
    • OnPush compare les inputs par référence (===) : muter un objet passé via un input ne met pas l’enfant à jour — la même règle d’immuabilité que pour les signaux.
    • Le bug classique : un simple champ modifié dans setTimeout, subscribe() ou un callback tiers (WebSocket, Maps) — « ça ne se met à jour qu’après un clic quelque part ». La solution est un signal ou toSignal() ; markForCheck() est la porte de secours pour le code hérité.
    • detectChanges() est synchrone et rarement nécessaire (mesures, tests) ; markForCheck() est la variante asynchrone recommandée.
    // ✗ worked with zone.js, does nothing zoneless + OnPush
    export class Clock {
      now = new Date();
      constructor() {
        setInterval(() => (this.now = new Date()), 1000);
      }
    }
    
    // ✓ the signal tells Angular what changed
    export class Clock {
      readonly now = signal(new Date());
      constructor() {
        const id = setInterval(() => this.now.set(new Date()), 1000);
        inject(DestroyRef).onDestroy(() => clearInterval(id));
      }
    }

    Signal d’alerte

    Semer des detectChanges() partout jusqu’à ce que l’écran se mette à jour, ou croire que zoneless veut dire « Angular vérifie moins souvent, donc il y a plus de bugs ».

  3. S-03Comment migreriez-vous une grosse application basée sur zone.js vers zoneless sans arrêter le développement de fonctionnalités ? Change Detection

    À dire en premier

    Progressivement : d’abord rendre les composants corrects sous OnPush avec l’état dans des signaux, pendant que zone.js reste là comme filet de sécurité, puis retirer zone.js une fois que plus rien n’en dépend. Le risque n’est pas Angular, mais le code et les bibliothèques qui comptaient silencieusement sur zone.js pour déclencher les rendus.

    Puis allez plus loin

    • Commencez par un inventaire : champs simples modifiés dans des callbacks asynchrones, subscribe() vers des champs, bibliothèques qui attendent NgZone.onStable. Angular 21 fournit un outil d’analyse (onpush_zoneless_migration, via le serveur MCP de la CLI, ng mcp).
    • Migrez d’abord les composants feuilles, fonctionnalité par fonctionnalité : supprimez Eager, déplacez l’état vers des signaux / toSignal(), relisez et testez. Chaque PR est petite et réversible.
    • Jusqu’à la fin, conservez provideZoneChangeDetection({ eventCoalescing: true }) et le polyfill zone.js ; puis retirez les deux et surveillez les bugs de type « ça ne se met à jour qu’après un clic ».
    • Les tests changent aussi : fakeAsync a besoin de zone.js, passez donc à await fixture.whenStable() et aux fake timers de Vitest.
    • En équipe : une règle de lint/relecture imposant les signaux pour l’état de template dans le nouveau code, et une courte checklist dans le template de PR, pour que la zone migrée ne régresse pas.

    Signal d’alerte

    « Retirer zone.js dans une seule grosse PR et corriger ce qui casse en QA » — pas d’inventaire, pas de filet de sécurité, pas de plan pour les bibliothèques et les tests.

  4. S-04Comment concevoir la couche d’état d’une grosse application : un service avec des signaux, NgRx SignalStore ou NgRx Store classique ? Architecture

    À dire en premier

    La première question n’est pas « quelle bibliothèque » mais où vit chaque morceau d’état et qui en est responsable. Un service avec des signaux privés, des sélecteurs computed() et des méthodes nommées couvre la plupart des applications ; SignalStore quand on veut une forme imposée, des entités et des features réutilisables entre plusieurs équipes ; NgRx Store classique seulement si l’on a vraiment besoin d’un journal d’actions, du time-travel ou de flux pilotés par les événements.

    Puis allez plus loin

    • Gardez l’état au niveau le plus bas qui en a besoin (composant → fonctionnalité → global) et ne le remontez que lorsqu’un nouveau consommateur apparaît. L’état d’une page est fourni sur la route ou le composant, pas en root, pour qu’il ne « colle » pas d’une visite à l’autre.
    • Les données serveur sont un cache, pas votre état : gardez-les dans httpResource/resource et rechargez après les écritures. L’URL est aussi de l’état (filtres, pagination).
    • Des règles qui passent à l’échelle : exposer des signaux en lecture seule (asReadonly()), une seule source de vérité avec le reste dérivé, pas de stores qui s’injectent mutuellement de façon circulaire — cela signale généralement une façade manquante ou un concept mal placé.
    • Avec SignalStore, seules les méthodes appellent patchState, rxMethod gère les flux asynchrones (avec catchError dans le flux interne) et signalStoreFeature supprime le code de chargement/erreur copié-collé. Les bibliothèques peuvent coexister avec un NgRx Store existant.

    Signal d’alerte

    « Toujours NgRx pour tout ce qui est sérieux » ou « un store global pour toute l’application » — l’outillage choisi avant d’avoir compris la propriété et la durée de vie de l’état.

  5. S-05Comment structurer une grosse base de code en code de fonctionnalité et code partagé, et comment faire respecter les frontières dans un monorepo ? Architecture

    À dire en premier

    Organisez par domaine, pas par type de fichier : les features dépendent de core et de shared, shared ne dépend de rien de spécifique à un domaine, et les features ne s’importent jamais directement entre elles. Les règles qui n’existent que dans un wiki cassent dès la première semaine, donc je les mets dans le lint — no-restricted-imports dans une seule application, ou des tags Nx avec @nx/enforce-module-boundaries dans un monorepo.

    Puis allez plus loin

    • Des tags sur deux dimensions : scope (qui parle à qui : scope:shop, scope:shared) × type (sens des dépendances : feature → ui / data-access → util). Les contraintes des deux s’appliquent avec un ET.
    • Une seule API publique par feature/bibliothèque (un unique barrel à la frontière, jamais dans chaque dossier) ; les imports profonds et les imports statiques de bibliothèques lazy sont des violations.
    • La granularité est un compromis : de petites bibliothèques par domaine et par type donnent des graphes nx affected étroits et des hits de cache ; une seule énorme bibliothèque shared fait que chaque PR affecte tout le dépôt.
    • Ne déplacez vers shared que ce qui est générique, stable et déjà utilisé dans plusieurs features (« règle de trois ») ; les composants métier utilisés ailleurs restent dans leur feature propriétaire et sont exportés via son API publique.
    '@nx/enforce-module-boundaries': ['error', {
      depConstraints: [
        // scope: who may talk to whom
        { sourceTag: 'scope:shop', onlyDependOnLibsWithTags: ['scope:shop', 'scope:shared'] },
        { sourceTag: 'scope:shared', onlyDependOnLibsWithTags: ['scope:shared'] },
        // type: which way dependencies flow
        { sourceTag: 'type:feature', onlyDependOnLibsWithTags: ['type:feature', 'type:ui', 'type:data-access', 'type:util'] },
        { sourceTag: 'type:ui', onlyDependOnLibsWithTags: ['type:ui', 'type:util'] },
        { sourceTag: 'type:util', onlyDependOnLibsWithTags: ['type:util'] },
        // ui never reaches data-access, not even transitively
        { sourceTag: 'type:ui', notDependOnLibsWithTags: ['type:data-access'] },
      ],
    }],

    Signal d’alerte

    Des dossiers par type (components/, services/) et un fourre-tout shared, avec des frontières « convenues oralement ».

  6. S-06Comment appliqueriez-vous ports & adapters dans Angular ? Où vit le HTTP, et quand cela n’en vaut-il pas la peine ? Architecture

    À dire en premier

    Le domaine est du TypeScript pur, les cas d’usage dépendent de ports écrits comme classes abstraites (qui sont aussi des tokens de DI), et le HTTP ne vit que dans des adapters à la périphérie, qui transforment les DTO en entités. Le seul endroit qui lie un port à un adapter est la composition root — app.config.ts ou les providers d’une route. Pour de simples écrans CRUD, c’est excessif.

    Puis allez plus loin

    • Classe abstraite plutôt qu’interface : une interface disparaît à la compilation, il faudrait donc un InjectionToken séparé ; la classe abstraite est à la fois le type et le token.
    • Les erreurs font partie du contrat : l’adapter traduit les timeouts/503 en erreur de domaine, de sorte qu’aucun composant ne voit jamais HttpErrorResponse.
    • Le bénéfice se voit dans les tests : règles de domaine en fonctions pures, cas d’usage avec un adapter en mémoire, et une suite de tests de contrat exécutée sur chaque adapter.
    • Variante pragmatique : un domaine pur pour les règles qui comptent, un port seulement là où il y a (ou aura) plusieurs implémentations, le reste dans la structure habituelle data-access/ui — et faire respecter les couches avec le lint.
    // application/task-repository.ts — the PORT (type + DI token)
    export abstract class TaskRepository {
      abstract list(): Promise<readonly Task[]>;
      abstract save(task: Task): Promise<void>;
    }
    
    // infrastructure/http-task-repository.ts — the ADAPTER (HttpClient + DTO mapping)
    @Injectable()
    export class HttpTaskRepository extends TaskRepository { /* … */ }
    
    // app.config.ts — the COMPOSITION ROOT
    providers: [{ provide: TaskRepository, useClass: HttpTaskRepository }]

    Signal d’alerte

    Des composants qui injectent HttpClient ou l’adapter concret directement, des DTO qui circulent dans les templates, ou quatre couches pour deux formulaires « à cause de la Clean Architecture ».

  7. S-07Quand choisiriez-vous des micro-frontends avec Native Federation, et quand refuseriez-vous ? Architecture

    À dire en premier

    Les micro-frontends résolvent un problème d’organisation — des équipes autonomes qui doivent déployer indépendamment et qui se bloquent aujourd’hui à la release — pas un problème technique. Si je ne peux pas nommer deux équipes qui se bloquent mutuellement, je recommande une seule application dans un monorepo avec des routes lazy et des frontières imposées ; les MFE dégradent les performances et ne font que déplacer du code enchevêtré entre dépôts.

    Puis allez plus loin

    • Raisons valables : de nombreuses équipes avec des cadences différentes, de très grandes organisations, une migration strangler-fig depuis une application legacy, des produits distincts sous une même expérience.
    • Angular doit être un singleton avec strictVersion, idéalement exactement la même version partout : deux runtimes, c’est des composants qui ne peuvent pas entrer dans l’arbre du shell et des services en double instance. Un remote sur une autre version majeure est isolé en Web Component ou en iframe.
    • Traitez les exposes de chaque remote comme une API publique semver ; communiquez via l’URL, puis des événements typés, puis quelques services singleton partagés — jamais un store global partagé (c’est un monolithe distribué).
    • Prévoyez les coûts dès le premier jour : un timeout et un fallback par remote, un manifest par environnement, le nom et la version du remote dans chaque erreur journalisée, des tests e2e sur la combinaison de production.

    Signal d’alerte

    « Il nous faut des micro-frontends pour la scalabilité et la performance » ou mettre strictVersion: false pour faire disparaître les avertissements de version.

  8. S-08Quand Angular Elements est-il le bon outil, et quels sont les pièges de la livraison d’un composant Angular comme custom element ? Composants

    À dire en premier

    Quand un composant doit vivre en dehors d’une application Angular : widgets de CMS, sites clients, design system pour d’autres frameworks, ou un remote qui ne peut pas partager la version d’Angular. createCustomElement() transforme les inputs en attributs/propriétés et les outputs en CustomEvent, mais chaque bundle embarque son propre runtime (≈ 33 kB transférés) et il n’y a pas de DI partagée — au sein de la même application Angular, c’est presque toujours le mauvais choix.

    Puis allez plus loin

    • Les attributs sont toujours des chaînes : les inputs numériques/booléens ont besoin de numberAttribute / booleanAttribute, et les objets/tableaux ne passent que par les propriétés.
    • Les événements ne remontent pas (bubble) et ne sont pas composed : écoutez sur l’élément lui-même. Utilisez des noms d’output spécifiques (ratingChange, pas change).
    • Utilisez ViewEncapsulation.ShadowDom pour que le CSS de la page hôte n’y fuite pas ; thématisez via des variables CSS et ::part().
    • Regroupez les widgets d’une équipe en un seul bundle (un seul createApplication(), plusieurs appels define()), utilisez une balise différente du sélecteur du composant, et ne réutilisez pas un élément détruit — créez-en un nouveau.

    Signal d’alerte

    Utiliser Angular Elements pour réutiliser des composants entre écrans d’une même application Angular, ou pour du contenu critique pour le SEO qui devrait être rendu côté serveur.

  9. S-09Comment choisir entre CSR, SSR et prerender dans une application Angular, et que coûte son exploitation ? SSR

    À dire en premier

    Par route, pas par projet : app.routes.server.ts définit RenderMode.Prerender, Server ou Client pour chaque chemin. Prérendez les pages publiques stables, rendez côté serveur les pages publiques aux données fraîches, et gardez côté client la zone derrière le login. Le SSR améliore le LCP et le SEO ; il ne réduit pas le JavaScript et ne corrige pas une application lente après l’hydration.

    Puis allez plus loin

    • Le prerender avec paramètres nécessite getPrerenderParams ; PrerenderFallback.Server (le défaut) permet de prérendre les N premiers produits et de rendre le reste à la demande. Chaque URL est un rendu complet au moment du build.
    • Le mode Server implique un processus Node : le TTFB est au moins celui de l’API la plus lente appelée pendant le rendu, du CPU par requête, et un état au niveau des modules partagé entre utilisateurs.
    • Les bons codes de statut comptent pour le SEO : définissez le 404 via RESPONSE_INIT au lieu d’un soft 404 ; le cache se règle par route — public, s-maxage uniquement pour un contenu identique, private, no-store pour du HTML personnel.
    • En v22, configurez allowedHosts (ou NG_ALLOWED_HOSTS) : un hôte inconnu reçoit un 400 en protection contre le SSRF. Si personne ne peut exploiter un serveur Node, utilisez le Prerender avec outputMode: 'static'.

    Signal d’alerte

    « Activez le SSR partout, ça rend l’application plus rapide » — en ignorant que le TTI change à peine, le coût serveur, et les pages personnelles mises en cache publiquement au CDN.

  10. S-10Expliquez l’incremental hydration et l’event replay en v22. Comment les utilisez-vous sur une page produit ? SSR

    À dire en premier

    En v22, un simple provideClientHydration() active l’incremental hydration, et avec elle l’event replay. Les blocs @defer (hydrate on …) sont rendus côté serveur mais restent déshydratés dans le navigateur — leur JS n’est même pas téléchargé — jusqu’au déclenchement ; les clics effectués avant sont mis en file puis rejoués, et un clic dans un bloc déshydraté l’hydrate.

    Puis allez plus loin

    • Contrairement à un @defer ordinaire, le contenu final est déjà à l’écran, donc vous pouvez différer au-dessus de la ligne de flottaison sans décalage de mise en page.
    • hydrate never est réservé au contenu vraiment statique : il n’est pas annoté, donc rien n’est rejoué. Gardez le @placeholder : les déclencheurs hydrate sont ignorés lors d’une navigation côté client.
    • withIncrementalHydration() est déprécié et withEventReplay() est redondant avec le défaut ; pour s’en désengager, on utilise withNoIncrementalHydration().
    • N’enveloppez pas des composants de quelques Ko : le chunk et la requête supplémentaires coûtent plus qu’ils n’économisent. Utilisez des états CSS (:active, :focus-visible) pour que les utilisateurs voient que le clic a été pris en compte.
    <app-product-header [product]="product()" />  <!-- hydrated at bootstrap -->
    
    @defer (hydrate on interaction) {
      <app-gallery [images]="product().images" />  <!-- JS only at the first click -->
    } @placeholder {
      <div class="gallery-skeleton"></div>         <!-- shown on client-side navigation -->
    }
    
    @defer (hydrate on viewport) {
      <app-reviews [productId]="id()" />           <!-- below the fold -->
    } @placeholder {
      <div class="reviews-skeleton"></div>
    }
    
    @defer (hydrate never) { <app-footer /> }      <!-- static HTML forever -->

    Signal d’alerte

    Mettre hydrate never sur quelque chose qui a des listeners, ou supprimer @placeholder « parce qu’il n’est jamais visible ».

  11. S-11Quel code n’est pas compatible SSR, et comment éviter les hydration mismatches dans une équipe ? SSR

    À dire en premier

    Tout ce qui touche à window, localStorage, à la mise en page ou aux observers dans les constructeurs, les initialiseurs de champs, ngOnInit ou effect() — cela s’exécute aussi côté serveur. Le travail réservé au navigateur va dans afterNextRender / afterRenderEffect ou derrière un provider de plateforme, et le rendu produit doit être identique sur les deux plateformes.

    Puis allez plus loin

    • @if (isPlatformBrowser(…)) dans un template ne lève pas d’erreur, mais Angular jette le DOM serveur de ce bloc et le reconstruit — un décalage de mise en page. Préférez @defer ou afterNextRender.
    • Causes de mismatch : manipulation directe du DOM (innerHTML, appendChild), HTML invalide (<div> dans <p>), scripts tiers, un CDN qui supprime les commentaires (NG0507), et des valeurs comme Date.now() qui diffèrent entre plateformes.
    • Les serveurs zoneless ne voient pas votre asynchrone maison (fetch, SDK, timers) : utilisez resource() ou PendingTasks, sinon le HTML part vide et le navigateur redemande les données.
    • Fuites d’état : les variables au niveau des modules et les objets useValue sont partagés par tous les utilisateurs du processus Node ; utilisez des services ou useFactory.
    • En équipe : un build SSR en CI, une checklist de relecture, et ngSkipHydration autorisé uniquement comme correctif documenté et temporaire.

    Signal d’alerte

    Des if (typeof window !== 'undefined') semés dans les composants, ou considérer effect() comme un « hook réservé au navigateur ».

  12. S-12Vous héritez d’une application Angular lente. Quelle est votre stratégie de performance, et comment éviter les régressions ? Performance

    À dire en premier

    Mesurer d’abord, puis corriger le plus gros goulot d’étranglement : les Core Web Vitals (LCP, INP, CLS) issus d’utilisateurs réels et le Profiler d’Angular DevTools me disent si le problème vient du chargement, du rendu ou d’un thread principal saturé. Ensuite je verrouille les gains avec des budgets de bundle en CI.

    Puis allez plus loin

    • Chargement : routes lazy, @defer pour le contenu lourd sous la ligne de flottaison (jamais l’élément LCP), injectAsync() pour les services lourds et rarement utilisés, NgOptimizedImage avec priority sur l’image LCP, imports ciblés vérifiés avec source-map-explorer.
    • Rendu : OnPush + signaux, track sur un identifiant stable, dérivations dans computed() plutôt que des méthodes de template ; pour des dizaines de milliers de lignes, le virtual scroll du CDK ou la pagination — OnPush réduit la fréquence, pas la quantité que vous rendez.
    • Thread principal : les calculs continus de plus de ~50 ms (parsing, gros filtres) vont dans un Web Worker ; transférez des ArrayBuffer au lieu de copier d’énormes données.
    • Contrôle des régressions : des budgets dans angular.json qui font échouer le build, une vérification de performance des pages clés à chaque release, et un responsable des chiffres.

    Signal d’alerte

    Optimiser sans mesurer, ou « ajouter du SSR et du @defer partout » — y compris sur le contenu principal, ce qui dégrade le LCP.

  13. S-13Comment Angular protège-t-il contre le XSS, où sont les portes dérobées, et quelle est votre seconde ligne de défense ? Sécurité

    À dire en premier

    Angular considère toute valeur liée comme non sûre : l’interpolation échappe, [innerHTML] est assaini, les schémas d’URL dangereux deviennent unsafe:. Le XSS dans les applications Angular vient presque toujours du contournement de cela — bypassSecurityTrust* sur des données non fiables, nativeElement.innerHTML, eval — donc la seconde ligne est une CSP stricte avec un nonce, plus Trusted Types.

    Puis allez plus loin

    • bypassSecurityTrust* n’est légitime que pour du contenu que vous construisez et contrôlez (une URL d’embed issue d’un identifiant validé). Une pipe générique « safeHtml » est un XSS réutilisable ; cherchez ces appels à chaque relecture.
    • CSP : Angular a besoin d’un nonce pour ses styles inline — ngCspNonce sur l’élément racine ou le token CSP_NONCE ; security.autoCsp calcule les hashes au build mais ne couvre que les scripts et ne fonctionne pas avec le SSR.
    • Déployez la CSP d’abord avec Content-Security-Policy-Report-Only, sinon les chunks lazy ou les styles inline cassent en production.
    • CSRF avec cookies de session : HttpClient envoie le cookie XSRF-TOKEN sous la forme X-XSRF-TOKEN sur les requêtes mutantes de même origine — mais le serveur doit le définir et le vérifier. Chaque vérification côté frontend est répétée côté serveur.

    Signal d’alerte

    « Angular assainit tout, donc nous sommes en sécurité » alors que la base de code contient une pipe bypassSecurityTrustHtml appliquée à du contenu de CMS.

  14. S-14Concevez un interceptor d’authentification avec des refresh tokens. Que se passe-t-il quand six requêtes reçoivent un 401 en même temps, et où stockez-vous les jetons ? HTTP

    À dire en premier

    Exactement un refresh en cours, partagé par toutes les requêtes en attente (un Observable mis en cache avec shareReplay(1), vidé dans finalize), puis chaque requête d’origine est rejouée avec le nouveau jeton. Access token en mémoire, refresh token dans un cookie HttpOnly, Secure, SameSite — ou un BFF, où le navigateur n’a qu’un cookie de session.

    Puis allez plus loin

    • Le « refresh à chaque 401 » naïf produit N refreshes ; avec la rotation des refresh tokens, ils s’invalident mutuellement et certains serveurs traitent la réutilisation comme une attaque et tuent la session.
    • Marquez le login/refresh avec un HttpContextToken (SKIP_AUTH) pour que l’interceptor ne s’intercepte pas lui-même, n’attachez le jeton qu’aux URL de votre API, et si le jeton a déjà changé depuis l’envoi de la requête, réessayez simplement.
    • Seul le 401 déclenche un refresh — le 403 signifie « non autorisé ». Un refresh qui échoue entraîne une déconnexion, le vidage des stores et des caches, et la propagation de la déconnexion aux autres onglets (BroadcastChannel).
    • Au démarrage, un provideAppInitializer tente un refresh avant la première route. Avec un fournisseur OIDC externe, utilisez une bibliothèque certifiée plutôt que de réimplémenter PKCE et la rotation à la main.
    private refreshInFlight: Observable<string> | null = null;
    
    refresh(): Observable<string> {
      this.refreshInFlight ??= this.http
        .post<{ accessToken: string }>('/auth/refresh', null, {
          withCredentials: true,                            // HttpOnly cookie
          context: new HttpContext().set(SKIP_AUTH, true),  // not intercepted again
        })
        .pipe(
          map((r) => r.accessToken),
          tap((t) => this.token.set(t)),                    // access token: memory only
          finalize(() => (this.refreshInFlight = null)),
          shareReplay(1),                                   // every 401 waits for the same one
        );
      return this.refreshInFlight;
    }

    Signal d’alerte

    Un JWT dans localStorage sans réfléchir au XSS ou à la CSP, un refresh distinct pour chaque 401, ou des contrôles de permissions fondés sur le décodage du JWT côté frontend.

  15. S-15Quelle est votre stratégie de gestion des erreurs dans une application Angular ? Architecture

    À dire en premier

    En couches : les erreurs prévisibles sont gérées localement près des données (un 404 ou un 422 affiché par le store/composant), les erreurs transverses de façon centralisée dans un interceptor (401, 5xx), les erreurs de rendu d’une zone avec @boundary (v22.2, developer preview), et tout le reste atteint un ErrorHandler global qui journalise vers la télémétrie.

    Puis allez plus loin

    • provideBrowserGlobalErrorListeners() achemine window.onerror et les rejets non gérés vers ErrorHandler ; gardez le handler simple et sûr — s’il lève une erreur, vous perdez toutes les erreurs.
    • @boundary n’intercepte que les erreurs de rendu, pas celles des gestionnaires d’événements ni du code asynchrone, et une erreur interceptée est quand même signalée à ErrorHandler.
    • L’interceptor relance après traitement (throwError(() => err)) pour que les appelants puissent encore réagir ; un catchError(() => of([])) sans journalisation rend les bugs invisibles.
    • UX et exploitation : centralisez et dédupliquez les toasts (pas cinq messages pour un problème), traduisez les erreurs techniques en erreurs de domaine au niveau de l’adapter, et joignez du contexte (route, version, remote) à chaque erreur journalisée.

    Signal d’alerte

    Un try/catch ou catchError dans chaque composant qui avale les erreurs, et un toast pour chacune, sans journalisation centrale.

  16. S-16Comment choisir entre switchMap, mergeMap, concatMap et exhaustMap, et quels sont les pièges de shareReplay et de la backpressure ? RxJS

    À dire en premier

    Le choix est la politique de chevauchement quand une nouvelle valeur arrive alors que la requête interne précédente tourne : switchMap annule (typeahead), mergeMap exécute en parallèle (uploads indépendants), concatMap met en file (sauvegarde automatique, commandes ordonnées), exhaustMap ignore (soumission, connexion). Les mauvais choix sont des race conditions qui n’apparaissent que sur un réseau lent.

    Puis allez plus loin

    • switchMap pour des écritures est dangereux : une sauvegarde annulée a pu atteindre le serveur ou non. Les écritures utilisent concatMap ou exhaustMap.
    • catchError va dans la projection interne ; placé après switchMap, la première requête échouée termine tout le flux et la recherche meurt en silence.
    • Un simple shareReplay(1) a refCount: false : sur une source infinie (websocket, interval), il ne se désabonne jamais. Utilisez { bufferSize: 1, refCount: true } — en sachant que le cache ne survit que si la source s’est terminée.
    • RxJS fonctionne en push sans backpressure intégrée, donc vous choisissez une politique : abandonner (switchMap, exhaustMap, throttleTime, auditTime, debounceTime), regrouper (bufferTime) ou limiter la concurrence (mergeMap(fn, 3)). Une file concatMap non bornée sur une source rapide fait grossir la mémoire.
    readonly results$ = this.query$.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      switchMap((q) =>
        this.api.search(q).pipe(
          catchError((err) => {
            this.log.error(err);
            return of([]);        // inside: only this request fails, the stream lives on
          }),
        ),
      ),
      shareReplay({ bufferSize: 1, refCount: true }),
    );

    Signal d’alerte

    « J’utilise toujours mergeMap » ou des appels subscribe() imbriqués — avec perte de l’annulation, de l’ordre et de la gestion d’erreurs.

  17. S-17Quelle stratégie de test fixeriez-vous pour une équipe : que met-on dans les tests unitaires, de composants et e2e, et que mocke-t-on ? Tests

    À dire en premier

    Testez le comportement, pas l’implémentation, et poussez chaque vérification au niveau le moins coûteux qui peut la détecter : fonctions pures et stores sans le DOM, composants dumb via leurs inputs et outputs, pages smart avec un faux store et HttpTestingController, et quelques parcours e2e (Playwright/Cypress) pour ce que seule l’intégration révèle. Mockez aux frontières — HTTP et ports — pas vos propres composants enfants.

    Puis allez plus loin

    • Depuis la v21, le runner par défaut est Vitest (@angular/build:unit-test) ; Karma est déprécié, et les anciennes suites peuvent être migrées avec le schematic expérimental refactor-jasmine-vitest.
    • Tests zoneless : await fixture.whenStable() au lieu de fakeAsync, fake timers de Vitest pour le debounce, TestBed.tick() pour vider les effects ; inputs sous forme de signaux via inputBinding.
    • Avec ports & adapters, exécutez une suite de tests de contrat sur chaque adapter ; les harnesses du CDK rendent les tests de composants indépendants de la structure du DOM.
    • En CI : nx affected plus le cache pour la vitesse, pas de cache pour les e2e non déterministes contre un vrai backend, et la couverture comme signal, pas comme objectif.

    Signal d’alerte

    « 100 % de couverture avec tout mocké » — des tests qui vérifient que des méthodes privées ont été appelées, cassent à chaque refactor et passent à côté de vrais bugs.

  18. S-18Quelle stratégie d’i18n choisiriez-vous : l’i18n d’Angular au build ou une bibliothèque de traduction à l’exécution ? i18n

    À dire en premier

    @angular/localize intégré par défaut : textes marqués avec i18n / $localize, extraits avec ng extract-i18n, et un build optimisé par locale sans recherche de clé à l’exécution. Une bibliothèque d’exécution seulement quand changer de langue sans rechargement est une exigence produit, ou que les traductions viennent d’un CMS sans déploiement.

    Puis allez plus loin

    • Changer de langue avec l’i18n intégrée implique un rechargement, même avec loadTranslations() à l’exécution, car les textes des templates sont évalués à la création des composants.
    • Les pluriels exigent l’ICU, pas n === 1 ? … : … : le roumain a one, few et other (« 20 de fișiere »). Ne concaténez jamais des phrases — l’ordre des mots diffère d’une langue à l’autre.
    • Des ids personnalisés (@@cart.title) pour que les traductions survivent aux modifications de la source ; dates, nombres et devises via des pipes avec LOCALE_ID et registerLocaleData.
    • Il s’adapte au SSR/prerender (une variante par locale, choisie par l’URL), et avec l’hydration vous ajoutez withI18nSupport(). Commencez dès le premier jour : ajouter l’i18n plus tard, c’est traquer chaque chaîne codée en dur.

    Signal d’alerte

    Des formats codés en dur comme 'dd/MM/yyyy' ou toFixed(2) + ' lei', la concaténation de chaînes pour les phrases, et aucun id de message personnalisé.

  19. S-19Votre entreprise construit son propre design system en Angular. Comment faire de l’accessibilité une propriété du système plutôt que de chaque écran ? Accessibilité

    À dire en premier

    Construisez les patterns interactifs sur Angular Aria (@angular/aria, depuis la v21) : des directives headless comme Tabs, Menu, Combobox, Listbox, Tree ou Grid qui fournissent les bons rôles ARIA, la navigation au clavier et la gestion du focus sans aucun style, pendant que l’équipe garde la main sur le CSS. Ainsi chaque équipe produit obtient l’accessibilité par défaut au lieu de réimplémenter à la main des onglets et des menus déroulants.

    Puis allez plus loin

    • Choix de la couche : Aria pour un design system sur mesure, CDK pour les primitives de bas niveau (overlay, drag & drop, utilitaires a11y), Material quand on veut rapidement une UI toute faite.
    • Stylez selon l’état ARIA que posent les directives ([aria-selected='true']), pour que l’état visuel et l’état accessible ne puissent pas diverger.
    • Compromis ailleurs : le virtual scroll retire les lignes hors écran du DOM (Ctrl+F et les lecteurs d’écran ne les voient pas) ; préférez la pagination là où l’accessibilité complète compte.
    • Processus : vérifications au clavier et au lecteur d’écran dans la definition of done, contrôles automatisés en CI, et revue a11y intégrée au contrat du design system.

    Signal d’alerte

    « On ajoute les attributs aria-* à la fin, avant la release » — l’accessibilité traitée comme une checklist finale plutôt que comme une propriété des composants.

  20. S-20Vous pilotez la mise à niveau d’une grosse application d’Angular 21 vers 22. Quels breaking changes vous inquiètent, et comment déployez-vous ? Architecture

    À dire en premier

    Une version majeure à la fois avec ng update @angular/core@22 @angular/cli@22, en laissant les migrations s’exécuter — p. ex. elles ajoutent ChangeDetectionStrategy.Eager là où les composants n’avaient pas de stratégie, afin que le comportement reste identique alors qu’OnPush devient le défaut. Le vrai risque se trouve dans les changements d’exécution que les migrations ne voient pas entièrement : le backend fetch, l’héritage des paramètres du routeur et la sémantique des templates.

    Puis allez plus loin

    • HTTP : fetch est le backend par défaut et withFetch() est déprécié ; la progression d’upload nécessite withXhr(), et reportProgress devient reportUploadProgress / reportDownloadProgress.
    • Routeur et templates : paramsInheritanceStrategy: 'always' (les routes enfants héritent des paramètres et des données de tous les parents), ?. renvoie undefined comme en TypeScript, un compilateur plus strict (inputs liés deux fois, data-* ne lie plus d’inputs), CanMatchFn nécessite currentSnapshot.
    • Plateforme et suppressions : TypeScript 6 et pas de Node 20 ; ComponentFactoryResolver, ComponentFactory et provideRoutes ont disparu ; pour le SSR, configurez allowedHosts et retirez withIncrementalHydration().
    • Déploiement : une branche de mise à niveau maintenue au vert en CI, bibliothèques et Node/TS mis à jour d’abord, e2e sur les parcours critiques, puis un plan de suivi pour retirer Eager fonctionnalité par fonctionnalité au lieu de le traiter comme permanent.

    Signal d’alerte

    Modifier les versions de package.json à la main et sauter ng update, ou enjamber plusieurs versions majeures d’un coup sans lire le changelog.

Comment bien répondre

Ce qui convainc
  • Une phrase claire d’abord, puis les détails seulement si on vous les demande.
  • Un exemple concret tiré d’un projet : « on avait X, on a choisi Y parce que Z ».
  • Nommer le compromis et le cas où vous choisiriez autrement.
  • « Je ne sais pas, mais voici comment je le découvrirais » quand vous ne savez vraiment pas.
Ce qui fait perdre des points
  • Réciter des définitions sans dire quand vous les utiliseriez.
  • Présenter de vieilles réponses comme actuelles (des NgModules partout, Zone.js comme une évidence).
  • « Ça dépend » sans dire de quoi.
  • Inventer une API pour éviter le silence.

Chap. 01 · 0 sur 4 terminées

Leçon terminée ?

Marquez-la : elle apparaîtra sur votre carte, dans le sommaire et sur la page d’accueil.

Se marque toute seule une fois la leçon lue en entier.

  1. Nouveautés d’Angular 21 & 22
  2. Votre première application, pas à pas
  3. Décisions rapides
  4. Questions d’entretien Angular
Voir toute votre carte

Les 75 pages · tapez pour filtrer

  • 01Nouveautés d’Angular 21 & 22Tous les changements importants de la v21 et de la v22, en bref, avec un lien vers la page qui explique chacun.Départ
  • 01Votre première application, pas à pasUne liste de lecture construite d’un composant vide jusqu’à une application terminée — signals, @for, inputs et outputs, un service et un formulaire, pas à pas.Départ
  • 01Décisions rapides« Où mettre ça ? », « Signal ou Observable ? », « Shared ou feature ? » — des réponses courtes aux questions de tous les jours.Départ
  • 01Questions d’entretien Angular60 vraies questions d’entretien pour profils junior, confirmé et senior, avec la réponse à donner, la relance et les signaux d’alerte. Plus un entretien blanc.Départ
  • 02Standalone et bootstrapComment une application démarre sans NgModule : bootstrapApplication, app.config.ts, imports dans le composant.Fondamentaux modernes
  • 02Data binding et événementsInterpolation, bindings de propriétés et d’attributs, événements avec $event et filtres de touches, et comment [( )] est construit — ce qui entre dans le DOM et ce qui en revient.Fondamentaux modernes
  • 02Control Flow : @if, @for, @switchLa syntaxe de blocs qui remplace *ngIf/*ngFor/ngSwitch, avec track, @empty et vérifications d’exhaustivité.Fondamentaux modernes
  • 02@let et les nouvelles expressions de templateVariables locales avec @let, fonctions fléchées, spread, regex, instanceof, commentaires dans les balises.Fondamentaux modernes
  • 02@defer — Lazy Loading dans les templatesChargez les composants lourds uniquement quand c’est nécessaire : triggers, prefetch, placeholder, loading, error.Fondamentaux modernes
  • 02Bindings de classe et de stylePourquoi [class.x] et [style.x] ont remplacé ngClass et ngStyle (avec migration automatique depuis la v21).Fondamentaux modernes
  • 02Styles des composants et encapsulation:host, les trois modes d’encapsulation, les thèmes avec des variables CSS, et pourquoi ::ng-deep est un dernier recours.Fondamentaux modernes
  • 02Pipes : quand et commentPipes pures vs impures, pipes personnalisées, et comment choisir entre une pipe, computed() et une méthode.Fondamentaux modernes
  • 03signal() et computed()Les primitives réactives : état modifiable, état dérivé, égalité, untracked et règles d’immutabilité.Signals et réactivité
  • 03effect() — quand OUI, quand NONEffets de bord synchronisés avec les Signals : localStorage, logs, API impératives. Et leurs pièges.Signals et réactivité
  • 03linkedSignal() — état dérivé modifiableUn signal qui se réinitialise quand sa source change, mais que vous pouvez aussi modifier à la main.Signals et réactivité
  • 03resource, rxResource, httpResourceDes données asynchrones sous forme de Signals : status, value, error, reload, annulation automatique. Stable depuis la v22.Signals et réactivité
  • 03debounced() — signals différésLa version « debounce » d’un signal, sans RxJS. Parfait pour la recherche.Signals et réactivité
  • 03Listes : rechercher, filtrer, trier, paginerUne chaîne de signals computed qui ne recalcule que ce qui a changé, avec les filtres dans l’URL pour qu’un lien rouvre la même vue.Signals et réactivité
  • 03Les Observables depuis zéroCe qu’est un Observable, subscribe et unsubscribe, les opérateurs que vous utilisez chaque semaine, froid ou chaud — et quand un signal suffit.Signals et réactivité
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — et quand RxJS reste le bon choix.Signals et réactivité
  • 04input() et output()Les inputs comme Signals (required, transform, alias) et les outputs typés. Les données descendent, les événements remontent.Composants : communication
  • 04model() — binding bidirectionnelLe « banana-in-a-box » [( )] avec une seule API. Quand l’utiliser, et quand input + output est préférable.Composants : communication
  • 04viewChild et contentChildLes queries comme Signals : viewChild, viewChildren, contentChild, contentChildren, l’option read.Composants : communication
  • 04Lifecycle et DestroyRefQuels hooks comptent encore à l’ère des Signals, afterNextRender, DestroyRef et ngOnChanges typé.Composants : communication
  • 04Projection de contenung-content avec select, plusieurs slots, contenu par défaut et le piège de la projection conditionnelle.Composants : communication
  • 04ng-template et ngTemplateOutletLes templates comme valeurs : les render props en Angular, contexte typé et personnalisation des composants.Composants : communication
  • 05Smart vs DumbContainer vs présentation : qui détient l’état, qui se contente d’afficher, et comment bien les séparer.Conception des composants
  • 05Composants génériques <T>Un DataTable<T> ou un Select<T> correctement typé, avec des templates pour les cellules.Conception des composants
  • 05Composants dynamiquesNgComponentOutlet, createComponent avec inputBinding/outputBinding, et un tableau de bord construit à partir d’une configuration.Conception des composants
  • 05Directives et composition d’hôteLes directives d’attribut, les métadonnées host: {} et la Directive Composition API (hostDirectives).Conception des composants
  • 05Contrôles de formulaire personnalisésFormValueControl (Signal Forms) vs ControlValueAccessor : une notation par étoiles, dans les deux variantes.Conception des composants
  • 05Change DetectionOnPush est la valeur par défaut depuis la v22, Zone.js a disparu depuis la v21. Ce qui déclenche le rendu et comment éviter les mauvaises surprises.Conception des composants
  • 06inject() et @Service()Le nouveau décorateur @Service() de la v22, inject() vs le constructeur, et le contexte d’injection.Dependency Injection
  • 06Providers et hiérarchie des injecteursRoot vs route vs composant, useClass/useValue/useFactory/useExisting, et les modificateurs optional/self/skipSelf.Dependency Injection
  • 06InjectionToken et provideX()Configuration typée, fonctions provideFeature(), makeEnvironmentProviders et initializers.Dependency Injection
  • 06injectAsync() — services en Lazy LoadingChargez un service lourd uniquement quand vous en avez besoin, avec prefetch à l’idle.Dependency Injection
  • 07Structure des dossierscore / shared / features, ce que contient chacun, et à quoi ressemble l’intérieur d’une feature.Architecture d’application
  • 07Shared vs featureQuand déplacer un composant dans shared et (surtout) quand NE PAS le faire. Une checklist concrète.Architecture d’application
  • 07Interfaces et modèlesInterface en ligne ou fichier séparé ? DTO vs modèle de domaine, type vs interface, où les ranger.Architecture d’application
  • 07State Management avec les SignalsStores simples à base de Signals, facades, état local vs global, et quand NgRx devient nécessaire.Architecture d’application
  • 07Guide de style et nommageLes conventions officielles actuelles : noms de fichiers sans suffixes, protected, readonly, ordre des membres.Architecture d’application
  • 07Barrels, alias et frontièresindex.ts, alias de chemins, API publique d’une feature et règles de dépendances vérifiables automatiquement.Architecture d’application
  • 07Erreurs : @boundary et ErrorHandlerLe nouveau bloc @boundary de la v22.2, l’ErrorHandler global, les erreurs HTTP et les erreurs de resource.Architecture d’application
  • 08Routing moderne et Lazy LoadingloadComponent, routes enfants, paramètres comme inputs et héritage des paramètres (le nouveau comportement par défaut de la v22).Routing
  • 08Guards et resolverscanActivate, canMatch, canDeactivate et resolve sous forme de fonctions, avec redirections via UrlTree / RedirectCommand.Routing
  • 09Signal Forms — les basesDes formulaires bâtis sur les Signals : form(), [formField], validation par schéma. Stable depuis la v22.Formulaires
  • 09Signal Forms — avancéValidation personnalisée et inter-champs, async/HTTP avec debounce, listes dynamiques, désactivé/masqué, schémas réutilisables.Formulaires
  • 09Reactive Forms typésFormGroup typé, nonNullable, FormArray — et leur cohabitation avec les Signal Forms.Formulaires
  • 10HttpClient, interceptors et couche d’APIHttpClient sans configuration (v21), Fetch par défaut (v22), interceptors fonctionnels et une couche d’API avec mapping des DTO.HTTP et données
  • 11Performance — une checklistCe qui compte vraiment : OnPush + Signals, track, @defer, routes en Lazy Loading, images, bundles et Hydration.Qualité et performance
  • 11Tests avec VitestVitest est le runner par défaut depuis la v21. TestBed, composants avec Signals, services, HTTP et harnesses.Qualité et performance
  • 11DevTools : regarder dans votre applicationChrome DevTools et Angular DevTools pas à pas : ce que montre chaque panneau, avec des exercices qui fonctionnent sur ce site.Qualité et performance
  • 11Angular AriaDes composants headless et accessibles que vous stylez comme vous voulez : tabs, accordion, menu, listbox...Qualité et performance
  • 12SSR, Prerendering et modes de renduQuand rendre sur le serveur, quand au build et quand uniquement dans le navigateur : RenderMode par route, app.routes.server.ts et les coûts réels.SSR et Hydration
  • 12Hydration : event replay et incrémentaleComment le navigateur reprend le HTML du serveur sans le re-rendre : Hydration complète, event replay, @defer (hydrate on …) et erreurs de mismatch.SSR et Hydration
  • 12Code compatible serveurwindow n’existe pas sur le serveur : PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, le cache HTTP et PendingTasks.SSR et Hydration
  • 13Micro frontendsQuand découper une application en plusieurs déploiements vaut le coup, et quand c’est juste de la complexité : Native Federation, Module Federation, iframes, Web Components.Architecture à grande échelle
  • 13Web Components avec Angular ElementscreateCustomElement : un composant Angular utilisé comme balise HTML n’importe où, plus la communication entre applications via des événements et BroadcastChannel.Architecture à grande échelle
  • 13Monorepo, bibliothèques et frontièresUn workspace avec plusieurs applications et bibliothèques : ng generate library, Nx, points d’entrée secondaires, publication et règles de dépendances.Architecture à grande échelle
  • 13Architecture hexagonale (ports et adapters)Un domaine sans Angular, des ports sous forme de classes abstraites et des adapters interchangeables : HTTP, localStorage ou en mémoire, sans toucher à l’UI.Architecture à grande échelle
  • 14Les Signals sous le capotLe graphe réactif : producteurs et consommateurs, push-pull, pourquoi computed est lazy et sans glitch, le problème du diamant et quand les effects s’exécutent.Réactivité avancée
  • 14RxJS avancé : opérateurs d’ordre supérieurswitchMap, mergeMap, concatMap et exhaustMap sur un diagramme interactif, plus retry avec backoff, shareReplay et comment éviter les fuites.Réactivité avancée
  • 14NgRx SignalStoreState Management à grande échelle sur les Signals : withState, withComputed, withMethods, rxMethod, entités et hooks — et quand un store simple suffit.Réactivité avancée
  • 14Machines à états avec les signalsFini les états impossibles : une machine typée sur des signals, avec transitions, gardes et un diagramme d’états en direct.Réactivité avancée
  • 15WebSockets : un temps réel qui résiste au réseauUn socket typé sous forme de signals : reconnexion avec backoff et jitter, heartbeat, file d’attente hors ligne et sécurité SSR.Temps réel et hors ligne
  • 15Mises à jour optimistes et rollbackUne UI instantanée qui reste honnête : snapshot et rollback, réponses dans le désordre, clés d’idempotence.Temps réel et hors ligne
  • 15Offline-first : Service Worker et cacheLe Service Worker d’Angular : stratégies de cache, annonce des nouvelles versions et file d’attente pour les modifications faites hors ligne.Temps réel et hors ligne
  • 16Web WorkersDes calculs lourds sans bloquer l’UI : un vrai worker dans la page, l’échange de messages et quand ça n’en vaut pas la peine.UI avancée et performance
  • 16Listes géantes : virtual scroll100 000 lignes sans latence : le virtual scroll du CDK, itemSize, trackBy et les alternatives (pagination, scroll infini).UI avancée et performance
  • 16Animations modernes et View Transitionsanimate.enter et animate.leave à la place de @angular/animations (déprécié), transitions CSS et View Transitions à la navigation.UI avancée et performance
  • 16Fuites mémoire : les trouver et les corrigerAbonnements, timers et listeners qui survivent à leur composant — comment les repérer dans les DevTools et les fermer.UI avancée et performance
  • 17Sécurité : XSS, sanitisation et CSPCe qu’Angular fait automatiquement contre le XSS, quand bypassSecurityTrust devient dangereux, plus la CSP avec nonce, Trusted Types et XSRF.Sécurité et internationalisation
  • 17Authentification : tokens et refreshUn interceptor qui ajoute le token, le renouvelle une seule fois sur une 401 et rejoue les requêtes en attente — plus où conserver le token.Sécurité et internationalisation
  • 17OAuth 2.1 et OpenID Connect avec PKCELe flux authorization code avec PKCE, pas à pas : verifier et challenge, state et nonce, l’ID token, et pourquoi un BFF est plus sûr.Sécurité et internationalisation
  • 17Internationalisation (i18n)$localize, l’attribut i18n, ICU plural/select, un build par langue vs des traductions à l’exécution, et le formatage selon la locale.Sécurité et internationalisation