Angularjs
Senior
Quiz-Angularjs-Senior
Digest-Cycle
Compile
Directives-Avancees
Migration-Angular
Performances-Angularjs
Tests-Unitaires
Entretien-Senior-Angularjs
Qcm-Angularjs-Avance
Legacy-Frontend
Javascript
Karma-Jasmine
📝
AngularJS — Entretien Senior
20 questions avancées AngularJS : cycle $digest, $compile, directives avancées, tests unitaires, migration Angular, performances. Idéal pour réussir un entretien AngularJS Senior.
Banque de révision : les 60 questions corrigées
Voici l'intégralité des 60 questions de « AngularJS — Entretien Senior », 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 :
Le cycle
$digest parcourt tous les watchers ($$watchers) en comparant les valeurs actuelles aux anciennes (dirty-checking). Il relance jusqu'à stabilité (max 10 boucles). Avec n watchers, chaque $digest coûte O(n). Une UI fluide à 60fps laisse ~16ms par frame — avec 5000 watchers complexes, cela peut dépasser ce budget.
Outils de diagnostic : angular-performance devtools, $rootScope.$$watchersCount. Stratégies : one-time binding (::), track by, ng-if vs ng-show, débouchement des $watch.
Explication :
AngularJS lève l'erreur
$rootScope:infdig : "10 $digest() iterations reached. Aborting!" quand la limite d'itérations est dépassée. Cela indique généralement une boucle infinie : un watcher modifie une valeur qui déclenche un autre watcher, etc. La limite de 10 est configurable via $rootScopeProvider.digestTtl().
Explication :
$compile(htmlString)(scope) compile dynamiquement du HTML AngularJS et le lie à un scope. Utilisé pour créer des directives qui génèrent du HTML dynamiquement, pour les widgets modaux, les tooltips, ou toute situation où on doit compiler du HTML au runtime. Retourne une fonction de liaison (link function).
Pattern : var el = $compile("")($scope); $element.append(el); — à utiliser avec parcimonie car contourne le mécanisme déclaratif standard.
Explication :
La décoration via
$provide.decorator() (ou app.decorator()) permet d'étendre ou modifier des services existants sans les remplacer. $delegate est l'instance originale du service. On peut ajouter des méthodes, logguer les appels, ou modifier le comportement.
Pattern courant : décorer $http pour ajouter du logging, ou décorer $exceptionHandler pour centraliser les erreurs.
Explication :
UpgradeModule (@angular/upgrade) permet de faire coexister les deux frameworks. Stratégie "strangler fig" : 1) Bootstrapper Angular avec UpgradeModule. 2) Migrer composant par composant. 3) Utiliser downgradeComponent() pour utiliser un composant Angular dans AngularJS, et UpgradeComponent pour l'inverse.
Ordre recommandé : migrer d'abord les services (plus faciles), puis les composants feuilles, puis remonter vers la racine.
Explication :
AngularJS ajoute
$$hashKey aux objets trackés par ng-repeat pour les identifier. Cela pollue les objets JavaScript et peut causer des problèmes lors de la sérialisation JSON (la propriété apparaît). angular.toJson() les nettoie. Solution : utiliser track by item.id pour éviter l'ajout de $$hashKey.
Explication :
Avec ngMock :
$httpBackend.expectGET("/api/data").respond(200, mockData) définit l'attente. Après l'action testée, $httpBackend.flush() simule la réponse HTTP et déclenche les callbacks. $httpBackend.verifyNoOutstandingExpectation() vérifie que toutes les attentes ont été satisfaites.
Pattern complet : beforeEach(inject(function(_$httpBackend_) { $httpBackend = _$httpBackend_; })); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); });
Explication :
when() définit une réponse stub sans assertion sur son appel. expect() vérifie obligatoirement que la requête est faite (échec de test si elle ne l'est pas). Utilisez when() pour les requêtes d'initialisation non critiques, expect() pour les comportements à tester.
Explication :
Dans
compile(tElement, tAttrs), on peut modifier le template DOM avant qu'il soit cloné pour chaque item ng-repeat. Les modifications coûteuses sont ainsi faites une seule fois au lieu de n fois. La fonction compile retourne la fonction link (ou un objet {pre, post}), qui s'exécute pour chaque instance.
Typiquement utilisé pour : précompiler des expressions complexes, retirer des nœuds conditionnels connus à la compilation, restructurer le template.
Explication :
$rootScope.$$phase indique si un cycle digest est en cours ("$apply" ou "$digest") ou null. Appeler $apply() pendant un cycle provoque une erreur. Pattern sécurisé : if (!$rootScope.$$phase) { $scope.$apply(fn); } else { fn(); }. En pratique, utilisez plutôt $scope.$evalAsync(fn).
$evalAsync est préféré à $$phase car il est un API public et gère correctement le timing.
Explication :
$evalAsync(fn) exécute fn dans le cycle digest courant ou le prochain — utile pour différer du travail tout en restant dans le flux digest. $applyAsync(fn) regroupe plusieurs appels $apply proches dans le temps en une seule exécution (debounce) — optimise les réponses HTTP multiples simultanées.
$http utilise $applyAsync par défaut depuis AngularJS 1.3. Configurable via $httpProvider.useApplyAsync(true/false).
Explication :
AngularJS 1.5+ supporte la transclusion multi-slot :
transclude: { titleSlot: "?h1", contentSlot: "p" }. Dans le template : <div ng-transclude="titleSlot">. Dans l'utilisation : <ma-directive><h1>Titre</h1><p>Contenu</p></ma-directive>.
Explication :
$animate est l'API JavaScript de ngAnimate. Il expose des méthodes comme enter(), leave(), move(), addClass(), removeClass() qui déclenchent des animations CSS définies par convention (.ng-enter, .ng-leave). Les directives structurelles l'utilisent internement.
Utilisez $animate directement quand vous créez des directives qui nécessitent des animations (modales, tooltips). L'API gère automatiquement le timing et les callbacks de fin d'animation.
Explication :
Un provider expose des méthodes de configuration publiques ET une méthode
$get. Exemple : app.provider("Config", function() { var apiUrl = "/api"; this.setApiUrl = function(url) { apiUrl = url; }; this.$get = function() { return { url: apiUrl }; }; }). Configurable dans .config(["ConfigProvider", function(cp) { cp.setApiUrl("/v2"); }]).
Explication :
La propriété
require dans une directive permet d'accéder au controller d'une directive parente. Exemples : require: "^ngModel" (directive ancêtre), require: "?^form" (optionnel), require: ["ngModel", "^form"] (plusieurs). Le controller est passé comme 4ème argument à la fonction link.
Pattern courant : valider un champ (accès à ngModelController) ou créer des composants composés (tabs/tab, accordion/panel).
Explication :
Le préfixe
^^ cherche la directive sur les ancêtres en excluant l'élément courant. ^ inclut l'élément courant. ?^ rend la recherche optionnelle (retourne null si non trouvé plutôt que d'erreur). La combinaison ?^^ = optionnel + ancêtres stricts.
Explication :
Le virtual scrolling ne rend que les éléments visibles dans le viewport + un buffer. Angular Material fournit
md-virtual-repeat. Des bibliothèques tierces comme angular-vs-repeat ou ng-infinite-scroll (infinite scroll vs virtual) sont aussi populaires. Réduit le DOM de milliers d'éléments à quelques dizaines.
Virtual scroll = seules les lignes visibles sont dans le DOM. Infinite scroll = toutes les lignes chargées s'accumulent. Pour 10 000+ éléments, le virtual scroll est indispensable.
Explication :
terminal: true arrête le processus de compilation pour les directives de priorité inférieure sur le même élément. Utilisé par ng-if, ng-repeat etc. pour contrôler la compilation conditionnelle. Couplé avec une priorité élevée, permet d'intercepter la compilation avant les autres directives.
Explication :
Pattern de test de directive :
var element = $compile("<mon-dir attr='val'></mon-dir>")($rootScope); $rootScope.$digest(); expect(element.text()).toBe("..."); expect(element.find("button").length).toBe(1);. L'élément compilé est un vrai élément DOM jQuery/jqLite.
Après compilation, isolez le scope avec element.isolateScope() si la directive a un scope isolé. Cela permet de tester les propriétés du scope isolé directement.
Explication :
Pour mocker un service dans un test :
beforeEach(module("monApp", function($provide) { $provide.value("MonService", { getData: jasmine.createSpy().and.returnValue($q.when(mockData)) }); }));. Le mock remplace l'implémentation réelle pour toute la durée du test.
Explication :
AngularJS 1.x
$q ne fournit pas race() nativement (contrairement à Promise ES6). Il faut l'implémenter manuellement ou utiliser une bibliothèque. AngularJS 1.7.3+ a ajouté un support limité. La workaround standard : $q.race = function(promises) { var d = $q.defer(); promises.forEach(p => $q.when(p).then(d.resolve, d.reject)); return d.promise; }.
Connaître les limites de $q vs Promise native montre une connaissance approfondie d'AngularJS.
Explication :
En production,
$compileProvider.debugInfoEnabled(false) dans .config() supprime les attributs CSS/data injectés par Angular pour le debugging (ng-scope, ng-binding, ng-isolate-scope). Cela réduit la taille du DOM et améliore les performances de ~5-15%. Les devtools Angular ne fonctionnent plus, mais c'est prévu en prod.
Explication :
Si ServiceA dépend de ServiceB et vice-versa, AngularJS lève une erreur. Solutions : 1) $injector.get("ServiceB") dans la méthode qui en a besoin (pas dans le constructeur) — lazy loading qui évite le cycle au bootstrap. 2) Service médiateur — ServiceC que A et B utilisent tous les deux. 3) Refactoriser pour supprimer la dépendance circulaire (meilleure approche).
Les dépendances circulaires indiquent généralement un problème de conception. Analysez si les responsabilités sont bien séparées.
Explication :
$templateCache est un service de cache pour les templates HTML. AngularJS y met automatiquement les templates chargés via ng-include ou les routes. On peut préremplir le cache lors du build (grunt-html2js, ng-html2js) pour éviter les requêtes HTTP en production.
En production, utilisez ng-html2js ou ngAnnotate pour intégrer tous les templates dans un bundle JavaScript — zéro requête HTTP pour les templates.
Explication :
$sce (Strict Contextual Escaping) est le mécanisme de sécurité central. Chaque contexte (HTML, CSS, URL, JS, resourceURL) a ses propres règles. ng-bind-html requiert un HTML sanitizé. Les URLs dans ng-src/ng-href sont validées. Le service $sanitize (ngSanitize) whitelist le HTML autorisé.
Ne jamais utiliser $sce.trustAs*() pour des données utilisateur. C'est uniquement pour du contenu dont VOUS contrôlez la source.
Explication :
$parse(expression) retourne une fonction fn(scope) qui évalue l'expression dans un contexte. La fonction a aussi une méthode assign(scope, value) pour setter la valeur. Plus performant que d'évaluer l'expression dans le template à chaque cycle, utile dans les directives à haute fréquence.
Explication :
Sources courantes de fuites : 1) Watchers non supprimés (conserver la fonction retournée par $watch et l'appeler dans $destroy). 2) Event listeners globaux (window.addEventListener — supprimer dans $destroy). 3) $interval/$timeout non annulés. Pattern :
$scope.$on("$destroy", function() { cleanup(); }).
Outil de détection : Chrome DevTools Memory Heap Snapshot. Comparez avant/après navigation pour identifier les objets retenus. Les contrôleurs qui restent en mémoire après navigation sont un signe de fuite.
Explication :
CSP peut bloquer
eval() utilisé par AngularJS pour certaines expressions. Solution : 1) Ajouter ng-csp sur l'élément ng-app désactive l'usage d'eval (performances réduites). 2) Configurer $compileProvider.aHrefSanitizationWhitelist() pour les URLs. 3) Utiliser nonces CSP pour les scripts inline.
Explication :
AngularJS 1.5 introduit
.component() comme sucre syntaxique sur les directives. Les composants ont : scope isolé par défaut, controllerAs "vm" par défaut, bindings unidirectionnels (<), lifecycle hooks ($onInit, $onChanges, $onDestroy, $postLink). C'est la passerelle conceptuelle vers Angular 2+.
Migrer vers .component() est la première étape vers Angular 2+. Les lifecycle hooks et les bindings one-way sont identiques à Angular.
Explication :
AngularJS 1.5+ composants exposent :
$onInit() — après bindings initialisés, $onChanges(changesObj) — quand les bindings one-way changent, $onDestroy() — avant destruction, $postLink() — après compilation enfants (équivalent ngAfterViewInit Angular), $doCheck() — chaque cycle digest (utiliser avec soin).
Explication :
Pattern FLUX en AngularJS 1.5+ : 1) Store : service singleton avec l'état et les méthodes de mutation. 2) Actions : méthodes du store (addItem, removeItem). 3) Composants : reçoivent les données via bindings
< (one-way) et appellent les actions via &. 4) Le store diffuse les changements via $broadcast ou callbacks.
ngRedux porte le pattern Redux sur AngularJS. Pour les grandes applications, c'est une architecture plus prévisible que le two-way binding libre.
Explication :
constant() crée une valeur injectable dans la phase .config() ET dans les services/contrôleurs. Elle ne peut pas être décorée ($provide.decorator ne fonctionne pas sur les constants). value() n'est injectable que dans la phase run (services, contrôleurs) et peut être décorée.
Explication :
downgradeComponent({component: MonComposantAngular}) crée une directive AngularJS qui wrape un composant Angular 2+. Utilisé pendant la migration hybride avec UpgradeModule. Permet d'utiliser de nouveaux composants Angular dans les templates AngularJS existants sans réécrire toute la vue.
Stratégie : créer les nouveaux composants dans Angular, les exposer avec downgradeComponent() dans AngularJS, migrer progressivement les anciens composants.
Explication :
Outils : 1) Batarang (extension Chrome) affiche le temps passé dans $digest et le nombre de watchers. 2) ng-inspector inspecte le scope. 3) Chrome DevTools Timeline enregistre les frames et identifie les longs JS tasks. 4)
$rootScope.$$watchers liste tous les watchers actifs (debug).
Explication :
Dans
.config(), $interpolateProvider.startSymbol("{{") et endSymbol("}}") permettent de changer les délimiteurs d'interpolation. Exemple : les utiliser avec Twig (PHP) qui utilise aussi {{ }} — configurer AngularJS pour utiliser [[/]] à la place évite le conflit.
Cas d'usage réel : SPA Angular intégrée dans un CMS PHP/Twig ou un projet Django. Changer les délimiteurs est la solution propre.
Explication :
La bibliothèque la plus populaire est
angular-translate ($translate service, translate filter, translate directive). Elle supporte : chargement asynchrone des traductions, pluralisation, formatage, fallback. AngularJS 1.x fournit aussi une localisation basique (filtres number/date/currency) via les fichiers i18n du package.
Explication :
AngularJS n'a pas de support SSR natif (pas d'AngularJS Universal). Approches possibles : 1) Prerender.io (service tiers) rend la page avec un headless browser et sert le HTML pré-rendu aux crawlers. 2) PhantomJS/Rendertron en proxy. 3) isomorphic-angular (limité). C'est une limitation majeure d'AngularJS vs Angular.
C'est l'une des raisons clés pour migrer vers Angular 2+ (Angular Universal). Mentionnez cette limitation en entretien montre une connaissance des compromis.
Explication :
$scope.$evalAsync(fn) diffère l'exécution de fn dans le cycle digest courant si possible, ou planifie un nouveau cycle. Contrairement à $timeout(fn, 0), il tente d'exécuter dans le cycle courant — plus performant et sans déclencher un nouveau cycle inutile.
Explication :
Tests unitaires : Karma (runner) + Jasmine (assertions) + ngMock (helpers Angular). Rapides, isolés, mockent les dépendances. Tests E2E : Protractor (wrapper Selenium/WebDriver) teste l'application réelle dans le navigateur. Lents mais testent les flux utilisateur complets. Complémentaires, pas interchangeables.
Note : Protractor est déprécié depuis Angular 12+. Pour AngularJS, il reste l'outil standard. Des alternatives modernes : Playwright, Cypress.
Explication :
karma.conf.js définit : frameworks: ["jasmine"], files: ["node_modules/angular/angular.js", "node_modules/angular-mocks/angular-mocks.js", "src/**/*.js", "test/**/*.spec.js"], browsers: ["ChromeHeadless"]. Le module angular-mocks est essentiel pour inject() et $httpBackend.
Explication :
$injector est le mécanisme d'injection sous-jacent. $injector.get("NomService") retourne l'instance d'un service sans qu'il soit un paramètre de constructeur. Utile pour : briser des dépendances circulaires (appeler get() dans une méthode, pas le constructeur), créer des plugins ou extensions dynamiques.
Explication :
Pattern classique de directive composée :
tabs expose un controller avec des méthodes addTab(), selectTab(). La directive tab utilise require: "^tabs" pour accéder à ce controller dans sa fonction link et s'enregistre. C'est le pattern "directive communication" standard.
Ce pattern est la base de nombreux composants UI complexes : accordéon, stepper, wizard. Maîtriser require + controller communication est essentiel.
Explication :
Quand un resolve rejette sa promise, ngRoute émet
$routeChangeError sur $rootScope. Dans .run(), écouter : $rootScope.$on("$routeChangeError", function(event, current, previous, rejection) { $location.path("/error"); });. Permet une gestion globale des erreurs de navigation.
Pattern complet : le resolve vérifie les droits, rejette si non autorisé, $routeChangeError redirige vers /login ou /403.
Explication :
L'interpolation
{{ expression }} et ng-bind encodent automatiquement le HTML (escape des caractères spéciaux). Pour afficher du HTML brut, ng-bind-html requiert soit $sanitize (ngSanitize) qui whitelist le HTML, soit $sce.trustAsHtml() qui désactive la protection — à n'utiliser que pour du contenu de confiance.
Explication :
La stratégie recommandée par Google est la migration incrémentale ("strangler fig pattern") : 1) Préparer : utiliser .component(), controllerAs, TypeScript si possible. 2) Ajouter Angular avec UpgradeModule. 3) Migrer les services en premiers (injectables partout). 4) Migrer les composants feuilles avec downgradeComponent. 5) Remonter progressivement vers la racine.
Le big bang rewrite est risqué — nouveau code non testé, features manquantes, timeline imprévisible. L'incrémental permet de livrer de la valeur tout en migrant.
Explication :
Pattern avec ocLazyLoad :
$stateProvider.state("admin", { resolve: { load: function($ocLazyLoad) { return $ocLazyLoad.load(["admin.module.js"]); } }, template: "..." }). Le module est chargé et enregistré dans Angular avant l'activation de la route.
Explication :
Optimisations bootstrap : 1) Template cache ($templateCache) — évite les requêtes HTTP pendant le bootstrap. 2) Bootstrap manuel différé (
angular.bootstrap()) après le chargement des ressources critiques. 3) Réduire les opérations lourdes dans .config()/.run(). 4) Bundling des fichiers JS. 5) $compileProvider.debugInfoEnabled(false).
Le Time To Interactive (TTI) inclut le bootstrap Angular. Profilez avec Chrome DevTools Performance pour identifier les fonctions lentes au bootstrap.
Explication :
angular.forEach(collection, fn) itère sur les tableaux ET les objets (clé/valeur). Il ne lève pas d'erreur si la collection est null ou undefined (itération sur 0 éléments). La callback reçoit (value, key). Moins performant que le for natif mais plus safe.
Explication :
Intercepteur retry : dans
responseError, vérifier si le code d'erreur justifie un retry (503, 429), maintenir un compteur via la config de requête (config.retryCount), et si < max, re-exécuter avec $injector.get("$http")(config) ($http injecté lazily pour éviter la circularité). Retourner la rejection si épuisé.
Implémentez un backoff exponentiel ($timeout avec délai croissant) pour ne pas surcharger le serveur.
Explication :
$templateRequest(url) charge un template HTML depuis l'URL et le stocke dans $templateCache. Retourne une promise avec le HTML. Utilisé en interne par ng-include, les directives avec templateUrl, et le routeur. Utile pour précharger des templates dans .run().
Explication :
Depuis la console Chrome DevTools : 1) Sélectionner un élément DOM. 2)
angular.element($0).scope() accède au scope de l'élément. 3) angular.element($0).injector() accède à l'injecteur. 4) angular.element($0).controller() accède au controller. Note : nécessite debugInfoEnabled(true) — désactivé par défaut en prod.
Activez temporairement le debug en production via la console : angular.reloadWithDebugInfo().
Explication :
AngularJS a une protection CSRF intégrée : il lit automatiquement le cookie
XSRF-TOKEN et l'ajoute dans le header X-XSRF-TOKEN de chaque requête non-GET. Le serveur doit définir le cookie XSRF-TOKEN et vérifier le header X-XSRF-TOKEN. Les noms sont configurables via $httpProvider.defaults.xsrfCookieName.
Explication :
jQuery (impératif) : "Trouver l'élément, changer sa classe, mettre à jour son texte, masquer cet autre élément" — série d'instructions DOM. AngularJS (déclaratif) : "Voici mes données (état). Voici comment la vue doit être en fonction de cet état. Angular se charge du DOM." Le développeur pense état, pas manipulation DOM.
Cette distinction conceptuelle est fondamentale pour expliquer pourquoi AngularJS (et tous les frameworks modernes) existent. C'est souvent une question d'architecture en entretien senior.
Explication :
$locationProvider.html5Mode(true) dans .config() active les URLs propres (/route) via l'API history.pushState HTML5, au lieu des hash URLs (/#/route). Nécessite une configuration serveur pour rediriger toutes les URLs vers index.html.
Sans configuration serveur, les URLs propres donnent des 404 lors d'un rechargement direct. Configurer Nginx/Apache pour servir index.html pour toutes les routes est obligatoire.
Explication :
Les expressions AngularJS supportent : arithmétique (
+, -, *, /, %), comparaison (==, !=, ===, !==, <, >), logique (&&, ||, !), ternaire (a ? b : c), accès aux propriétés (a.b, a[0]), appels de fonctions. Non supportés : déclarations de variables, boucles, throw/catch, accès à window/document.
Explication :
Angular analyse le nom des paramètres de la fonction (
toString()) pour résoudre les dépendances. C'est pourquoi la minification (qui renomme les paramètres) casse l'injection. Les annotations $inject (tableau de noms en chaînes) contournent ce problème : MyCtrl.$inject = ["$scope", "$http"].
Explication :
Par défaut,
$resource (comme $http) ne met pas les requêtes en cache. Pour activer : $resource(url, params, { get: { cache: true } }). Le cache est partagé avec $http — les deux utilisent le même $cacheFactory("$http").
Explication :
Une bibliothèque/plugin AngularJS est un module Angular qui expose ses propres services, directives, filtres et constants. Les applications l'incluent comme dépendance :
angular.module("monApp", ["maLib"]). Exemples : angular-translate, angular-ui-router, ngMessages — tous sont des modules Angular.
Structure recommandée : un seul module principal + modules fonctionnels optionnels. Publier sur npm avec le keyword "angularjs" pour la découvrabilité.
Explication :
Pattern recommandé :
angular.module("config", []).constant("APP_CONFIG", { apiUrl: "/api", timeout: 5000 }). Ce module est inclus comme dépendance principale. constant() est injectable dans .config() (contrairement à value()), ce qui permet de configurer les providers.
Explication :
Symptômes de problèmes $digest : 1) Lag à la frappe dans les inputs (chaque frappe déclenche $digest). 2) Animations saccadées (60fps = 16ms/frame, $digest lent casse ce budget). 3) Scroll saccadé avec ng-repeat. 4) Lenteur au chargement initial (trop de $watch initialisés). Diagnostic : compter les watchers, profiler le CPU.
Optimisations clés : one-time binding (::), track by, ng-if > ng-show, $watchCollection > $watch deep, déplacement de la logique des templates vers les contrôleurs.