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
📝
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.
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.
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.
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.
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.
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);
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".
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.
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.
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()).
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é.
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.
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)".
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.
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.
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).
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.
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.
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.
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é.
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.
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 }].
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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).
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>.
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.
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.
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().
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.
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: [...] }.
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".
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().
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.
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.
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.
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).
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; })).
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.
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.
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.
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 }] }).
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.
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.
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]); } }.
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.
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.
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.
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.
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.
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 }.
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.
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.
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.