Préparation d'entretien 100% Gratuit angularforall.com

Entretien Angular Senior — OnPush, SSR et Architecture

Angular Senior Quiz-Angular-Senior Onpush Zoneless Angular-Ssr Hydratation Injection-Hierarchique Aot-Compilation Qcm-Angular-Avance Architecture-Angular Tree-Shaking Performance-Angular Angular-17
Sénior 🔀 Mixte 20 questions ⏱ 15 min
📝

Entretien Angular Senior — OnPush, SSR et Architecture

20 questions avancées Angular 17 : détection OnPush/zoneless, SSR et hydratation, injection hiérarchique, signals internes, AOT, tree-shaking, microfrontend et optimisation de performance.

20 questions ⏱ ~15 min Niveau Sénior

Partager

Banque de révision : les 60 questions corrigées

Voici l'intégralité des 60 questions de « Entretien Angular Senior — OnPush, SSR et Architecture », chacune avec sa bonne réponse et son explication. Le quiz interactif ci-dessus en tire une sélection aléatoire à chaque tentative — utilisez cette liste complète pour réviser.

  • Le composant n'est vérifié que lorsque ses @Input() changent de référence ou qu'un événement émane du composant ou de ses enfants (bonne réponse)
  • Le composant n'est jamais vérifié automatiquement, il faut appeler detectChanges() manuellement
  • Le composant est vérifié à chaque cycle de détection mais ignore les modifications profondes des objets
  • Le composant est vérifié uniquement lors du ngOnInit()
Explication : Avec ChangeDetectionStrategy.OnPush, Angular ne vérifie le composant que dans les cas suivants : 1) une des @Input() change de référence (nouvel objet), 2) un événement (click, etc.) se produit dans le composant ou un enfant, 3) un Observable lié avec le pipe async émet une nouvelle valeur, 4) markForCheck() est appelé manuellement.
Utilisez ChangeDetectorRef.markForCheck() plutôt que detectChanges() pour signaler qu'un composant OnPush doit être vérifié au prochain cycle.

  • markForCheck() marque le composant et ses ancêtres comme devant être vérifiés au prochain cycle ; detectChanges() exécute une détection immédiate et synchrone (bonne réponse)
  • Ils font la même chose
  • detectChanges() est plus performant que markForCheck()
  • markForCheck() est réservé aux composants standalone
Explication : markForCheck() signale à Angular que le composant doit être vérifié au prochain cycle normal de détection (qui est asynchrone), remontant jusqu'à la racine. detectChanges() exécute immédiatement une détection complète sur le composant et ses enfants, ce qui peut causer des problèmes de performance si mal utilisé.
Privilégiez markForCheck() dans les applications OnPush. detectChanges() est généralement réservé aux cas de test ou à l'intégration avec du code non-Angular.

  • Chaque composant a son propre injecteur qui remonte vers le parent jusqu'à l'injecteur racine (root injector) (bonne réponse)
  • Tous les composants partagent le même injecteur global
  • L'injection ne fonctionne pas avec les composants standalone
  • Seuls les services avec providedIn: "root" sont disponibles
Explication : Angular maintient une hiérarchie d'injecteurs parallèle à la hiérarchie des composants. Chaque composant peut avoir son propre injecteur (via providers: []). La résolution remonte de l'injecteur du composant vers le parent jusqu'à l'injecteur racine. Cela permet d'avoir des instances différentes d'un même service pour différentes branches de l'application.
Utilisez @SkipSelf() pour ignorer l'injecteur courant, @Optional() pour rendre une dépendance optionnelle, ou @Self() pour ne chercher que dans l'injecteur courant.

  • ViewContainerRef est un conteneur qui peut héberger des vues ; TemplateRef représente un template inline qu'on peut instancier (bonne réponse)
  • Ils sont interchangeables
  • TemplateRef est pour les composants, ViewContainerRef pour les directives
  • ViewContainerRef ne fonctionne qu'avec ng-template
Explication : ViewContainerRef est un conteneur qui peut contenir une ou plusieurs vues (composants ou templates embarqués). TemplateRef représente un template <ng-template> qui peut être instancié dans un ViewContainerRef. Pour créer un composant dynamiquement, on utilise ViewContainerRef.createComponent().
Exemple d'utilisation : const viewContainerRef = this.viewContainerRef; puis const componentRef = viewContainerRef.createComponent(MonComponent);

  • Le service n'est inclus dans le bundle que s'il est effectivement injecté quelque part dans l'application (bonne réponse)
  • Le service est toujours inclus dans le bundle principal
  • Le tree-shaking ne fonctionne pas avec les services Angular
  • providedIn: "root" empêche le tree-shaking
Explication : Avec providedIn: "root", Angular peut analyser statiquement l'application pour détecter si le service est utilisé. Si aucune injection n'est trouvée, le service est exclu du bundle final par le tree-shaking. C'est pourquoi cette approche est préférable aux providers: [] dans les modules.
Pour maximiser le tree-shaking, évitez d'exporter des services depuis des modules. Préférez providedIn: "root" ou providedIn: "platform".

  • Un signal est un graphe acyclique dirigé (DAG) où chaque signal maintient une liste de ses dépendants (consumers) et notifie les changements sans cycles (bonne réponse)
  • Un signal est un simple wrapper autour d'une valeur avec Zone.js
  • Les signals utilisent un système de dirty checking comme dans AngularJS
  • Les signals ne garantissent pas la cohérence des mises à jour
Explication : Les signals implémentent un graphe acyclique dirigé. Chaque signal garde la liste de ses "consumers" (les computed signals ou effets qui en dépendent). Quand un signal source change, il notifie ses consumers, qui se marquent comme "dirty" et se recalculeront à la demande. Le système garantit l'absence de cycles (d'où la Directed Acyclic Graph).
Contrairement à Zone.js qui vérifie tout, les signals permettent des mises à jour O(1) pour la propagation des changements.

  • Utiliser takeUntil avec un Subject qui se complète dans ngOnDestroy
  • Utiliser unsubscribe() manuellement dans ngOnDestroy pour chaque subscription
  • Utiliser le pipe async dans le template
  • Toutes ces réponses sont valides selon le contexte (bonne réponse)
Explication : Les trois méthodes sont correctes selon le contexte. takeUntil est pratique pour plusieurs subscriptions. unsubscribe() manuel est simple pour peu de subscriptions. Le pipe async est idéal car il gère automatiquement la désinscription dans le template. Le choix dépend de la complexité et des préférences de l'équipe.
La meilleure pratique moderne : préférez le pipe async dans les templates, et pour la logique complexe, utilisez takeUntilDestroyed() (Angular 16+) qui automatise la désinscription.

  • Un opérateur RxJS qui désinscrit automatiquement l'Observable lors de la destruction du composant (bonne réponse)
  • Une fonction qui détruit tous les Observables d'un composant
  • Un remplacement de ngOnDestroy
  • Une fonction qui empêche la création d'Observables
Explication : takeUntilDestroyed() est un opérateur RxJS qui se lie au cycle de vie du composant (ou de l'injecteur). Il émet automatiquement une notification de complétion quand le composant est détruit, provoquant la désinscription. Exemple : myObservable$.pipe(takeUntilDestroyed()).subscribe().
Pour l'utiliser, importez takeUntilDestroyed depuis @angular/core/rxjs-interop. Il nécessite d'être exécuté dans un contexte d'injection (constructeur ou fonction inject()).

  • Exécuter Angular sans Zone.js, en utilisant les signals pour déclencher la détection de changement, ce qui améliore les performances (bonne réponse)
  • Une version sans zones d'affichage
  • La suppression de toutes les animations
  • Un mode de débogage
Explication : Le mode "zoneless" (expérimental dans Angular 17) permet d'exécuter Angular sans Zone.js. Au lieu d'intercepter toutes les opérations asynchrones, la détection de changement est déclenchée explicitement par les signals, les événements du template, les Observables avec async, ou markForCheck(). Cela réduit la surcharge CPU et améliore les performances.
Activez-le avec provideExperimentalZonelessChangeDetection() dans app.config.ts. Attention : tout code qui utilise Zone.js de manière implicite devra être adapté.

  • O(n) où n est le nombre de composants, mais Angular 17 peut ignorer les branches non affectées grâce à un meilleur marquage (bonne réponse)
  • O(n²) dans les deux versions
  • O(log n) dans Angular 17
  • O(1) dans Angular 17
Explication : L'algorithme classique parcourt tout l'arbre des composants, soit O(n) où n est le nombre de composants. Angular 17 a introduit des optimisations qui permettent de sauter des branches entières de l'arbre si aucun changement n'est attendu (grâce aux signals et à un meilleur marquage), réduisant la complexité effective dans la plupart des cas.
Cette optimisation est automatique. Pour en bénéficier pleinement, utilisez OnPush et les signals.

  • output() crée un OutputRef qui peut être utilisé avec des Observables, plus flexible et compatible avec les signals (bonne réponse)
  • C'est exactement la même chose qu'EventEmitter, juste un alias
  • output() ne peut émettre que des valeurs synchrones
  • EventEmitter est déprécié au profit de output()
Explication : La nouvelle API output() (expérimentale dans Angular 17) retourne un OutputRef qui expose un Observable. Elle est plus flexible car elle peut être connectée à des signals et permet une meilleure intégration avec la réactivité moderne. Elle n'est pas identique à EventEmitter, mais EventEmitter reste supporté.
Exemple : deleteItem = output<number>(); puis this.deleteItem.emit(id); La syntaxe d'écoute reste la même : (deleteItem)="onDelete($event)".

  • Le routeur utilise l'importation dynamique de JavaScript (import()) et Webpack split chunks. Optimiser avec preloading stratégies (PreloadAllModules, ou personnalisée avec avecPreloading) (bonne réponse)
  • Le routeur charge tout au démarrage
  • Le lazy loading est automatique sans possibilité d'optimisation
  • Le routeur utilise des iframes
Explication : Le routeur utilise import() pour charger dynamiquement les chunks via Webpack/Vite. On peut optimiser avec des stratégies de préchargement : PreloadAllModules (précharge tout après le chargement initial), ou des stratégies personnalisées avec withPreloading pour précharger sélectivement certaines routes.
Exemple de stratégie personnalisée : précharger uniquement les routes les plus probables après un délai ou une interaction utilisateur.

  • Le HTML serveur est comparé avec ce qu'Angular attend de générer ; une mismatch peut causer des erreurs ou une perte d'état utilisateur (bonne réponse)
  • L'hydration recrée complètement le DOM à partir de zéro
  • L'hydration ignore le HTML serveur
  • L'hydration ne fonctionne qu'en développement
Explication : L'hydration compare le HTML existant (généré par SSR) avec ce qu'Angular attend de générer. En cas de mismatch (ex: contenu dynamique différent entre serveur et client), Angular peut soit réessayer (mode non-strict) soit lever une erreur (mode strict). Les pièges courants incluent : données non déterministes (Date.now()), IDs aléatoires, ou dépendances à window/document.
Utilisez ngSkipHydration sur les composants problématiques. Pour les données dynamiques, assurez une génération identique serveur/client.

  • inject() peut être appelé à tout moment dans le contexte d'injection ; le constructeur est appelé lors de l'instanciation de la classe (bonne réponse)
  • inject() est plus lent que le constructeur
  • Le constructeur est appelé après inject()
  • Ils sont strictement équivalents
Explication : Alors que l'injection par constructeur ne peut avoir lieu qu'au moment de l'instanciation de la classe, inject() peut être appelée dans n'importe quelle fonction qui s'exécute dans un contexte d'injection (comme une fonction guard, un resolver, ou même une méthode de classe appelée après la construction, tant qu'elle est dans la pile d'appels d'Angular).
Attention : inject() ne peut pas être appelée dans une fonction qui s'exécute en dehors du contexte d'injection (ex: setTimeout, Promise.then sans garder le contexte).

  • useFactory: (dep1, dep2) => new MonService(dep1, dep2), deps: [Dep1, Dep2] (bonne réponse)
  • factory: (injector) => new MonService(injector.get(Dep1))
  • useValue: (dep1, dep2) => new MonService(dep1, dep2)
  • provideFactory: MonService, deps: [Dep1, Dep2]
Explication : La syntaxe correcte pour un factory provider est : { provide: MonService, useFactory: (dep1, dep2) => new MonService(dep1, dep2), deps: [Dep1, Dep2] }. Le tableau deps indique à Angular quelles dépendances injecter dans la fonction factory.
Les factory providers sont utiles quand la création du service nécessite une logique complexe ou dépend de services non disponibles via providedIn.

  • Angular détecte les cycles lors de la compilation et lève une erreur. Solutions : forwardRef() pour les références anticipées, ou restructuration du code (bonne réponse)
  • Angular résout automatiquement tous les cycles
  • Les cycles sont ignorés silencieusement
  • forwardRef() est la seule solution
Explication : Angular peut détecter les dépendances circulaires au moment de la compilation (notamment via les tokens d'injection). La solution rapide est forwardRef(() => Classe) qui permet de référencer une classe avant sa définition. La meilleure solution est de restructurer le code pour éliminer le cycle (ex: introduire un service intermédiaire, utiliser un Subject).
Exemple : providers: [{ provide: Parent, useExisting: forwardRef(() => ParentComponent) }]. Utilisez forwardRef avec parcimonie.

  • ViewChildren est disponible après ngAfterViewInit ; ContentChildren après ngAfterContentInit (bonne réponse)
  • ViewChildren est disponible immédiatement dans ngOnInit
  • ContentChildren est disponible dans le constructeur
  • Ils sont disponibles au même moment
Explication : @ViewChildren interroge les éléments du template du composant (vue). Il n'est disponible qu'après que la vue a été initialisée, donc dans ngAfterViewInit. @ContentChildren interroge les éléments projetés via <ng-content> et n'est disponible qu'après l'initialisation du contenu projeté, soit dans ngAfterContentInit.
Pour accéder aux enfants projetés, utilisez ContentChildren. Pour les enfants directement dans le template, utilisez ViewChildren.

  • Les intercepteurs s'exécutent dans l'ordre de leur déclaration (providers) pour la requête, et dans l'ordre inverse pour la réponse (bonne réponse)
  • L'ordre est aléatoire
  • Les intercepteurs s'exécutent tous en parallèle
  • Seul le dernier intercepteur déclaré est exécuté
Explication : Les intercepteurs HTTP forment une chaîne. Le premier intercepteur déclaré dans les providers reçoit la requête en premier, peut la transformer, puis appelle next.handle() qui passe au suivant. Quand la réponse revient, elle traverse les intercepteurs dans l'ordre inverse.
Exemple concret : un intercepteur d'authentification doit être déclaré avant un intercepteur de logging pour que le token soit déjà ajouté avant d'être loggé.

  • Angular priorise les microtasks (Promise) sur les macrotasks (setTimeout), et les tâches de rendu sur les autres opérations (bonne réponse)
  • Toutes les tâches ont la même priorité
  • Angular n'a pas de système de scheduling
  • Les tâches asynchrones sont bloquées
Explication : Angular utilise l'event loop du navigateur. Il traite d'abord toutes les microtasks (Promises, observables synchrones), puis une tâche de macrotask (setTimeout, I/O), puis le rendu. Les changements détectés dans une microtask sont rendus immédiatement, tandis que ceux dans une macrotask attendent le prochain cycle.
Pour optimiser, utilisez Promise.resolve() pour passer en microtask, ou setTimeout pour différer à la prochaine macrotask.

  • Implémenter l'interface ControlValueAccessor avec writeValue, registerOnChange, registerOnTouched, setDisabledState (bonne réponse)
  • Étendre AbstractControl
  • Utiliser ngModel directement
  • Impossible d'intégrer des composants tiers
Explication : Pour qu'un composant personnalisé fonctionne avec les formulaires Angular (template-driven ou réactifs), il doit implémenter ControlValueAccessor. Les méthodes : writeValue() pour recevoir la valeur du formulaire, registerOnChange() pour notifier Angular des changements, registerOnTouched() pour marquer comme "touché", et optionnellement setDisabledState().
Enregistrez votre composant avec NG_VALUE_ACCESSOR dans les providers. Exemple : providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MonComposant), multi: true }].

  • full: l'URL doit correspondre exactement ; prefix: l'URL doit commencer par le path (bonne réponse)
  • full: correspond toujours ; prefix: ne correspond jamais
  • full est pour les routes avec paramètres ; prefix pour les routes statiques
  • Ils sont identiques
Explication : pathMatch: "full" signifie que l'URL complète doit correspondre exactement au chemin (utile pour la route vide ""). pathMatch: "prefix" (par défaut) signifie que l'URL peut commencer par le chemin et avoir des segments supplémentaires. Exemple : pour "", "full" capture tout ; pour "users", "prefix" capturerait "users" ET "users/123".
Utilisez toujours pathMatch: "full" pour la route racine { path: "", component: HomeComponent } sinon elle capturerait toutes les URLs.

  • Le resolver précharge des données avant d'activer la route ; il bloque la navigation jusqu'à résolution, ce qui peut créer un délai visible (bonne réponse)
  • Le resolver charge les données après l'activation de la route
  • Le resolver annule la navigation si les données ne sont pas disponibles
  • Le resolver fonctionne uniquement avec des Observables
Explication : Un resolver (resolve dans la configuration de route) charge des données avant que la route ne soit activée. La navigation est bloquée jusqu'à ce que toutes les données soient résolues. L'avantage est que les données sont disponibles immédiatement dans le composant. L'inconvénient est un délai perceptible par l'utilisateur si les données sont lentes à charger.
Pour une meilleure UX, préférez charger les données dans le composant avec un loading spinner plutôt que d'utiliser un resolver pour des données lentes.

  • Zone.js intercepte trop d'événements (animation frame, WebSocket, etc.) causant des cycles de détection inutiles. Solution : utiliser NgZone.runOutsideAngular() pour les opérations lourdes (bonne réponse)
  • Une pollution de l'espace mémoire
  • Une erreur de compilation
  • Un problème de style CSS
Explication : Zone.js patche de nombreuses APIs navigateur (setTimeout, addEventListener, etc.) pour savoir quand déclencher la détection de changement. Cela peut causer des cycles inutiles pour des opérations fréquentes (animation, WebSocket, scroll). La solution est d'utiliser this.ngZone.runOutsideAngular(() => { ... }) pour exécuter du code sans déclencher la détection.
Exemple typique : les animations dans requestAnimationFrame doivent être exécutées en dehors d'Angular pour éviter des milliers de détections inutiles.

  • Chaque composant standalone a son propre contexte, mais peut importer d'autres composants ; les services sont partagés via l'injecteur racine ou des providers locaux (bonne réponse)
  • Les composants standalone ne peuvent pas partager de services
  • Les services sont automatiquement réinitialisés à chaque lazy loading
  • Les composants standalone ne supportent pas le lazy loading
Explication : Les composants standalone fonctionnent parfaitement avec le lazy loading (loadComponent). Les services avec providedIn: "root" restent des singletons globaux. Les services fournis dans un composant standalone (via providers: []) sont locaux à ce composant et ses enfants, et sont réinitialisés si le composant est détruit (important pour le lazy loading).
Attention : un service fourni dans un composant lazy-loaded sera réinitialisé à chaque chargement du composant, ce qui peut être souhaitable ou non selon le cas.

  • @for avec track obligatoire permet des mises à jour O(k) où k est le nombre d'éléments changés ; *ngFor sans trackBy est O(n) complet (bonne réponse)
  • Les deux sont O(n²)
  • @for est plus lent que *ngFor
  • Il n'y a pas de différence de performance
Explication : Sans trackBy (ou track), Angular doit reconstruire tout le DOM à chaque mise à jour car il ne peut pas identifier les éléments. Avec track obligatoire dans @for, Angular utilise les clés pour identifier les éléments modifiés, ajoutés ou supprimés, permettant des mises à jour O(k) où k est le nombre d'éléments affectés.
Utilisez toujours une clé unique et stable (comme un ID) pour le tracking, jamais l'index sauf pour des listes statiques.

  • multi: true permet d'enregistrer plusieurs providers pour un même token ; Angular les combine dans un tableau (bonne réponse)
  • multi: true remplace le provider précédent
  • multi: true est utilisé pour les providers asynchrones
  • multi: true est une option dépréciée
Explication : Quand multi: true est défini, Angular n'écrase pas les providers précédents pour le même token, mais les accumule dans un tableau. C'est utilisé pour HTTP_INTERCEPTORS : chaque intercepteur est ajouté à la chaîne. Sans multi: true, le dernier intercepteur remplacerait les précédents.
Cas d'usage typique : les intercepteurs HTTP, les guards multiples sur une route, les stratégies de préchargement personnalisées.

  • Les composants dynamiques ne participent pas automatiquement à la détection de changement parent-enfant ; il faut gérer manuellement les @Input et @Output (bonne réponse)
  • Les composants dynamiques ont un cycle de vie différent
  • Les composants dynamiques ne peuvent pas utiliser @Input
  • Il n'y a aucune différence
Explication : Les composants créés dynamiquement sont "orphelins" de la hiérarchie de détection de changement standard. Les changements dans le parent ne déclenchent pas automatiquement la détection dans l'enfant dynamique, et vice-versa. Les @Input() doivent être définis manuellement via componentRef.instance.maProp = valeur. Les @Output() doivent être souscrits manuellement.
Pour reconnecter un composant dynamique à la détection, utilisez ChangeDetectorRef ou passez le Injector parent lors de la création.

  • Angular peut treeshaker les pipes/directives importés mais non utilisés dans le template grâce à l'analyse statique (bonne réponse)
  • Les imports non utilisés sont toujours inclus dans le bundle
  • Le tree-shaking ne fonctionne que pour les services
  • Il faut manuellement supprimer les imports non utilisés
Explication : Lorsque vous importez un pipe ou une directive dans un composant standalone via imports: [], Angular analyse statiquement le template pour déterminer si l'import est effectivement utilisé. Si ce n'est pas le cas, le code de ce pipe/directive n'est pas inclus dans le bundle final. C'est un avantage majeur des composants standalone par rapport aux NgModules.
Cette analyse statique est possible car le template est analysé au build time (AOT compilation).

  • inject() ne peut être appelée que dans un contexte où l'injecteur est disponible (constructeur, fonction d'initialisation, factory provider) (bonne réponse)
  • inject() peut être appelée n'importe où, même en dehors d'Angular
  • L'injection context est une erreur de compilation
  • inject() fonctionne uniquement dans les templates
Explication : La fonction inject() nécessite un "contexte d'injection" actif, c'est-à-dire qu'elle doit être appelée pendant l'exécution d'Angular (constructeur, méthode ngOnInit, factory provider, guard, resolver). En dehors de ces contextes (ex: setTimeout, Promise.then, callback d'événement DOM natif), inject() échoue car il n'y a pas d'injecteur disponible.
Pour utiliser inject() dans une fonction asynchrone, capturez les services dans le contexte synchrone d'abord, ou utilisez injector = inject(Injector) puis injector.get() dans le callback.

  • Combiner : lazy loading des routes, décalage des scripts tiers avec @defer, préchargement intelligent, hydration SSR, et optimisation du bundle avec les standalone components (bonne réponse)
  • Utiliser uniquement JIT compilation
  • Désactiver toutes les animations
  • Mettre tout dans le bundle principal
Explication : Pour optimiser les Core Web Vitals : LCP (Largest Contentful Paint) → lazy loading, SSR/hydration, optimisation des images ; FID (First Input Delay) → décaler les scripts tiers avec @defer ; CLS (Cumulative Layout Shift) → réserver l'espace pour le contenu asynchrone avec @placeholder et dimensions explicites. Les standalone components aident au tree-shaking.
Utilisez ng build --optimization, activez le SSR avec Angular Universal, et mesurez avec Lighthouse ou Web Vitals extension.

  • AOT compile les templates au build time, réduisant la taille du bundle et détecter les erreurs de template à la compilation (bonne réponse)
  • AOT est plus lent que JIT en production
  • AOT remplace Webpack uniquement
  • AOT désactive la détection de changement
Explication : L'AOT (Ahead-of-Time) compile les templates Angular en JavaScript optimisé lors du build. Avantages : rendu plus rapide (pas de compilation au runtime), bundle plus petit (pas besoin du compilateur dans le bundle), détection des erreurs de template à la compilation, et meilleure sécurité (les templates ne sont pas évalués dynamiquement).
Depuis Angular 9, Ivy est le moteur de rendu par défaut qui utilise AOT. La différence JIT/AOT ne se voit qu'en développement (ng serve utilise JIT par défaut pour un rebuild plus rapide).

  • Utiliser @defer avec on viewport pour hydrater les composants uniquement quand ils deviennent visibles, combiné avec ngSkipHydration pour les parties statiques (bonne réponse)
  • Désactiver l'hydration sur toute l'application
  • Utiliser serverSideRender: false dans angular.json
  • La partial hydration n'est pas disponible dans Angular 17
Explication : Angular 17 supporte une forme de partial hydration via @defer (on viewport) : les composants hors écran ne sont pas hydratés immédiatement. Combiné avec ngSkipHydration pour les composants purement statiques, cela réduit le JavaScript exécuté lors du chargement initial.
Utilisez ngSkipHydration sur les composants qui produisent le même HTML serveur et client : <app-static-nav ngSkipHydration></app-static-nav>.

  • Permet d'injecter des valeurs primitives, des configs, ou des interfaces sans classe concrète (bonne réponse)
  • Remplace les services Angular
  • Est utilisé uniquement pour les tests
  • Est une fonctionnalité dépréciée
Explication : InjectionToken permet d'injecter n'importe quelle valeur : const API_URL = new InjectionToken<string>("API_URL"). Dans les providers : { provide: API_URL, useValue: "https://api.example.com" }. Dans le composant : apiUrl = inject(API_URL). Idéal pour les configurations et les interfaces TypeScript.
Contrairement à l'injection par classe, les InjectionTokens permettent de définir des abstractions (interfaces) sans couplage à une implémentation spécifique.

  • Angular DevTools extension Chrome : profiler les cycles de détection, visualiser l'arbre des composants, identifier les renders excessifs (bonne réponse)
  • Utiliser uniquement console.log()
  • Utiliser le débogueur du navigateur sans Angular DevTools
  • Il n'y a pas d'outils de diagnostique Angular
Explication : Angular DevTools (extension Chrome) offre : un profiler qui enregistre les cycles de détection de changement, une vue de l'arbre des composants avec leurs états, la possibilité de voir le temps passé dans chaque composant, et des indicateurs de renders excessifs.
En mode dev, activez enableDebugTools(applicationRef) pour accéder à ng.profiler et ng.probe() dans la console.

  • Le préfixe * est un sucre syntaxique qui transforme l'élément en ng-template avec la directive comme binding (bonne réponse)
  • Le * est simplement un style de nommage
  • Le * appelle la méthode createEmbeddedView() automatiquement
  • Les directives structurelles ne fonctionnent qu'avec ng-template
Explication : La syntaxe *maDirective="expression" est transformée par le compilateur en : <ng-template [maDirective]="expression"><élément /></ng-template>. La directive reçoit un TemplateRef et un ViewContainerRef pour créer et détruire des vues avec createEmbeddedView() et clear().
Pour créer une directive structurelle : injectez TemplateRef<any> et ViewContainerRef, puis utilisez vcr.createEmbeddedView(tmpl) ou vcr.clear().

  • Renderer2 est une couche d'abstraction compatible SSR et WebWorkers ; nativeElement est direct mais couplé au navigateur (bonne réponse)
  • Renderer2 est plus rapide que nativeElement
  • nativeElement est la seule façon d'accéder au DOM dans Angular
  • Renderer2 et nativeElement sont identiques
Explication : Renderer2 est une abstraction du DOM qui fonctionne dans tous les environnements Angular (navigateur, SSR avec Angular Universal, Web Workers). L'accès direct via nativeElement est plus simple mais rompt la compatibilité SSR car window, document et le DOM ne sont pas disponibles côté serveur.
Dans les directives : utilisez this.renderer.setStyle(el, "color", "red") plutôt que el.style.color = "red" pour maintenir la compatibilité SSR.

  • Un service fourni dans un composant lazy-loadé crée une instance scope au chunk chargé, détruite quand le composant est détruit (bonne réponse)
  • Les services sont toujours des singletons globaux
  • Le lazy loading ne fonctionne que pour les composants, pas les services
  • Les services lazy-loadés ne peuvent pas être partagés entre composants
Explication : Quand un service est fourni dans un composant lazy-loadé (providers: [MonService]), il est créé quand la route est activée et détruit quand le composant lazy-loadé est détruit. Cela permet des services scopés à une feature spécifique, avec état propre à chaque visite de la route.
Pour un service scopé à une route entière (y compris ses enfants), utilisez providers dans la configuration de la route parente : { path: "admin", providers: [AdminService], children: [...] }.

  • Une fonction qui retourne un Observable ou une Promise, vérifiable avec un appel HTTP (bonne réponse)
  • Une fonction synchrone qui fait un appel HTTP avec await
  • Utiliser Validators.asyncRequired()
  • Les validateurs asynchrones ne sont pas supportés dans les reactive forms
Explication : Un validateur asynchrone est une fonction qui prend un AbstractControl et retourne un Observable ou une Promise : emailExists = (ctrl: AbstractControl) => this.userService.checkEmail(ctrl.value).pipe(map(exists => exists ? { emailExists: true } : null)). Il s'ajoute en 3ème argument : new FormControl("", [Validators.email], [this.emailExists]).
Ajoutez debounceTime(300) dans votre validateur async pour éviter des requêtes à chaque frappe. Affichez un état "pending" : form.get("email")?.status === "PENDING".

  • Une API qui permet de tester des composants UI de manière stable, indépendante des détails d'implémentation HTML (bonne réponse)
  • Un plugin de testing Jasmine
  • Une façon de mocker les composants enfants
  • Un type de fixture spécial pour les tests d'intégration
Explication : Un Component Harness est une classe qui expose une API de test stable pour un composant. Au lieu d'interroger le DOM directement (fragile), vous utilisez l'API harness. Angular Material fournit des harnesses pour tous ses composants. Vous pouvez créer les vôtres en étendant ComponentHarness.
Exemple : const buttonHarness = await loader.getHarness(MatButtonHarness.with({ text: "Submit" })); await buttonHarness.click(); — plus stable que fixture.nativeElement.querySelector("button").click().

  • input() crée un InputSignal en lecture seule dont la valeur est synchronisée automatiquement avec le @Input() passé par le parent (bonne réponse)
  • input() remplace complètement @Input() dans Angular 17
  • input() crée un signal bidirectionnel avec le parent
  • input() est une fonction qui émet des events vers l'extérieur
Explication : La nouvelle API input() crée un InputSignal : protected userId = input<number>(). La valeur est automatiquement mise à jour par Angular quand le parent change le binding. Lecture : this.userId(). Elle peut être utilisée dans des computed().
Différences avec @Input() : l'InputSignal est en lecture seule (pas d'assignation directe), change-detection est plus précise avec signals, et il s'intègre naturellement avec computed/effect.

  • platform-browser bootstrap une application dans un navigateur ; platform-server pour le SSR (rendu côté serveur) (bonne réponse)
  • platform-server est obsolète dans Angular 17
  • platform-browser est pour la production, platform-server pour le développement
  • Ce sont des alias du même package
Explication : @angular/platform-browser fournit les primitives pour lancer Angular dans un navigateur DOM. @angular/platform-server fournit les primitives pour le SSR en Node.js (Angular Universal). Les deux partagent le même runtime Angular, mais avec des implémentations différentes des services DOM (DomAdapter).
Dans une app SSR Angular 17+, main.ts utilise bootstrapApplication avec platform-browser, et main.server.ts utilise @angular/platform-server.

  • Importer uniquement les composants/directives/pipes nécessaires, utiliser @defer pour les composants lourds, et analyser avec ng build --stats-json (bonne réponse)
  • Désactiver le tree-shaking dans webpack.config.js
  • Utiliser NgModules au lieu de standalone components
  • Supprimer les types TypeScript du bundle
Explication : Avec les standalone components, Angular peut tree-shaker agressivement. Pratiques : importer uniquement ce qui est utilisé dans chaque composant (pas de SharedModule massif), utiliser @defer pour les composants rarement vus, analyser le bundle avec ng build --stats-json + webpack-bundle-analyzer.
Exemple d'analyse : ng build --stats-json && npx webpack-bundle-analyzer dist/stats.json pour visualiser la composition du bundle.

  • Module Federation permet à plusieurs applications Angular indépendantes de partager des composants à l'exécution, chargeant des chunks depuis des remotes (bonne réponse)
  • Les microfrontends ne sont pas supportés par Angular
  • Module Federation est une fonctionnalité native d'Angular CLI
  • Les microfrontends nécessitent des iframes en Angular
Explication : Avec Webpack 5 Module Federation (plugin @angular-architects/module-federation), chaque micro-app Angular expose des composants via des "remotes". L'app hôte les charge dynamiquement à l'exécution. Les micro-apps sont déployées indépendamment.
Commande : ng add @angular-architects/module-federation. Défi principal : la compatibilité des versions Angular entre l'hôte et les remotes (shared libraries).

  • retry(n) réessaie l'Observable n fois en cas d'erreur ; retryWhen permet une logique complexe avec délai exponentiel (bonne réponse)
  • retry() annule la requête en cas d'erreur
  • retryWhen supprime les erreurs silencieusement
  • Ces opérateurs ne fonctionnent pas avec HttpClient
Explication : retry(3) réessaie automatiquement 3 fois. Pour une logique plus avancée (délai exponentiel) : retry({ count: 3, delay: (err, count) => timer(Math.pow(2, count) * 1000) }). Idéal pour les erreurs réseau transitoires.
Pattern robuste : http.get().pipe(retry({ count: 3, delay: 1000 }), catchError(err => { if(err.status >= 500) return EMPTY; throw err; })).

  • Créer un service avec des signals privés, des computed signals publics et des méthodes pour les mutations (bonne réponse)
  • Utiliser BehaviorSubject uniquement
  • Les signals ne peuvent pas gérer l'état global
  • Créer un service avec des propriétés publiques mutables
Explication : Pattern signal-store : @Injectable({ providedIn: "root" }) class CounterStore { private _count = signal(0); readonly count = this._count.asReadonly(); readonly doubled = computed(() => this._count() * 2); increment() { this._count.update(c => c + 1); } }. Simple, performant, et type-safe.
Pour les états complexes, regardez @ngrx/signals (NgRx SignalStore) qui étend ce pattern avec des features réutilisables.

  • Les computed signals sont recalculés en mode "lazy" (à la demande) et non en "eager", évitant les calculs intermédiaires incohérents (bonne réponse)
  • Les signals se synchronisent par WebSocket
  • La détection est désactivée automatiquement pour les signals
  • Les signals recalculent toujours immédiatement
Explication : Le système de signals utilise un mode "lazy" pour les computed signals : ils sont marqués comme "dirty" mais ne se recalculent que quand on les lit. Cela évite les "glitches" où un computed signal aurait une valeur intermédiaire incohérente pendant la propagation d'un changement.
Contrairement aux BehaviorSubjects (eager), les computed signals sont recalculés uniquement quand ils sont consommés, ce qui garantit la cohérence des données à tout moment.

  • Identifier les composants non-déterministes (Date.now, Math.random) et leur appliquer ngSkipHydration, activer le mode strict d'hydratation pour détecter les mismatches (bonne réponse)
  • Désactiver complètement l'hydratation
  • Régénérer tout le HTML côté client
  • Utiliser setTimeout pour retarder l'hydratation
Explication : Les mismatches d'hydratation surviennent quand le HTML serveur diffère de ce qu'Angular attend de générer côté client. Solutions : ngSkipHydration sur les composants problématiques, withDebugMode() pour voir les mismatches, et s'assurer que les données sont identiques serveur/client (transfert d'état avec TransferState).
Utilisez provideClientHydration(withDebugMode()) en développement pour visualiser exactement les nodes qui ne correspondent pas.

  • Utiliser overrideProvider() dans TestBed pour remplacer des tokens ou services par des mocks sans reconfigurer tout le TestBed (bonne réponse)
  • Utiliser spy.on() uniquement
  • Réécrire le service original pour les tests
  • Importer un module de test spécial
Explication : Dans les tests, TestBed.overrideProvider(ServiceToken, { useValue: mockService }) remplace un provider après la configuration initiale. Combiné avec useValue, useClass, ou useFactory, cela permet des tests très flexibles sans refactoriser l'application.
Pour les tests, préférez TestBed.configureTestingModule({ providers: [{ provide: MonService, useValue: mockService }] }).

  • Permet de transférer les données HTTP chargées côté serveur vers le client pour éviter les double-requêtes (bonne réponse)
  • Transfère l'état de l'application entre les onglets
  • Synchronise l'état avec une base de données
  • Est une alternative aux cookies
Explication : Avec SSR, le serveur fait des requêtes HTTP puis envoie le HTML au client. Sans Transfer State, le client refait les mêmes requêtes. Avec TransferState ou provideClientHydration(withHttpTransferCache()), les réponses HTTP du serveur sont sérialisées dans le HTML et réutilisées côté client.
Angular 17+ rend cela automatique avec withHttpTransferCache() dans provideClientHydration() — les requêtes HttpClient côté serveur sont automatiquement mises en cache.

  • Chaque composant peut avoir son propre injecteur local ; Angular remonte la hiérarchie jusqu'à l'injecteur racine pour trouver un provider (bonne réponse)
  • Il n'y a qu'un seul injecteur global dans Angular
  • Les composants enfants ne peuvent pas accéder aux services des parents
  • L'injecteur racine est le seul qui peut fournir des services
Explication : Angular crée un injecteur enfant pour chaque composant qui définit des providers. Quand Angular cherche une dépendance, il commence par l'injecteur du composant courant, puis remonte jusqu'aux parents, et finalement à l'injecteur racine. Cela permet d'isoler des instances de services à des sous-arbres.
Cas d'usage : composant de formulaire qui fournit un FormService local → ses enfants partagent la même instance, mais d'autres composants ont leur propre instance.

  • Exposer des méthodes publiques dans le service signal-store qui mutent les signals internes, et des computed signals pour les projections (bonne réponse)
  • Utiliser les EventEmitters comme commandes
  • Implémenter une interface Command avec execute()
  • Utiliser NgRx actions directement avec les signals
Explication : Le pattern Command avec signals : un service expose des méthodes (commandes) qui modifient l'état interne (signals privés) et des computed signals (projections) qui reflètent l'état dérivé. Les composants appellent les commandes et lisent les projections. C'est une implémentation légère du CQRS.
Exemple : class CartStore { items = signal([]); totalPrice = computed(() => items().reduce(...))); addItem(item: Item) { this.items.update(l => [...l, item]); } }.

  • Sans Zone.js, seuls les signals, les async pipes et les events du template déclenchent la détection, réduisant drastiquement les cycles inutiles (bonne réponse)
  • Le mode zoneless est identique au mode Zone.js en performance
  • Le mode zoneless désactive toute détection de changement
  • Zone.js est supprimé dans Angular 17
Explication : Zone.js patche toutes les opérations asynchrones du navigateur pour déclencher la détection à chaque fois. En mode zoneless, seuls les changements explicites (signals, async pipe, markForCheck) déclenchent la détection. Résultat : beaucoup moins de cycles de détection, meilleures performances CPU.
Le mode zoneless est expérimental dans Angular 17. Toute bibliothèque qui dépend de Zone.js pour déclencher la détection devra être adaptée.

  • Un schematic est un template de génération de code ; on peut en créer avec @angular-devkit/schematics pour automatiser des tâches répétitives dans les projets (bonne réponse)
  • Les schematics sont uniquement pour la génération de composants
  • Les schematics font partie de Webpack
  • Les schematics personnalisés nécessitent Angular Enterprise
Explication : Les schematics (ng generate, ng add) sont des programmes qui modifient un projet Angular de manière idempotente. Avec @angular-devkit/schematics, on peut créer des schematics qui génèrent du code, modifient des fichiers existants, ou installent des dépendances.
Création : npm install -g @angular-devkit/schematics-cli puis schematics blank --name=mon-schematic. Les schematics sont souvent distribués via des ng-add dans les bibliothèques.

  • Utiliser des marble tests (rx-marbles) ou TestScheduler de RxJS pour tester le comportement asynchrone de manière synchrone (bonne réponse)
  • Utiliser uniquement des spies Jasmine
  • Attendre avec await dans les tests async
  • Mocker tous les Observables avec of()
Explication : Les marble tests permettent de décrire des séquences Observable de manière visuelle : cold("--a--b--|", { a: 1, b: 2 }). TestScheduler de RxJS permet de contrôler le temps de manière synchrone dans les tests. Utile pour tester des opérateurs comme debounceTime, delay, etc.
Pour les tests simples, of(valeur) et throwError() suffisent. Pour les tests complexes de timing, utilisez les marble tests de rxjs/testing.

  • ng add @angular/pwa ajoute un Service Worker et manifest.json pour le mode hors-ligne, l'installation et les notifications push (bonne réponse)
  • Les PWA nécessitent un framework séparé
  • Les PWA ne sont pas supportées dans Angular
  • Il faut configurer Nginx pour les PWA Angular
Explication : ng add @angular/pwa configure automatiquement : un Service Worker (via @angular/service-worker), un manifest.json, des icônes, et les headers nécessaires. Le Service Worker met en cache les assets pour le mode hors-ligne et améliore les temps de chargement.
Testez la PWA avec ng build --prod + http-server dist/ (pas ng serve qui désactive le Service Worker). Configurez la stratégie de cache dans ngsw-config.json.

  • @defer (on viewport; prefetch on idle) télécharge le code en avance quand le navigateur est inactif, mais ne l'initialise que quand l'élément devient visible (bonne réponse)
  • prefetch charge et initialise le composant immédiatement
  • @defer prefetch n'existe pas dans Angular 17
  • prefetch désactive le lazy loading
Explication : Le paramètre prefetch dans @defer sépare le téléchargement (prefetch) de l'activation (trigger). Exemple : @defer (on viewport; prefetch on idle) — le code est préchargé quand le navigateur est inactif, mais le composant n'est rendu que quand il devient visible dans le viewport.
Stratégie recommandée : prefetch on idle pour le code fréquemment accédé, prefetch when condition pour du code conditionnel.

  • Créer une couche d'abstraction avec une interface Repository, implémentée par HttpRepository, et un InMemoryRepository pour les tests (bonne réponse)
  • Utiliser directement HttpClient dans les composants
  • Stocker les données dans localStorage
  • Le pattern Repository n'est pas applicable à Angular
Explication : Le pattern Repository abstrait l'accès aux données : interface UserRepository { getAll(): Observable<User[]>; getById(id: number): Observable<User>; }. L'implémentation HTTP est injectable. Pour les tests, une implémentation InMemory permet des tests rapides sans HTTP.
Enregistrez les deux avec un InjectionToken : { provide: USER_REPOSITORY, useClass: environment.test ? InMemoryUserRepository : HttpUserRepository }.

  • Les signals permettent des mises à jour plus granulaires que le async pipe car Angular sait exactement quelle partie du template est affectée (bonne réponse)
  • Ils ont exactement les mêmes performances
  • async pipe est plus performant car il évite la récursion
  • OnPush ne fonctionne pas avec les signals
Explication : Avec async et OnPush, Angular appelle markForCheck() sur tout le composant. Avec les signals, Angular peut potentiellement mettre à jour uniquement le binding qui dépend du signal modifié, sans vérifier les autres bindings. C'est la granularité fine que permettent les signals.
Cette granularité fine des signals est en cours de développement dans Angular 17. La pleine utilisation nécessitera un "signal-based rendering" que l'équipe Angular développe pour les versions futures.

  • Angular sanitize automatiquement les templates, mais [innerHTML] nécessite DomSanitizer.bypassSecurityTrustHtml() avec prudence (bonne réponse)
  • La sécurité XSS n'est pas gérée par Angular
  • Il faut toujours utiliser encodeURIComponent() manuellement
  • Les templates Angular sont par défaut non sécurisés
Explication : Angular sanitize automatiquement les valeurs dans les bindings templates (interpolation, property binding). Pour [innerHTML] avec du HTML dynamique de sources non fiables, Angular sanitize le HTML par défaut. Utiliser DomSanitizer.bypassSecurityTrustHtml() contourne la sanitization et doit être réservé au HTML vérifié.
Angular bloque aussi l'injection dans les URLs de ressources : les URLs JavaScript (javascript:) sont bloquées par défaut. Utilisez bypassSecurityTrustUrl() uniquement pour des URLs de confiance.

  • Combiner Angular DevTools profiler, Lighthouse, Web Vitals, et des marqueurs custom Performance API pour identifier les goulots d'étranglement (bonne réponse)
  • Utiliser uniquement console.time()
  • Les performances ne peuvent être mesurées qu'en développement
  • Angular optimise automatiquement tout sans intervention
Explication : Approche multicouche : 1) Angular DevTools profiler (cycles CD, composants lents), 2) Lighthouse CI (scores CWV automatisés), 3) Web Vitals library (onLCP, onFID, onCLS) pour les métriques terrain, 4) performance.mark() / performance.measure() pour les benchmarks custom Angular.
En production, configurez un Real User Monitoring (RUM) comme Google Analytics 4 ou Sentry Performance pour collecter les métriques réelles des utilisateurs.