Introduction
Un entretien pour un poste de développeur Frontend ou Angular n'est jamais un simple test de la syntaxe. Celui qui mène l'entretien veut comprendre comment vous pensez face à un problème, à quel point vous connaissez parfaitement les outils que vous utilisez quotidiennement et comment vos choix changent lorsque vous le faites Le scénario passe d'« un composant » à « une application d'entreprise utilisée par des centaines de des milliers d'utilisateurs".
Ce guide rassemble 25 vraies questions, organisées en quatre niveaux de ancienneté — Junior, Niveau intermédiaire, Senior et Expert/Responsable — avec des réponses complètes que vous pouvez utiliser à la fois pour vous préparer à un interviewez les deux, si vous êtes de l'autre côté de la table, pour en mener une vous-même d'une manière structuré.
Les questions de premier niveau testent les bases (HTML, CSS, JavaScript, TypeScript, angulaire de base); ceux des niveaux intermédiaires entrent RxJS, composants, services, routage et la gestion de l'État ; les Seniors abordent la détection des changements, le rendu, les performances avancées, modèles de test et de conception ; les Experts/Lead s'adressent aux grandes architectures d'applications, microfrontend, SSR, sécurité et les compromis techniques qu'un responsable doit être capable d'argumenter, pas juste savoir.
Sujets : #Angular #Frontend #JobInterview #TypeScript #JavaScript #RxJS #DéveloppementWeb #ConseilsCarrière #ArchitectureLogiciel #Entretien Technique
Comment ce guide est structuré
Chaque question est conçue pour refléter ce qui est réellement demandé lors des entretiens techniques, pas des questions « de manuels » isolées du contexte. Les réponses ne se limitent pas à la définition formelle : ils expliquent pourquoi la réponse est que, quand la règle a des exceptions, et que un intervieweur expérimenté s'attend à entendre pour distinguer une réponse d'un manuel de une réponse de quelqu'un qui a réellement résolu le problème en production.
- Niveau Junior (6 questions) : principes fondamentaux de HTML, CSS, JavaScript, TypeScript et Angular.
- Niveau intermédiaire (6 questions) : RxJS, composants, services, routage, gestion des états, performances de base.
- Niveau senior (7 questions) : architecture, détection des changements, rendu, performances avancées, tests, modèle de conception.
- Niveau Expert/Lead (6 questions) : grandes architectures d'applications, évolutivité, microfrontends, SSR, sécurité, compromis techniques.
Niveau junior — Principes fondamentaux HTML, CSS, JavaScript, TypeScript et Angular
A ce niveau, le but de l'enquêteur n'est pas de trouver l'erreur, mais de comprendre si vous avez des bases solides sur lesquelles bâtir. Les meilleures réponses sont celles qui, en plus de la définition, ils expliquent l’impact pratique de la connaissance.
1. Quelle est la différence entre les éléments HTML au niveau bloc et en ligne, et pourquoi est-il important de la connaître ?
Les éléments block-level (comme <div>,
<section>, <p>) occupent toujours toute la largeur
disponible du parent et commencer sur une nouvelle ligne, en acceptant width,
hauteur et marges/rembourrage de tous les côtés. Les éléments inline
(comme <span>, <a>, <strong>) occupent
seulement l'espace nécessaire au contenu, ils sont disposés en harmonie avec le texte environnant et
ignorer width/height, ainsi que gérer les marges d'une manière spéciale
verticale.
Le savoir est important car cela explique des bugs de mise en page très courants : a <span>
auquel vous définissez width et rien ne se passe, ou un élément qui "ne s'aligne pas"
comme vous l'attendez. Avec display : flex, grid et inline-block
cette distinction classique s'estompe, mais comprendre le comportement par défaut reste fondamental
pour prédire le comportement d'un élément avant même d'appliquer CSS.
2. Quels sont le modèle de boîte CSS et les différences entre content-box et border-box ?
Le box model décrit comment le navigateur calcule les dimensions finales d'un
élément : content (le contenu), padding (l'espace interne),
border (bordure) et margin (espace extra-atmosphérique), concentriques l'un à l'autre
dans l'autre. La propriété box-sizing détermine comment width et
hauteur sont interprétés :
/* content-box (default del browser): width/height si riferiscono SOLO al contenuto.
padding e border si sommano, ingrandendo la dimensione finale renderizzata. */
.box-legacy {
box-sizing: content-box;
width: 200px;
padding: 20px;
border: 5px solid black;
/* larghezza finale renderizzata: 200 + 20*2 + 5*2 = 250px */
}
/* border-box: width/height includono padding e border.
La dimensione dichiarata è la dimensione finale renderizzata. */
.box-modern {
box-sizing: border-box;
width: 200px;
padding: 20px;
border: 5px solid black;
/* larghezza finale renderizzata: 200px, esattamente come dichiarato */
}
En pratique, presque tous les projets modernes se déroulent
*, *::avant, *::après { box-sizing: border-box; } dans le monde simplement parce que
rend les calculs de mise en page prévisibles, empêchant l'ajout de remplissage de « casser » une grille
déjà dimensionné.
3. Quelle est la différence entre var, let et const en JavaScript, et quels problèmes let résout-il ?
var a portée de fonction (ou portée globale si déclarée en dehors
fonctions) et souffre du hoisting avec initialisation à undefined, qui
vous permet de "l'utiliser avant de le déclarer" sans erreurs - un comportement bogué
silencieux. let et const ont à la place block scope
(visibles uniquement à l'intérieur des accolades dans lesquelles ils sont déclarés) et vivent dans un
zone morte temporelle : y accéder avant que la déclaration lance une
ReferenceError explicite au lieu de revenir silencieusement
non défini.
// Il classico bug da var dentro un loop asincrono
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var:', i), 10);
}
// Stampa: var: 3, var: 3, var: 3
// perché var è condivisa da tutte le iterazioni (una sola variabile, function-scoped)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log('let:', i), 10);
}
// Stampa: let: 0, let: 1, let: 2
// perché let crea un nuovo binding per ogni iterazione del loop
const empêche la réaffectation de liaison (ne rend pas l'objet immuable :
un tableau ou un objet déclaré avec const peut toujours être muté vers son
interne). La règle empirique que suivent presque toutes les équipes : const par défaut,
let uniquement lorsqu'une réaffectation est nécessaire, var jamais dans un nouveau code.
4. Que sont les fermetures en JavaScript et à quoi servent-elles en pratique ?
Un closure est créé lorsqu'une fonction "se souvient" des variables de portée dans lequel il a été défini, même après la fin de l'exécution de cette portée. En pratique, le la fonction interne maintient une référence en direct aux variables de la fonction externe.
function createCounter() {
let count = 0; // variabile "chiusa" nella closure
return {
increment: () => ++count,
decrement: () => --count,
getValue: () => count,
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getValue()); // 2
// count non è accessibile dall'esterno: è uno stato privato reso possibile dalla closure
console.log(counter.count); // undefined
Les fermetures sont partout dans le vrai code Angular : à chaque fois que vous passez une fonction de rappel
à un subscribe(), à un setTimeout, ou définir un
computed()/effect() d'un Signal, cette fonction "se ferme" sur
variables des composants. Comprendre les fermetures explique aussi un bug fréquent : la capture par
référencer une variable qui change (par exemple l'index d'une boucle) au lieu de la valeur attendue à
heure de l’appel.
5. Que sont les génériques dans TypeScript et pourquoi sont-ils utiles ?
Les generics vous permettent d'écrire des fonctions, des classes et des interfaces qui
ils travaillent avec différents types sans perdre la sécurité des types, en remplaçant l'alternative
pire - utilisez any, qui désactive effectivement la vérification de type.
// Senza generics: perdiamo informazione di tipo, il chiamante deve fare un cast manuale
function wrapInArrayUnsafe(value: any): any[] {
return [value];
}
const result = wrapInArrayUnsafe('hello'); // result è any, nessun autocompletamento
// Con generics: il tipo si propaga automaticamente dall'input all'output
function wrapInArray(value: T): T[] {
return [value];
}
const strings = wrapInArray('hello'); // TypeScript inferisce string[]
const numbers = wrapInArray(42); // TypeScript inferisce number[]
// Un caso reale: un servizio HTTP generico
class ApiService {
constructor(private endpoint: string) {}
getAll(): Observable {
return this.http.get(this.endpoint);
}
}
const productsApi = new ApiService('/api/products');
// productsApi.getAll() restituisce Observable, tipizzato correttamente
Dans Angular, les génériques sont partout : Observable,
signal, Component dans les tests, je
Repository Côté NestJS. Savoir les utiliser de première main (et pas seulement)
les reconnaître) est ce qui distingue ceux qui écrivent du code typé robuste de ceux qui simplement
copier des modèles existants.
6. Qu'est-ce qu'un composant angulaire et quels sont ses principaux éléments ?
Un composant angulaire est une classe TypeScript décorée avec @Component qui
contrôle une partie de l’interface utilisateur. Ses principaux éléments sont :
- Decorator
@Component: métadonnées décrivant le sélecteur, le modèle et le style du composant. - Modèle : HTML (en ligne ou dans un fichier séparé) avec liaison, directives et interpolation.
- Class : contient l'état (propriétés, signaux) et le comportement (méthodes, hooks de cycle de vie).
- Styles : CSS avec portée encapsulée dans le composant par défaut (View Encapsulation).
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-greeting',
standalone: true, // non richiede più NgModule dal 2023 in poi
template: `
Ciao, {{ name() }}!
Cambia nome
`,
styles: [`h2 { color: var(--color-primary); }`],
})
export class GreetingComponent {
name = signal('Mondo');
changeName(): void {
this.name.set('Angular');
}
}
À partir de 2023 (Angular 14+ pour les composants autonomes, puis par défaut à partir d'Angular 17) un composant
il n'a plus besoin d'être déclaré dans un NgModule : il déclare directement le
propres dépendances via imports dans le décorateur. C'est le modèle auquel vous vous attendez
être utilisé dans tout nouveau projet aujourd'hui.
Niveau intermédiaire — RxJS, composants, services, routage et gestion de l'état
A ce niveau l'enquêteur vérifie si vous savez connecter les concepts : cela ne suffit pas sachant ce qu'est un observable, vous devez savoir quel opérateur RxJS utiliser dans quel scénario, et car un mauvais choix provoque de vrais bugs (race conditions, fuites mémoire, requêtes doublons).
7. Quelle est la différence entre switchMap, mergeMap, concatMap et exhaustMap dans RxJS, et quand les utiliser ?
Tous les quatre sont des opérateurs « d'aplatissement » qui gèrent un Observable d'Observable, mais avec des stratégies de concurrence opposées :
| Opérateur | Comportement | Cas d'utilisation typique |
|---|---|---|
switchMap | Supprimer la demande inachevée précédente lorsqu'une nouvelle arrive | Champ de recherche avec saisie semi-automatique (compte de la dernière saisie uniquement) |
mergeMap | Exécuter toutes les requêtes en parallèle, sans rien supprimer | Uploads multiples de fichiers indépendants les uns des autres |
concatMap | Mette les requêtes en file d'attente, exécute la suivante seulement une fois la précédente terminée | Opérations qui doivent se produire dans un ordre strict (par exemple, écriture séquentielle sur un journal) |
exhaustMap | Ignorer les nouveaux événements jusqu'à ce que celui en cours soit terminé | Bouton Soumettre — évitez les soumissions en double-clic multiples |
// L'errore più comune: usare mergeMap per una ricerca con autocomplete
searchInput.valueChanges.pipe(
debounceTime(300),
mergeMap(query => this.api.search(query)), // ❌ le risposte possono arrivare fuori ordine!
).subscribe(results => this.results.set(results));
// Se l'utente digita velocemente "an" poi "angular", e la richiesta per "an"
// impiega più tempo a rispondere di quella per "angular", l'utente vede
// i risultati sbagliati (quelli di "an") sovrascrivere quelli corretti.
// La scelta corretta: switchMap cancella la richiesta obsoleta
searchInput.valueChanges.pipe(
debounceTime(300),
switchMap(query => this.api.search(query)), // ✅ solo l'ultima richiesta conta
).subscribe(results => this.results.set(results));
Une réponse solide de niveau intermédiaire mentionne explicitement ce bug de tri : c'est la raison
réel donc switchMap est presque toujours le bon choix pour la recherche, pas un
règle arbitraire à apprendre par cœur.
8. Comment fonctionne l'injection de dépendances dans Angular et quelle est la différence entre les fournisseurs provideIn: 'root' et les fournisseurs au niveau des composants ?
Angular maintient une hiérarchie de injector : un niveau racine
application, et un pour chaque composant (et ses enfants, s'ils ne sont pas remplacés). Quand un
le composant nécessite une dépendance dans le constructeur (ou avec inject()), Angular
recherche un fournisseur dans la hiérarchie, depuis l'injecteur de composants jusqu'à la racine.
// providedIn: 'root' — un'unica istanza condivisa in tutta l'applicazione (singleton)
@Injectable({ providedIn: 'root' })
export class AuthService {
private currentUser = signal(null);
}
// providers a livello di componente — una nuova istanza per ogni istanza del componente
@Component({
selector: 'app-product-form',
providers: [FormStateService], // ogni ottiene la SUA istanza
standalone: true,
})
export class ProductFormComponent {
private formState = inject(FormStateService);
}
Le choix a de réelles conséquences : si l'on met un état qui doit être partagé (ex.
authentification) dans les fournisseurs d'un composant au lieu de
providedIn : 'root', chaque instance de composant aura un état isolé — un bug
classique "Pourquoi mon service ne voit-il pas les données mises à jour ?" qui vient presque toujours de cela
confusion entre les portées des injecteurs.
9. Que sont les Route Guards et quels types existent dans Angular moderne ?
Les guard sont des fonctions (dans Angular moderne, des fonctions pures, plus des classes avec interfaces) qui décident si une navigation peut continuer, doit être redirigée ou bloqué. Les principaux sont :
CanActivateFn: décide si un itinéraire peut être activé (par exemple, contrôle d'authentification).CanActivateChildFn: comme ci-dessus, mais appliqué aux routes enfants.CanDeactivateFn: décide si un itinéraire peut être quitté (par exemple, avertissement « modifications non enregistrées »).CanMatchFn: décide si un itinéraire peut être mis en correspondance, ce qui est utile pour masquer des sections entières chargées paresseusement aux utilisateurs sans autorisations.ResolveFn: n'est pas techniquement un garde, mais il précharge les données avant que l'itinéraire ne soit activé.
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isAuthenticated()) return true;
router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
return false;
};
// registrazione nelle route
export const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] },
];
10. Comment géreriez-vous l'état partagé entre des composants non liés dans une application angulaire de taille moyenne ?
Pour les composants non liés (qui n'ont pas de relation parent-enfant directe), la solution
la norme est un service partagé avec une portée providedIn : 'root'
qui expose l'état via Signal (ou, dans les anciennes bases de code, via
ComportementSujet).
@Injectable({ providedIn: 'root' })
export class CartStateService {
private readonly _items = signal([]);
readonly items = this._items.asReadonly(); // esposto in sola lettura
readonly total = computed(() =>
this._items().reduce((sum, i) => sum + i.price * i.quantity, 0)
);
addItem(item: CartItem): void {
this._items.update(items => [...items, item]);
}
}
Pour les applications de taille moyenne, ce modèle est suffisant : il est simple, de type sécurisé, réactif et ne nécessite aucune dépendance externe. Une bibliothèque comme NgRx ne se justifie que lorsque des besoins concrets émergent - débogage des voyages dans le temps, actions traçables avec DevTools, Logique d'état complexe partagée par des dizaines de fonctionnalités - non pas « parce que c'est la norme » entreprise". Son introduction prématurée ajoute du passe-partout sans avantages proportionnels.
11. Que sont les signaux dans Angular et en quoi diffèrent-ils d'un observable RxJS ?
Un Signal est un conteneur réactif d'une valeur qui informe
automatiquement celui qui le lit lorsqu'il change, sans avoir besoin de s'abonner/désabonner.
Par rapport à un observable RxJS, la principale différence est qu'un signal a toujours un
valeur actuelle synchrone et immédiatement disponible (lire en l'appelant
en fonction : count()), tandis qu'un Observable représente un
flux d'événements au fil du temps qui n'a peut-être encore rien émis.
import { signal, computed, effect } from '@angular/core';
const count = signal(0);
const doubled = computed(() => count() * 2); // si ricalcola automaticamente
effect(() => {
console.log('Il valore doppio è ora:', doubled()); // si riesegue ad ogni cambiamento
});
count.set(5); // stampa: Il valore doppio è ora: 10
count.update(v => v + 1); // stampa: Il valore doppio è ora: 12
En pratique : Les signaux sont idéaux pour l'état synchrone local d'un composant (compteurs, formes
état, données dérivées), tandis que RxJS reste l'outil idéal pour les flux asynchrones complexes
(rebondir sur une entrée, réessayer sur un appel HTTP, combinant plusieurs flux). Angulaire
modern les fait interagir avec toSignal() et toObservable(), donc
la question n'est pas "lequel" mais "lequel pour ce cas précis".
12. Quelle est la différence entre les @Input()/@Output() traditionnels et les nouveaux input()/output() basés sur les signaux ?
Les décorateurs traditionnels @Input()/@Output() déclarent des propriétés
normales (ou EventEmitter) qu'Angular remplit/observe via son mécanisme
interne; les nouvelles fonctions input()/output() (Angular 17.1+)
ils renvoient respectivement un signal en lecture seule et un émetteur typé, intégrant
nativement avec computed() et effect().
// Stile tradizionale
@Component({ selector: 'app-counter', standalone: true })
export class CounterComponentLegacy {
@Input() initialValue = 0;
@Output() valueChange = new EventEmitter();
}
// Stile moderno basato su Signals
@Component({ selector: 'app-counter', standalone: true })
export class CounterComponent {
initialValue = input(0); // Signal, in sola lettura
initialValueRequired = input.required(); // obbliga il chiamante a passarlo
valueChange = output(); // tipizzato, senza dover importare EventEmitter
// Con input() come Signal, puoi derivare stato reattivo direttamente:
doubledInitial = computed(() => this.initialValue() * 2);
}
L'avantage pratique du nouveau input()/output() n'est pas seulement
syntaxique : étant Signal, ils sont automatiquement intégrés au graphe de réactivité angulaire
(particulièrement utile dans les applications sans zone), et input.required() déplace une erreur
qui a été précédemment découvert au moment de l'exécution lors d'une vérification vérifiable au moment de la compilation avec lo
vérification stricte des modèles.
Niveau supérieur — Architecture, détection des changements, rendu, performances et tests
Au niveau Senior, l’enquêteur ne cherche pas la bonne définition : il cherche le raisonnement. Les questions n'ont souvent pas une seule bonne réponse, mais elles évaluent si vous pouvez présenter un argument. compromis éclairé.
13. Comment fonctionne le mécanisme de détection des changements d'Angular et quelle est la différence entre la stratégie par défaut et la stratégie OnPush ?
Avec la stratégie Default, chaque fois qu'Angular exécute une boucle de changement détection (déclenchée par des événements DOM, des minuteries, des appels HTTP terminés, via Zone.js), chaque Le composant d'arborescence est vérifié pour voir si les liaisons du modèle sont changé, quel que soit l’endroit où l’événement s’est réellement produit.
Avec OnPush, Angular vérifie un composant uniquement lorsque : (1) un
@Input()/modifications d'entrée de signal par référence (pas par mutation interne
de l'objet), (2) un événement DOM se produit à l'intérieur de celui-ci, (3) un observable auquel il est
connecté via | async génère une nouvelle valeur, ou (4) est marqué
explicitement avec markForCheck()/mettre à jour un signal qui lit.
@Component({
selector: 'app-product-card',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
template: `{{ product().name }} — {{ product().price | currency }}
`,
})
export class ProductCardComponent {
product = input.required();
}
// ❌ Questo NON scatena il change detection su ProductCardComponent con OnPush,
// perché muta l'oggetto esistente invece di sostituirlo (stesso riferimento):
someProduct.price = 99;
// ✅ Questo sì, perché crea un nuovo riferimento:
this.products.update(list =>
list.map(p => p.id === someProduct.id ? { ...p, price: 99 } : p)
);
Une réponse Senior doit mentionner explicitement la notion d’égalité pour référence : c'est la cause numéro un du bug "J'ai mis à jour les données mais l'interface utilisateur ne fonctionne pas" update" lors du passage à OnPush sans adopter des modèles immuables de manière cohérente tout au long la demande.
14. Qu'est-ce que Zoneless Angular et comment le modèle de détection des changements change-t-il ?
Historiquement, Angular utilise Zone.js pour "patcher des singes" aux API asynchrones navigateur (événements, minuterie, promesse, récupération) et sait automatiquement quand cela pourrait arriver un cycle de détection des changements est nécessaire. Le modèle zoneless (stable depuis Angular 18+) supprime entièrement cette dépendance : Angular s'appuie exclusivement sur Signal savoir exactement ce qui a changé, sans avoir à vérifier l'ensemble de l'arbre "juste au cas où" chaque fois que quelque chose d'asynchrone se produit en arrière-plan.
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()],
});
Les avantages pratiques : bundle plus petit (Zone.js pèse environ 30 Ko), détection des changements
nettement plus ciblé (seuls les composants qui dépendent d'un signal modifié sont
mis à jour), et un débogage plus prévisible car chaque mise à jour de l'interface utilisateur est traçable à un
Changement de signal explicite, pas à un événement asynchrone générique capturé "un
parapluie". L'inconvénient : des bibliothèques tierces obsolètes qui attendent Zone.js (certaines
versions des composants Material, bibliothèques graphiques) peuvent nécessiter un
NgZone.run() manuel pour continuer à fonctionner correctement dans
sans zone.
15. Comment diagnostiqueriez-vous et résoudriez-vous un problème de performances dû à une détection excessive de changements en production ?
La première étape n'est pas de deviner : elle mesure avec Angular DevTools, onglet
Profileur. En enregistrant une interaction problématique (par exemple en tapant dans un champ de recherche), le
Profiler affiche un graphique à barres où chaque barre est un composant contrôlé dans ce cycle
— si une composante sans rapport avec l'interaction apparaît de manière répétée, c'est le signal manquant
OnPush ou qu'il existe un modèle réactif mal conçu.
Le chemin de résolution typique, par ordre d’impact :
- Appliquez
ChangeDetectionStrategy.OnPushaux composants « feuilles » les plus coûteux à restituer, garantissant ainsi que les données circulent de manière immuable. - Remplacez les mutations directes de tableau/objet par des modèles immuables (spread,
map/filter) ou passez à Signal, qui rendent l'égalité par référence automatique et correcte. - Ajoutez le bon
track(l'identifiant unique, pas l'index) dans les blocs@forpour empêcher Angular de détruire et de recréer inutilement des nœuds DOM lorsqu'une liste est réorganisée. - Évaluer l'adoption du sans zone pour éliminer les cycles de détection de changements déclenchés par des événements asynchrones non liés à l'interface utilisateur visible.
Une réponse senior efficace mentionne toujours Angular DevTools Profiler en premier étape : L'optimisation au ressenti sans données de profilage réelles est une erreur courante même au niveau senior, et celui qui mène l'entretien le remarque immédiatement.
16. Quels modèles de conception appliquez-vous le plus souvent dans une architecture d'entreprise angulaire ?
| Pattern | Application en angulaire |
|---|---|
| Facade | Un service qui cache la complexité de plusieurs services/magasins sous-jacents derrière une simple API pour les composants (par exemple CartFacade qui orchestre CartService, PricingService, InventoryService). |
| Repository | Un service dédié à l'accès aux données (HTTP, cache local) qui isole les composants des détails d'implémentation de la source de données. |
| Strategy | Injection d'implémentations interchangeables via InjectionToken, utile pour les comportements qui varient selon l'environnement ou la configuration (par exemple, les stratégies de paiement différent). |
| Composant intelligent/dumb | Séparation entre les composants « conteneur » (gérer l'état et les effets secondaires) et les composants « présentation » (recevoir des données via l'entrée, émettre des événements via la sortie, sans dépendances directes sur services). |
| Adaptateur | Isolez la forme des données externes (API tierces) derrière un mappage avec les modèles internes de l'application, de sorte qu'une modification de l'API externe n'impacte qu'un seul point du code. |
Une réponse Senior distingue clairement quand chaque modèle ajoute de la valeur et alors qu'au contraire il s'agit d'une ingénierie excessive : introduire une façade pour un seul service simple, par exemple Par exemple, cela ajoute une indirection sans réel avantage.
17. Comment structurez-vous les tests d'une application angulaire complexe et quels compromis envisagez-vous ?
Je suis la pyramide de tests : de nombreux tests unitaires rapides et isolés (service, pipe,
logique pure extraite des composants), un nombre modéré de tests d'intégration (composants
avec TestBed, vérifiant l'interaction réelle avec les services), et quelques tests E2E
ciblé sur les flux réellement critiques pour l'entreprise (connexion, paiement), et non sur chacun d'entre eux
page.
// Unit test: logica pura, veloce, nessuna dipendenza da Angular
describe('calculateDiscount', () => {
it('applica correttamente uno sconto percentuale', () => {
expect(calculateDiscount(100, 20)).toBe(80);
});
});
// Integration test: componente reale con TestBed, verifica il comportamento visibile
describe('ProductCardComponent', () => {
it('emette addToCart quando si clicca il pulsante', () => {
const fixture = TestBed.createComponent(ProductCardComponent);
fixture.componentRef.setInput('product', mockProduct);
fixture.detectChanges();
let emitted: Product | undefined;
fixture.componentInstance.addToCart.subscribe(p => (emitted = p));
fixture.debugElement.query(By.css('[data-testid="add-btn"]')).nativeElement.click();
expect(emitted).toEqual(mockProduct);
});
});
Le principal compromis dont je discute toujours dans les conversations : les E2E donnent une confiance maximale réels (ils testent l'application comme un utilisateur l'utiliserait) mais ils sont lents et plus fragiles ; tests unitaires ils sont très rapides mais ne détectent pas les problèmes d'intégration. La bonne relation n'est pas fixe, dépend de la criticité du domaine — un flux de paiement mérite plus de couverture E2E qu'un autre page d'informations statique.
18. Qu’est-ce que le tremblement des arbres et comment les choix architecturaux l’affectent-ils ?
tree-shaking est le processus par lequel le bundler (esbuild en Angular 17+/20+) élimine le code qui a été exporté mais jamais réellement importé du bundle final ou utilisé, réduisant ainsi la taille du JavaScript téléchargé par le navigateur.
Les choix architecturaux l'influencent de manière concrète : les standalones
les composants avec des imports explicites rendent les dépendances statiques et
analysable, améliorant le tremblement d'arbre par rapport à l'ancien NgModule avec
déclarations déclarations « parapluie » qui comptaient souvent plus que nécessaire. Même le
barrel file (index.ts qui réexporte un module entier) peut
aggraver le tremblement de l'arbre si le bundler ne peut pas déterminer statiquement quelles exportations
sont réellement utilisés, ce qui conduit à l'inclusion de code mort dans le bundle final.
// ❌ Import "largo": può impedire il tree-shaking se lodash non è in formato ESM puro
import _ from 'lodash';
const chunks = _.chunk(array, 3);
// ✅ Import mirato: il bundler include SOLO la funzione realmente usata
import chunk from 'lodash/chunk';
const chunks = chunk(array, 3);
19. Comment implémenteriez-vous la mise en cache côté client pour réduire les appels HTTP redondants de manière évolutive ?
La solution la plus élégante dans Angular moderne est un HttpInterceptor qui
intercepte les requêtes GET et renvoie une réponse mise en cache lorsqu'elle est disponible et non
expiré, invalidant spécifiquement le cache lorsque les données sous-jacentes changent.
@Injectable()
export class CacheInterceptor implements HttpInterceptor {
private cache = new Map; expiry: number }>();
intercept(req: HttpRequest, next: HttpHandler): Observable> {
if (req.method !== 'GET') return next.handle(req);
const cached = this.cache.get(req.urlWithParams);
if (cached && cached.expiry > Date.now()) {
return of(cached.response.clone());
}
return next.handle(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
this.cache.set(req.urlWithParams, {
response: event,
expiry: Date.now() + 60_000, // 60 secondi
});
}
}),
);
}
}
Le point qui distingue une réponse Senior : savoir quoi chier et quoi ne pas chier . Les données du catalogue changent rarement et se prêtent bien à la mise en cache ; données spécifiques de l'utilisateur authentifié nécessitent une clé de cache qui inclut l'identité de l'utilisateur (ou doit être complètement exclu), sinon vous risquez de montrer les données d'un utilisateur à un autre — une erreur de sécurité, pas seulement une erreur de performance.
Niveau Expert / Lead — Architectures d'entreprise, évolutivité, microfrontend, SSR et sécurité
Les questions des experts/responsables ont rarement une « bonne » réponse unique. Ils évaluent la capacité de argumenter un compromis, défendre une décision avec des données concrètes et reconnaître quand le La solution « plus sophistiquée » n’est en réalité pas la bonne pour le contexte.
20. Quand est-il judicieux d’adopter une architecture microfrontend par rapport à un monolithe angulaire modulaire, et quels sont les véritables compromis ?
Les microfrontends ont du sens lorsqu'il y a un réel problème organisationnel, pas seulement technique : plusieurs équipes qui doivent déployer indépendamment des piles ou des versions Différence angulaire entre les parties de l'application, ou nécessité d'isoler complètement le cycle de libération d’une section critique du reste.
| Apparence | Monolithe modulaire | Microfrontend |
|---|---|---|
| Complexité opérationnelle | Faible — une construction, un déploiement | Élevée — orchestration, gestion des versions, contrats d'équipe |
| Déploiements indépendants | Non | Oui, par équipe/section |
| Duplication des dépendances d'exécution | Aucun | Risque concret (plusieurs copies d'Angular/RxJS) s'il n'est pas géré avec la fédération de modules |
| Cohérence UX entre les sections | Naturel | Nécessite une gouvernance explicite (système de conception partagé) |
| Intégration de nouveaux développeurs | Repo/architecture plus simple et unique | Plus complexe, nécessité de comprendre les frontières entre les applications |
Ma position dans une interview : un monolithe modulaire bien structuré avec des limites de fonctionnalités clear (chargé paresseux, avec des interfaces explicites entre les modules) résout la plupart des problèmes problèmes que les équipes pensent devoir résoudre avec des microfrontends, avec une fraction du complexité opérationnelle. Les microfrontends sont un outil organisationnel pour les problèmes d'échelle des équipes, et non une « meilleure architecture » dans l'abstrait — et ne devrait être adoptée que lorsque le coût de coordination entre les équipes dépasse déjà le coût d'une complexité technique supplémentaire.
21. Comment concevriez-vous la stratégie SSR/hydratation pour une application Angular d’entreprise avec des exigences SEO strictes ?
Je commencerais par Angular Universal avec provideClientHydration() pour
l'hydratation complète, évaluant hydratation incrémentielle
(withIncrementalHydration()) pour les sections inutiles et lourdes de pages
au premier rendu (graphiques, éditeurs de texte enrichi, cartes), combiné avec @defer pour
reporter leur chargement jusqu'à ce qu'ils entrent dans la fenêtre.
// app.config.server.ts
import { provideServerRendering } from '@angular/platform-server';
import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser';
export const serverConfig = [
provideServerRendering(),
provideClientHydration(withIncrementalHydration()),
];
<!-- Il componente si idrata solo quando entra in viewport, non al bootstrap iniziale -->
@defer (on viewport) {
<app-heavy-dashboard-chart [data]="chartData()" />
} @placeholder {
<div class="chart-skeleton"></div>
}
Pour les exigences SEO strictes, la partie architecturale critique ne se limite pas au rendu :
vous avez besoin d'un SeoService centralisé qui met à jour dynamiquement le titre, la méta
description, canonique et JSON-LD pendant rendu côté serveur (pas après, quand
est trop tard pour les robots d'exploration qui n'exécutent pas JavaScript), ainsi qu'une stratégie explicite pour
gérer le code qui accède aux API du navigateur uniquement (window, document)
derrière un contrôle isPlatformBrowser(), pour éviter les crashs lors du rendu
côté serveur.
22. Quelles mesures de sécurité sont essentielles dans une application d'entreprise Angular ?
- fiable — jamais en cas de saisie utilisateur non valide.
- CSRF (Cross-Site Request Forgery) :
HttpClientXsrfModulegère automatiquement le modèle de cookie à double soumission, mais uniquement si le backend définit correctement le cookie XSRF-TOKEN. - Politique de sécurité du contenu (CSP) : En-tête configuré côté serveur qui limite les sources à partir desquelles les scripts/styles peuvent être chargés, atténuant ainsi l'impact d'un XSS même réussi.
- Gestion des tokens JWT : jeton d'accès éphémère, rafraîchissement du token géré côté serveur (jamais lisible par JavaScript si possible, via cookie httpOnly), et invalidation réelle à la déconnexion.
- Validation côté client en tant qu'UX, pas sécurité : toute validation côté client doit toujours être répliquée côté serveur — un client peut être manipulé avec des outils de développement.
Une réponse d'expert/responsable doit souligner explicitement que la sécurité côté client c'est une défense en profondeur, pas la seule ligne de défense : qui pense que ça valide/ protéger uniquement le côté angulaire est suffisant, il y a un sérieux vide conceptuel pour cela niveau.
23. Comment géreriez-vous le contrôle de version et la migration incrémentielle d'une base de code Angular héritée vers des signaux autonomes au sein d'une équipe de plus de 20 développeurs ?
Pas avec une « réécriture big bang » – trop risquée pour une équipe de cette taille. La stratégie
que j'adopte est la migration incrémentielle par limites de fonctionnalités : Angulaire
prend en charge la coexistence de NgModule et de composants autonomes dans le même
application, afin que vous puissiez migrer un module à la fois, en vérifiant que chaque étape est
déployable indépendamment en production.
- Automatisez la première étape avec le
schematicofficiel ng generate @angular/core:standalone, qui convertit automatiquement les composants/directives/pipes. - Établissez une limite claire : les nouvelles fonctionnalités sont écrites uniquement de manière autonome, immédiatement, pour éviter que la dette technique ne continue de croître pendant la migration.
- Migrez les modules existants par ordre d'augmentation du « rayon d'impact » : fonctionnalités isolées à faible trafic d'abord, puis progressivement le cœur de l'application.
- Introduire les signaux en parallèle mais séparément de la migration vers autonome : ce sont deux axes orthogonaux, il n'est pas nécessaire de les faire ensemble et les mélanger augmente le risque par changement unique.
Le point recherché par un intervieweur principal : la capacité à séquencer les risques, pas seulement à découvrez les outils de migration. Communiquez clairement à l'équipe pourquoi vous migrez progressivement (et pas d'un seul coup) fait autant partie du travail que l'écriture du code.
24. Quels critères utilisez-vous pour choisir entre NgRx, un simple magasin de signaux personnalisé ou aucune bibliothèque de gestion d'état ?
| Criterion | Aucune bibliothèque (Signal/services) | Light Custom Signal Store | NgRx |
|---|---|---|---|
| Complexité du domaine | Faible/moyenne | Moyenne | Élevée, avec de nombreuses actions interconnectées |
| Besoin d'un débogage du voyage dans le temps | Non | Non | Oui |
| Équipe habituée aux modèles Redux | Non requis | Non requis | Avantage si déjà présent |
| Patron acceptable | Minimal | Modéré | Important, atténué par les schémas officiels |
| Testabilité à l'état isolé | Bon | Excellent | Excellent, avec des modèles établis |
Mon critère de décision en entretien : je pars toujours de l'option la plus simple (Signal en
un service providedIn: 'root') et je ne le fais évoluer que lorsque des symptômes concrets apparaissent
— logique d'état dupliquée entre les fonctionnalités, besoin réel de débogage avancé ou équipe qui
fonctionne déjà bien avec les modèles Redux sur d'autres projets. Introduire NgRx "par défaut" sur chaque
un nouveau projet est un choix qui a tendance à ralentir le développement initial sans en tirer des bénéfices
proportionné jusqu’à ce que la complexité réelle le justifie.
25. Comment équilibreriez-vous vitesse de développement, performances et maintenabilité dans une décision architecturale avec des délais stricts ?
Un exemple concret d'un véritable compromis : une équipe doit fournir un tableau de bord avec des graphiques interactif dans deux semaines. La bibliothèque de graphiques la plus performante nécessite trois jours intégration et configuration avancées ; une bibliothèque plus simple, moins optimisée mais avec excellente documentation, prend une demi-journée.
La décision que je soutiendrais : utiliser la simple bibliothèque now, mais l'isoler derrière une interface/adaptateur interne explicite (ne l'appelez pas directement depuis chaque composant), comme celui-ci que si à l'avenir les mesures de performance réelles (et non hypothétiques) montrent que le bibliothèque plus sophistiquée, la substitution n'affecte qu'un seul point du code au lieu d'en être un réécriture généralisée.
Le principe général que je communique toujours dans les conversations : rapidité de livraison et la qualité architecturale ne sont pas toujours en conflit — le véritable conflit se situe souvent entre rapidité de livraison et choix irréversibles prématurés. Investir du temps pour entretenir Une décision réversible (via une couche d'abstraction minimale et non excessive) permet pour respecter le délai sans compromettre la maintenabilité future. Des décisions en effet coûteux à modifier ultérieurement (schéma de base de données, contrats d'API publics, choix de framework) méritent plus de temps d’analyse ; décisions facilement réversibles (quelle bibliothèque des graphistes) ne le méritent pas, et insister pour "faire les choses tout de suite" est souvent une perte de temps par rapport à la date limite.
Comment se préparer au mieux à un entretien angulaire
- Ne vous contentez pas de mémoriser les définitions : Pour chaque concept, demandez-vous « quel véritable bug le fait de savoir cela évite-t-il ? » — est exactement le genre de réponse qui distingue un candidat solide.
- Entraînez-vous avec du code réel, pas seulement de la théorie : construisez un petit projet qui utilise Signal, OnPush, le chargement paresseux et au moins un test — les connaissances pratiques émergent toujours dans les questions de suivi.
- Préparez des exemples concrets tirés de votre travail : pour les questions Senior/Expert, avoir un exemple réel (même simplifié) d'un compromis auquel vous avez été confronté vaut plus qu'une réponse théorique sans faille.
- Étudiez les erreurs courantes, pas seulement les meilleures pratiques : savoir ce qui ne va pas lorsque vous appliquez mal un modèle (par exemple OnPush avec des mutations directes) démontre une compréhension plus profonde que la simple citation de la règle.
- Restez à jour sur les versions récentes : Le signal, les composants autonomes, le sans zone et le nouveau flux de contrôle (
@if/@for) sont désormais la norme attendue dans une interview angulaire en 2026, pas la connaissance facultatif.
Conclusion
Ces 25 questions couvrent le cheminement de croissance naturel d'un développeur Frontend/Angular : depuis les fondements de HTML/CSS/JavaScript/TypeScript, en passant par RxJS et la gestion des états, jusqu'aux décisions architecturales qu'un Lead doit être capable de motiver devant une équipe ou un individu parties prenantes. Que vous prépariez ou meniez un entretien, le fil conducteur est toujours pareil : comprendre pourquoi un choix technique est le bon dans un contexte spécifique, ne répétez pas une règle de mémoire.
Si vous préparez un entretien Senior ou Expert, le conseil le plus concret est le suivant : choisissez trois ou quatre décisions architecturales réelles que vous avez prises (même imparfaites, même dans un projet personnel) et soyez prêt à leur dire en détail : ce que vous avez choisi, quelles alternatives vous avez rejetés et pourquoi, que feriez-vous différemment aujourd'hui. C'est le genre de réponse qu'aucun la définition du manuel peut remplacer.