Skip to content
Web and Mobile Development

AngularJS interview questions and answers

96 questions, 40 with code. About 6 h 36 min to read them all.

By TestLab · Updated

Plan for an interview
96 questions

Track what you know and what to revise.

Sign in to track progress

You can open a few full answers without signing in. Sign in to read all

1

Can you describe what AngularJS is and outline its primary features?

AngularJS is an open-source JavaScript structural framework maintained by Google for building dynamic single-page applications (SPAs) using extended HTML syntax. It connects application data to the DOM through directives and scopes, allowing developers to declare behaviour in markup rather than manipulating elements manually. Because AngularJS reached end of life on 31 December 2021, it receives no security updates and should not be used for new projects.

Read full answer

Applies toAngularJS 1.8.x (final release)

Core Capabilities

  • Two-way data binding: Automatically synchronises data between the model and the view, so changes in an input update the model and vice versa without manual DOM manipulation.

  • Dependency injection: A built-in subsystem provides components with their dependencies, such as $http or $scope, making code modular and easier to test.

  • Directives: Custom attributes or elements like ng-model, ng-repeat and ng-if that extend the HTML vocabulary to attach behaviour to the DOM.

  • MVC architecture: Encourages separating data (model), logic (controller) and presentation (view), though it is often described as MVVM due to its binding system.

  • Testing support: Designed for testability from the start, integrating with runners like Karma and frameworks like Jasmine.

Change Detection and the Digest Cycle

AngularJS detects changes using a digest cycle rather than modern zone-based detection. When an event occurs outside the framework, you must call $scope.$apply to trigger a digest, which checks all registered watchers for value changes. - Watchers: Each binding creates a watcher; too many watchers slow down the digest cycle. - One-time binding: Using the :: prefix (e.g. {{ ::value }}) removes the watcher once the value stabilises, improving performance. - ng-repeat optimisation: Always use track by with ng-repeat to avoid recreating DOM elements when collection order changes.

Component-Based Architecture (1.5+)

Since version 1.5, the recommended style uses angular.module(...).component() instead of the older .directive() for application structure. - Controller alias: Components use controllerAs syntax, exposing the controller as $ctrl in the template. - Bindings: Inputs use one-way < bindings to prevent accidental mutation, while outputs use & for callback expressions. - Lifecycle hooks: Components support $onInit, $onChanges, $postLink and $onDestroy for setup and teardown logic. - When to use directives: Reserve .directive() for cases a component cannot handle, such as creating custom ng-model-like DOM manipulation.

HTTP and Dependency Injection Safety

The $http service returns standard promises, so responses are handled with .then()/.catch(). - Removed methods: The older .success()/.error() shortcut methods were removed in version 1.6 and must not be used. - Minification-safe DI: Write injection using array notation, such as ['$http', function ($http) {...}], or the $inject property, so renaming variables during minification does not break the app.

Limitations and Migration

The framework is no longer maintained, which creates security and compatibility risks for production systems. - Migration paths: Teams typically rewrite the application in Angular (2+) or run both frameworks side by side using the ngUpgrade library. - Performance: Complex scopes and heavy watcher counts can cause noticeable lag compared to modern virtual-DOM libraries.

2

How does AngularJS automatically synchronize data between the model and the view, and what underlying mechanism drives this synchronization?

Two-way data binding in AngularJS ensures that changes in the model update the view and vice versa without manual DOM manipulation, powered by the digest cycle. This mechanism allows developers to declare bindings in templates that stay synchronized with underlying JavaScript properties automatically.

Read full answer

Applies toAngularJS 1.8.x

How the Digest Cycle Works

AngularJS maintains a list of watchers for each scope to monitor expression values. When an event occurs, such as user input or an HTTP response handled inside Angular’s contexts, AngularJS triggers a digest cycle to check for changes. - Watchers: Functions registered (via ng-model, {{ }} interpolation, ng-repeat, $watch, and similar) to monitor specific expressions for value changes. - Dirty Checking: During each cycle, AngularJS compares each watcher’s current value against its previously stored value; if any differ, the cycle is marked “dirty” and the affected listeners and bindings are re-evaluated. - Stability: The cycle repeats until no more changes are detected or the time-to-live (TTL) limit is reached, preventing infinite loops from mutually dependent watchers.

How the View Updates the Model

Directives such as ng-model register their own listeners on DOM events (input, click, and others). When the user interacts with a control, the directive’s handler updates the bound scope property and calls $scope.$apply() (or relies on Angular’s own event wrappers), which starts a digest cycle so every dependent watcher and binding reflects the new value.

How the Model Updates the View

Any change made inside Angular’s own execution contexts — a controller method, a $http promise callback, a $timeout, or an ng-click handler — is picked up automatically by the next digest cycle. Changes made by plain browser callbacks, third-party libraries, or setTimeout happen outside Angular’s context and need $scope.$apply() (or $timeout) to trigger synchronization.

Binding Types in Components

Since AngularJS 1.5, the component model (angular.module(...).component()) encourages specific binding types defined in the bindings object. - Two-way (=): Synchronizes the child component’s scope property with the parent scope property; changes propagate in both directions. - One-way (<): Passes data from parent to child only. Preferred for immutability and performance. - Output (&): Allows the child to invoke a parent function, enabling event emission without two-way binding.

While = provides classic two-way binding, modern AngularJS style favors < combined with & to reduce coupling and watcher overhead.

Performance Implications

Every binding creates watchers that consume memory and CPU during digest cycles. - Watcher Count: High watcher counts slow down applications, especially on mobile devices, because each digest evaluates every watcher. - One-time Binding: Use ::expression to bind data once and deregister the watcher when the value stabilizes. - ng-repeat Optimization: Always use track by so that AngularJS reuses DOM nodes when list items change identity instead of re-creating them.

AngularJS reached end of life on 31 December 2021 (long-term support ended), so these optimizations matter mainly for maintaining existing applications rather than starting new ones.

Common Pitfalls

  • Outside Angular’s Context: Modifying scope from plain browser callbacks or third-party code without calling $scope.$apply() means the view will not update until the next digest.

  • Deep Watching: Using $watch(expr, listener, true) for objects is expensive because it performs a full recursive comparison each cycle; prefer watching a specific property, one-way bindings with immutable data, or explicit updates.

  • Legacy Directives: Prefer .component() over .directive() for UI blocks to take advantage of lifecycle hooks such as $onInit, $onChanges, and $onDestroy.

3

Can you explain what directives are in AngularJS and provide some examples of how they're used?

In AngularJS, directives are markers placed on DOM elements—such as attributes, element names, CSS classes, or comments—that instruct the framework’s HTML compiler to attach specific behavior to that element or transform its rendering. They extend HTML with new vocabulary and are the primary way AngularJS builds reusable, declarative user interfaces. Note that AngularJS reached end of life with long-term support ending 31 December 2021, so it should not be chosen for new projects.

Read full answer

Applies toAngularJS 1.8.x

What Directives Do

When AngularJS bootstraps, the $compile service walks the DOM, matches directives against elements, and combines their behavior with the current $scope. A directive can: - Listen to events: react to clicks, input changes, or form submissions. - Manipulate the DOM: add, remove, or update elements dynamically. - Create reusable components: encapsulate template, logic, and styling. - Enforce validation: control form input rules and error display.

Built-in Directives

AngularJS ships with many commonly used directives. The most frequent ones include:

Directive Purpose
ng-model Two-way data binding on form inputs
ng-repeat Renders a list by repeating a template per item
ng-if Adds or removes an element from the DOM based on a condition
ng-show / ng-hide Toggles CSS display without removing the element
ng-click Binds a click handler expression
ng-bind Renders a scope value as text (avoids flash of uncompiled content)
ng-class Conditionally applies CSS classes
ng-src / ng-href Safely binds src and href so URLs are interpolated before the browser requests them
ng-app Auto-bootstraps the application module

Custom Directives

You define a custom directive by registering it on a module. Since AngularJS 1.5, the component-based style via angular.module(...).component() is preferred for most UI pieces, using controllerAs ($ctrl), one-way bindings ('<') for inputs, and '&' for outputs, along with lifecycle hooks $onInit, $onChanges, $postLink, and $onDestroy. Use .directive() only when you need direct DOM manipulation, a custom restrict value, or transclusion that a component cannot express.

Directive Definition Options

When using .directive(), the factory returns a directive definition object with these common properties: - restrict: 'A' (attribute), 'E' (element), 'C' (class), or 'M' (comment); 'A' is the default. - template / templateUrl: inline HTML or a path to an external template file. - scope: an isolated scope with '<' (one-way), '=' (two-way), '&' (expression), or '@' (string) bindings. - link: a function (scope, element, attrs) for DOM manipulation after the template is linked. - controller / controllerAs: component logic, minification-safe via array notation.

Code Example

<!-- item-list.html: template of the itemList component; $ctrl is the controller -->
<input type="text" ng-model="$ctrl.name" placeholder="New item" app-focus />
<button ng-click="$ctrl.addItem()">Add</button>
<ul>
  <li ng-repeat="item in $ctrl.items track by item.id">
    <span ng-bind="item.label"></span>
    <span ng-if="item.new">New</span>
  </li>
</ul>
<!-- Used as: <item-list items="$ctrl.catalog"></item-list> -->
// Minification-safe module and component (AngularJS 1.5+ style)
angular.module('app', [])
  .component('itemList', {
    bindings: {
      items: '<'          // one-way input binding from the parent
    },
    controller: function ItemListController() {
      var $ctrl = this;
      $ctrl.$onInit = function () {
        // items is bound from the parent before $onInit runs
        $ctrl.items = $ctrl.items || [];
        $ctrl.name = '';
      };
      $ctrl.addItem = function () {
        if ($ctrl.name) {
          $ctrl.items.push({ id: Date.now(), label: $ctrl.name, new: true });
          $ctrl.name = '';
        }
      };
      $ctrl.$onChanges = function (changes) {
        if (changes.items) { /* react to new input */ }
      };
      $ctrl.$onDestroy = function () {
        // clean up watchers or event listeners
      };
    },
    templateUrl: 'item-list.html'
  })
  // Plain directive for direct DOM work (use only when a component cannot do it)
  .directive('appFocus', ['$timeout', function ($timeout) {
    return {
      restrict: 'A',
      link: function (scope, element, attrs) {
        // focus the element after the current digest cycle
        $timeout(function () { element[0].focus(); });
      }
    };
  }]);

Best Practices and Pitfalls

  • Prefer .component() over .directive() for component-style UI since AngularJS 1.5; it gives sensible defaults and clearer lifecycle hooks.

  • Keep watchers low: use track by in ng-repeat and one-time bindings (::) where values never change, to reduce digest-cycle cost.

  • Write minification-safe DI: always use array notation ['$timeout', function ($timeout) {...}] or the $inject property, since uglification renames parameters.

  • Avoid the old promise methods: $http returns promises; use .then()/.catch() because .success()/.error() were removed in 1.6.

  • Use ng-src and ng-href instead of raw src/href so the browser does not request a broken URL before interpolation finishes.

  • Do not manipulate the DOM from a controller; put DOM logic in a directive’s link function or a component’s $postLink hook.

4

Can you explain how the digest cycle drives two-way data binding in AngularJS?

The digest cycle is AngularJS’s change-detection mechanism: it walks the list of registered watchers on every scope, compares each watched expression’s current value against its previous value, and updates the DOM (or other listeners) whenever a value has changed — repeating until the model stabilises.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; AngularJS reached end of life on 31 December 2021, so it should not be used for new projects).

How It Works

  • Watchers: every binding in a template ({{ name }}, ng-model, ng-bind, ng-repeat, ng-show, and explicit $scope.$watch() calls) registers a watcher on the current scope. A watcher stores its last value and a listener function that runs when the value changes.

  • Dirty checking: the digest loop iterates over all watchers, evaluates each expression, and compares the result with the stored previous value using angular.equals. If they differ, the watcher is “dirty” and its listener runs, which typically updates the view and may itself change other watched values.

  • The loop repeats: because one listener can dirty another watcher’s value, AngularJS re-runs the whole check until a full pass finds no changes (a “stable” model) or until 10 iterations are reached. If it never stabilises, AngularJS throws the infdig error (“10 $digest() iterations reached”), which is the classic symptom of a listener that unconditionally changes a watched value.

  • Entering the digest: AngularJS calls $scope.$digest() automatically after events it knows about — ng-click, ng-change, $http responses, $timeout and $interval callbacks, promise resolution, and location changes. Each digest also propagates down child scopes and up to $rootScope.

Key Points

  • $scope.$apply(expr) is the manual entry point for changes coming from outside AngularJS: raw DOM listeners, setTimeout, WebSockets, third-party libraries. It evaluates the expression on $rootScope and then runs a full $digest(). Nesting $apply inside an already-running digest throws $digest already in progress.

  • $scope.$evalAsync(expr) schedules work on the current scope and runs a digest only if one is not already in flight — usually the safer choice than $apply when you are unsure of the context.

  • $timeout(fn) is the recommended wrapper for deferred work: it triggers a digest automatically and can be flushed in unit tests with $timeout.flush(), unlike raw setTimeout.

  • Performance scales with watcher count: the digest cost is roughly linear in the number of watchers, and every digest can run up to 10 passes. Keeping the watcher count low (the widely cited guideline is under roughly 2,000 per page) is the main AngularJS performance lever.

  • One-time bindings ({{ ::user.name }}) remove the watcher permanently once the value is defined, which is the single most effective way to cut watcher counts for static display data.

  • track by in ng-repeat avoids re-creating DOM nodes and reduces digest work when a collection is refreshed; without it, AngularJS re-renders items it cannot match by identity or $$hashKey.

Best Practices and Common Pitfalls

  • Prefer $timeout and $applyAsync over $apply; reach for $apply only at a known framework boundary, and guard it if nesting is possible.

  • Never mutate a watched value inside a $watch listener without a terminating condition — that is what produces the infdig error.

  • Use :: one-time bindings for anything that does not change after first render, and track by in every ng-repeat over non-trivial collections.

  • For long lists, paginate or virtualise rather than rendering thousands of items, since each rendered item multiplies the watcher count.

5

Can you explain what a scope is in AngularJS and how it differs from the scope concept in plain JavaScript?

In AngularJS, a scope is a JavaScript object that acts as the glue between a controller and the view: it holds the model data and the expressions the template binds to, and it participates in the framework’s two-way data binding. It is not the same thing as JavaScript’s lexical scope, which is the compile-time visibility rules that control which variables a function can access.

Read full answer

Applies toAngularJS 1.8.x (end of life: long-term support ended 31 December 2021)

What an AngularJS Scope Actually Is

  • A plain object, not a language construct: A scope is a plain JavaScript object (or the root scope, $rootScope, at the top of the hierarchy), created by the $rootScope service via $rootScope.$new(). You attach properties to it from a controller and read them from the template.

  • The model in MVC: AngularJS scopes carry the application data — for example, $scope.user = { name: 'Ada' } is what makes {{ user.name }} render in the view.

  • Hierarchical: Scopes form a tree rooted at $rootScope. Child scopes are created for directives such as ng-controller, ng-repeat, ng-if and ng-include, and they prototypally inherit from their parent, so a child can read parent properties unless it shadows them.

  • Change detection: Most bindings registered on a scope become a watcher (one-time bindings using :: do not). During the digest cycle, AngularJS re-evaluates each watched expression and updates the DOM when a value changes. This is why calling $scope.$apply() is needed when you change a scope value from outside AngularJS (a raw setTimeout, a jQuery handler or a WebSocket callback).

  • Event propagation: Scopes provide $emit (bubbles upward), $broadcast (flows downward) and $on (listener registration), a mechanism that has no counterpart in plain JavaScript scoping.

What JavaScript Scope Means

  • Lexical visibility: JavaScript scope determines which identifiers are in scope at a given point in the code. var is function-scoped, while let and const are block-scoped; closures capture the surrounding lexical environment.

  • Static and structural: It is a property of where code is written, not a runtime object you can inspect or pass around. It governs variable access only — it knows nothing about the DOM or data binding.

  • No hierarchy of instances: JavaScript has a global scope and nested function/block scopes, but there is no tree of inheritable “scope objects” that a framework walks to detect changes.

Key Differences

Aspect AngularJS scope JavaScript scope
Nature Runtime object provided by the framework Language-level visibility rule
Purpose Carries model data and drives view binding Controls which variables code can access
Lifetime Tied to the controller, directive or component instance Tied to function or block execution
Structure Prototype-chain tree rooted at $rootScope Nested function/block environments
Change detection Watchers evaluated in the digest cycle None
Events $emit, $broadcast, $on Not applicable

Where the Two Concepts Meet

The confusion is understandable because an AngularJS scope is implemented with JavaScript scoping: the controller function’s parameters ($scope, $http) are injected thanks to closure and lexical scope, and closures are how watchers keep references to expressions. The digest cycle itself is ordinary JavaScript code — but the scope object it traverses is a framework abstraction layered on top of the language.

Practical Guidance

  • Prefer controllerAs and this: In the component-based style (angular.module(...).component()), bindings live on the controller instance ($ctrl) rather than being assigned directly to $scope, which reduces reliance on scope inheritance and accidental shadowing.

  • Keep watchers low: Every {{ }} interpolation and two-way binding adds a watcher. Use one-time bindings ({{ ::user.name }}) and track by in ng-repeat to keep digest cycles fast.

  • Use $timeout, not raw setTimeout: $timeout triggers the digest automatically; with a plain callback you must call $scope.$apply() yourself, and calling it while a digest is already running throws a “$digest already in progress” error.

  • Do not use scopes as a message bus: $rootScope.$broadcast is a common but hard-to-trace coupling; prefer component bindings (< for inputs, & for outputs) or a service.

  • Remember the framework’s status: AngularJS 1.8.x no longer receives security patches or browser-compatibility fixes, so new work should use a supported framework; existing apps can be migrated incrementally with ngUpgrade or rewritten in Angular.

6

What is the primary role and responsibility of a controller within an AngularJS application?

In AngularJS, a controller is a JavaScript constructor function whose responsibility is to initialize and own the state for a specific part of the view, exposing data and behavior that the template binds to. It acts as the intermediary between the model (services and data) and the view (template), following the Model-View-Controller pattern, and it must never manipulate the DOM directly.

Read full answer

Applies toAngularJS 1.8.x

Core Responsibilities

The controller’s job is to prepare everything a view needs to render and respond to user input.

  • State Initialization: It sets the initial values on the scope (or on this when using controllerAs) that the template displays immediately.

  • Behavior Exposure: It defines functions that user interactions, such as clicks or form submissions, trigger from the view through directives like ng-click.

  • Mediation: It coordinates between services that hold data and the scope that the template watches, updating the model when events fire so the view reflects current application state.

How Controllers Work with Scope and the Digest Cycle

AngularJS change detection runs through the digest cycle, which checks registered watchers for changes and re-renders bindings.

  • Watchers: Every binding in the template (such as {{ }} or ng-model) creates a watcher that the digest cycle evaluates.

  • Triggering Digest: Code that runs inside AngularJS, like ng-click handlers or $http callbacks, automatically triggers a digest. Code that runs outside AngularJS, such as a third-party library callback, must call $scope.$apply() to start one.

  • Keeping It Fast: Because every watcher runs on each digest, controllers should keep the watched state small. Use one-time bindings (::) for data that never changes and track by in ng-repeat to reduce the number of watchers.

Registration and Dependency Injection

Controllers are registered on an AngularJS module and instantiated by the framework when a route loads or a template requests them.

  • Module Registration: Register with angular.module('name').controller('Name', ...).

  • Minification Safety: Because AngularJS resolves dependencies by parameter name, minification breaks plain function arguments. Use array notation, for example ['$scope', '$http', function($scope, $http) {...}], or set the $inject property on the constructor.

  • Lifecycle: The constructor runs once when the controller is instantiated, before the associated view is rendered.

Controllers vs Components

Since AngularJS 1.5, the recommended way to build UI logic is the .component() method rather than standalone .controller() definitions.

Aspect Standalone Controller Component
Template External or none; bound via scope Defined inline with the component
Bindings Manual $scope properties or controllerAs Declarative one-way (<) inputs and & outputs
Lifecycle hooks Constructor only $onInit, $onChanges, $postLink, $onDestroy
Recommended use Legacy views, simple cases New UI development since 1.5
  • Components: Wrap a controller with a template and bindings, encouraging encapsulation and reuse.

  • Standalone Controllers: Still functional but discouraged for new UI; when used, prefer controllerAs syntax to avoid scope pollution.

Best Practices and Common Pitfalls

Keeping controllers lean ensures better testability and performance within the digest cycle.

  • Keep Them Thin: Move business logic, data access, and complex transformations into services or factories; controllers should only orchestrate data flow.

  • Avoid DOM Manipulation: Never query or manipulate the DOM inside a controller; put DOM-related logic in directives.

  • Handle Promises: $http returns promises; use .then() and .catch(). The legacy .success() and .error() methods were removed in version 1.6.

  • End of Life Note: AngularJS reached end of life on 31 December 2021. For new projects, use a modern framework; for existing applications, plan migration by rewriting in Angular or running both frameworks together with ngUpgrade.

7

Describe the purpose of a service in AngularJS and how it fits into an application.

A service in AngularJS is a singleton object that encapsulates reusable, stateful or stateless logic which does not belong in a view or a controller — data access, business rules, validation, shared state, third-party API wrappers and the like. Services are the main mechanism for keeping controllers thin, sharing data and behaviour across the application, and making code testable through dependency injection.

Read full answer

Applies toAngularJS 1.8.x (AngularJS reached end of life on 31 December 2021, so it should not be chosen for new projects; existing apps can be rewritten in Angular or migrated gradually with ngUpgrade).

Core Characteristics

  • Singleton by design: each service is instantiated once per injector (in practice, once per application bootstrapping with a single angular.bootstrap call). Every component, controller or other service that asks for it receives the same instance, which is why services are the natural place for shared state and caches.

  • Registered on a module: services are declared with angular.module('app').service('userService', UserService), factory(), or provider(), and are only available to the injector that loaded that module.

  • Injected, not instantiated by hand: AngularJS resolves dependencies by name, so a controller declares ['userService', 'authService', function (userService, authService) { ... }] and the injector supplies the instances. This is what makes services easy to unit-test with $inject or a test harness that mocks each dependency.

  • Lifecycles are implicit: there is no $onInit or $onDestroy hook on a service as there is on a component controller. A service lives for the lifetime of the injector; cleanup must be handled by the caller or by listening for the $destroy event on a scope if a service is tied to a view’s lifetime.

The Three Registration Styles

Recipe What you pass What AngularJS does Typical use
service('name', Constructor) A constructor function (or ES2015 class) Calls new Constructor() once and caches the result Object-oriented services where methods are attached to this or to the prototype
factory('name', fn) A factory function returning an object Calls fn() once and caches the returned value The most flexible style: return an object literal, a class instance, or expose a private implementation
provider('name', fn) A provider with a $get method Instantiates the provider, calls $get() once Services that need configuration at .config() time, such as a default API base URL

constant() and value() are related but simpler recipes for registering plain values that cannot themselves take dependencies.

Built-in Services

AngularJS ships with many services whose names start with $: $http for XHR-based communication with a server (it returns a promise, so use .then() and .catch() — the old .success() and .error() callbacks were removed in AngularJS 1.6), $q for promises, $timeout and $interval for timer-based work that must run inside a digest cycle, $location for URL parsing and navigation, $route and $state (from angular-ui-router) for routing, and $filter for formatting. Application services depend on these rather than reimplementing them.

Where Services Fit in the Architecture

  • Controllers orchestrate a view: they read from services, expose data to the template and handle user actions. They should not fetch or transform data directly.

  • Components (angular.module(...).component()) follow the same rule: bindings carry data in and out, and the component’s controller delegates to injected services.

  • Directives that need shared behaviour should also receive it through a service rather than duplicating logic.

  • Services may depend on other services, but circular dependencies are not supported and will fail at injection time.

Best Practices and Common Pitfalls

  • Prefer factory() or service() with an ES2015 class for new code; keep one responsibility per service so they stay readable and testable.

  • Use minification-safe injection: always annotate with the array form ['$http', 'userService', function ($http, userService) {}] or attach a $inject property, otherwise dependency names will be mangled and injection will break in production builds.

  • Do not put view logic in services: DOM access, formatting for display and event handling tied to a template belong in the component or a directive.

  • Avoid storing large, short-lived objects in a shared service when several components use it, since the singleton never resets; scope lifetime data belongs on a component’s bindings or controller.

  • Return promises from asynchronous services and let callers chain .then()/.catch(), so error handling stays consistent and services remain easy to mock in tests.

  • Keep change detection in mind: when a service updates data asynchronously outside AngularJS (a raw XMLHttpRequest, a WebSocket callback, a library event), the view will not refresh until a digest runs; use $apply() or, preferably, $timeout() or $q to schedule the update so a digest is triggered safely.

8

What approaches can you use to make data and state available across multiple controllers in AngularJS?

The most reliable way to share data between controllers in AngularJS is to put that state in a shared service or factory: services are singletons, so every controller that injects the same service gets the same object and its changes are picked up automatically by the digest cycle. Other valid options include $broadcast/$emit events, routing resolves, browser storage for persistent state, and component inputs and outputs when the controllers have a parent-child relationship.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; long-term support ended 31 December 2021, so this applies to maintaining existing applications rather than starting new ones).

Why Services and Factories Work

AngularJS instantiates a service, factory, or provider only once per injector, which makes them natural singletons. When a factory returns an object, controllers receive a reference to that same object, so mutating its properties or calling its methods affects every other consumer. Because the shared object is watched by the digest cycle like any other bound expression, views update without manual work; the only time you need $scope.$apply is when the mutation happens from outside an AngularJS event handler, such as a raw browser callback.

Main Sharing Techniques

  • Factory or service: the default choice for shared models, API caches, authentication state, and cross-controller logic. Register it with angular.module(...).factory() or .service() and inject it by name using array annotation (['cartService', function (cartService) {...}]) so the code stays minification-safe.

  • $broadcast and $emit: useful for one-off notifications, especially between sibling controllers or up/down the scope hierarchy. Use $rootScope.$broadcast sparingly, since it notifies every scope.

  • Router resolve: with ngRoute or ui-router, a resolve block can load shared data before controllers activate, then inject the resolved value into each controller.

  • Browser storage: localStorage or sessionStorage (wrapped in a service) shares state that must survive page reloads; watch for the storage limit and serialize with JSON.parse/JSON.stringify.

  • Component bindings: for hierarchical components, pass data down with one-way '<' bindings and send events up with '&' outputs instead of using a global service. This is the component-based style available since 1.5.

Comparing the Common Approaches

Approach Best for Watch out for
Factory/service Shared state and logic Do not store per-controller data that should be reset
$broadcast/$emit Decoupled notifications Events are hard to trace; avoid them for shared state
$rootScope as a global bag Quick prototypes Pollutes the global scope; prefer a named service
localStorage/sessionStorage Persistence across reloads Must handle serialization, quotas, and private browsing
Component bindings Parent-child component trees Not for unrelated controllers

Best Practices and Common Pitfalls

  • Prefer a named service over $rootScope: it is testable, documented, and does not leak state into every scope.

  • Keep services focused: expose methods plus a small shared model rather than dumping controller-specific variables into the singleton.

  • Reset shared state when needed: a singleton survives route changes, so add a clear() method for logout or navigation between independent views.

  • Use track by in ng-repeat and one-time bindings ({{ ::value }}) when shared lists grow, to keep the number of watchers low and the digest cycle fast.

  • Do not abuse events: they are good for notifications, not as a replacement for a shared model, because they hide data flow and are easy to leak.

9

Can you explain the role of the ng-app directive within an AngularJS application?

The ng-app directive designates the root element of an AngularJS application and instructs the framework to automatically initialize and bootstrap when the document is ready. By placing it on an element, you bind that part of the DOM to a specific AngularJS module, making the module’s controllers, services, filters and directives available to that element and everything nested inside it.

Read full answer

Applies toAngularJS 1.8.x

Core Purpose

  • Root Element Definition: It defines where the AngularJS application begins in the HTML document, typically placed on the <html> or <body> tag.

  • Module Association: The value assigned to ng-app is the name of the module to load; if the attribute is left empty, AngularJS bootstraps without loading any module, so only built-in services are available and your application code is not registered.

  • Automatic Initialization: It removes the need for explicit startup code, allowing the application to start as soon as the browser finishes parsing the DOM.

How Auto-Bootstrapping Works

  1. The browser downloads and parses the HTML document.

  2. When the DOMContentLoaded event fires, AngularJS scans the document for the ng-app directive.

  3. It loads the specified module and its dependencies via the dependency injector.

  4. It compiles the DOM within the root element, linking directives to scopes and starting the digest cycle.

Constraints and Rules

  • Single Auto-Bootstrapped App: Only one AngularJS application can be automatically bootstrapped per HTML document. If multiple ng-app directives are found, only the first one encountered is used.

  • Nesting Restriction: AngularJS applications cannot be nested within each other; a second ng-app inside an already bootstrapped element is ignored by the framework.

  • Placement Impact: Placing it on <html> ensures the entire page is managed by AngularJS, whereas placing it on a <div> limits the app scope to that container and its children.

Manual Bootstrapping Alternative

For scenarios where automatic startup is insufficient, you can use the angular.bootstrap function instead of relying on ng-app. This approach is necessary when loading scripts asynchronously, integrating with other libraries, or running multiple applications on a single page.

Feature ng-app (Auto) angular.bootstrap (Manual)
Setup Effort Minimal, declarative Requires JavaScript code
Timing On DOMContentLoaded Controlled by developer
Multiple Apps Not supported automatically Supported
Async Scripts Problematic Recommended

Security and Maintenance

  • End of Life: AngularJS reached end of life on 31 December 2021 and no longer receives security patches or bug fixes. It should not be used for new projects; migration to modern Angular or another framework is recommended.

  • Content Security Policy: The ng-app directive itself is declarative and does not require inline scripts, but the AngularJS library must still be loaded from a CSP-allowed source. Manual bootstrapping with angular.bootstrap requires an explicit script call, which under a strict CSP needs a nonce or hash if written inline.

  • Explicit Module Names: Always specify an explicit module name rather than leaving ng-app empty; an empty value bootstraps with no module, so your controllers, services and components will not be registered.

10

Describe the purpose and behavior of the ng-model directive in AngularJS.

The ng-model directive creates a two-way data binding between a form control (or another editable directive) and a property on the scope, so changes in the DOM update the model and changes in the model update the DOM. It is the standard way to bind <input>, <select>, <textarea> and custom controls to data, and it also integrates with form validation and the ng-change directive. AngularJS reached end of life on 31 December 2021, so this applies to legacy 1.8.x applications rather than new projects.

Read full answer

Applies toAngularJS 1.8.x (JavaScript, ES5/ES2015+)

How It Works

  • Registration and linking: ng-model is a built-in directive. During the compile phase AngularJS parses the attribute expression (for example ng-model="user.email") into a model accessor. At link time it registers a listener on the element and a watcher on the scope.

  • View to model: when the user types or selects something, the input/change event fires, the directive reads the control value, runs it through any registered parsers and validators, and assigns the result to the scope expression. It then triggers a digest cycle so dependent watchers and expressions update.

  • Model to view: during each digest, the directive compares the current control value with the scope value. If they differ, it writes the model value back into the DOM after running it through formatters.

  • Expression requirements: the expression must be assignable. Literal primitives such as ng-model="5" or ng-model="foo.bar" where foo is undefined cannot be assigned and will throw or silently fail, which is why bindings should point to properties on an existing object.

Key Features

  • Two-way binding: one declaration keeps the element and the scope property in sync in both directions.

  • Validation integration: ng-model sets CSS classes and controller state such as ng-valid, ng-invalid, ng-dirty, ng-pristine, ng-touched and ng-untouched, and updates formName.inputName.$valid, $error, $dirty and $touched.

  • ng-change: because ng-change is only evaluated on the same digest that ng-model produces, the recommended pattern is ng-model plus ng-change rather than mixing in jQuery DOM listeners.

  • Custom controls: a directive that implements require: 'ngModel' receives the NgModelController and can hook into the same pipeline through $render, $setViewValue, $parsers, $formatters and $validators.

  • ng-model-options: lets you tune behavior such as debounce time ({ debounce: 300 }), when updates happen (updateOn: 'blur'), timezone handling for date inputs, and whether to allow invalid model values ({ allowInvalid: true }).

ng-model Compared to Related Bindings

Feature ng-model {{ expression }} / ng-bind One-way component input '<'
Direction Two-way (DOM ↔ scope) One-way (scope → DOM) One-way (parent → component)
Typical use Form controls Displaying text Component inputs in .component()
Assignable expression required Yes No No
Updates the parent scope Yes, through the bound property No No (except object/reference mutation)

Best Practices and Common Pitfalls

  • Bind to a property of an object, not a bare primitive on the scope: prefer ng-model="vm.user.name" over ng-model="name". Primitives on child scopes (for example inside ng-if, ng-repeat or a directive scope) create a local copy and break the write-back to the parent.

  • Use controllerAs syntax: with controllerAs: 'vm' (or the default $ctrl in .component()), write ng-model="$ctrl.user.email" so the binding clearly points at the controller instance.

  • Prefer ng-change over ng-click or jQuery listeners for reacting to value changes, so logic runs at the right point in the digest cycle.

  • Avoid ng-model on elements without a value, such as plain <div>; pair it with a custom directive that provides ngModel support instead.

  • Keep digest cost low: large forms with many ng-model bindings create many watchers. Use ng-model-options with debounce to reduce update frequency, and one-time bindings ({{ ::value }}) for display-only text.

  • Do not mix ng-model with ng-value incorrectly: ng-value sets the value of a radio or checkbox option; ng-model on the same control holds the selected value.

  • Note for modern work: in Angular (2+), two-way binding is done with [(ngModel)] (the ngModel directive with banana syntax) or with reactive forms; migrating an AngularJS app means rewriting in Angular or running both frameworks together with ngUpgrade.

11

What purpose does $scope serve in an AngularJS application?

$scope is the shared context that connects a controller to its view: it holds the model data and functions the template binds to, and it is the object AngularJS uses to run change detection through the digest cycle.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; AngularJS reached end of life on 31 December 2021, so this matters mainly for maintaining existing apps or planning a migration to Angular).

The View–Controller Bridge

A template is compiled with a scope as its evaluation context, so every expression like {{ user.name }} or ng-model="user.name" reads and writes properties on that scope. The controller’s job is to initialise those properties and expose behaviour, while the directive’s link function can read or augment the scope it is given. The scope is therefore the single place where the two halves of a component meet.

A Prototype-Chained Hierarchy

Scopes form a tree that mirrors the DOM and the application structure:

  • Root scope: created when the application is bootstrapped, the common ancestor of all scopes.

  • Child scopes: created by directives such as ng-controller, ng-repeat (one per item), ng-include and ng-if.

  • Isolate scopes: created by directives that request scope: {} so they do not inherit from their parent, which is essential for reusable components.

Child scopes inherit properties from their parents through JavaScript’s prototype chain. Reading an inherited property walks up the tree; assigning a primitive on a child scope shadows the parent instead of updating it, which is the classic reason ng-model on a primitive in a child scope appears to “lose” changes.

Change Detection

AngularJS does not observe the DOM; it compares watched values:

  • Watchers: every binding and $scope.$watch() registration adds a watcher to the scope.

  • The digest cycle: $scope.$digest() iterates over all watchers until their values stop changing, then updates the DOM.

  • $scope.$apply(): re-enters AngularJS execution from third-party code (a setTimeout, a jQuery handler, a WebSocket callback) so the digest runs and the view stays in sync.

  • $scope.$evalAsync() and $timeout: preferred for deferring work, because they schedule the digest for you and avoid the “$apply already in progress” error that a raw $apply inside a digest throws.

Scope as an Event Bus

Scopes also implement a publish/subscribe channel: $scope.$on, $scope.$emit (up the hierarchy) and $scope.$broadcast (down). This is useful for communication between a component and its descendants, though for sibling or unrelated components a shared service is the cleaner choice.

Modern Component Style

Since AngularJS 1.5, the recommended structure is angular.module(...).component() with controllerAs ($ctrl), one-way '<' input bindings and '&' output callbacks. Data and methods live on the controller instance rather than being injected into $scope, and lifecycle hooks ($onInit, $onChanges, $postLink, $onDestroy) replace manual scope setup. The scope still exists under the hood — the template’s expressions are evaluated against the controller’s scope — but application code should rarely need to touch it directly.

Best Practices and Pitfalls

  • Prefer controllerAs and components over $scope assignment; it avoids inheritance surprises and makes migration to Angular easier.

  • Keep watcher counts low: use one-time bindings ({{ ::user.name }}) and track by in ng-repeat; large watcher sets make the digest slow.

  • Use $timeout instead of $apply when integrating async code, so the digest is queued safely.

  • Do not use $scope for cross-component state; put shared state in a service and inject it where needed.

  • Clean up in $onDestroy: unregister $on listeners and watchers that would otherwise outlive their component.

12

How do you work with $rootScope when developing in AngularJS?

$rootScope is the top-level scope in AngularJS, attached to the root element of the application, and every other $scope inherits from it prototypally. You inject it like any service to share data or broadcast events across unrelated controllers and components, but it should be used sparingly because it effectively behaves as a global variable.

Read full answer

Applies toAngularJS 1.8.x (note: AngularJS reached end of life, with long-term support ending 31 December 2021, so avoid it for new projects)

What $rootScope Is

  • Root of the scope hierarchy: created automatically on the element with ng-app; all child scopes created by ng-controller, ng-repeat, routes and components prototypically inherit from it.

  • Injectable service: like $http or $timeout, you request it via dependency injection, always in minification-safe array notation.

  • Event bus: it provides $broadcast(event, data) (propagates down to all child scopes), $emit(event, data) (bubbles up toward parents), and $on(event, listener) to listen.

When to Use It

  • Cross-cutting data: a small piece of state that truly belongs to the whole app, such as the current theme, language or authenticated user identity.

  • Decoupled events: notifying many unrelated components about something like user:loggedOut without wiring each component through parent bindings.

  • Manual digest triggering: calling $rootScope.$apply() when code runs outside AngularJS (for example a third-party callback) and model changes need to be detected.

For most shared state, a service or factory is a better choice than $rootScope, because services are singleton, testable and do not pollute the scope chain.

Approach Best for Drawback
$rootScope App-wide flags, global events Hard to test, tight coupling, memory leaks if listeners are not removed
Service / factory Shared state and business logic No automatic view updates unless you trigger $apply or use bindings
Component bindings (<, &) Parent-to-child data and callbacks Requires a component tree relationship

Code Example

angular.module('app', [])
  // Set app-wide data during bootstrap
  .run(['$rootScope', function ($rootScope) {
    $rootScope.appName = 'MyApp';
  }])
  .component('userStatus', {
    template: '<span>{{ $ctrl.label }}</span>' +
      '<button ng-click="$ctrl.login()">Log in</button>',
    controller: ['$rootScope', function ($rootScope) {
      var $ctrl = this;
      // Listen for a global event on $rootScope
      var unbind = $rootScope.$on('user:loggedIn', function (event, user) {
        $ctrl.label = 'Welcome, ' + user.name;
      });
      // Deregister the listener to avoid memory leaks
      $ctrl.$onDestroy = function () {
        unbind();
      };
      // Broadcast an event on $rootScope
      $ctrl.login = function () {
        $rootScope.$broadcast('user:loggedIn', { name: 'Ada' });
      };
    }]
  });
<!-- Use the component anywhere in the app -->
<user-status></user-status>

Best Practices and Pitfalls

  • Deregister listeners: $rootScope.$on returns a function; call it in $onDestroy, otherwise the listener survives after the component is gone and leaks memory.

  • Do not dump state on it: putting many properties on $rootScope makes data flow hard to follow and breaks unit tests that rely on isolated scopes.

  • Prefer one-way bindings: pass data into components with '<' and emit upward with '&' instead of reaching for $rootScope for every parent-child interaction.

  • Keep watchers low: avoid functions in templates and use track by in ng-repeat; heavy watchers on $rootScope run on every digest across the whole app.

  • Remember the lifecycle: the old $http.success()/$http.error() methods were removed in 1.6; use .then()/.catch() on the promise instead.

13

Could you describe how scopes are organized and inherited within an AngularJS application structure?

In AngularJS, the scope hierarchy forms a tree structure rooted at $rootScope, where child scopes access parent properties through prototypal inheritance. This mechanism allows data and events to flow downward while keeping controllers isolated, though writing to primitives on a child scope can shadow parent values unexpectedly.

Read full answer

Applies toAngularJS 1.8.x

Scope Tree Structure

When an application bootstraps, AngularJS creates a single $rootScope instance. Every directive or controller that requests a new scope generates a child node, creating a tree that often mirrors the DOM layout. Scopes can be traversed using $scope.$parent to move upward, though direct manipulation of the tree is discouraged in favor of data binding. This hierarchical organization ensures that context is preserved as you nest views and controllers.

Prototypal Inheritance Rules

Child scopes prototypally inherit from their parents using JavaScript’s prototype chain. When reading a variable, the engine walks up the chain until it finds the property, allowing access to parent model data without duplication. However, assigning a primitive value on a child scope creates a new local property, hiding the parent’s value rather than updating it. To modify parent state safely, always bind to objects (e.g., user.name) instead of primitives (e.g., count). - Reading: Walks up the prototype chain to the root. - Writing: Creates a shadow property on the current scope if primitive.

Component Isolated Scopes

Since AngularJS 1.5, components use isolated scopes by default rather than inheriting from the parent scope prototypally. Data is passed explicitly via bindings defined in the component definition, preventing accidental leakage of parent scope properties. Inputs use one-way binding (<), outputs use function binding (&), and string interpolation uses attribute binding (@). This isolation makes components more predictable and reusable compared to traditional directives with inherited scopes.

Digest Cycle Propagation

The digest cycle propagates changes through the scope hierarchy to synchronize views with models. When a model changes, $digest runs on the affected scope and its children, checking all registered watchers for updates. If changes occur outside the Angular context, such as native DOM events, you must call $scope.$apply() to trigger a digest starting from the root. Excessive watchers slow down this traversal, so keep the tree shallow and use one-time binding (::) where data does not change.

Best Practices and Migration

AngularJS reached end of life on 31 December 2021, so it should only be used for maintenance or migration projects. When working within the framework, prefer the component-based style with controllerAs: '$ctrl' to avoid scope confusion. Always use array notation for dependency injection to ensure code survives minification. For new projects, consider rewriting in Angular or running both frameworks simultaneously using ngUpgrade.

14

Describe the primary function and responsibilities of a controller within the AngularJS framework.

In AngularJS, a controller serves as the logic layer that binds the view to the model, managing state and behavior without directly manipulating the DOM. It initializes scope data and exposes methods for the template to invoke, following the Model-View-Controller pattern while relying on the digest cycle to propagate changes.

Read full answer

Applies toAngularJS 1.8.x

Core Responsibilities

The primary job of a controller is to prepare the scope for the view. This involves setting initial values, fetching data via services, and defining event handlers. Controllers should remain thin, delegating data manipulation to services or factories. Direct DOM access belongs in directives, not controllers, ensuring separation of concerns. When a user interacts with the view, the controller processes the input and updates the model, triggering a digest cycle to refresh the UI. Changes to scope variables are detected during this cycle, updating the bindings in the template.

Component-Based Architecture

Since AngularJS 1.5, the preferred way to define controllers is within .component() definitions rather than .controller(). This enforces an isolate scope and encourages the use of controllerAs: '$ctrl'. Inputs use one-way bindings denoted by <, while outputs use callback bindings denoted by &. This style aligns closer to modern Angular and reduces accidental scope inheritance issues common in earlier versions. The component definition specifies the template, bindings, and controller logic in a single configuration object, promoting reusability.

Lifecycle Management

Component controllers have access to specific lifecycle hooks that manage initialization and cleanup. The $onInit hook is called after bindings are initialized and is ideal for setup logic. $onChanges is triggered when one-way bindings change, receiving a changes object to inspect previous and current values. $postLink is called after the element is linked, making it suitable for DOM manipulation via jqLite. Finally, $onDestroy is invoked before removal, used for cleanup tasks like cancelling timeouts or unregistering event handlers.

Dependency Injection and Scope

To ensure code survives minification, dependency injection must be explicit using array notation or the $inject property. Services like $http or $timeout are injected into the controller constructor. Note that $http returns promises, so use .then() and .catch(); the older .success() and .error() methods were removed in AngularJS 1.6 and are unavailable in 1.8.x. Scope inheritance is minimized in components, but traditional controllers may inherit from parent scopes, which can lead to unexpected behavior if not managed carefully. Keeping the number of watchers low is critical for performance, achieved through one-time bindings (::) and track by in ng-repeat.

Best Practices and Migration

Keep controllers focused on a single view or component to maintain testability. Avoid deep scope nesting by preferring components over nested controllers. Remember that AngularJS reached end of life on 31 December 2021, meaning no further security updates or bug fixes are provided. For new projects, consider migrating to Angular or another modern framework. If maintaining a legacy application, use ngUpgrade to run AngularJS and Angular simultaneously during a transition period. Security practices remain vital; never expose sensitive logic or secrets within client-side controllers.

15

In AngularJS, what is the process for attaching functions to a controller so they can be invoked from the view or other components?

In AngularJS, you attach a controller’s method by assigning a function to the controller instance (this) or the $scope object, depending on the architectural pattern chosen. The recommended approach since version 1.5 uses the component style with controllerAs, where methods are attached to this inside the controller constructor and invoked from the template via the controller alias. This pattern improves encapsulation and eases future migration to modern frameworks.

Read full answer

Applies toAngularJS 1.8.x

Controller Definition Patterns

Historically, controllers were registered with angular.module().controller(), attaching methods directly to the injected $scope object. This often caused scope pollution and made testing harder. Since AngularJS 1.5, the preferred approach is angular.module().component(), which automatically sets controllerAs to $ctrl. Methods assigned to this inside the controller function become available as $ctrl.methodName() in the template without explicit scope manipulation, isolating each component’s state.

Minification-Safe Dependency Injection

JavaScript minifiers rename function parameters, breaking AngularJS’s dependency injection, which reads parameter names from the function signature. Use one of these patterns to list dependencies as strings: - Array notation: ['$http', function($http) { ... }] - $inject property: MyController.$inject = ['$http'];

This ensures the injector resolves the correct services after minification, which is essential for production builds.

Invoking Methods from the View and Other Components

  • From the view: Call methods with directives like ng-click or ng-submit, prefixing with the controller alias (e.g. $ctrl.saveUser($ctrl.user)). Pass arguments explicitly so the controller signature matches the call.

  • From other components: Use an & binding to expose a callback. The parent component defines the function; the child declares it in bindings as onSave: '&' and invokes it with a locals object, e.g. $ctrl.onSave({ user: $ctrl.user }). This is the standard way a child component triggers behaviour owned by a parent.

If a method returns a promise (such as from $http), handle success and error states inside the controller with .then()/.catch(), not in the template. Note that the legacy .success()/.error() methods were removed in AngularJS 1.6.

Code Example

angular.module('app', [])
  .component('userCard', {
    controller: ['$http', function($http) {
      var $ctrl = this;
      // Called from the view via ng-click
      $ctrl.saveUser = function(user) {
        return $http.post('/api/users', user)
          .then(function(response) { $ctrl.saved = true; })
          .catch(function(error) { $ctrl.error = error; });
      };
      // Called from a child component via the & binding
      $ctrl.notifyParent = function() {
        $ctrl.onSave({ user: $ctrl.user });
      };
    }],
    bindings: {
      user: '<',   // one-way input binding
      onSave: '&'  // output callback to parent component
    },
    template: '<button ng-click="$ctrl.saveUser($ctrl.user)">Save</button>'
  });

Best Practices and Migration Considerations

  • Keep methods lightweight: heavy logic inside methods called by watchers slows the digest cycle; keep them idempotent since they may run multiple times per cycle.

  • Minimize watchers: use one-time bindings (::) and track by in ng-repeat to reduce overhead.

  • End of life: AngularJS reached end of life on 31 December 2021; new projects should use Angular (2+) or another modern framework. For existing apps, prefer one-way bindings (<) and consider ngUpgrade to run AngularJS and Angular side-by-side during migration.

  • Security: validate data passed to methods, especially when sending it to backend APIs via $http.

16

Which built-in directives does AngularJS provide out of the box, and what is each one used for?

AngularJS ships with a large set of built-in directives: predefined markers on HTML elements that extend the vocabulary of the template language and tell the compiler ($compile) to attach behaviour or transform the DOM. They are registered on the ng module, so they are available as soon as you include angular.js and bootstrap your application with ng-app.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; AngularJS reached end of life on 31 December 2021, so these directives are relevant for maintaining existing apps, not for new projects).

How Directives Are Written

A directive name in JavaScript is camelCase, but in HTML it is written in lower-case-with-dashes, so ngModel becomes ng-model and ngRepeat becomes ng-repeat. The normalisation is done by the compiler, which also accepts data-ng-model, x-ng-model and ng:model variants. Directives can appear as attributes (<div ng-show="x">), elements (<ng-include src="...">), class names (class="ng-cloak") or comments, although attribute form is by far the most common for the built-ins.

Core Structural Directives

These change the structure of the rendered DOM rather than just styling it:

  • ng-repeat: instantiates a template once per item in a collection. Use track by $index (or a unique id such as track by item.id) so AngularJS can reuse DOM nodes instead of destroying and recreating them, which matters for performance and for inputs that keep focus.

  • ng-if: adds or removes the element from the DOM based on a truthy/falsy expression. A new child scope is created and the element’s controllers are re-instantiated each time it re-enters, unlike ng-show.

  • ng-switch / ng-switch-when: a multi-branch equivalent of ng-if, useful when one element must render different inner markup depending on a value.

  • ng-repeat alternatives: ng-repeat-start and ng-repeat-end let one logical repeat span several sibling elements.

  • ng-include: fetches and compiles an external HTML fragment by URL, typically used for partials that are not full components.

Data Binding and Forms

  • ng-model: the two-way binding directive. It binds an input, select or textarea to a scope property and registers the control with the enclosing form so that validation state ($valid, $dirty, $error) becomes available.

  • ng-bind / ng-cloak: one-way alternatives to {{ }} interpolation. ng-cloak hides raw template text until the browser has compiled the page; pair it with the .ng-cloak CSS rule that ships with the framework.

  • ng-init: evaluates an expression in the current scope. It is occasionally handy for aliasing a ng-repeat loop value, but setting up state belongs in a controller, not in ng-init.

  • ng-change: evaluates an expression when the model changes, but only when used together with ng-model.

  • ng-model-options: tunes how ng-model updates, for example {'debounce': 300} to delay updates on every keystroke or updateOn: 'blur'.

  • ng-disabled, ng-readonly, ng-checked, ng-selected, ng-href, ng-src: attribute-binding helpers that avoid interpolating into an attribute that the browser reads before AngularJS runs (a plain href="{{url}}" can produce a broken request to /%7B%7Burl%7D%7D).

Display, Classes and Styles

  • ng-show / ng-hide: toggle the display CSS property without removing the element, so controllers and scroll state survive. Prefer ng-if when the content is heavy and rarely shown.

  • ng-class: sets classes dynamically. It accepts a space-separated string, an object map ({'is-error': form.$invalid}) or an array, with the object form being the most expressive.

  • ng-style: binds an inline style object, useful when the styles are computed at runtime.

  • ng-style‘s sibling ng-attr-*: sets an attribute whose name itself is dynamic, e.g. ng-attr-cx on SVG elements.

Events and Application Bootstrap

  • ng-app: marks the root element of the application and, when given a module name, bootstraps that module automatically.

  • ng-controller: attaches a controller to an element. In a component-based codebase this is mostly replaced by angular.module(...).component(), where the component element itself declares its own controller.

  • ng-click, ng-dblclick, ng-mousedown, ng-mouseenter, ng-keydown, ng-keyup, ng-submit, ng-focus, ng-blur: declarative wrappers around standard DOM events. ng-submit on a form is preferred over ng-click on a button because it also fires on Enter.

  • ng-focus / ng-blur: note these evaluate an expression when the element receives or loses focus; they do not programmatically set focus, which surprises many candidates.

Best Practices and Common Pitfalls

  • Keep the digest cycle cheap: prefer one-time bindings ({{ ::item.name }}) for static values and always use track by in ng-repeat to avoid unnecessary watcher churn.

  • Do not put complex logic in template expressions; move it into a controller or a filter so it is testable.

  • Avoid combining ng-repeat and ng-if on the same element — ng-repeat (priority 1000) runs before ng-if (600), so the condition is evaluated per cloned item and the result is confusing; put ng-if on a wrapper element or filter the collection in the controller instead.

  • Use ng-href and ng-src for any URL that comes from scope data to avoid a flash of broken requests during the first compile.

  • Prefer components (.component()) over hand-written directives for new template markup; reach for .directive() only when you need something a component cannot express, such as a decorator-style attribute directive that does not add its own template.

Commonly confused pairs:

Directives Effect on the DOM When to prefer
ng-if vs ng-show ng-if removes the node and its child scopes; ng-show only sets display: none ng-if for expensive, rarely used content; ng-show for toggles that must keep state
ng-bind vs {{ }} Both render one text value; {{ }} shows raw braces before first compile ng-bind (or ng-cloak) for the top of the page, {{ }} for readability elsewhere
ng-repeat with ng-if Can be combined, but ng-repeat (priority 1000) runs before ng-if (600), so the condition is evaluated per cloned item Put ng-if on a wrapper element or filter the collection in the controller
17

What is the process for implementing a custom directive within an AngularJS application?

In AngularJS 1.8, you typically define reusable UI elements using the component API, which simplifies the older directive definition object. While traditional directives are still supported for complex DOM manipulation, components are the recommended standard for most custom elements because they enforce a clear structure with lifecycle hooks and one-way data flow.

Read full answer

Applies toAngularJS 1.8.x

Components Versus Directives

Since version 1.5, AngularJS introduced .component() as a simplified wrapper around .directive(). A component always uses isolate scope, controllerAs syntax, and restricts itself to element usage (E). You should use .component() for UI widgets and .directive() only when you need direct DOM manipulation, custom element names that don’t match component conventions, or attribute-only behavior that components cannot express.

Lifecycle and Change Detection

Components implement lifecycle hooks such as $onInit, $onChanges, $postLink, and $onDestroy. Change detection relies on the digest cycle. AngularJS checks watchers during $digest; avoid heavy logic inside watchers. Use one-time bindings (::) for static data to reduce watcher count. If external code modifies scope outside AngularJS context, wrap it in $scope.$apply() or use $timeout to trigger a digest.

Code Example

To create a custom element, register it on a module. Bindings define inputs (< one-way, = two-way) and outputs (&). The controller holds logic using array notation for minification-safe dependency injection.

angular.module('app').component('userCard', {
  bindings: {
    user: '<',
    onUpdate: '&'
  },
  controller: ['$http', function ($http) {
    this.$onInit = function () {
      // Initialization logic
    };
    this.save = function () {
      this.onUpdate({ user: this.user });
    };
  }],
  templateUrl: 'user-card.html'
});

The component is used as a custom HTML element. Data flows in via attributes, and events flow out via callbacks.

<user-card user="$ctrl.currentUser" on-update="$ctrl.handleUpdate(user)"></user-card>

Best Practices and Migration

  • Minification Safety: Always use array notation for DI or define $inject property to preserve argument names after minification.

  • One-Way Binding: Prefer < over = to prevent accidental parent scope mutations and make data flow predictable.

  • End of Life: AngularJS reached end of life on 31 December 2021. For new projects, consider Angular (2+). For legacy maintenance, isolate business logic to ease future migration via ngUpgrade.

  • DOM Access: Perform DOM manipulation in $postLink using this.$element (via $element injection) rather than in the controller constructor.

Common Pitfalls

  • Scope Inheritance: Do not rely on prototypal scope inheritance; components use isolate scope by default.

  • Async Handling: $http returns promises. Use .then() and .catch(); the legacy .success() and .error() methods were removed in AngularJS 1.6.

  • Watcher Bloat: Excessive watchers degrade performance. Use track by in ng-repeat and one-time bindings where possible.

19

Can you explain how the '@', '=' and '&' symbols differ when defining isolate scope bindings in an AngularJS directive?

The three symbols specify how an isolate scope binding reads data from, or calls code on, the parent scope: @ passes the attribute’s literal text (a string), = creates a two-way reference to a parent expression, and & wraps the attribute as a callable expression executed in the parent’s scope. AngularJS 1.5 also added <, a one-way binding that is now the recommended way to pass inputs into components.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; long-term support ended 31 December 2021)

How Each Binding Works

Symbol Name Attribute contains Isolate scope value Direction
@ Text / attribute binding a string, optionally with {{ }} interpolation a string (or the interpolated result) parent → child, string only
= Two-way binding an expression, usually a model reference a reference to the same object/primitive both ways
< One-way binding (since 1.5) an expression a copy of the reference; parent changes propagate down parent → child
& Expression binding an expression, often a callback a function that executes the expression in the parent scope child → parent
  • @ (text): the directive receives the attribute’s raw value. Writing name="{{parent.user.name}}" interpolates the expression first, so the directive gets the evaluated string, not the reference. It is read-only from the directive’s point of view and useful for configuration values such as labels, IDs and template parameters.

  • = (two-way): AngularJS sets up watchers so that changes made inside the directive propagate to the parent, and vice versa. The attribute must be an assignable expression (user="parent.user", not user="parent.user.name + '!'"). Each = adds watchers to the digest cycle, so prefer < for plain inputs.

  • & (expression): the attribute is not evaluated immediately; it is packaged as a function. Calling it runs the expression against the parent scope, which makes & the standard mechanism for output callbacks. Arguments are passed as a map of locals, e.g. onSave({ user: ctrl.user }), which the parent expression can consume (on-save="parent.saveUser(user)").

Code Example

<div ng-controller="ParentController as parent">
  <user-card label="{{parent.titleLabel}}"
             user="parent.user"
             on-save="parent.saveUser(user)"></user-card>
</div>
angular.module('app', []);

angular.module('app').directive('userCard', function () {
  return {
    restrict: 'E',
    scope: {
      label: '@',    // string: value of the label attribute, interpolated
      user: '<',     // one-way input: reference to parent.user (use '=' for two-way)
      onSave: '&'    // output: calls parent.saveUser(user) in the parent scope
    },
    controllerAs: '$ctrl',
    bindToController: true,
    controller: function () {
      this.save = function () {
        // Invoke the parent expression, passing the local 'user'
        this.onSave({ user: this.user });
      };
    },
    template: '<h3>{{$ctrl.label}}</h3>' +
              '<p>{{$ctrl.user.name}}</p>' +
              '<button ng-click="$ctrl.save()">Save</button>'
  };
});

Choosing Between Them

  • Pass configuration strings with @.

  • Pass data inputs with < (one-way); use = only when the directive genuinely must write back to the parent model.

  • Send events or callbacks out with &.

  • Prefer angular.module(...).component() over .directive() for new UI: components use bindings with the same symbols, default inputs to <, expose controllerAs as $ctrl, and support the $onInit, $onChanges, $postLink and $onDestroy lifecycle hooks.

  • Optional bindings are written @?, =?, &? or <?; they are still supported, but one-way < inputs are the current recommendation.

  • Keep watchers low: every = and < binding participates in the digest cycle, so use one-time bindings ({{ ::value }}) and track by in ng-repeat where the list is static.

Because AngularJS is no longer maintained, new projects should not start on 1.8.x; existing applications can be migrated either by rewriting in Angular or by running both frameworks together with ngUpgrade.

20

How can you set up two-way data binding between an isolate scope and its parent scope inside a custom directive?

In an AngularJS directive, two-way data binding is implemented by declaring an isolate scope property with the = binding, for example scope: { value: '=' }. AngularJS then keeps the directive’s isolate scope property and the parent expression passed through the matching attribute synchronized during each digest cycle, so updates from either side propagate to the other.

Read full answer

Applies toAngularJS 1.8.x

How the = Binding Works

When a directive uses an isolate scope, it no longer inherits the surrounding scope prototypically. Instead, you explicitly declare which properties are bound from the outside:

  • =: two-way binding to an expression or property on the parent scope.

  • @: one-way string binding from an attribute value.

  • &: one-way binding of an expression or callback, commonly used for component outputs.

  • <: one-way binding introduced for components, generally preferred over = for inputs.

With value: '=', the HTML attribute value="user.name" binds the directive’s value property to user.name on the parent scope. If the directive changes value, AngularJS writes the new value back to the parent expression during the digest. If the parent changes user.name, the next digest updates the isolate scope.

Choosing Between =, < and &

Binding Direction Typical use
= Two-way Directive genuinely needs to mutate a parent property directly
< One-way in Component or directive input where the parent owns the data
& One-way out Notify the parent of an event or pass a value upward through a callback

For component-based code, the recommended pattern since AngularJS 1.5 is one-way inputs with < and outputs with &, because it makes data flow easier to follow. Use = in directives when the directive is specifically designed to mutate a bound model directly, such as a low-level input control.

Code Example

angular.module('app', [])

  .controller('ExampleController', function () {
    this.user = {
      name: 'Ada'
    };
  })

  .directive('twoWayInput', function twoWayInput() {
    return {
      restrict: 'E',
      scope: {
        // Two-way binding to the parent expression passed through value=""
        value: '='
      },
      template: '<label>Name <input type="text" ng-model="value"></label>'
    };
  });
<div ng-controller="ExampleController as example">
  <two-way-input value="example.user.name"></two-way-input>
  <p>Hello, {{ example.user.name }}!</p>
</div>

Because ng-model="value" updates the isolate scope property, the = binding propagates the change back to example.user.name. The parent expression is then re-evaluated in the digest, so the greeting updates automatically.

Best Practices and Common Pitfalls

  • Prefer object-property expressions: bind to example.user.name rather than a bare primitive where practical, since the intent and ownership of the data are clearer.

  • Avoid unnecessary two-way bindings: if the directive only receives data, use <; if it only reports changes, use &.

  • Keep digest performance in mind: every = binding adds a watcher, so avoid large numbers of two-way bindings in lists.

  • Do not fight the digest cycle: updates made inside AngularJS templates, ng-model, $timeout, or promises are already digest-aware; call $scope.$apply() only for changes from non-AngularJS callbacks.

  • Remember the framework status: AngularJS 1.8.x is the final release and its long-term support ended on 31 December 2021, so this approach is for maintaining existing AngularJS applications rather than starting new projects.

21

In AngularJS, what distinguishes a service from a factory, and when would you choose one over the other?

A service and a factory are both dependency-injection recipes that produce the same kind of object — a lazily created, per-injector singleton — and they differ only in how that object is constructed. With module.service() AngularJS invokes your constructor with new and uses the resulting instance; with module.factory() AngularJS calls your function as a plain function and uses whatever value it returns.

Read full answer

Applies toAngularJS 1.8.x (final release; long-term support ended 31 December 2021)

How Each Recipe Works

  • module.service(name, constructor): you write a constructor function (or an ES2015 class) and attach members to this. AngularJS instantiates it once, effectively doing new constructor() internally, then caches and injects that instance.

  • module.factory(name, factoryFn): you write a function that builds and returns an object — an object literal, a closure-backed object, or new SomeClass(). AngularJS calls the function once (without new), caches its return value, and injects that value.

  • Both are declared on a module and injected identically elsewhere; callers cannot tell which recipe created the injected object.

  • Both should use minification-safe annotation, for example ['$http', function($http) { ... }] or a myService.$inject = ['$http']; property.

Key Differences

Aspect module.service() module.factory()
What you provide A constructor function or class A factory function returning an object
How AngularJS creates it Calls it with new Calls it and uses the return value
Where members are defined On this inside the constructor On the object you return
Best fit Constructor functions, ES2015 classes Object literals, closures, wrapping/returning existing instances
Resulting object Singleton, one per injector Singleton, one per injector

Choosing Between Them

  • Prefer service when you are already thinking in terms of a class or constructor: the code reads naturally and this behaves as expected.

  • Prefer factory when you need to return an existing object, build an object from a closure with private state, or adapt a third-party API into an injectable singleton.

  • Many teams standardize on one recipe for consistency; the injected consumers are unaffected, so it is largely a stylistic decision.

  • Use neither when you need configuration before bootstrap — that is what provider is for, since a provider exposes a config-time factory plus configurable state. Use value/constant for simple, non-computed values.

Common Pitfalls

  • In a service constructor, forgetting this and returning an object works only because the returned object overrides the instance; mixing both styles is confusing, so pick one per service.

  • In a factory, returning nothing (no explicit return) yields undefined being cached, which surfaces later as confusing injection errors.

  • Both recipes are singletons: do not store per-request or per-component state on them, because that state is shared across the whole application.

  • Note that service is implemented on top of factory: registering service('x', Ctor) behaves like factory('x', ['$injector', function($injector) { return $injector.instantiate(Ctor); }]). Understanding this makes the two recipes feel like one mechanism with two construction styles.

22

How do you register a service with the dependency injection system through `$provide`?

$provide is the low-level service that backs AngularJS dependency injection: every recipe registered on a module (service, factory, provider, value, constant) is ultimately delegated to it, and injecting $provide into a config block lets you register, configure or intercept services while the injector is still being built.

Read full answer

Applies toAngularJS 1.8.x (final 1.x release; AngularJS reached end of life on 31 December 2021)

How It Works

The injector maintains a recipe book mapping names to creation instructions. When a component asks for a dependency, the injector looks up the recipe, runs it if the instance does not exist yet, caches the result, and returns it. Services are therefore singletons and are created lazily, on first injection.

$provide is the API that writes into that recipe book. It is injectable into config blocks, which run before the injector finishes building, so it is the only place where you can configure a provider or register a service that must exist before the application starts.

Registration Methods

  • $provide.provider(name, recipe): the most flexible form. recipe is either an object exposing a $get factory function, or a constructor function whose instance must expose $get. Providers are injectable into config blocks under the name name + 'Provider' (constants are also available in config; value, factory and service recipes are not).

  • $provide.factory(name, $getFn): shorthand for a provider whose only member is $get; the function is invoked once and its return value is the singleton.

  • $provide.service(name, constructor): shorthand for a factory that does new constructor(); ideal for stateful services built as constructor functions.

  • $provide.value(name, value): registers a plain value as a singleton; cannot be injected into config.

  • $provide.constant(name, value): like value, but available during the config phase, and (as the name suggests) meant for values that never change.

  • $provide.decorator(name, $getFn): registers an interceptor for an existing service; the original instance is injected as $delegate, so you can extend, wrap or replace it without touching its source.

Choosing the Right Method

Need Use
Configuration before startup, injectable into config provider
Return an existing object or a function factory
Stateful service written as a constructor/class service
Simple shared value or config constant value / constant
Extend or patch a third-party service decorator

Code Example

angular.module('app', [])

  // constant: available during config, before the injector is fully built
  .constant('API_BASE', 'https://api.example.com')

  // provider: the only recipe injectable into a config block
  .provider('userService', function () {
    var endpoint = '/users';

    this.setEndpoint = function (url) {
      endpoint = url;
    };

    // $get is the factory; the array notation keeps it minification-safe
    this.$get = ['$http', 'API_BASE', function ($http, API_BASE) {
      return {
        list: function () {
          return $http.get(API_BASE + endpoint);
        }
      };
    }];
  })

  .config(['$provide', 'userServiceProvider',
    function ($provide, userServiceProvider) {
      // configure the provider before the service is instantiated
      userServiceProvider.setEndpoint('/members');

      // factory: a function invoked once, its return value is the singleton
      $provide.factory('sessionCache', function () {
        var store = {};
        return {
          get: function (key) { return store[key]; },
          put: function (key, value) { store[key] = value; }
        };
      });

      // service: a constructor the injector calls with new
      $provide.service('logger', function () {
        this.log = function (msg) { console.log(msg); };
      });

      // value: a plain singleton, not injectable into config
      $provide.value('pageSize', 20);

      // decorator: $delegate is the original instance; return the replacement
      $provide.decorator('logger', ['$delegate', function ($delegate) {
        $delegate.logWithTimestamp = function (msg) {
          $delegate.log(new Date().toISOString() + ' ' + msg);
        };
        return $delegate;
      }]);
    }])

  .controller('UserListController',
    ['userService', 'logger', 'pageSize',
      function (userService, logger, pageSize) {
        var vm = this;

        userService.list().then(function (response) {
          vm.users = response.data;
        });

        logger.logWithTimestamp('loading ' + pageSize + ' users');
      }]);

Best Practices and Pitfalls

  • Prefer the module shortcuts (.factory, .service, .provider) for readability; reach for $provide directly inside a config block when you need registration or decoration at startup.

  • Always annotate dependencies for minification safety, either with the array notation shown above or by attaching a $inject property — unannotated parameter names break under minified builds.

  • Decorators are applied at instantiation time, not registration time, so register them before the decorated service is first injected.

  • Do not perform expensive work in a factory body expecting it to run eagerly; factories run only when their service is first requested.

  • For new work, remember AngularJS is end of life: prefer a modern framework, or migrate via a rewrite to Angular or by running both frameworks together with ngUpgrade.

23

Describe how dependency injection works in AngularJS and the different ways you can declare injectable components and their dependencies.

Dependency injection in AngularJS is the mechanism by which the framework creates and wires together the objects an application needs — services, directives, controllers and filters — so that each component simply declares what it depends on instead of constructing its dependencies itself. AngularJS resolves these declarations through an injector that maintains a cache of singletons and uses reflection or explicit metadata to figure out which arguments to supply.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; AngularJS reached end of life on 31 December 2021, so this matters for maintenance and migration planning rather than new projects).

How It Works

  • Injectables: AngularJS registers recipes in a module using .value(), .constant(), .service(), .factory() and .provider(). Each recipe tells the injector how to create one object, and the injector caches the result so most services are singletons within the app.

  • The injector: angular.injector(['ng', 'myApp']) creates an injector; injector.get('serviceName') returns the cached instance, invoking the recipe the first time. Internally, injector.invoke(fn) reads the function’s parameter names and looks up a matching service for each one.

  • Resolution order: dependencies are resolved lazily on first request, and circular dependencies between two services are not supported.

Declaring Dependencies

There are three ways to tell the injector what to inject, and only two of them survive minification:

  • Inferring from parameter names: the simplest form, where AngularJS reads the function argument names. It breaks as soon as a minifier renames the parameters, so it is not recommended for shipped code.

  • Array annotation: ['$http', '$log', function($http, $log) { ... }] — the strings are the service names and are preserved by minifiers. This is the most common and safest style.

  • $inject property: attaching $inject = ['$http', '$log'] to a function or a controller constructor achieves the same result and works well when the same constructor is reused.

Recipe Types

Recipe Signature Typical use
.constant() value available even in config phase App-wide settings such as an API base URL
.value() plain value, not available in config phase A helper object or primitive
.factory() function returns the instance Most services; full control over creation
.service() constructor function, new-ed by the injector Object-oriented services that rely on this
.provider() object with a $get function Services that must be configured before startup

Only .constant() and .provider() are injectable into a .config() block, because the other recipes are not yet available while the injector is being set up.

Where Injection Happens

  • Controllers: app.controller('UserCtrl', ['$scope', 'UserService', ...]), with $scope itself an injectable.

  • Components and directives: angular.module(...).component('userPanel', { controller: ['UserService', function(UserService) {...}], bindings: { user: '<' } }) uses the same array notation for its controller.

  • Services depending on services: a .factory() can list other services in its array annotation, letting you build layered data access code.

  • Run and config blocks: app.run(['$rootScope', ...]) and app.config(['$routeProvider', ...]) are themselves injected.

Code Example

angular.module('app', [])

  // Config-phase constant, injectable into config blocks
  .constant('API_BASE', 'https://api.example.com')

  // Factory with array annotation: safe under minification
  .factory('UserService', ['$http', 'API_BASE', function ($http, API_BASE) {
    return {
      getUser: function (id) {
        return $http.get(API_BASE + '/users/' + id);
      }
    };
  }])

  // Service recipe: the injector calls it with 'new'
  .service('Session', ['UserService', function (UserService) {
    this.current = null;

    this.load = function (id) {
      var self = this;
      return UserService.getUser(id).then(function (response) {
        self.current = response.data;
        return self.current;
      });
    };
  }])

  // Component controller using the same array notation
  .component('userPanel', {
    bindings: { userId: '<' },
    controller: ['Session', function (Session) {
      var $ctrl = this;

      $ctrl.$onInit = function () {
        Session.load($ctrl.userId).then(function (user) {
          $ctrl.user = user;
        });
      };
    }],
    template: '<p>{{ $ctrl.user.name }}</p>'
  });

Best Practices and Pitfalls

  • Always use array annotation or $inject in any code that will be minified; name inference silently fails only after a production build.

  • Prefer .factory() for most services and reserve .service() for cases where a constructor with prototype methods reads naturally; both end up as singletons.

  • Reach for .provider() only when a service genuinely needs configuration at bootstrap, since it adds boilerplate.

  • Keep controllers thin: inject services that hold data and logic rather than duplicating that logic per controller.

  • Do not inject $scope into services; $scope belongs to controllers and directives, and services should stay reusable.

  • Note the framework’s status: AngularJS no longer receives security patches, so teams maintaining such code typically plan a migration — either a rewrite in modern Angular or running both frameworks together with ngUpgrade.

24

Can you explain the roles of the $injector and $provide services in AngularJS?

The $provide service is the registry that defines how each injectable component in an AngularJS application is created, while the $injector is the runtime engine that looks those components up, resolves their dependencies and instantiates them. Together they implement AngularJS dependency injection: $provide holds the recipes and $injector bakes the objects.

Read full answer

Applies toAngularJS 1.8.x, the final 1.x release (AngularJS reached end of life on 31 December 2021, so it should not be used for new projects).

How Dependency Injection Fits Together

AngularJS applications are built from injectable components: services, factories, values, constants, providers, controllers, directives, filters and the framework’s own services such as $http and $scope. Each is registered under a unique name. When something asks for a component by name, the injector walks the recipe registered by $provide, creates the object (caching singletons where appropriate), and recursively satisfies that object’s own declared dependencies first.

$provide: Registering Injectables

$provide offers several recipes, each suited to a different kind of object:

  • value(name, value): registers a simple, already-constructed value — useful for configuration strings or flags.

  • constant(name, value): like value, but also injectable into config blocks and available before the injector is fully bootstrapped.

  • factory(name, $getFn): registers a function that is called once and whose return value becomes the singleton.

  • service(name, constructor): registers a constructor function that the injector invokes with new; the resulting instance is the singleton.

  • provider(name, providerObject): the most powerful recipe — an object with a $get factory function plus extra configuration methods that can be called from a config block before instantiation.

  • decorator(name, $delegate): intercepts an existing service so you can extend or replace its behaviour without changing its registration.

Recipe Injected into config? When to use
value No A plain, static value
constant Yes A value needed during configuration
factory No Logic that builds and returns an object
service No A constructor/class-style object
provider Yes Configurable services with setup steps

$injector: Resolving and Creating Instances

The injector exposes a small, stable API:

  • get(name): returns the singleton for a registered component name, creating it (and its dependencies) on first request.

  • invoke(fn, self, locals): calls a function, injecting its declared dependencies; locals can override individual dependencies.

  • instantiate(Type, locals): creates an instance of a constructor function with its dependencies injected.

  • annotate(fn): returns the array of service names a function depends on — the basis of minification-safe annotation.

  • has(name): checks whether a component is registered.

You rarely call these directly; AngularJS uses the injector internally for controllers, services and run/config blocks. Direct use is justified for dynamic resolution, plug-in systems, testing (injecting mocks) or framework-level code.

Best Practices and Common Pitfalls

  • Always use minification-safe annotation: either the array form (['service', function (service) {}]) or a $inject property (MyController.$inject = ['httpClient'];). Without it, minifiers rename parameters and injection fails. ng-strict-di makes this error surface during development.

  • Prefer the simplest recipe that works: value/constant for data, factory for most services, provider only when configuration is genuinely needed.

  • Inject providers only in config blocks, instances only in run blocks and components — mixing them causes errors.

  • Avoid calling $injector.get inside hot paths: it bypasses the declarative style and hides dependencies from readers and tooling. Inject dependencies through function signatures instead.

  • Do not create circular dependencies (A needs B, B needs A): the injector cannot resolve them and will fail at bootstrap.

25

In AngularJS, how do you implement and inject configuration data using the value and constant methods?

In AngularJS, both constant and value are provider recipes used to inject configuration data into services, components, and directives, but they differ primarily in availability during the configuration phase. A constant can be injected into .config() blocks, whereas a value cannot, making constants suitable for configuration data needed before the application runs.

Read full answer

Applies toAngularJS 1.8.x

Core Differences

  • Configuration Phase Availability: The most critical distinction is that constant values are accessible during the module configuration phase (.config()), while value instances are only available during the run phase (.run()) and within controllers, components, or services.

  • Decorators and Interception: Constants cannot be decorated or intercepted by $provide.decorator, ensuring their value remains fixed throughout the application lifecycle. Values can be decorated, allowing runtime modification or wrapping with additional logic.

  • Mutability: While both can hold objects, primitives, or functions, constants are intended for immutable configuration. Values are mutable: consumers receive the same instance, so its properties can be changed after injection.

Usage Scenarios

  • Use constant: Ideal for settings required during configuration, such as API base URLs, authentication tokens needed by interceptors configured in .config(), or references to global libraries (e.g., window.google).

  • Use value: Suitable for data that might change during runtime or needs decoration, such as user preferences, default form values, or mock service implementations during testing.

  • Avoid for Logic: Neither should contain complex business logic; use services or factories for logic that requires dependency injection, instantiation, or isolation.

Dependency Injection Safety

AngularJS dependency injection relies on parameter names, which break when code is minified. To ensure safety, use array notation or the $inject property when injecting constants or values. - Array Notation: List dependency names as strings before the function: ['API_URL', function(API_URL) {...}]. - $inject Property: Attach an array to the function: MyCtrl.$inject = ['API_URL'];. - This applies to constants and values injected into components, services, and run blocks, and to constants (and providers) injected into config blocks.

Code Example

angular.module('app', [])
  // Immutable config available in .config()
  .constant('API_URL', 'https://api.example.com/v1')
  // Mutable data, not available in .config()
  .value('appName', 'MyApp')
  .config(['API_URL', function(API_URL) {
    console.log('Config phase:', API_URL);
  }])
  .component('mainComponent', {
    // One-time bindings keep the watcher count low
    template: '<p>API: {{::$ctrl.url}}</p>',
    controller: ['API_URL', 'appName', function(API_URL, appName) {
      this.url = API_URL;
      this.name = appName;
    }]
  });

Best Practices and Migration

  • Immutability: Treat constants as truly immutable; do not attempt to modify them inside components, as this confuses other consumers relying on stable configuration.

  • Watcher Count: Avoid binding constants or values directly to the view with {{ }} inside ng-repeat loops without track by, as this adds unnecessary watchers. Use one-time binding {{ ::value }} where possible to reduce digest cycle load.

  • End of Life: AngularJS reached end of life on 31 December 2021. For new projects, use Angular (2+). For legacy maintenance, plan migration via rewriting or ngUpgrade if mixing frameworks.

  • Security: Never store secrets (like private API keys) in constants or values, as they are exposed in the client-side bundle. Use server-side configuration or environment variables instead.

26

What is the way to declare and configure a module in AngularJS?

In AngularJS you define a module with the angular.module() method, passing the module’s name and an array of other modules it depends on, and then attach components, services, directives and configuration blocks to that module. A module is the top-level container for an application’s code: it groups related pieces, declares dependencies, and provides the hooks (config and run blocks) that run at bootstrap.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; AngularJS reached end of life on 31 December 2021, so it should not be used for new projects).

The Two Call Signatures

angular.module behaves differently depending on its arguments, and mixing them up is the most common mistake:

  • Setter — angular.module('app', []): two arguments, a name and an array of dependencies. This creates a new module. Passing an empty array means the module has no dependencies; omitting the array is not the same thing.

  • Getter — angular.module('app'): one argument. This retrieves an existing module and throws if it has not been defined yet. Use the getter when you split controllers, components and services across several files that all belong to the same module.

Registering Things on a Module

The setter returns the module instance, which exposes a fluent API for registering application building blocks:

  • .component(name, options): the component-based style available since 1.5, preferred for UI widgets. It uses controllerAs ($ctrl) by default.

  • .controller(name, constructor): register a controller, typically for use with ng-controller or a route/template.

  • .service(name, constructor) / .factory(name, function) / .provider(name, type): three related ways to register injectable services, differing in how much configuration control they expose.

  • .directive(name, factory): needed only when a component cannot do the job, for example when you need DOM manipulation in the linking function or a multi-element directive.

  • .filter(name, factory): register a formatter used in templates with the | pipe syntax.

  • .config(fn): runs during provider configuration, before injection is finished, so it can only receive providers and constants.

  • .run(fn): runs after the injector is created; use it for one-time startup work such as fetching reference data or authenticating the user.

  • .constant(name, value) / .value(name, value): register simple injectable values; constants are available in config blocks, values are not.

Dependency Injection and Minification Safety

AngularJS infers a component’s dependencies from the names of its constructor’s parameters. Minifiers rename those parameters, which silently breaks the app in production. Always use one of the two minification-safe forms:

  • Array annotation: ['$http', function($http) { ... }] — the strings survive minification and are matched positionally to the parameters.

  • $inject property: attach MyController.$inject = ['$http']; to the constructor function.

The same rule applies to config and run blocks, which are also injectable.

Bootstrapping the Module

A module is started by adding ng-app="app" to the HTML element that roots the application, or by calling angular.bootstrap(document.documentElement, ['app']) for manual control (useful when you need to set up providers first or bootstrap after the DOM is ready). The ng-app directive can only appear once per page; for multiple regions use manual bootstrap.

Code Example

// app.module.js — create the module (setter) with one dependency
angular.module('app', ['ngRoute']);

// config block: providers and constants only, array notation for minification safety
angular.module('app').config(['$routeProvider', function ($routeProvider) {
  $routeProvider.when('/', { template: '<hello></hello>' });
}]);

// run block: runs once at startup, services are injectable here
angular.module('app').run(['$log', function ($log) {
  $log.info('Application started');
}]);

// hello.component.js — retrieve the module (getter) and register a component
angular.module('app').component('hello', {
  bindings: { name: '<' },      // one-way input binding
  template: '<p>Hello, {{ $ctrl.name }}</p>',
  controller: ['$timeout', function ($timeout) {
    this.$onInit = function () {          // lifecycle hook, runs once inputs are set
      var self = this;
      $timeout(function () { self.greeted = true; }, 0); // triggers a safe digest
    };
  }]
});
<!DOCTYPE html>
<html ng-app="app">
<body>
  <hello name="'AngularJS'"></hello>
  <script src="angular.js"></script>
  <script src="angular-route.js"></script>
  <script src="app.module.js"></script>
  <script src="hello.component.js"></script>
</body>
</html>

Best Practices and Common Pitfalls

  • Define one root module per application and give it a name that matches the app, not myApp or app in a shared codebase.

  • Use the getter form in secondary files so each file can be loaded independently; re-calling the setter with a dependency array in two files silently recreates the module and loses registrations.

  • Prefer .component() over .directive() for new UI code; reserve .directive() for cases that genuinely need a link function.

  • Always use array annotation or $inject, and test a production build with minification enabled — DI failures only appear there.

  • Keep the dependency list honest: listing a module that is not loaded makes the injector fail at bootstrap, while forgetting to list a module whose components you use fails when those components are first referenced.

  • Remember the framework’s status: AngularJS no longer receives security patches or browser-compatibility fixes, so for greenfield work or long-lived systems, plan a migration — either a rewrite in modern Angular or running AngularJS side by side with Angular via ngUpgrade.

27

What purpose does the ngRoute module serve in an AngularJS application?

The ngRoute module is AngularJS’s built-in client-side routing mechanism: it maps URL paths to views and controllers, loads the matching template into an ngView region, and lets a single-page application navigate between “pages” without a full browser reload.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; the module ships separately as `angular-route.js`)

How It Works

You add ngRoute as a dependency of your module and configure routes in a config block using $routeProvider. When the URL changes, the $route service matches it against the registered patterns, resolves any data declared in the resolve property, instantiates the route’s controller, and renders the template inside the element carrying the ngView directive. Location handling is done by the $location service; by default URLs use a hash prefix (#!/path), which works without server configuration.

Core Components

  • $routeProvider: the provider used in config() to declare routes with .when(path, options) and a catch-all with .otherwise(options).

  • ngView: the directive (ng-view attribute or <ng-view> element) that acts as the viewport for the matched template.

  • $route: the service that holds the current route and triggers navigation; call $route.reload() to re-render the current route.

  • $routeParams: a service injected into a controller to read URL parameters such as /:id.

  • $location: reads and changes the browser URL; switch to HTML5 mode with $locationProvider.html5Mode(true), which requires a <base href="/"> tag and server-side rewrite rules so deep links return index.html.

Key Features

  • Route parameters: define dynamic segments like /users/:id; the values are available through $routeParams after navigation.

  • resolve: an optional map of dependencies (often $http calls) that must resolve before the controller is instantiated; the resolved values are injected into the controller, and the route change only completes when they succeed.

  • Redirects: redirectTo (a string or a function) sends one route to another, and otherwise defines the fallback for unmatched URLs.

  • Lifecycle events: $routeChangeStart, $routeChangeSuccess and $routeChangeError are broadcast on $rootScope, so you can implement guards or loading indicators.

  • Controller scoping: a new controller instance is created per route change, which suits the recommended controllerAs / $ctrl style.

Best Practices and Limitations

  • Prefer controllerAs (controllerAs: '$ctrl') in route definitions instead of relying on $scope for view model data.

  • Put authentication and data-loading checks in resolve or a $routeChangeStart listener so incomplete data never reaches the controller.

  • Keep templates and controllers small and component-oriented; AngularJS 1.5+ components can be used directly as route controllers.

  • ngRoute supports only one viewport and no nested states. Applications needing nested views or state machines typically use the third-party ui-router library instead.

  • AngularJS reached end of life on 31 December 2021 (long-term support ended), so it receives no further security patches: new work should use modern Angular, and existing apps should plan migration through a rewrite or by running both frameworks together with ngUpgrade.

28

What approaches ensure dependency injection works correctly in AngularJS, particularly when code is minified?

Dependency injection in AngularJS is configured by explicitly declaring service names as strings alongside the function that requires them, allowing the injector to resolve dependencies even when parameter names are altered. The critical practice is annotation, which preserves the mapping between logical dependency names and their registered providers during the build process.

Read full answer

Applies toAngularJS 1.8.x

How the Injector Resolves Dependencies

AngularJS maintains an internal injector that looks up registered services, factories, and providers by name. When a component, controller, or service is instantiated, the injector reads the dependency list and passes the corresponding instances into the constructor function. Without explicit annotation, the injector relies on JavaScript function parameter names to determine what to inject, which breaks when production builds minify variable names into single characters.

Minification-Safe Annotation Patterns

To survive minification, you must provide dependency names explicitly using one of two supported patterns. Both approaches achieve the same result, so the choice depends on team style preferences and code readability.

  • Array Notation: Dependencies are listed as strings in an array before the function argument list. This is the most common approach because it keeps the declaration close to the usage.

  • $inject Property: A static property named $inject is assigned to the function itself, containing the array of dependency names. This is useful when defining controllers as separate named functions.

Component-Based Configuration Style

Since AngularJS 1.5, the recommended way to build UI logic is using .component() rather than .directive(). Components use controllerAs syntax, typically binding the controller instance to $ctrl, and define inputs and outputs via the bindings object. Dependencies are injected into the component controller using the same annotation rules as services.

  • One-way bindings (<): Use for input data to prevent accidental mutation of parent scope.

  • Output bindings (&): Use for callbacks to communicate events back to the parent.

  • Lifecycle hooks: Implement $onInit, $onChanges, $postLink, and $onDestroy for controlled initialization and cleanup.

Code Example

The following example demonstrates a service and a component configured with minification-safe dependency injection using ES5 syntax.

angular.module('app', [])
  .factory('DataService', ['$http', function ($http) {
    return {
      getUsers: function () {
        return $http.get('/api/users');
      }
    };
  }])
  .component('userList', {
    templateUrl: 'user-list.html',
    controller: ['DataService', function (DataService) {
      this.$onInit = function () {
        var self = this;
        DataService.getUsers().then(function (response) {
          self.users = response.data;
        });
      };
    }]
  });

Best Practices and Migration Context

Always avoid implicit annotation (relying solely on parameter names) because it causes runtime errors in production builds. Additionally, AngularJS reached end of life on 31 December 2021, meaning it no longer receives security updates or bug fixes. For new projects, consider modern frameworks; for legacy maintenance, ensure all dependencies are annotated and plan a migration strategy using ngUpgrade if moving to Angular (2+). Keep the number of watchers low by using one-time bindings (::) where data does not change, reducing digest cycle overhead.

29

How can you ensure dependency injection remains functional after code minification in AngularJS applications?

To ensure dependency injection survives minification in AngularJS, you must explicitly specify dependency names as strings instead of relying on function parameter inference. The framework supports two standard patterns for this: the array notation and the $inject property, both of which map shortened variable names back to their original provider tokens during runtime.

Read full answer

Applies toAngularJS 1.8.x

Why Minification Breaks Dependency Injection

AngularJS infers dependencies by inspecting the names of function parameters using toString(). When a build tool minifies JavaScript, it renames local variables to single characters to reduce file size (e.g., $http becomes a). Without explicit annotations, the injector cannot resolve a to the $http service, resulting in an “Unknown provider” error at runtime. This affects controllers, services, factories, and component controllers alike, making safe annotation mandatory for any production build.

Using Array Notation

The array notation lists dependency names as strings before the function definition within the registration call. The last element of the array is the function itself, whose parameters correspond positionally to the preceding strings. This approach is inline and keeps the dependency list visible at the point of definition, which is convenient for small controllers or services. - Syntax: ['dep1', 'dep2', function(dep1, dep2) {...}] - Benefit: Self-contained declaration that cannot be separated accidentally.

Using the $inject Property

Alternatively, you can assign an array of strings to the $inject static property of the function before it is registered with the module. This separates the dependency declaration from the function logic, which can improve readability for large controllers or reusable service functions. The function must be defined as a named function or assigned to a variable before the property is set. - Syntax: function MyCtrl ($http) {...}; MyCtrl.$inject = ['$http']; - Benefit: Cleaner registration line and easier to test independently.

Code Example

The following example demonstrates both methods within a modern AngularJS 1.5+ component definition and a service registration. The component uses controllerAs: '$ctrl' and the $onInit lifecycle hook.

angular.module('app', []);

// Method 1: Array notation in a service
angular.module('app').service('DataService', ['$http', function ($http) {
    this.getData = function () {
        // $http returns a promise; resolve it with .then()
        return $http.get('/api/data').then(function (response) {
            return response.data;
        });
    };
}]);

// Method 2: $inject property in a component controller
function DataComponentController (DataService) {
    this.$onInit = function () {
        var self = this;
        DataService.getData().then(function (data) {
            self.data = data;
        });
    };
}
DataComponentController.$inject = ['DataService'];

angular.module('app').component('dataComponent', {
    template: '<div>{{$ctrl.data}}</div>',
    controller: DataComponentController,
    controllerAs: '$ctrl'
});

Best Practices and Migration Context

  • Always Annotate: Never rely on parameter inference, even if your local build seems to work without it.

  • Manual Annotation Preferred: Avoid depending on third-party tools like ng-annotate; manual array or $inject annotation is the most reliable and works with any bundler.

  • Build Tooling: If you use a bundler, configure it to preserve function names (e.g., keep_fnames in Terser) as a backup, but do not rely on it alone.

  • End of Life: AngularJS reached end of life on 31 December 2021. For new projects, consider rewriting in Angular (2+) or using ngUpgrade to run both frameworks during migration.

  • Watchers: While fixing DI, also review watchers. Use one-time bindings (::) and track by in ng-repeat to keep the digest cycle efficient.

30

Can you explain how AngularJS expressions function within templates and how they differ from standard JavaScript code?

AngularJS expressions are code snippets enclosed in double curly braces that bind application data from the scope to the view, enabling dynamic rendering directly within HTML templates. These AngularJS expressions look like JavaScript but are intentionally limited, evaluated against the current scope, and designed to keep the presentation layer declarative and safe.

Read full answer

Applies toAngularJS 1.8.x

How Expressions Work

Expressions are evaluated by the $parse service during the digest cycle. When you write {{ user.name }}, AngularJS looks up user on the current scope and renders the name property. If the value changes, the digest cycle detects the difference and updates the DOM. Expressions can also include operators, literals and filters, such as {{ price | currency }}. Unlike raw JavaScript, they are evaluated against the scope, so only properties and functions published on the scope chain are reachable; globals like window.alert are not.

Differences from JavaScript

While expressions resemble JavaScript, they have strict limitations that keep templates declarative, secure and fast. - No control flow: statements such as if, for or while cannot be used inside an expression. - No comma operator: multiple expressions cannot be chained in one block. - Error tolerance: accessing properties of undefined or null does not throw; AngularJS treats the result as undefined and renders nothing. - Scope context: identifiers are resolved on the scope, not against the global window object. - No void or new: these operators, and function definitions, cannot appear in a template expression.

Common Use Cases

Expressions are mainly used for data binding and simple, view-level transformations. - Interpolation: {{ }} inserts evaluated text into the DOM. - Directives: attributes such as ng-bind, ng-show and ng-class take expressions to control behaviour. - Filters: formatting values before display, e.g. {{ createdAt | date:'medium' }}. - Event handling: ng-click evaluates an expression that calls a scope or controller method, e.g. ng-click="$ctrl.save()".

Code Example

<user-card></user-card>
angular.module('app', [])
.component('userCard', {
  template: [
    '<p>Hello, {{ $ctrl.name }}!</p>',           // interpolation against the controller
    '<p>Total: {{ $ctrl.total | currency }}</p>', // filter applied in the expression
    '<p>{{ $ctrl.missing.nope }}</p>',           // no error; renders empty
    '<p>{{ :: $ctrl.label }}</p>'                // one-time binding: no long-lived watcher
  ].join(''),
  controller: ['$log', function ($log) {
    this.name = 'Alice';
    this.total = 12.5;
    this.label = 'One-time bound';
    $log.log('userCard initialized');
  }]
});

Best Practices and Limitations

Keeping templates simple matters for maintainability and performance; heavy logic belongs in the controller or component, not the markup. - Keep logic out of templates: perform calculations in the controller and bind the result. - Limit watchers: every interpolated expression registers a watcher, and many watchers slow the digest cycle; use one-time binding {{ ::value }} for data that will not change. - Sanitize user input: expressions are sandboxed, but user-supplied HTML should still be sanitized (for example with $sanitize) before it is rendered. - End of life: AngularJS reached end of life on 31 December 2021, so keep expressions minimal and components self-contained to ease a later migration to Angular, or to running both frameworks together with ngUpgrade.

31

Explain the purpose of filters in AngularJS and describe how you apply the built-in ones and write your own.

Filters are formatting and selection functions that you pipe data through in an AngularJS template, so you can transform a value for display (dates, currency, casing) or narrow an array before iterating it, without putting that logic in a controller.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; AngularJS reached end of life on 31 December 2021, so this applies to maintaining existing apps, not starting new ones).

How Filters Work

A filter is a function registered on a module that takes an input value and optional arguments and returns a transformed value. In a template you apply it with the pipe character (|), pass arguments after colons (:), and chain multiple filters by adding another pipe:

<p>{{ 1234.5678 | currency:'USD':0 }}</p>
<p>{{ user.birthday | date:'mediumDate' | uppercase }}</p>

Filters in a binding are re-evaluated on every digest cycle, which is why they must be cheap and free of side effects.

Built-in Filters

Filter Purpose Example
currency Formats a number as money, with symbol and optional fraction precision {{ price | currency:'EUR':2 }}
date Formats a Date, timestamp or ISO string using a pattern like 'yyyy-MM-dd' or a named format like 'short' {{ user.created | date:'fullDate' }}
number Adds thousands separators and fixed fraction precision {{ ratio | number:2 }}
json Renders any object as indented JSON, useful for debugging <pre>{{ model | json }}</pre>
lowercase / uppercase Changes string case {{ name | uppercase }}
limitTo Takes the first or last N items of an array, or characters of a string (negative N counts from the end) {{ items | limitTo:5 }}
orderBy Sorts an array by an expression string, an array of expressions, or a comparator function; prefix with - to reverse ng-repeat="u in users | orderBy:'-age'"
filter Selects a subset of an array by matching a string, object or predicate function ng-repeat="u in users | filter:query"

The currency, number and date filters are locale-aware: they follow the rules of the loaded locale, which by default is en-us from angular-locale_en-us.js. To localise an app you include the matching angular-locale_*.js file from the angular-i18n distribution, which sets $locale and changes separators, currency symbols and date formats.

Using Filters in Templates

The most common use is inside ng-repeat, where you compose selection and ordering filters before looping. Combine that with track by so AngularJS reuses DOM nodes instead of re-rendering the whole list:

<li ng-repeat="u in $ctrl.users | filter:$ctrl.query | orderBy:'name' track by u.id">
  {{ u.name }} — {{ u.joined | date:'yyyy' }}
</li>

You can also use a filter in ng-bind, ng-class, ng-if or any other expression — anywhere an expression is allowed.

Using Filters in JavaScript

Inject the $filter service and invoke a filter by name when you need the formatted value in a controller, service or directive. This is preferred over duplicating formatting logic, and it runs once rather than every digest:

angular.module('app').controller('InvoiceController', ['$filter', function ($filter) {
  var vm = this;
  var total = 1299.5;

  // Call the currency filter directly: $filter('name')(input, arg1, arg2)
  vm.summary = $filter('currency')(total, 'USD', 0);
}]);

Code Example: Writing a Custom Filter

Register a factory with .filter(); the factory returns the actual filtering function. This one truncates text and appends an ellipsis:

angular.module('app', [])
  .filter('truncate', function () {
    // input is the piped value; limit and suffix are the ':' arguments
    return function (input, limit, suffix) {
      if (!angular.isString(input)) {
        return input;
      }
      var max = angular.isNumber(limit) ? limit : 20;
      var tail = angular.isDefined(suffix) ? suffix : '…';

      return input.length > max ? input.slice(0, max) + tail : input;
    };
  });
<p>{{ product.description | truncate:120:'...' }}</p>

Best Practices and Pitfalls

  • Keep filters fast: they run on every digest. Move expensive transformations into the controller or a service and store the result, especially for large arrays.

  • Prefer one-time bindings ({{ ::value | date }}) for data that does not change after the first render; the binding is deregistered and the filter stops running.

  • Do not mutate input: a filter must return a new value and leave the original untouched, since it may be called many times with the same input.

  • Validate input types: guard against undefined, non-strings or empty arrays so a missing value does not throw in production.

  • Use track by with ng-repeat whenever items have stable IDs, to avoid DOM churn when a filter reorders or shrinks the list.

  • Test filters directly: they are plain functions, so unit-test the factory’s returned function with inputs and arguments rather than through a template.

32

What is the process for defining and registering a custom filter in an AngularJS module?

Defining and registering a custom filter in AngularJS means attaching a factory function to a module with the .filter() method; that factory returns the function that transforms values, and the filter then becomes available in templates through the pipe syntax and in JavaScript through the $filter service.

Read full answer

Applies toAngularJS 1.8.x

How Filters Are Registered

  • Module registration: call .filter('filterName', factoryFunction) on an angular.module. The factory is invoked once when the filter is first needed, so filters behave as stateless singletons.

  • Dependency injection: the factory participates in DI like any service, so you can inject $http, $log, or other services using minification-safe array notation such as ['$http', function($http) {...}].

  • Template usage: apply the filter with the pipe operator, written as {{ expression | filterName:arg1:arg2 }}.

  • Programmatic usage: inject the $filter service and invoke it as $filter('filterName')(input, arg1).

Filter Function Signature

  • The function returned by the factory receives the input value as its first parameter; each argument passed after the pipe in the template becomes a subsequent parameter in order.

  • It must return the transformed value; returning undefined renders nothing in the view.

  • Guard against null and undefined inputs at the top of the function to prevent runtime errors during the digest cycle.

Change Detection and Performance

  • AngularJS re-evaluates filters on every digest cycle, so expensive logic inside a filter runs repeatedly and can noticeably slow the application.

  • Keep filters pure: do not mutate the input object, do not read or write external mutable state, and avoid side effects such as network calls.

  • For heavy work, compute the result once in the controller or component and bind the pre-computed value, or use one-time binding :: when the value never changes to stop the filter from re-running.

Code Example

angular.module('app', [])
  // Register the filter; the factory returns the filtering function.
  .filter('capitalize', function() {
    return function(input) {
      // Guard against non-string, null, or undefined input.
      if (typeof input !== 'string') return input;
      return input.charAt(0).toUpperCase() + input.slice(1);
    };
  })
  // Inject $filter with minification-safe array notation.
  .controller('MainCtrl', ['$scope', '$filter', function($scope, $filter) {
    $scope.name = 'angular';
    // Invoke the filter programmatically.
    $scope.formatted = $filter('capitalize')($scope.name);
  }]);
<div ng-controller="MainCtrl">
  <!-- Apply the filter in a template with the pipe syntax. -->
  {{ name | capitalize }}
  <p>{{ formatted }}</p>
</div>

Best Practices and Migration

  • Use filters for presentation formatting such as dates, currency, and text transformation, not for business logic.

  • Keep the number of watchers low and avoid filters that allocate new objects or arrays on every call, since this forces extra digest work.

  • Remember that AngularJS reached end of life on 31 December 2021, so custom filters should be maintained only in legacy systems. For new development, migrate by rewriting in Angular, where pipes serve a similar purpose, or run both frameworks together with ngUpgrade during a gradual migration.

33

How do filters differ when used inside a controller versus inside a directive in AngularJS?

A filter in AngularJS is a pure function that formats or transforms a value, and it can be used in two distinct contexts: declaratively in a template with the pipe syntax ({{ value | filterName }}), which includes the markup of any directive, or programmatically by injecting the $filter service (or the filter’s own injectable, e.g. currencyFilter) into a controller or a directive’s controller and calling it from JavaScript.

Read full answer

Applies toAngularJS 1.8.x, the final 1.x release (long-term support ended 31 December 2021)

How Filters Are Applied in Each Place

  • In templates (including directive templates): the compiler parses the interpolation or binding expression, so {{ user.name | uppercase }} is re-evaluated by the digest cycle whenever the referenced values change. A directive’s template is part of the same compiled template tree, so filters used there behave exactly like filters used in any other view markup.

  • In controllers: you inject $filter and call it, e.g. $filter('uppercase')(user.name), or you inject the named filter service directly (uppercaseFilter). The controller decides when the filter runs and where the result goes, typically assigning it to a property exposed to the view.

  • Registration is shared: angular.module('app').filter('initials', fn) defines the filter once; both the template pipe and the injected function resolve to the same filter instance. The difference is therefore not the filter itself but the caller, the timing, and the frequency of execution.

Key Differences

Aspect Filter in a template / directive markup Filter via $filter in a controller
Syntax ‘{{ value filterName:arg }}orng-repeat=“x in xs | orderBy:‘name’”‘ $filter('filterName')(value, arg) or injected filterNameFilter
Who triggers it The digest cycle, through interpolation and watchers Your controller code, at a moment you choose
How often it runs On every digest that touches the watched expression unless a one-time :: binding is used Once per call; you can compute a result once and reuse it
Best fit Pure presentation formatting: currency, date, number, uppercase Logic that consumes the result: sorting, grouping, deriving data other bindings depend on
Testing Verified through compiled markup / DOM assertions Verified as a plain function call in a unit test

Choosing Between Them

  • Prefer the template pipe when the output exists only for display and the input is already on the scope; it keeps controllers free of view-only formatting.

  • Reach for $filter in a controller when the transformed value drives other controller logic, must be passed to a service, or is expensive to recompute — compute it once and expose the result.

  • Inside a directive, the same split applies: use the pipe in template/templateUrl markup for rendering, but use the injected filter in the directive’s controller when the directive has to prepare data for its isolated-scope bindings.

  • Filters are pure and stateless, so AngularJS reuses one filter function instance for the whole app, but it does not cache results: an expensive filter applied to a large collection in markup still runs on every digest.

Code Example

angular.module('app', [])
  .filter('initials', function () {
    return function (input) {
      if (!input) {
        return '';
      }
      return input.split(' ')
        .map(function (word) { return word[0].toUpperCase(); })
        .join('');
    };
  })
  // Controller: inject the filter directly (minification-safe array notation)
  .controller('UserCtrl', ['initialsFilter', function (initialsFilter) {
    var vm = this;
    vm.fullName = 'ada lovelace';
    vm.initials = initialsFilter(vm.fullName); // computed once, reused by the view
  }])
  // Directive: filter used declaratively in its own template
  .directive('userBadge', function () {
    return {
      restrict: 'E',
      scope: { name: '<' },                 // one-way input binding
      bindToController: true,
      controllerAs: '$ctrl',
      controller: function () {},           // bindToController requires a controller
      template: '<span class="badge">{{ ::$ctrl.name | initials }}</span>'
    };
  });
<div ng-controller="UserCtrl as $ctrl">
  <p>{{ $ctrl.initials }}</p>          <!-- AL, produced by the controller -->
  <user-badge name="Ada Lovelace"></user-badge>  <!-- AL, produced by the template -->
</div>

Best Practices and Pitfalls

  • Prefer injecting the specific filter (initialsFilter) over $filter('initials') for readability and to keep the dependency explicit; both are valid in 1.8.x.

  • Use one-time bindings (::) for filtered values that never change after first render, so the expression stops registering a permanent watcher.

  • Do not put business logic in a filter and then invoke it from a controller to hide side effects — filters must stay pure; move real logic into a service.

  • Watch performance in ng-repeat: filtering a large list in markup runs on every digest; precompute the list in the controller when the data is static.

  • For any new work, remember AngularJS is end of life: greenfield projects should use modern Angular, and existing apps can migrate incrementally with ngUpgrade or a rewrite.

34

What is the function of the ng-repeat directive in AngularJS, and what mechanism does it use to render collections?

The ng-repeat directive instantiates a template once for every item found in a specified collection, enabling dynamic list rendering within the DOM. It works by registering a watcher on the collection expression at link time; the digest cycle then evaluates that watcher and re-renders the affected DOM nodes whenever the collection changes.

Read full answer

Applies toAngularJS 1.8.x

Core Purpose and Syntax

ng-repeat is used to loop over arrays or objects on the scope, generating DOM elements for each entry. The basic syntax is ng-repeat="item in collection", placed on any HTML element, and it can also iterate over object properties with (key, value) in object. Within the repeated template, special properties are available: $index (current index), $first, $last, $middle, $even, and $odd. This allows developers to conditionally style or position items, such as applying a different class to the first row of a table without manual indexing logic.

Internal Mechanism and Watchers

When AngularJS compiles a template containing ng-repeat, it clones the template element once per collection item and links each clone in its own child scope, which exposes the current item variable without polluting the parent scope. A watcher is registered for the collection expression, and during each digest cycle the framework compares the previous and current collection. When items are added, removed or reordered, ng-repeat reconciles the DOM: it clones the template for new items, removes the nodes of deleted items, and moves nodes when the order changes, while reusing the child scopes and DOM nodes that still correspond to the same tracked items.

Performance Optimization with track by

By default, ng-repeat tracks items by their identity (object reference). If the collection is replaced with new objects containing identical data, AngularJS may destroy and recreate all DOM nodes, which is expensive. Using track by allows tracking via a unique key, such as ng-repeat="user in users track by user.id". This preserves DOM elements when only data properties change, significantly improving rendering speed. For static or read-only data, one-time bindings such as {{ ::user.name }} further reduce the watcher count, since the binding is deregistered after its first stable value.

Best Practices

  • Keep the collection expression simple: complex expressions are re-evaluated in every digest cycle, so move expensive logic into the controller and bind to a prepared array.

  • Prefer track by for collections that are refreshed from the server, especially lists rendered from objects without stable references.

  • Keep the number of watchers low, since each repeated binding adds watchers that the digest cycle must check on every run; this matters most on mobile devices and long lists.

  • Use one-time bindings for values that will not change, such as labels or static names inside a repeated row.

  • Remember the migration context: AngularJS reached end of life on 31 December 2021, so new projects should use modern Angular. Existing applications can be migrated by rewriting in Angular or by running both frameworks side by side with ngUpgrade.

35

How do you configure ng-repeat to track repeated elements by their index position?

To track repeated elements by their position rather than their identity, you add the track by $index clause to the end of your ng-repeat expression. This tells AngularJS to key the internal watcher collection and DOM nodes to the numeric index, so swapping or replacing the array does not force the framework to destroy and rebuild every element.

Read full answer

Applies toAngularJS 1.8.x (`track by` was introduced in 1.2)

Syntax

The $index variable is provided automatically by the repeater scope. You place the tracking clause after the collection name, separated by the track by keyword.

  • Default: ng-repeat="item in items" tracks by object identity (===).

  • By index: ng-repeat="item in items track by $index" tracks by position.

How It Works

By default, ng-repeat associates each DOM node with a specific object instance. If you assign a new array to items, even with identical content, AngularJS sees different object references and recreates the entire DOM subtree. Using track by $index decouples the DOM node from the object instance. The element at position 0 remains in the DOM; only the bindings inside it update to reflect the new data at that position. This reduces digest cycle work and preserves DOM state such as focus or CSS transitions tied to the node.

Risks and Limitations

  • Input State: If a row contains an <input> bound with ng-model, the input value follows the index, not the data. Reordering the list can make a value appear attached to the wrong object.

  • Animations: ngAnimate enter and leave animations may behave unexpectedly because nodes are reused rather than moved or recreated.

  • Duplicates: It allows rendering arrays that contain duplicate object references, which the default tracker would reject with a duplicate key error.

Code Example

<div ng-app="app">
  <!-- The item-list component renders a list using track by $index -->
  <item-list></item-list>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.js"></script>
(function () {
  'use strict';
  angular
    .module('app', [])
    .component('itemList', {
      controller: ItemListController,
      // The template uses track by $index to key DOM nodes by index position
      template:
        '<ul>' +
          '<li ng-repeat="item in $ctrl.items track by $index">' +
            '{{ $index }}: {{ item.name }}' +
          '</li>' +
        '</ul>'
    });
  function ItemListController() {
    var $ctrl = this;
    $ctrl.items = [
      { name: 'Alpha' },
      { name: 'Beta' }
    ];
  }
})();

Best Practices

  • Prefer tracking by a unique identifier, such as track by item.id, to keep DOM nodes tied to the correct data regardless of list order.

  • Reserve track by $index for static lists, read-only displays, or performance-critical renders where rows have no internal state.

  • Keep the number of watchers low by using one-time bindings (::) inside the repeat when data does not change.

  • Remember that AngularJS reached end of life on 31 December 2021; avoid using it for new projects and plan migration to a supported framework.

36

What guidelines should developers follow when working with the DOM in AngularJS applications?

The core best practice for DOM manipulation in AngularJS is to let the framework handle the DOM through data binding and directives, manipulating it directly only inside directives (or component controllers) where AngularJS provides the proper hooks and services. Controllers and services should never touch the DOM; they manage data and business logic, while directives and components own all DOM-related behavior.

Read full answer

Applies toAngularJS 1.8.x (end of life; long-term support ended 31 December 2021)

Where DOM Manipulation Belongs

  • Controllers: hold application state and logic only; inject $scope or use controllerAs ($ctrl), never $element.

  • Directives: the classic, most flexible place for DOM manipulation; use the link function or lifecycle hooks, which receive the DOM element and scope.

  • Components: use angular.module(...).component() (available since 1.5) for UI widgets; inject $element and do DOM work in $postLink, cleaning up in $onDestroy.

  • Services: contain reusable business logic; DOM access here makes code untestable and tightly coupled.

Prefer .component() for self-contained UI widgets with a template and one-way bindings (< inputs, & outputs). Reach for .directive() when a component cannot express the behavior, for example attribute-level directives, low-level DOM/event integration, or wrapping a third-party jQuery plugin.

Using AngularJS Services Instead of Raw DOM APIs

  • angular.element (jqLite): wrap elements with angular.element() rather than document.querySelector; it integrates with AngularJS’s data and event system. jQuery may be used if loaded before AngularJS, but jqLite is preferred for bundle size.

  • $timeout: use $timeout instead of setTimeout so changes trigger a digest cycle automatically; setTimeout requires a manual $scope.$apply().

  • $scope.$apply(): only call this when running code outside AngularJS’s context (e.g., a third-party callback); overusing it causes digest errors.

  • Data binding: prefer ng-class, ng-style, ng-show/ng-hide, and ng-if over manually adding classes or setting styles in JavaScript.

Keeping Change Detection Efficient

  • One-time bindings (::): use {{::value}} for data that never changes to remove the watcher after the first digest.

  • track by in ng-repeat: always add track by with a unique id to avoid recreating DOM nodes on every digest.

  • Limit watchers: each binding adds a watcher; excessive watchers slow down every digest cycle.

  • Clean up listeners: unregister $scope event listeners and destroy any third-party instances in $onDestroy (or on $scope.$on('$destroy', ...)) to prevent memory leaks.

Common Pitfalls

  • Manipulating the DOM in a controller: breaks testability and violates separation of concerns; move the logic to a directive or component.

  • Using setTimeout without $apply(): the view will not update because no digest cycle runs.

  • Direct jQuery selection inside controllers: bypasses AngularJS’s lifecycle and can cause memory leaks.

  • Forgetting cleanup: event listeners and third-party instances persist after the component or directive is destroyed.

  • Mixing AngularJS with Angular (2+): the syntax and concepts differ entirely; for new projects, consider rewriting in Angular or running both side by side with ngUpgrade.

37

What approaches does AngularJS provide for responding to user interactions and for passing events between components?

AngularJS handles events in two main ways: built-in directives such as ng-click, ng-change and ng-submit that respond to browser DOM events, and the $scope event system ($on, $emit and $broadcast) used for application-wide or parent/child communication. Components can also expose custom callbacks to their parents through '&' bindings, which is usually the cleanest option.

Read full answer

Applies toAngularJS 1.8.x (JavaScript, ES5/ES2015+)

Built-in DOM event Directives

The most common events come from directives that AngularJS attaches to the DOM for you:

  • ng-click: fires when an element is clicked. You write an expression, not a function reference: ng-click="save()".

  • ng-change: fires when the ngModel value changes, but only after the model has been updated, so form logic sees the new value. It requires ngModel on the same element.

  • ng-submit: binds to a form’s submit event and prevents the default page reload, so it is the right way to handle Enter key and submit button actions.

  • Other event directives: ng-dblclick, ng-mousedown, ng-mouseup, ng-mouseenter, ng-mouseleave, ng-mouseover, ng-mousemove, ng-focus, ng-blur, ng-keydown, ng-keyup, ng-keypress, ng-copy, ng-cut, ng-paste.

Inside any of these expressions, the current scope is available, so you can call controller methods or assign values directly. Because these expressions run inside a digest cycle, AngularJS picks up any resulting model changes automatically.

The $event Object

Event directives expose the browser event as $event, which you can pass to a handler: ng-click="save($event)". This gives access to target, keyCode, preventDefault(), stopPropagation() and similar DOM properties. Use it when you need the raw event, but keep DOM manipulation out of controllers where possible.

Custom Events with $emit, $broadcast and $on

When components need to talk to each other, AngularJS provides scope-level events:

  • $scope.$emit(eventName, data): dispatches the event upward, from the current scope to $rootScope, invoking listeners registered with $on on each parent scope.

  • $scope.$broadcast(eventName, data): dispatches downward to the current scope and all its children.

  • $scope.$on(eventName, listener): registers a listener that receives (event, data...). The listener is only called for events that reach that scope.

$on returns a deregistration function; calling it removes the listener. Listeners registered on $rootScope persist for the application lifetime unless deregistered, so always clean them up in $onDestroy to avoid leaks and repeated firing.

Component Outputs with '&' Bindings

For parent/child communication, prefer component bindings over scope events. A child component declares an output with bindings: { onSave: '&' } and calls this.onSave({ id: 42 }), passing arguments through a hash. The parent supplies the callback in its template: <user-editor on-save="saveUser(id)"></user-editor>. This keeps the data flow explicit and avoids the global event bus.

Best Practices and Common Pitfalls

  • Prefer component '&' outputs over $rootScope.$broadcast for parent/child notification; reserve scope events for genuinely global concerns such as login state changes.

  • Always deregister $on listeners, especially those on $rootScope, otherwise handlers accumulate and fire multiple times.

  • Do not call $scope.$apply inside event handlers triggered by AngularJS directives; the digest is already running. Use $timeout or $scope.$applyAsync when integrating with non-AngularJS code such as a raw WebSocket callback.

  • Keep handlers in controllers, not in templates: complex logic in ng-click expressions is hard to test and debug.

  • Note that AngularJS 1.8.x is end of life (long-term support ended 31 December 2021), so event-handling code like this should be maintained with that risk in mind, or migrated to modern Angular.

38

Explain the purpose of the ng-click directive in AngularJS and describe the mechanism behind its event handling.

The ng-click directive binds a click event listener to an HTML element, executing a specified expression or controller method whenever the user clicks it. It automatically triggers AngularJS’s digest cycle so that any model modifications performed inside the handler are reflected in the view immediately.

Read full answer

Applies toAngularJS 1.8.x

How It Works

Under the hood, ng-click uses the jqLite or jQuery wrapper to attach a native click event listener. When fired, the handler wraps the execution of the expression in $scope.$apply(), ensuring exceptions are caught and the digest cycle runs. This integration means developers do not need to manually call $apply to update bindings after a user interaction.

Component-Based Implementation

Since version 1.5, the recommended pattern uses .component() with controllerAs syntax. The controller is exposed as $ctrl in the template by default. Dependency injection must be minification-safe, using array notation when services are injected.

Code Example

angular.module('app', [])
.component('clickExample', {
  template: '<button ng-click="$ctrl.onPress()">Submit</button>',
  controller: ['$log', function($log) {
    this.onPress = function() {
      $log.log('Button clicked');
    };
  }]
});
<div ng-app="app">
  <click-example></click-example>
</div>

Digest Cycle and Performance

Because ng-click initiates a digest cycle, heavy logic inside the handler can block the UI. Keep handlers thin and delegate business logic to services. Avoid complex expressions in the template attribute itself, such as ng-click="doA(); doB()", as this complicates testing and maintenance.

Best Practices and End of Life

AngularJS reached end of life on 31 December 2021, meaning it receives no security updates. For new projects, use Angular (2+). For legacy maintenance, keep logic inside controllers rather than templates, and migrate to components over classic directives. If upgrading, consider ngUpgrade to run AngularJS and Angular side by side.

39

How does the event propagation mechanism operate across the scope hierarchy in AngularJS?

In AngularJS, event propagation is managed through a custom pub-sub system on the $scope hierarchy, allowing events to travel upward via $emit or downward via $broadcast independently of DOM bubbling.

Read full answer

Applies toAngularJS 1.8.x

Scope Event Propagation Model

AngularJS decouples logical communication from the DOM using three primary methods attached to scope objects. - $scope.$emit(name, args): Sends an event upward through the scope chain from the current scope to all ancestors, ending at $rootScope. - $scope.$broadcast(name, args): Sends an event downward to all child scopes and their descendants. - $scope.$on(name, listener): Registers a callback to handle events matching name on the current scope. This model enables loose coupling between controllers or components that do not share a direct binding relationship.

Direction and Stopping Propagation

The choice between $emit and $broadcast depends on the intended audience of the event. Use $emit when a child needs to notify a parent, such as signaling a form completion. Use $broadcast for system-wide notifications, like a session timeout warning. - Stopping propagation: Listeners can call event.stopPropagation() to prevent $emit from traveling further up the hierarchy. - Irreversible broadcast: $broadcast events cannot be stopped; they always reach every child scope. - Event metadata: The listener receives an event object exposing targetScope (where it started) and currentScope (where it is being processed), plus name and defaultPrevented.

DOM Events Versus Scope Events

Standard DOM events follow the DOM tree structure, whereas AngularJS scope events follow the scope tree, which may differ if directives create isolated scopes. - UI interactions: Handle clicks or input changes with ng-click or ng-change, which respond to DOM events. - Logical communication: Use scope events for state changes that require coordination between non-adjacent components. - Digest cycle: Both $emit and $broadcast trigger a digest cycle, so frequent firing can impact performance by increasing watcher evaluation frequency.

Comparison of $emit and $broadcast

Feature $emit $broadcast
Direction Upward (current scope → ancestors → $rootScope) Downward (current scope → all descendants)
Stoppable Yes, via event.stopPropagation() No, always reaches every child scope
Performance Traverses only ancestor scopes Traverses all child scopes, typically more expensive
Typical use Child-to-parent communication System-wide notifications (e.g., session timeout)

Best Practices and Migration

AngularJS reached end of life on 31 December 2021, so these patterns should only be maintained in legacy systems. - Prefer bindings: For direct parent-child relationships, use component bindings (< for inputs, & for outputs) instead of scope events. - Cleanup listeners: Always deregister $on listeners in $onDestroy to prevent memory leaks and orphaned digest cycles. - Limit usage: Avoid using scope events as a replacement for shared services; use a service with a callback or promise for cross-cutting concerns. - Migration: When upgrading to Angular (2+), replace $emit/$broadcast with @Output() event emitters and RxJS Subjects within services.

40

Can you explain the mechanism and typical usage of the $http service for server communication in AngularJS?

The $http service is a core built-in service in AngularJS that simplifies communication with remote HTTP servers using the browser’s XMLHttpRequest object or JSONP. It returns standard promises, allowing developers to handle asynchronous responses with .then() and .catch() methods rather than deprecated callbacks, and it automatically integrates with the digest cycle to update views upon response.

Read full answer

Applies toAngularJS 1.8.x

Core Mechanism

The service wraps the browser’s native XMLHttpRequest object, abstracting away cross-browser inconsistencies. When a request is sent, AngularJS creates a promise object that resolves when the server responds or rejects if the request fails. Crucially, once the response is received, AngularJS automatically triggers a digest cycle so that any data bound to the view updates without manual intervention. This automatic integration prevents common synchronization issues where the model changes but the UI does not reflect them.

Promise-Based API

Since AngularJS 1.6, the legacy .success() and .error() callback methods have been removed. Developers must use the standard promise interface returned by $http. The .then() callback receives a response object containing data, status, headers, and config. Error handling is done via .catch() or the second argument of .then(). This aligns AngularJS with standard JavaScript promise conventions and improves interoperability with other libraries.

Interceptors

Interceptors provide a global way to handle requests and responses across the application. They are registered via $httpProvider.interceptors.push. Common use cases include adding authentication tokens to outgoing headers or redirecting users on 401 errors. Interceptors can transform request data before it is sent or modify response data before it reaches the controller, centralizing cross-cutting concerns like logging or security.

Code Example

angular.module('app').component('userList', {
  controller: ['$http', function($http) {
    var $ctrl = this;
    $ctrl.users = [];

    $ctrl.loadUsers = function() {
      $http.get('/api/users')
        .then(function(response) {
          // Response data is available in response.data
          $ctrl.users = response.data;
        })
        .catch(function(error) {
          // Handle errors centrally or locally
          console.error('Failed to load users', error);
        });
    };
  }]
});

Best Practices and Limitations

  • Minification Safety: Always use array notation for dependency injection (e.g., ['$http', function($http) {...}]) to ensure code survives minification.

  • End of Life: AngularJS long-term support ended on 31 December 2021. It should not be used for new projects; consider migrating to Angular or another modern framework.

  • Watcher Count: Avoid storing large datasets directly in scope-bound variables without pagination, as this increases watcher count and slows digest cycles.

  • Security: Never hardcode secrets in client-side code. Use HTTPS for all API calls and rely on server-side validation for security logic.

41

How does the $http service expose asynchronous results through its promise interface in AngularJS?

The $http service returns a promise object backed by the $q service, which represents the eventual success or failure of an asynchronous HTTP request. Instead of blocking the browser while waiting for the server, it allows you to register callbacks that run once the response arrives or the request fails.

Read full answer

Applies toAngularJS 1.8.x

How the Promise Chain Works

When you call $http(config) or a shortcut like $http.get(url), the returned promise supports standard chaining methods. You attach handlers to react to the outcome without nesting logic deeply.

  • .then(onFulfilled, onRejected): Registers a callback for a successful response (status 2xx) and optionally one for errors. It returns a new promise, allowing chaining.

  • .catch(onRejected): Syntactic sugar for .then(null, onRejected), used to handle errors at the end of a chain.

  • .finally(onSettled): Executes logic regardless of success or failure, useful for hiding loading spinners.

Because these methods return promises, you can transform data in one step and pass the result to the next .then() in the sequence.

The Response Object

When the promise resolves, the success callback receives a single response object rather than raw data. This object contains the full context of the HTTP transaction.

Property Description
data The response body transformed by $http transformers (e.g. JSON parsed).
status The HTTP status code, such as 200 or 404.
headers A function to retrieve response headers by name.
config The configuration object that was used to generate the request.
statusText The HTTP status text returned by the server.

You access the payload via response.data, not the response object itself.

Legacy Methods Removed

Older AngularJS versions offered shortcut methods directly on the promise, but these are no longer available.

  • .success() and .error(): These were deprecated in AngularJS 1.4 and removed in AngularJS 1.6. They passed data, status, headers, and config as separate arguments instead of a single object.

  • Replacement: Use .then() and .catch() exclusively. Mixing legacy and current styles will throw errors in 1.8.x.

Code Example

This component uses minification-safe dependency injection and lifecycle hooks to fetch data when initialized.

angular.module('app')
  .component('userList', {
    template: '<div>{{$ctrl.users.length}} users loaded</div>',
    controller: ['$http', function UserListController($http) {
      var $ctrl = this;
      $ctrl.users = [];

      $ctrl.$onInit = function() {
        $http({
          method: 'GET',
          url: '/api/users'
        })
        .then(function(response) {
          // Access the payload on response.data
          $ctrl.users = response.data;
        })
        .catch(function(error) {
          // Handle network or server errors
          console.error('Request failed', error.status);
        });
      };
    }]
  });

Best Practices and Pitfalls

  • Minification Safety: Always use array notation ['$http', function($http) {...}] or the $inject property so dependency names survive minification.

  • Error Handling: Always attach a .catch() handler; unhandled promise rejections can pollute browser consoles and leave UI state inconsistent.

  • One-way Data Flow: Assign fetched data to component properties (like $ctrl.users) rather than mutating $scope directly.

  • End of Life: AngularJS reached end of life (long-term support ended 31 December 2021). For new projects, choose a modern framework; for existing apps, plan migration to Angular or run both frameworks with ngUpgrade.

  • Watchers: Avoid assigning functions to the template that call $http, as this triggers requests on every digest cycle.

43

Can you explain the distinct roles and usage patterns of the $http and $resource services within AngularJS?

$http is the core low-level service for making HTTP requests, whereas $resource is a higher-level abstraction built on top of $http designed specifically for interacting with RESTful APIs. While $http provides full control over request configuration, $resource simplifies CRUD operations by mapping them to methods on a resource object.

Read full answer

Applies toAngularJS 1.8.x

Core Purpose and Abstraction Level

$http is a fundamental service included in the core ng module that provides a promise-based API around the browser’s XMLHttpRequest. It offers granular control over request configuration, including headers, timeouts, response transforms and interceptors. In contrast, $resource is a factory within the ngResource module that abstracts common RESTful operations (GET, POST, PUT, DELETE) into methods on a resource object, reducing boilerplate code for standard CRUD interactions.

Return Values and Data Handling

When you call $http, it returns a standard promise that resolves with a response object containing data, status, headers and config. You must manually assign this data to your scope or component state. $resource returns an instance of the resource class itself. Class methods such as get, query, save and remove return instances that expose the $promise property and the instance methods $save, $remove and $delete. This allows two-way binding directly to the resource instance in the view before the data arrives, though you should still wait on $promise before acting on the data.

Dependencies and Setup

Because $http is part of the core framework, no additional files or module declarations are required beyond including angular.js. $resource requires including angular-resource.js in your HTML and declaring 'ngResource' as a dependency when defining your module. Without this, injecting $resource will result in an injector error.

When to Use Which

Choose $http when you need fine-grained control over the request, such as custom authentication headers, non-RESTful endpoints, or specific timeout handling. Use $resource when your backend follows strict REST conventions and you want to reduce boilerplate for standard entity management. Avoid $resource for complex query parameters or non-standard actions unless you heavily customize the action definitions.

Code Example

angular.module('app', ['ngResource'])
  .component('userCard', {
    controller: ['$http', '$resource', function($http, $resource) {
      var $ctrl = this;

      // $http usage: returns a promise resolving with the full response
      $http.get('/api/users/1')
        .then(function(response) {
          $ctrl.user = response.data;
        })
        .catch(function(error) {
          $ctrl.error = 'Failed to load user: ' + error.status;
        });

      // $resource usage: class method returns a resource instance
      var User = $resource('/api/users/:id', { id: '@id' });
      $ctrl.resourceUser = User.get({ id: 1 });
    }],
    template: '<p>{{ $ctrl.user.name }}</p>'
  });

Best Practices and Pitfalls

  • AngularJS EOL: AngularJS reached end of life on 31 December 2021. For new projects, consider migrating to Angular or another modern framework; use these services only for maintaining legacy applications.

  • Promise Handling: Always use .then() and .catch() with $http. The .success() and .error() methods were removed in AngularJS 1.6.

  • Digest Cycle: Both services trigger a digest cycle upon resolution. Be mindful of watcher counts; use one-time bindings (::) where possible to improve performance.

  • Minification Safety: Always use array notation for dependency injection (as shown above) to ensure code survives minification.

44

What approach allows you to globally capture and modify HTTP requests and responses in an AngularJS application?

In AngularJS, you intercept HTTP traffic by registering factory services with the $httpProvider.interceptors array during the configuration phase. This mechanism allows global modification of requests and responses before they reach the application code or the server, enabling cross-cutting concerns like authentication and logging.

Read full answer

Applies toAngularJS 1.8.x

Mechanism Overview

Interceptors implement a chain-of-responsibility pattern within the $http service. When a request is made, it passes through each interceptor’s request method in the order they were registered. Responses travel back through the response methods in reverse order. Each method must return a value or a promise; returning a rejected promise short-circuits the chain and triggers the corresponding error handler.

Registration and Dependency Injection

Interceptors are defined as factories that return an object containing interceptor methods. They must be registered in a module configuration block by pushing the factory name onto $httpProvider.interceptors. Dependency injection must be minification-safe, using array notation where strings precede the function arguments. This ensures the service survives build processes that rename variables.

Interceptor Lifecycle Methods

An interceptor object can implement up to four methods: - request: Receives the request configuration object. Used to add headers or modify URLs. Return the config or a promise resolving to it. - requestError: Called if a previous interceptor rejected the request promise. Useful for recovery logic. - response: Receives the response object. Used to transform data or extract tokens. Return the response or a promise resolving to it. - responseError: Called if the HTTP status indicates failure or a previous interceptor rejected. Handle status codes like 401 or 500 here.

Code Example

angular.module('app')
  // Keep the token in memory rather than localStorage (XSS-safe)
  .factory('authService', function() {
    var token;
    return {
      setToken: function(t) { token = t; },
      getToken: function() { return token; }
    };
  })
  .factory('authInterceptor', ['$q', '$location', 'authService',
    function($q, $location, authService) {
      return {
        request: function(config) {
          var token = authService.getToken();
          if (token) {
            config.headers.Authorization = 'Bearer ' + token;
          }
          return config;
        },
        responseError: function(rejection) {
          if (rejection.status === 401) {
            $location.path('/login');
          }
          // Propagate the error so the original caller sees it
          return $q.reject(rejection);
        }
      };
    }])
  .config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('authInterceptor');
  }]);

Best Practices and Pitfalls

  • Avoid Circular Dependencies: Do not call $http directly inside an interceptor unless you exclude the URL from interception (e.g., by checking config.url), otherwise the interceptor will trigger itself infinitely.

  • Error Handling: Always return $q.reject(rejection) in responseError if you cannot handle the error; otherwise the original caller’s success callback may run unexpectedly.

  • Security: Never hardcode secrets in client-side code. Store tokens in memory (e.g., a service) or rely on secure, HttpOnly cookies that the browser sends automatically—note that HttpOnly cookies cannot be read by JavaScript, so they cannot be manually added to headers. Avoid localStorage for sensitive tokens because it is vulnerable to XSS.

  • Maintenance Context: AngularJS reached end of life on 31 December 2021. For new projects, use Angular (2+); for existing apps, plan migration via ngUpgrade or a full rewrite.

45

How do $routeProvider and $location function within an AngularJS application to manage navigation and URL state?

In AngularJS, $routeProvider defines the mapping between URLs and views within the ngRoute module, whereas $location allows controllers and services to inspect and change the browser’s URL programmatically. Together they enable single-page navigation by swapping templates without a full page reload and keep the address bar in sync with application state.

Read full answer

Applies toAngularJS 1.8.x with ngRoute

Configuring Routes with $routeProvider

The $routeProvider is used during the configuration phase to declare which template, controller, and optional dependencies correspond to a URL path. It is only available when the ngRoute module is listed as a dependency. Routes are registered with when(path, route) and a fallback is defined with otherwise(params).

  • Path syntax: Paths use simple string matching with optional parameter segments such as /user/:id; parameters are exposed on $routeParams.

  • Route configuration object: Besides templateUrl and controller, it supports controllerAs, resolve (to preload data before the controller instantiates), redirectTo (a string or function for redirects), and reloadOnSearch (to avoid re-instantiating when only query parameters change).

  • View rendering: The matched template is compiled and injected into the element marked with ng-view; a new controller instance is created for each activation unless reloadOnSearch is false.

  • Unmatched routes: otherwise({ redirectTo: '/home' }) catches any URL that does not match a when clause.

Managing URL State with $location

The $location service is injected into controllers, services, or directives to read or modify the current URL. It abstracts the browser’s address bar so that changes to path, query string, or fragment happen without a full page refresh, and the browser UI updates during the next digest cycle.

  • path(): Gets or sets the path portion (e.g., /dashboard).

  • search(): Gets or sets query parameters as an object or string; setting it replaces the entire query string.

  • hash(): Reads or writes the fragment identifier, useful for deep linking in hashbang mode.

  • url(): Gets or sets the full URL string in one call.

  • replace(): Replaces the current history entry instead of creating a new one, preventing duplicate back-button entries.

  • Modes: By default AngularJS uses hashbang URLs (#!/path). Calling $locationProvider.html5Mode(true) switches to HTML5 mode, which requires the server to serve index.html for every application route.

Integration and Lifecycle

Navigation is bidirectional. When the user changes the URL, $location detects the event and $route looks up the matching configuration from $routeProvider. If a match is found, $route resolves any dependencies, instantiates the controller, updates the scope, and ng-view renders the template. The $rootScope broadcasts $locationChangeStart (cancelable) and $locationChangeSuccess, while $route broadcasts $routeChangeStart, $routeChangeSuccess, and $viewContentLoaded. Conversely, calling $location.path('/user/42') inside a controller updates the address bar and triggers the same sequence. Because all of this happens inside AngularJS’s digest cycle, any model changes made during route activation are automatically reflected in the view.

Best Practices and Considerations

AngularJS reached end of life on 31 December 2021; new projects should use Angular or another modern framework, and legacy applications should plan a migration (rewrite or ngUpgrade for hybrid operation).

  • Minification-safe injection: Always use array notation ['$location', function($location){...}] or $inject to preserve dependency names after minification.

  • Keep config blocks simple: Services such as $http are not available in config; use resolve to load data before a route activates.

  • Reduce watchers: Prefer one-time bindings :: and track by in ng-repeat inside route templates to keep the digest cycle fast.

  • HTML5 mode: Use it for cleaner URLs, but ensure the server is configured to return index.html for all client-side routes; otherwise deep links 404.

  • Avoid .success()/.error(): Those promise methods were removed in AngularJS 1.6; use .then()/.catch() instead.

46

How does AngularJS let a single-page application show several different views, and how do you configure the router that maps URLs to those views?

Multiple views and routing in AngularJS mean that one HTML page hosts several templates and a router swaps the right template into the page as the URL changes, without a full browser reload. The official ngRoute module provides a basic router, while the widely used UI-Router module (@uirouter/angularjs) adds nested states and multiple named views in a single state change.

Read full answer

Applies toAngularJS 1.8.x, with `ngRoute` 1.8.x and UI-Router for AngularJS 1.0.x.

The Problem Routing Solves

A browser normally requests a new document for every URL. AngularJS is a single-page framework: the browser loads one HTML shell once, and JavaScript replaces parts of the DOM as the user navigates. The router is the component that decides which template and controller correspond to the current URL, so the back button, bookmarks and deep links keep working.

Built-in Routing with ngRoute

ngRoute provides the ngView directive plus the $routeProvider and $route services. You register one rule per URL with $routeProvider.when(path, options) and a fallback with .otherwise(). Each route can define a template or templateUrl, a controller with controllerAs, a resolve map for preloading data, and reloadOnUrl. Only one view per route is rendered, into the single <div ng-view></div> in the shell page.

UI-Router removes that restriction. Instead of URL-to-template pairs you define a hierarchy of states, and each state can fill several named <div ui-view="name"></div> placeholders at once. States can also be nested, so a child state re-renders only the views it declares while the parent’s views stay in place.

Key Concepts

  • State versus URL: a state has a name such as app.users.detail and an optional url. Navigation targets the state, and the URL is derived from it, which is why ui-sref="app.users.detail({id: 42})" works without you spelling out the path.

  • Named views: a state’s views object maps view names to template/controller definitions. The name header@app means “the header view inside the state named app”, an absolute target that keeps unrelated regions stable.

  • Resolve: dependencies declared in resolve are injected into the controller after the promises resolve, so the view renders with data already available and the router waits instead of showing an empty template.

  • URL synchronization: $locationProvider.html5Mode(true) removes the # from URLs but requires a <base href="/"> tag and server-side rewrites to the index page, otherwise a refresh of /users/42 returns a 404.

  • Programmatic navigation: $state.go(state, params) and the ui-sref attribute change state; $stateParams reads route parameters and $state.includes() checks active states for navigation highlighting.

Comparing the Two Routers

Feature ngRoute UI-Router for AngularJS
Views per route One, in ngView Multiple named ui-view regions
Nesting Flat route list Nested states with inheritance
Navigation by name No, URL strings Yes, ui-sref and $state.go
Data preloading resolve per route resolve per state
Best for Simple two- or three-screen apps Dashboards and master-detail layouts

Code Example

Shell page with named view placeholders:

<!DOCTYPE html>
<html ng-app="app">
<head>
  <title>Multiple views</title>
  <base href="/">                        <!-- required by html5Mode -->
  <script src="angular.min.js"></script>
  <script src="angular-ui-router.min.js"></script>
  <script src="app.js"></script>
</head>
<body ng-controller="ShellController as shell">
  <nav>
    <a ui-sref="users">Users</a>          <!-- link by state name -->
    <a ui-sref="users.detail({ id: 7 })">User 7</a>
  </nav>
  <div ui-view="header"></div>            <!-- named view regions -->
  <div ui-view="content"></div>
</body>
</html>

State configuration with multiple views and resolves:

angular.module('app', ['ui.router'])      // depends on ui.router, not ngRoute
  .config(['$stateProvider', '$urlRouterProvider', '$locationProvider',
    function ($stateProvider, $urlRouterProvider, $locationProvider) {
      $urlRouterProvider.otherwise('/users');   // unknown URLs land here

      $stateProvider
        .state('users', {
          url: '/users',
          resolve: {                       // resolved before the state activates
            users: ['userService', function (userService) {
              return userService.list();   // returns a promise
            }]
          },
          views: {
            'header': { templateUrl: 'users-header.html' },
            'content': {
              templateUrl: 'users.html',
              controller: 'UsersController',
              controllerAs: '$ctrl'
            }
          }
        })
        .state('users.detail', {           // child state, url is appended
          url: '/{id:int}',                // typed parameter
          resolve: {
            user: ['userService', '$stateParams',
              function (userService, $stateParams) {
                return userService.get($stateParams.id);
              }]
          },
          views: {
            'content@': {                  // absolute target: content in the root state
              templateUrl: 'user-detail.html',
              controller: 'UserDetailController',
              controllerAs: '$ctrl'
            }
          }
        });

      $locationProvider.html5Mode(true);  // needs <base href="/"> and server rewrites
    }]);

angular.module('app')
  .controller('UsersController', ['users',
    function (users) {
      this.users = users;                  // injected from the state's resolve
    }])
  .controller('UserDetailController', ['user', '$stateParams',
    function (user, $stateParams) {
      this.user = user;                    // injected from the state's resolve
      this.id = $stateParams.id;           // from url '/{id:int}'
    }]);

Best Practices and Pitfalls

  • Prefer UI-Router whenever the layout has more than one simultaneously updating region; ngRoute cannot fill two views in one transition.

  • Keep navigation declarative with ui-sref so refactoring a URL does not require touching every link.

  • Use resolve for data the view needs immediately, but keep it light: a rejected promise blocks the state change and can leave the user on the previous screen.

  • Set html5Mode only when the server is configured to serve the index page for all application URLs; otherwise leave hash-based URLs.

  • Bind parameters in templates with :: one-time syntax inside routed views to limit watchers during digest cycles.

  • Note that AngularJS reached end of life on 31 December 2021: it no longer receives security patches, so new work should go to Angular or another supported framework, with existing apps migrated through a rewrite or by running both frameworks with ngUpgrade.

47

How does the ng-href directive differ from using the standard href attribute when binding dynamic URLs in AngularJS?

The ng-href directive ensures that dynamic URLs are evaluated by AngularJS before the browser attempts to use them, preventing broken links during initial page load. In contrast, the standard href attribute is processed by the browser immediately upon parsing the HTML, often before AngularJS has bootstrapped and interpolated expressions. This distinction avoids navigation errors and console warnings when binding scope variables to anchor tags.

Read full answer

Applies toAngularJS 1.8.x

Why Standard href Fails with Interpolation

When the browser encounters <a href="{{ user.url }}">Link</a>, it parses the attribute before AngularJS compiles the DOM. Depending on the browser, this may result in: - A literal request to a URL containing curly braces (e.g., /{{ user.url }}). - A navigation attempt to an empty or undefined path. - Console errors indicating invalid resource loading. This happens because the browser’s native HTML parser does not understand AngularJS expression syntax.

How ng-href Works

The ng-href directive replaces the href attribute behavior within AngularJS-managed DOM elements. It operates during the compilation and linking phases: 1. AngularJS intercepts the ng-href attribute. 2. It observes the interpolated expression provided (e.g., ng-href="{{ user.url }}"). 3. The actual href property is only set once the expression evaluates to a defined, non-empty value. This ensures the link remains inert until the data is ready, aligning with the digest cycle.

Comparison of href and ng-href

Aspect Standard href ng-href
Evaluation timing Browser parses immediately AngularJS evaluates during the digest cycle
Interpolation support Not supported; raw {{ }} is sent to the browser Fully supports AngularJS expressions
Initial behavior May cause a broken link or navigation error Link stays inactive until the expression resolves
Recommended use Static URLs only Dynamic URLs bound to scope data

Best Practices and Considerations

  • Always use ng-href for dynamic bindings: If the URL depends on scope data, use ng-href.

  • Static links are fine with href: For fixed URLs like <a href="/about">, standard href is preferred and simpler.

  • Other attribute directives: Similar logic applies to ng-src for images and ng-srcset, which prevent broken image requests before interpolation.

  • End of Life Notice: AngularJS reached end of life on 31 December 2021. For new projects, consider modern frameworks like Angular, React, or Vue; use ng-href only for maintaining legacy applications.

Performance and Digest Cycle

Using ng-href registers an interpolation observer that participates in the digest cycle. While necessary for correctness, excessive observers can slow down the digest cycle. - Minimize observers: Use one-time binding (::) if the URL does not change after initial load: <a ng-href="{{ ::staticUrl }}">. - Track by: When using ng-repeat with links, combine track by to reduce DOM manipulation overhead. - Avoid complex expressions: Keep expressions inside ng-href simple to reduce evaluation time during each digest cycle.

48

Explain the mechanism AngularJS provides for validating user input in forms.

AngularJS validates forms declaratively in the HTML template: you add validation attributes to input, select and textarea elements, and the framework exposes the resulting state through the form controller and CSS classes on each control. Form validation in AngularJS is built on the ngModel controller, which holds the validity flags that templates and controllers read.

Read full answer

Applies toAngularJS 1.8.x (AngularJS reached end of life on 31 December 2021; use it only for maintaining existing apps, not new projects)

How It Works

  • ngModel: every form control binds with ng-model, which registers the control with the parent form (or ng-form) and creates an NgModelController.

  • Validation attributes: AngularJS ships directives that mirror HTML5 constraint attributes and adds its own: required, ng-required, min/max, ng-minlength/ng-maxlength, ng-pattern, type="email", type="url", type="number". Each sets a key on the model controller’s $error object.

  • State properties: each control exposes $pristine, $dirty, $touched, $untouched, $valid, $invalid, and the form exposes the same flags plus $submitted (set by ng-submit).

  • CSS classes: AngularJS automatically adds ng-pristine, ng-dirty, ng-touched, ng-untouched, ng-valid, ng-invalid and per-error classes such as ng-invalid-required, so styles can react to state without any code.

  • The digest cycle: validators run inside the digest loop whenever the bound value changes. Custom validators that respond to asynchronous events must call $setValidity inside $scope.$apply (or use $timeout) so the digest runs.

Built-in Validators

Attribute / input type $error key Purpose
required, ng-required required Value must be non-empty
ng-minlength minlength Minimum number of characters
ng-maxlength maxlength Maximum number of characters
ng-pattern pattern Value must match a regex
min, max (with type="number") min, max Numeric bounds
type="email" email RFC-like email format check
type="url" url URL format check

Displaying Errors

Read state in the template from the form’s name; do not inspect raw DOM values. A common pattern is to show an error only when the control is both invalid and touched or the form has been submitted, so the user is not scolded before typing. Use ng-messages (from the angular-messages module) to render the first failing error instead of repeating ng-if blocks for every key.

Custom Validators

For synchronous checks, write a directive that adds a validator to the ngModel controller through $validators (an object of functions returning a boolean). For checks that need a server round-trip, use $asyncValidators and return a promise; the control is marked $pending until it resolves. Register the module dependency (angular-messages for ng-messages) with angular.module('app', ['ngMessages']).

Best Practices and Common Pitfalls

  • Add novalidate to the form so the browser’s native validation does not fight AngularJS; otherwise you get two sets of error messages and styling.

  • Name every form and control; without name, there is no controller to read and validation cannot be checked in the template.

  • Bind with ng-model-options such as { updateOn: 'blur' } when you want validation to fire only after the user leaves the field, which reduces churn and watcher churn during typing.

  • Do not rely on ng-disabled alone to prevent submission; check the validity argument in the submit handler as well.

  • Keep watchers low: prefer ng-messages (one block per field) over many ng-if siblings, and use one-time bindings such as {{ ::label }} for static labels inside large forms.

  • Avoid $scope.$apply inside validators that already run in a digest; use it only for events originating outside AngularJS, and prefer $timeout when unsure.

  • Server-side validation is still mandatory: client-side validation in AngularJS is a usability layer and can be bypassed, so always re-validate on the backend.

49

Can you explain the purpose and behavior of the ng-submit directive in AngularJS?

ng-submit is a built-in AngularJS directive that binds an expression to the submit event of a <form> element, so a controller method runs when the user submits the form. It automatically prevents the browser’s default full-page form submission, keeping the interaction inside the single-page application, and it only evaluates the expression when the form passes AngularJS validation.

Read full answer

Applies toAngularJS 1.8.x

How It Works

  • You place ng-submit as an attribute on a <form> tag, passing an expression such as ng-submit="$ctrl.save()".

  • When the user triggers submission by pressing Enter in a text field or clicking a button of type="submit", AngularJS intercepts the native submit event.

  • Internally it calls event.preventDefault(), so the page does not reload or navigate away.

  • The expression is evaluated against the current scope, or against the component controller when using the controllerAs syntax (the recommended style since AngularJS 1.5).

  • Because AngularJS 1.8.x is the final release and long-term support ended on 31 December 2021, ng-submit should only be used when maintaining an existing AngularJS application, not for new projects.

Validation Behavior

  • ng-submit respects the form controller’s validity state: the expression is evaluated only when the form is $valid.

  • If any control in the form is invalid, the submit event is prevented and the bound expression never runs, which protects your handler from receiving bad data.

  • This works together with ngModel validation directives such as required, ng-minlength, and custom validators.

  • Note that programmatic submission (calling a method directly from code) bypasses this check, so validate in the handler as well if the method can be invoked another way.

ng-submit vs ng-click on the Submit Button

Aspect ng-submit on <form> ng-click on submit <button>
Triggered by Enter key in a text field Yes No
Triggered by any submit button in the form Yes Only that specific button
Respects form $valid state Yes No, runs regardless of validity
Prevents page reload automatically Yes Only if you call event.preventDefault()
Recommended usage Primary choice for forms Only for non-submit actions

Best Practices

  • Call a method, not inline logic: write ng-submit="$ctrl.onSubmit()" and put the work inside the controller method rather than building a long expression in the template.

  • Use component style: define the form inside an angular.module(...).component() with controllerAs: '$ctrl', one-way bindings ('<') for inputs, and lifecycle hooks such as $onInit.

  • Keep DI minification-safe: use array notation like ['$http', function ($http) {...}] or the $inject property, since ng-submit expressions often call services through the controller.

  • Use promises: $http returns promises in AngularJS 1.8.x, so handle results with .then() and .catch(); the old .success() and .error() methods were removed in 1.6.

  • Avoid double handling: do not attach both ng-submit on the form and ng-click on the submit button for the same action, or the logic may run twice or behave inconsistently.

Common Pitfalls

  • Button type matters: a <button> without an explicit type defaults to submit in most browsers, which can accidentally trigger ng-submit; set type="button" for actions that should not submit.

  • Invalid forms appear unresponsive: if the expression never fires and there is no visual feedback, the form is likely invalid; bind error messages to the form controller’s $error object so users see what needs fixing.

  • Nested forms are not supported: AngularJS does not allow nested <form> elements, so keep each ng-submit on its own top-level form.

  • Dynamic form controls: controls added with ngIf or ngRepeat may not register with the form controller immediately; reference them carefully if your handler reads individual control state.

50

What mechanisms does AngularJS provide for animating DOM elements during state transitions?

AngularJS enables DOM animations primarily through the ngAnimate module, which hooks into the digest cycle to trigger CSS classes or JavaScript callbacks when built-in directives such as ng-show, ng-repeat, or ng-view modify the DOM.

Read full answer

Applies toAngularJS 1.8.x

The ngAnimate Module

To use animations you include the angular-animate.js script and declare ngAnimate as a dependency of your application module. The module intercepts structural and class changes made by built-in directives and coordinates the animation lifecycle without blocking the digest cycle. Without the module loaded, AngularJS applies DOM changes immediately, with no animation hooks at all.

CSS-Based Animations

The most common approach uses CSS transitions or keyframes. When an element enters or leaves the DOM, ngAnimate adds specific classes; you define the starting state in the base class and the active state in the -active class, which is added on the next frame so the browser runs the transition.

  • ng-enter: applied immediately when an element is inserted.

  • ng-enter-active: added one frame later to trigger the transition.

  • ng-leave: applied when removal begins.

  • ng-leave-active: added to start the exit transition.

  • ng-move / ng-move-active: applied when an existing element is moved, for example when ng-repeat reorders items.

For class-based animations triggered by ng-class, ng-show or ng-hide, the generated classes are derived from the class being toggled: [class]-add and [class]-add-active when the class is added, [class]-remove and [class]-remove-active when it is removed — for example, ng-hide-add and ng-hide-remove for ng-show/ng-hide. The browser handles all rendering, which is generally more performant than JavaScript manipulation.

JavaScript-Based Animations

For logic that CSS cannot express, you register animations with the module’s .animation() factory, keyed by a CSS class name. The factory returns an object with lifecycle methods such as enter, leave, move, addClass and removeClass. In AngularJS 1.8 these methods should return a promise that resolves when the animation finishes; the older done callback style still works but the promise form is the recommended, current API. Because the methods receive the element directly, you can integrate libraries such as jQuery or the Web Animations API here.

The $animate Service

Behind the directives sits the $animate service, which you can inject and call yourself to animate elements from a controller or a custom directive. Its main methods include enter, leave, move, addClass, removeClass, setClass and animate; each returns a promise and, like the CSS hooks, drives the same .ng-enter/.ng-leave class lifecycle. This is the mechanism to reach for when an animation is not tied to one of the built-in directives — for example, animating an element you insert manually after an $http call. $animate.enabled(false) globally disables animations, which is useful in tests or on low-power devices.

Supported Directives

Animations are triggered automatically by directives that change DOM structure or classes:

Directive Animation events
ng-repeat enter, leave, move as items are added, removed or reordered
ng-show / ng-hide addClass/removeClass for the ng-hide class
ng-if, ng-switch, ng-include enter/leave when the element is inserted or removed
ng-view (with ngRoute) enter/leave between route templates
ng-class class-based add/remove animations

Best Practices and Considerations

  • Prefer CSS over JavaScript: transitions and keyframes run off the main thread where possible and keep the digest cycle free.

  • Animate cheap properties: use transform and opacity rather than layout-affecting properties such as width, height or top, which force reflow and drop frames.

  • Keep it short: durations under 300ms avoid perceived sluggishness.

  • Mind the digest cycle: animations are triggered by digests, so changes made inside async callbacks may need $scope.$apply or, better, $timeout to run in a digest.

  • Remember the lifecycle: AngularJS reached end of life on 31 December 2021 (long-term support ended), so for new projects use a modern framework; for existing apps, keep ngAnimate usage simple and consider $animate.enabled(false) where performance matters.

51

Which built-in module provides animation support in AngularJS?

The module that enables animations in AngularJS is ngAnimate. It is a separate, optional module that you add as a dependency of your application module; on its own it does not animate anything, but it hooks into the framework’s built-in directives so that CSS classes are added and removed at the right moments, and it exposes the $animate service for driving animations imperatively from JavaScript.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release). Note that AngularJS reached end of life on 31 December 2021, so it no longer receives security patches or browser-compatibility fixes; for new projects, a modern framework is recommended, and existing apps can migrate to Angular (a rewrite, or running both frameworks together with ngUpgrade).

What ngAnimate Actually Does

  • It is an opt-in module: it ships in the separate angular-animate.js file, so you include that script after angular.js and then declare the dependency: angular.module('myApp', ['ngAnimate']).

  • It extends existing directives: once enabled, directives such as ngRepeat, ngInclude, ngIf, ngSwitch, ngClass, ngShow, ngHide and ngView automatically trigger animation hooks. You do not need to change the markup to get the animation hooks; you only add CSS (or a JavaScript animation definition).

  • It works through the $animate service: the service is the underlying engine, and CSS animations/transitions are detected by inspecting the element’s computed styles, so a plain CSS transition or @keyframes rule is enough for most cases.

Two Ways to Animate

Approach How it works When to use it
CSS-based animations You write transition or @keyframes rules against the classes ngAnimate adds, such as .ng-enter, .ng-enter-active, .ng-leave, .ng-leave-active, .ng-move, plus .ng-hide-add/.ng-hide-remove for ngShow/ngHide. The default and usually best choice: declarative, offloaded to the browser, and keeps animation logic out of your controllers.
JavaScript-based animations You register an animation with myModule.animation('.my-class', ['$animate', function ($animate) { ... }]) (or $animateProvider.registerAnimation()), returning handlers for enter, leave, move, addClass, removeClass that perform DOM work and signal completion. When you need orchestration across elements, physics, or a third-party animation library that must be driven from JavaScript.

Key Concepts to Mention in an Interview

  • Class-based hooks: for ngRepeat, elements get .ng-enter when they appear, .ng-leave when removed, and .ng-move when reordering; the ...-active class is added on the next frame to trigger the transition.

  • ngClass and ngShow/ngHide hooks: adding or removing a class, or toggling ng-hide, produces addClass/removeClass animation hooks, so toggling visibility can be animated purely in CSS.

  • Promise-based API: $animate methods such as $animate.enter(element, parent, after) return a promise that resolves when the animation completes, which is useful for sequencing.

  • Disabling animations: $animate.enabled(false) turns them off globally (handy for tests or performance-sensitive screens), and ngAnimate can be disabled per-element with the ng-animate attribute set to a falsy value.

  • Performance caveat: animations run inside the digest cycle for JavaScript-based animations and can add watchers and layout work; prefer CSS transforms and opacity over properties that trigger layout, and avoid animating large ngRepeat lists simultaneously.

Best Practices and Common Pitfalls

  • Prefer CSS animations: they are hardware-accelerated and keep controllers free of presentation logic; reach for JavaScript animations only when CSS cannot express the effect.

  • Include the file before the dependency: forgetting to load angular-animate.js while listing ngAnimate as a dependency is a common cause of the $injector:modulerr error.

  • Do not animate everything: enabling ngAnimate makes previously plain class toggles animateable, which can surprise you if broad CSS rules match .ng-hide-add or .ng-enter unintentionally.

  • Test with animations disabled: in unit tests, call $animate.enabled(false) (or let ngMock handle it) so animations do not make assertions timing-dependent.

52

Is it possible to combine CSS3 transitions with the animation support in AngularJS?

Yes — AngularJS animations are built directly on top of CSS3 transitions (and CSS keyframe animations). The ngAnimate module adds special hook classes to elements when supported directives change them, and your CSS uses ordinary transition properties to animate those classes.

Read full answer

Applies toAngularJS 1.8.x with `ngAnimate` 1.8.x (AngularJS 1.x reached end of life on 31 December 2021, so this applies to maintaining existing applications rather than starting new ones).

How It Works

  1. Include angular-animate.js (via npm or a CDN) after angular.js.

  2. Add ngAnimate as a dependency of your module: angular.module('app', ['ngAnimate']).

  3. Supported directives such as ngRepeat, ngIf, ngShow, ngHide, ngClass, ngView, ngInclude and ngSwitch add animation hook classes automatically.

  4. You write pure CSS against those classes; AngularJS detects the running transition and waits for it to finish before removing the element.

Key Animation Hook Classes

  • Entering elements (ngRepeat new items, ngIf becoming true, route changes): .ng-enter, then .ng-enter-active.

  • Leaving elements: .ng-leave, then .ng-leave-active.

  • Moving/reordering in ngRepeat: .ng-move, then .ng-move-active.

  • Showing/hiding: .ng-hide-add, .ng-hide-remove (with -active variants) for ngShow/ngHide.

The pattern is always the same: define the start state on .ng-enter, the end state on .ng-enter-active, and put the transition declaration on the base class so it applies in both directions.

Advantages and Limitations

  • Declarative and simple: no JavaScript is needed for common list, tab and route animations.

  • Hardware accelerated: transitions on transform and opacity run on the GPU.

  • Fallback behaviour: without ngAnimate, the classes are simply never added and the application works normally.

  • Limitations: transitions on properties such as height, width or margin are not GPU-accelerated and can cause layout thrashing with large lists; use transform where possible.

Best Practices and Common Pitfalls

  • Keep the number of animated elements low: animating hundreds of ngRepeat rows at once degrades performance. Use track by to reduce re-rendering, and consider limitTo for paging.

  • Do not animate display: ngHide toggles the .ng-hide class, so transition opacity, transform or max-height instead of trying to transition display: none.

  • Always give the base class a transition duration: a zero-duration transition means no animation and AngularJS resolves the hook immediately.

  • Use $animate for programmatic control: inject $animate and call methods such as $animate.addClass(element, 'highlight') when you need to trigger animations from a controller or directive.

  • Prefer CSS over the .animation() API: register JavaScript-based animations with module.animation(...) only when CSS cannot express the effect.

Code Example

<!DOCTYPE html>
<html lang="en" ng-app="demoApp">
<head>
  <meta charset="UTF-8">
  <title>AngularJS CSS3 transition demo</title>
  <style>
    /* Base list styling plus the transition declaration */
    .item {
      transition: all 0.4s ease;
      opacity: 1;
      transform: translateX(0);
      padding: 8px;
      border-bottom: 1px solid #ddd;
    }

    /* Start state applied by ngAnimate when an item enters */
    .item.ng-enter {
      opacity: 0;
      transform: translateX(-20px);
    }

    /* End state; the transition defined above animates to it */
    .item.ng-enter-active {
      opacity: 1;
      transform: translateX(0);
    }

    /* Leaving items fade and slide out */
    .item.ng-leave {
      opacity: 1;
      transform: translateX(0);
    }

    .item.ng-leave-active {
      opacity: 0;
      transform: translateX(20px);
    }
  </style>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular-animate.min.js"></script>
</head>
<body ng-controller="DemoController as $ctrl">
  <button ng-click="$ctrl.addItem()">Add item</button>
  <ul>
    <li class="item" ng-repeat="item in $ctrl.items track by item.id">{{ item.text }}</li>
  </ul>

  <script>
    angular.module('demoApp', ['ngAnimate']) // ngAnimate enables the hook classes
      .controller('DemoController', function () {
        var vm = this;
        vm.items = [{ id: 1, text: 'First' }];

        vm.addItem = function () {
          var nextId = vm.items.length
            ? Math.max.apply(null, vm.items.map(function (i) { return i.id; })) + 1
            : 1;
          vm.items.push({ id: nextId, text: 'Item ' + nextId });
        };
      });
  </script>
</body>
</html>
53

How would you describe the practice of unit testing applications built with AngularJS?

Unit testing in AngularJS means verifying the behavior of the framework’s smallest building blocks — components, controllers, services, filters and directives — in complete isolation, replacing their real dependencies with controlled fakes so that each test proves one piece of logic rather than the whole page. Because AngularJS runs in the browser and resolves dependencies through its injector, its tests are typically executed in a real browser by the Karma test runner, with Jasmine providing the assertion style and the angular-mocks (ngMock) library wiring the framework into the test harness.

Read full answer

Applies toAngularJS 1.8.x, using Karma, Jasmine and the official `angular-mocks` (ngMock) module.

What Unit Testing Covers in an AngularJS Application

The units worth testing are the ones that hold logic:

  • Components and controllers: the controllerAs view model (usually $ctrl), the data and methods it exposes, and the lifecycle hooks $onInit, $onChanges, $postLink and $onDestroy.

  • Services, factories and providers: singletons that contain most application rules, especially HTTP orchestration and data transformation.

  • Filters: pure formatting functions, which are the easiest units to test.

  • Directives: only when a component cannot do the job; directive tests usually need $compile and a linked scope.

  • Templates and markup: generally left to integration or end-to-end tests, not unit tests.

The Tools and Their Roles

  • Karma: the test runner that launches real browsers, loads the application and spec files, executes the tests and reports results. It is the standard runner shipped with the AngularJS generator tooling.

  • Jasmine: the default behavior-driven assertion library, providing describe, it, expect, beforeEach and matchers such as toBe and toEqual.

  • ngMock (angular-mocks.js): the bridge between Jasmine and AngularJS. It exposes the global helpers module() and inject(), plus the HTTP fake $httpBackend.

  • module(): registers an AngularJS module (and optionally applies configuration blocks) so the test’s injector knows about its providers, components and services.

  • inject(): resolves services from the injector created for the test, so a spec can obtain $httpBackend, $controller, $rootScope, $compile or any application service.

  • $httpBackend: intercepts calls made through $http so tests never hit the network. Expected routes are declared with methods such as expectGET or whenGET, then satisfied with flush(), after which pending promises resolve.

How a Test Exercises AngularJS Mechanics

A typical spec loads the module that declares the unit under test, then injects the collaborators it needs. Controllers are instantiated through $controller with explicit locals, and components can be built with $compile so their bindings, template and isolated scope participate in the test. Because AngularJS change detection is driven by the digest cycle, asynchronous results from $http, $timeout or $q only settle when the digest runs; tests therefore call $httpBackend.flush() or trigger a digest on $rootScope before asserting final state. This mirrors production behavior while keeping the test deterministic and synchronous from the spec’s point of view.

Best Practices and Common Pitfalls

  • Test behavior, not implementation: assert what the unit outputs or delegates, not how many internal watchers it registers.

  • Fake every external dependency: use $httpBackend for HTTP, Jasmine spies for callbacks, and simple objects for bindings instead of wiring real modules.

  • Verify HTTP expectations: flush scheduled requests and ensure no unexpected request or outstanding expectation remains, so silent API calls are caught.

  • Prefer the component style: components with one-way '<' inputs and '&' outputs are easier to test than directives with inherited scopes, and $onChanges can be exercised by invoking the hook with a changes object that mimics the framework’s shape.

  • Keep units small and digest-friendly: units with few watchers and logic moved into services are simpler to reason about; performance-sensitive bindings such as one-time bindings and track by also reduce the cost of each digest.

  • Write minification-safe code: array-notation or $inject dependency annotation keeps production builds working and makes dependencies explicit in tests.

When to Choose Unit Testing and What to Remember About AngularJS Itself

Unit tests are the fastest feedback loop for AngularJS logic and are the first safety net during refactoring, complemented by integration tests for compiled templates and end-to-end tests for user flows. It is important to remember that AngularJS reached end of life when its long-term support ended on 31 December 2021: it receives no further releases, so new work should generally not start on it, and existing applications should plan to either rewrite in modern Angular or run AngularJS alongside Angular through ngUpgrade. Until migration is complete, a strong unit-test suite is exactly what makes such refactoring safe.

54

Which testing framework is most commonly associated with AngularJS development?

The standard testing framework for AngularJS applications is Jasmine, typically executed through the Karma test runner. This combination was officially endorsed by the Angular team and integrates tightly with AngularJS dependency injection and mocking utilities. While AngularJS reached end of life in December 2021, this stack remains the standard for maintaining legacy applications.

Read full answer

Applies toAngularJS 1.8.x

Core Testing Stack

Jasmine provides the syntax for defining test suites (describe) and specifications (it), along with matchers like toEqual and toBeDefined. Karma serves as the test runner, launching browsers to execute the tests and reporting results back to the command line. Together they form the default configuration generated by tools like the AngularJS seed project and Yeoman generators.

Unit Testing with angular-mocks

To test AngularJS components, services, or controllers, the angular-mocks library is essential. It provides angular.mock.module to load modules and angular.mock.inject to resolve dependencies from the injector, allowing testers to isolate units by substituting real implementations with stubs or spies. The injector uses an underscore naming convention: injecting MyService strips the underscores and provides the MyService factory, which avoids variable name collisions. For HTTP testing, $httpBackend intercepts requests so you can declare expected calls with expectGET or expectPOST, define their responses with respond, and force resolution with flush.

End-to-End Testing

Historically, Protractor was the recommended end-to-end (E2E) test framework for AngularJS applications. It was built on top of Selenium WebDriver and understood AngularJS-specific concepts like waiting for the digest cycle to settle. However, Protractor is now deprecated and in maintenance mode. Modern teams maintaining AngularJS apps often migrate E2E testing to WebDriverIO or Cypress, though these require manual handling of AngularJS stability signals.

Best Practices and Limitations

  • Dependency Injection: Ensure controllers and services use array notation for DI so tests work after minification.

  • Mocking HTTP: Always use $httpBackend to intercept requests; never let tests hit real servers.

  • Digest Cycle: When testing outside AngularJS context (e.g., DOM events), call $scope.$digest() or $scope.$apply() to trigger change detection.

  • Legacy Status: Since AngularJS is end-of-life, focus tests on critical paths rather than expanding coverage while planning migration to Angular or another framework.

Common Pitfalls

  • Asynchronous Tests: Failing to call $httpBackend.flush() leaves pending requests, causing timeouts.

  • Global State: Not resetting modules between tests can lead to leaked state; rely on beforeEach blocks.

  • Watcher Leaks: Creating scopes in tests without destroying them can pollute the global watcher count, affecting performance metrics.

55

How do you use the $httpBackend service when writing tests?

$httpBackend is the mock HTTP backend shipped with the angular-mocks module that intercepts $http and $resource calls in unit tests, letting you declare fake responses instead of contacting a real server. You inject it, set up expectations or backend definitions, run the code under test, call flush() to process the pending request, and then assert on the resolved data.

Read full answer

Applies toAngularJS 1.8.x (with angular-mocks)

What $httpBackend Is

  • $httpBackend is a fake implementation of the browser’s XMLHttpRequest backend, distributed in the angular-mocks.js file.

  • Load it in your test runner after angular.js, then load the ngMock module with beforeEach(module('ngMock')) (or include it in your app’s test module dependencies).

  • It works with both $http and $resource, so services using either can be tested the same way.

expect() vs when()

Method Behaviour Use when
expect(method, url, data, headers) Declares a required request; the test fails if it is never made You want to verify a specific call happens
when(method, url, data, headers) Declares a backend definition; matching requests are answered but not required Stubbing optional, repeated, or out-of-scope calls
  • Both return a request handler on which you call .respond(status, data, headers) to define the fake response.

  • expect() enforces order: requests must arrive in the order the expectations were declared.

  • when() has no ordering requirement and can satisfy multiple matching requests.

  • The url argument can be a string, a regular expression, or a matching function for flexible URL matching.

Typical Test Flow

  1. Inject $httpBackend and the service or controller under test; use underscore-prefixed parameter names so the injected values do not shadow the outer variables the it() blocks read.

  2. Set up expect(...).respond(...) or when(...).respond(...).

  3. Execute the code that triggers the HTTP call (it returns a promise).

  4. Attach a .then() handler to capture the resolved value.

  5. Call $httpBackend.flush() to force the pending request through the digest cycle.

  6. Assert on the captured data.

  7. In afterEach, call verifyNoOutstandingExpectation() and verifyNoOutstandingRequest() so unhandled requests fail the test.

Code Example

describe('UserService', function () {
  var UserService, $httpBackend, resolved;

  beforeEach(module('myApp'));

  // Inject dependencies; underscore-prefixed names avoid shadowing
  // the outer variables that the it() blocks read.
  beforeEach(inject(function (_UserService_, _$httpBackend_) {
    UserService = _UserService_;
    $httpBackend = _$httpBackend_;
  }));

  it('fetches a user and resolves the promise with the data', function () {
    var expected = { id: 1, name: 'Alice' };

    // Declare the expected GET request and its mock response
    $httpBackend.expect('GET', '/api/users/1').respond(200, expected);

    // Code under test triggers the request
    UserService.getUser(1).then(function (data) {
      resolved = data;
    });

    // Before flush the request is still pending
    expect(resolved).toBeUndefined();

    // Process the pending request and run the digest
    $httpBackend.flush();

    expect(resolved.name).toBe('Alice');
  });

  afterEach(function () {
    // Fail if any expectation or request was left unhandled
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });
});

Common Pitfalls

  • Forgetting flush(): the promise never resolves, so assertions run before data arrives.

  • URL mismatch: expectations match exactly, including query strings and trailing slashes; use a regex or function matcher when the URL is dynamic.

  • Wrong order with expect(): a second request arriving before an earlier declared expectation fails the test; use when() for calls whose order you do not care about.

  • Leftover requests: always verify in afterEach so a test cannot silently pass with unhandled requests.

  • Removed APIs: $http.success() and .error() were removed in 1.6; use .then() and .catch() on the promise.

Note that AngularJS reached end of life on 31 December 2021, so $httpBackend is relevant only for maintaining existing AngularJS 1.x applications, not for new projects.

56

What strategies would you use to reduce the performance cost of watchers during the digest cycle in AngularJS?

Every binding in an AngularJS template registers a watcher, and each digest cycle re-evaluates all of them until the model stabilizes, so optimization is mostly about having fewer watchers, cheaper comparisons, and fewer digests per user interaction.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; long-term support ended 31 December 2021, so this applies to maintaining existing apps rather than starting new ones).

How Watchers Affect the Digest Cycle

  • Each {{ }} interpolation, ng-bind, ng-model, ng-repeat, ng-show and similar directive creates a watcher on a scope.

  • A digest iterates every watcher and, if any value changed, runs again until nothing changes, for up to 10 iterations.

  • Cost grows roughly linearly with the number of watchers, and a single event (a click, an HTTP response, a setTimeout wrapped in $apply) can trigger several digests.

Reduce the Number of Watchers

  • One-time bindings with the :: prefix, e.g. {{::user.name}}, deregister the watcher after the value first stabilizes. Use them for anything that will not change after initial render: labels, headers, list cells in a static list.

  • Prefer components over plain directives and templates: angular.module(...).component() with bindings: { data: '<' } and controllerAs: '$ctrl' keeps bindings one-way and explicit, which makes it easier to reason about which inputs actually need watching.

  • Move logic out of templates: an expression like {{ total(order) }} calls a function on every digest. Compute the value in the controller when the input changes (for example in $onChanges) and bind the result.

  • Avoid filters in ng-repeat: | filter:query rebuilds the filtered array on every digest. Filter the data in the controller instead.

Choose the Right Watch Type

  • A plain $watch(expr, fn) compares by reference — cheapest.

  • $watchCollection(expr, fn) watches one level of an array or object: it notices added/removed items but not changes deep inside them. Use it for lists.

  • $watch(expr, fn, true) performs a deep comparison with angular.equals and is the most expensive option; use it only when genuinely needed, and prefer watching a smaller derived value.

Optimize ng-repeat

  • Add track by user.id so AngularJS reuses existing DOM nodes and their scopes when the collection is replaced or reordered. Without it, AngularJS tracks by object identity and may rebuild the whole list, recreating every watcher.

  • Combine track by with one-time bindings inside the repeated element when the row data is immutable.

Code Example

<!-- Bad: a watcher per cell, plus a filter array rebuilt every digest -->
<div ng-repeat="user in $ctrl.users | filter:$ctrl.query">
  <span>{{ user.name }}</span>
</div>

<!-- Better: precomputed list, stable DOM, one-time text -->
<div ng-repeat="user in $ctrl.filtered track by user.id">
  <span>{{::user.name}}</span>
</div>

<input type="search" ng-model="$ctrl.query"
       ng-model-options="{ debounce: 300 }"
       ng-change="$ctrl.applyFilter()">
angular.module('app').component('userList', {
  bindings: { users: '<' },          // one-way input, no two-way watcher
  templateUrl: 'user-list.html',
  controller: ['userService', function (userService) {
    var $ctrl = this;

    $ctrl.query = '';                // initialised before any lifecycle hook runs

    // Defined on the controller so $onChanges (which fires before $onInit)
    // can always call it.
    $ctrl.applyFilter = function () {
      // Filtering happens once per change, not once per digest
      $ctrl.filtered = userService.filter($ctrl.users, $ctrl.query);
    };

    $ctrl.$onInit = function () {
      $ctrl.applyFilter();           // build the initial list
    };

    $ctrl.$onChanges = function (changes) {
      if (changes.users) {
        $ctrl.applyFilter();         // re-filter when the input binding changes
      }
    };
  }]
});

Best Practices and Common Pitfalls

  • Debounce input-driven digests: ng-model-options="{ debounce: 300 }" collapses rapid typing into one digest.

  • Keep third-party callbacks inside the digest loop: wrap non-AngularJS callbacks in $apply (or use $timeout/$applyAsync) so you get one digest instead of several.

  • Do not deep-watch large objects; watch the specific property that drives the view.

  • Profile before optimizing: use the browser devTools and, if available, an AngularJS inspector extension to count watchers per scope instead of guessing.

  • Remember the framework is end of life: for apps still on AngularJS, these techniques reduce cost in the short term, but the supported paths forward are a rewrite in modern Angular or a hybrid migration with ngUpgrade.

57

Which performance problems most frequently affect AngularJS applications, and what typically causes them?

Most AngularJS performance problems trace back to the digest cycle and the number of watchers it must evaluate on every change-detection run. Because AngularJS 1.8.x relies on dirty-checking instead of a virtual DOM, each binding you declare costs CPU time on every digest, so unbounded watcher growth or expensive watch functions make the UI feel sluggish.

Read full answer

Applies toAngularJS 1.8.x (note: AngularJS reached end of life on 31 December 2021 and long-term support has ended; for new or security-sensitive projects, these concerns also motivate migration to Angular via a rewrite or a hybrid app with ngUpgrade)

The Digest Cycle and Watchers

  • Watcher: every interpolation ({{ }}), ng-model, and directive binding registers a watch function that the digest cycle runs to detect changes.

  • Dirty-checking: unlike Angular (2+) with its component tree and zone.js, AngularJS re-evaluates all watchers until the model stabilizes, so cost grows with watcher count rather than with what actually changed.

  • Digest frequency: digests fire on user events, $http responses, $timeout, and manual $scope.$apply() calls; code that triggers digests too often multiplies the cost.

Common Bottlenecks

  • Too many watchers: large pages with thousands of bindings can push a single digest past 16 ms, dropping below 60 fps.

  • ng-repeat without track by: AngularJS re-creates DOM nodes on every collection change instead of moving existing ones, causing heavy re-rendering.

  • Missing one-time binding: data that never changes still gets watched; the :: prefix ({{ ::user.name }}) deregisters the watcher after the first stable value.

  • Expensive watch functions: calling functions or complex expressions inside templates runs on every digest; compute values once in the controller instead.

  • Filters in ng-repeat: filters execute on every digest for every row; filter the collection in the controller or use a dedicated filtering component.

  • Deep object watching: $scope.$watch('obj', fn, true) compares every property each cycle; prefer watching specific fields or immutable replacements.

  • Unnecessary $apply / $digest: manual calls outside Angular contexts force extra cycles; wrap only the code that actually changed.

Memory Leaks and Cleanup

  • Event listeners: handlers attached with angular.element(...).on() or third-party libraries must be removed in $onDestroy.

  • Timers: $interval and $timeout return promises that should be cancelled to avoid callbacks firing after a scope is destroyed.

  • Detached scopes: components or directives that create isolate scopes should clean up in $onDestroy so garbage collection can reclaim them.

Optimization Comparison

Pattern Cost Better approach
ng-repeat="item in items" re-renders all rows add track by item.id
{{ user.name }} for static data watcher forever {{ ::user.name }} one-time
ng-click="compute()" in template runs each digest bind a precomputed value
$watch('obj', fn, true) deep compare all props watch obj.field only
filter in ng-repeat runs per row per digest filter in the controller

Best Practices

  • Use the component-based style from 1.5 (angular.module(...).component()) with one-way bindings ('<') to limit two-way propagation.

  • Keep controllers lean and move heavy logic out of watch functions and templates.

  • Profile with the browser DevTools Performance panel and the legacy Batarang extension to find hot watchers before optimizing.

  • Write dependency injection in minification-safe array notation so tooling does not break your services.

  • Remember AngularJS is past end of life; plan migration to Angular with ngUpgrade for hybrid apps or a full rewrite for long-term maintainability.

58

How does using an isolated scope affect the performance of an AngularJS application?

In AngularJS, isolated scopes carry a negligible direct performance cost compared to inherited child scopes. The primary driver of slowdown is the total number of watchers evaluated during each digest cycle, not the isolation mechanism itself. While isolation changes how property lookups resolve, it does not inherently make the application slower unless used at a massive scale without optimization.

Read full answer

Applies toAngularJS 1.8.x

How the Digest Cycle Affects Scopes

AngularJS uses dirty checking via the digest cycle. During $digest, the framework iterates over every watcher registered on every scope in the hierarchy to detect model changes.

  • Scope count: Every new scope, isolated or inherited, adds a node to the scope tree that the digest loop must visit.

  • Watcher count: The cost is proportional to the number of watchers, not the scope type. A scope with ten watchers costs more than an isolated scope with none.

  • Evaluation cost: Watchers that execute complex functions or filters during digestion consume more CPU than simple property references.

Creating hundreds of scopes, such as inside a large ng-repeat, amplifies this cost regardless of isolation.

Isolation Versus Prototypical Inheritance

The main technical difference is how JavaScript property lookup behaves.

  • Inherited scope: Uses the JavaScript prototype chain. If a property is not found on the child, lookup traverses up to the parent and eventually $rootScope. Deep chains can make lookups slightly more expensive.

  • Isolated scope: Does not prototypically inherit from the parent. Properties must be passed explicitly via bindings. Lookups resolve locally, which can be marginally faster per access.

In practice, the lookup speed difference is microseconds and rarely measurable. The benefit of isolation is architectural safety, such as avoiding accidental parent mutation, rather than raw speed.

Binding Types and Watcher Overhead

Since AngularJS 1.5, the recommended approach is angular.module(...).component(), which always creates an isolated scope. Performance depends heavily on the binding definition used within that scope.

Binding Syntax Watcher Behavior Cost
One-way < Watches input for reference changes Low
Two-way = Watches both parent and isolate side High
Attribute @ Observes string interpolation Low
Method & Passes a function reference Lowest

Using one-way bindings (<) instead of two-way (=) reduces the number of watchers registered. In deep component trees, replacing two-way bindings with one-way bindings and explicit output events (&) lowers the work per digest cycle.

When Isolation Actually Matters

Performance issues related to scopes typically appear only in specific scenarios:

  • Large lists: Rendering thousands of components, each with an isolated scope and multiple watchers, can cause visible lag during input or scrolling.

  • Frequent digests: Applications triggering $apply or $digest rapidly will feel the cumulative cost of every watcher on every scope.

  • Deep trees: Excessive nesting of components increases the traversal path, though modern browsers handle this well.

For most business applications, the clarity and maintainability gained from isolated scopes outweigh the micro-costs.

Best Practices and Migration Context

  • Limit watchers: Use one-time bindings (::) for static data that never changes after initialization.

  • Optimize repeats: Use track by in ng-repeat to avoid recreating scopes and DOM nodes unnecessarily.

  • Avoid heavy logic: Never put expensive computations inside watcher functions or templates.

  • Consider EOL: AngularJS reached end of life on 31 December 2021. It no longer receives security patches. For new performance-critical projects, migrate to Angular using ngUpgrade to run both frameworks, or rewrite entirely.

Prioritize reducing watcher count and digest frequency over worrying about scope isolation type.

59

What security measures should developers prioritize when maintaining applications built with AngularJS?

AngularJS security focuses primarily on mitigating Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) risks using built-in services like $sce and $http. Developers must avoid bypassing automatic escaping mechanisms and ensure all dynamic content is sanitized or explicitly trusted before rendering. Because AngularJS reached end of life on 31 December 2021, maintaining these practices is essential for legacy systems awaiting migration.

Read full answer

Applies toAngularJS 1.8.x

Built-in Sanitization and Escaping

AngularJS automatically escapes values inserted via double curly braces {{ }} and ng-bind, preventing script injection by default. To render trusted HTML, use the $sce service to explicitly mark content as safe rather than disabling sanitization globally. Always use ng-href and ng-src instead of href or src so the browser does not navigate to an un-interpolated or malicious URL before Angular processes the binding. - $sce.trustAsHtml: Marks a string as trusted for HTML binding; it does not sanitize, so only use it on content you control. - ng-bind-html: Renders trusted or sanitized HTML and requires the ngSanitize module, which sanitizes untrusted values passed to it. - Never disable SCE: Do not call $sceProvider.enabled(false) in a config block; turning off strict contextual escaping globally removes the framework’s core XSS protection.

CSRF Protection with $http

The $http service includes built-in CSRF protection: it reads a cookie named XSRF-TOKEN set by the server and sends it back in a header named X-XSRF-TOKEN on same-origin requests. This behavior is enabled by default but requires the server to set the cookie upon authentication. If you add custom request logic, use interceptors with minification-safe dependency injection so the token handling survives minification. - Same-origin policy: The built-in protection applies to same-origin requests; cross-origin setups need additional server-side CORS and CSRF controls. - Custom interceptors: Must not strip the X-XSRF-TOKEN header logic when transforming requests.

Secure Coding Patterns

Avoid eval, string concatenation into window.location, and direct DOM manipulation such as element.html() with user input. Prefer the component-based architecture available since 1.5, with one-way bindings (<) for inputs and & for outputs, to isolate data flow and reduce unintended exposure. Ensure all dependency injection uses array notation or $inject properties so function parameter names survive minification without breaking the injector. - One-time bindings: Use :: syntax to reduce watchers and freeze bindings after initialization. - ControllerAs: Use the $ctrl alias instead of attaching everything to $scope to avoid scope inheritance surprises. - Keep watchers low: Excessive $scope.$watch registrations degrade digest-cycle performance; performance and maintainability issues compound security review difficulty.

End-of-Life and Migration

AngularJS long-term support ended on 31 December 2021, meaning the team releases no further security patches. Running AngularJS in production exposes applications to newly discovered vulnerabilities unless mitigated by external layers such as a Web Application Firewall (WAF), strict Content Security Policy headers, and timely patching of third-party dependencies. Teams should plan migration to Angular (2+), either gradually with ngUpgrade running both frameworks side-by-side or through a complete rewrite for security-critical systems. - ngUpgrade: Allows AngularJS and Angular to run in the same application during incremental migration. - Risk assessment: Audit third-party AngularJS modules and libraries for unmaintained or vulnerable code.

Common Pitfalls

Calling $sce.trustAsHtml() on user-generated content is a critical vulnerability that enables XSS attacks, because it marks the string as trusted rather than cleaning it. Using .success() or .error() on $http promises is obsolete — those methods were removed in AngularJS 1.6; always use .then() and .catch(). Finally, avoid storing sensitive tokens in localStorage or sessionStorage, where they are readable by any JavaScript XSS payload; prefer secure, httpOnly, SameSite cookies managed by the server.

60

What mechanisms does AngularJS provide for XSRF protection, and how do they work?

AngularJS provides built-in XSRF protection for the $http service through a cookie-to-header double-submit pattern: the server sets a JavaScript-readable cookie containing a per-session token, Angular reads that cookie and sends its value in a custom HTTP header on mutating requests, and the server verifies that the header matches the token it issued. The framework only supplies the client side; the server must still generate, transmit, and validate the token. AngularJS reached end of life on 31 December 2021, so it receives no security patches and should not be used for new projects.

Read full answer

Applies toAngularJS 1.8.x

How the Double-Submit Pattern Works

  • Server issues the cookie: On page load or login, the server sets a cookie (default name XSRF-TOKEN) with a random, per-session value. It must not be HttpOnly, because Angular needs to read it; it should also carry the Secure flag and a SameSite attribute.

  • Angular reads the cookie: Before dispatching an $http request, the framework looks up the configured cookie name through $browser.cookies.

  • Header is added only for mutating methods: Angular sends the token in the configured header (default X-XSRF-TOKEN) for POST, PUT, and DELETE requests. It does not add the header to GET or HEAD, which are treated as safe.

  • Server validates the token: The server compares the header value with the token it stored for the current session. An attacker on another origin cannot read the cookie because of the same-origin policy, so it cannot supply the required header.

  • Browser cookie scope: The browser automatically includes the cookie only for the origin that set it. For cross-origin APIs, CORS and withCredentials must be configured, and the token is meaningful only to the server that issued it.

Configuring Names and Disabling Protection

  • The defaults (XSRF-TOKEN / X-XSRF-TOKEN) match frameworks such as Django, Spring Security, and Laravel.

  • Override them in a minification-safe configuration block by assigning $httpProvider.defaults.xsrfCookieName and $httpProvider.defaults.xsrfHeaderName.

  • Disable the header for a particular request by setting xsrfHeaderName: null on that request config, or globally by clearing the default.

Scope and Limitations

Aspect Behavior
Covered APIs $http and services built on it, such as $resource
Uncovered traffic Plain XMLHttpRequest, <form> submissions, <img>/<script> tags, and WebSocket handshakes
Safe methods GET and HEAD do not carry the XSRF header; state-changing GET endpoints need separate protection
Cross-origin The header is added whenever the cookie exists, but the browser sends the cookie only to its originating domain unless CORS/withCredentials allow it
Authentication XSRF proves the request came from your page, not that the user is authenticated; it does not replace session or token auth

Related Built-In Security Features

  • Automatic HTML escaping: Interpolation {{ }} and ng-bind escape values before DOM insertion, blocking most XSS payloads.

  • $sce (Strict Contextual Escaping): Raw HTML bound with ng-bind-html must be explicitly trusted via $sce.trustAsHtml().

  • ngSanitize module: Strips dangerous tags and attributes from HTML; prefer it over trusting raw content.

  • XSRF cookie-to-header: Covered above; protects $http mutating requests from CSRF.

Best Practices and Migration

  • Never mark the XSRF cookie HttpOnly: Angular must read it. Keep authentication in a separate HttpOnly cookie if needed.

  • Enforce HTTPS: Set the Secure flag so the token is never transmitted in cleartext.

  • Use SameSite cookies: SameSite=Strict or Lax adds a server-side layer of CSRF defense even when the Angular header is absent.

  • Sanitize dynamic HTML: Use ngSanitize and avoid ng-bind-html with untrusted content.

  • Limit watchers: One-time bindings (::) and track by in ng-repeat reduce digest-cycle cost, though this is a performance concern rather than an XSRF one.

  • Plan migration: Because AngularJS is end of life, evaluate a rewrite in Angular (2+) or a hybrid upgrade with ngUpgrade.

61

What mechanisms does AngularJS provide for sanitizing user input to prevent security vulnerabilities?

AngularJS mitigates cross-site scripting (XSS) primarily through Strict Contextual Escaping (SCE) and the $sanitize service, which together treat untrusted expressions as text and strip dangerous HTML unless a value is explicitly trusted. Developers must load the sanitization module, render dynamic HTML through ng-bind-html, and avoid marking unverified content as trusted.

Read full answer

Applies toAngularJS 1.8.x

Core Security Mechanisms

  • Strict Contextual Escaping (SCE): Enabled by default in the ng module; interpolation {{ }} and attribute bindings render values as text, so <script> tags from user input are not executed.

  • $sanitize service: Provided by the ngSanitize module; parses HTML and removes unsafe elements such as <script> and <iframe> and unsafe attributes such as onerror or javascript: URLs.

  • ng-bind-html: The directive that binds sanitized HTML to the DOM; it invokes $sanitize automatically when ngSanitize is loaded.

Module Setup and Dependency Injection

  • Add angular-sanitize.js to the build and declare ngSanitize as a module dependency: angular.module('app', ['ngSanitize']).

  • Inject $sce and $sanitize with minification-safe array notation, e.g. ['$sce', '$sanitize', function($sce, $sanitize) {...}].

  • Do not disable SCE with $sceProvider.enabled(false); doing so removes contextual escaping for the whole application.

Trust Contexts and Rendering

Mechanism Purpose When to use
$sce.trustAsHtml(value) Marks a string as safe HTML Only for content from a verified source, such as a hardened CMS API
$sce.trustAsUrl(value) Marks a URL as safe For href or src values that must bypass URL sanitization
$sce.trustAsResourceUrl(value) Marks a template or script URL as safe For ng-include or script sources from trusted origins
$sanitize(value) Strips dangerous markup For user-generated rich text before binding
  • $sce contexts include HTML, CSS, URL, RESOURCE_URL, and JS; using a value in a context other than the one it was trusted for throws a runtime error.

  • Prefer ng-bind-html="$ctrl.safeHtml" over interpolation for rich text; interpolation escapes HTML and displays tags as text.

  • One-time bindings (::) reduce watchers but do not affect sanitization; use them for static trusted content.

Best Practices and Security Warnings

  • Server-side validation is mandatory: Client-side sanitization can be bypassed; always validate, sanitize, and encode input on the server.

  • Never trust user input: Even with $sanitize, treat user content as untrusted and avoid trustAsHtml on it.

  • Keep ngSanitize loaded: Without it, the $sanitize service and ng-bind-html directive are unavailable, so dynamic HTML cannot be safely rendered.

  • End of life: AngularJS long-term support ended 31 December 2021. For new projects use Angular or another modern framework; for legacy apps, migrate with ngUpgrade or a full rewrite.

62

Can you explain what the digest cycle is in AngularJS and describe the process by which it detects and applies changes?

The digest cycle is AngularJS’s change-detection mechanism: the loop that walks every watcher registered on a scope hierarchy, checks whether the watched value has changed, runs the associated listeners, and repeats until no further changes are detected or the iteration limit is reached.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; AngularJS reached end of life on 31 December 2021, so this applies to maintaining existing applications rather than starting new ones).

What Triggers a Digest Cycle

AngularJS does not poll for changes continuously. A digest cycle starts when something asks the framework to check for changes:

  • Built-in directives and services: ng-model, ng-click, ng-repeat, $http callbacks and other AngularJS-aware APIs call $scope.$apply() internally, so a digest runs automatically after they execute.

  • Explicit calls: application code calls $scope.$digest() to run a cycle on a scope and its children, or $scope.$apply(expr) to run an expression inside $rootScope.$digest().

  • External events: browser callbacks that AngularJS does not wrap (a plain addEventListener, a jQuery handler, a setTimeout or a WebSocket message) change data outside the framework, so the DOM will not update until you call $apply() or use $timeout, which triggers a digest safely.

How the Loop Works

  1. A trigger calls $apply(), which evaluates the expression and then starts $rootScope.$digest().

  2. The digest traverses every scope from the root down, evaluating each registered watcher’s expression and comparing the result with its previous value—using strict equality (===) by default, or angular.equals when the watcher was created with object equality.

  3. If a value changed, the corresponding listener function runs — updating the view, or setting another scope value that other watchers observe.

  4. Because one listener can change data another watcher depends on, the digest repeats the whole pass. This repetition is why the cycle is also called “dirty checking”: it keeps going while values are still “dirty”.

  5. The loop exits when a full pass produces no changes, or after a maximum of 10 iterations. Reaching the limit throws the well-known [$rootScope:infdig] error, which usually points to a listener that changes a watched value on every pass.

Performance Characteristics and Watcher Counts

The cost of a digest grows roughly linearly with the number of watchers, and every digest can run multiple passes, so large applications can become slow in the browser’s main thread. Practical ways to reduce the work:

  • Use one-time bindings ({{ ::user.name }}) so the watcher is removed once the value is first resolved.

  • Use track by in ng-repeat to avoid re-creating DOM nodes and to reduce the number of watchers AngularJS must reconcile for collections.

  • Prefer bindToController and one-way '<' bindings in components instead of large scope hierarchies with two-way '=' bindings.

  • Move expensive logic out of watchers into code that runs only when the underlying data actually changes.

Common Pitfalls

  • “Digest already in progress”: calling $scope.$apply() from inside a digest throws. Wrap the change in $timeout(fn) when you are unsure, since it schedules the digest on a later turn.

  • Updates not appearing: modifying a scope variable from a non-AngularJS callback leaves the view stale until a digest runs; this is a symptom of a missing $apply(), not of broken binding.

  • Firing digests too often: listening to rapidly firing events such as scroll or mousemove and calling $apply() on each one can freeze the UI. Debounce the handler or update the DOM outside the digest where appropriate.

  • Watchers that always return new values: watching an expression that produces a new object or array reference each time prevents the cycle from stabilising and triggers the iteration limit error.

Best Practices

  • Let built-in directives and services trigger digests for you; call $apply() only for genuinely external code paths.

  • Prefer $timeout over a manual $apply() when a digest may already be running.

  • Keep the watcher count low and bindings shallow, and profile with the browser’s developer tools when a page feels sluggish, since a long digest is the most common cause of AngularJS rendering delays.

63

Describe how manual bootstrapping operates in AngularJS and identify the situations where it is preferable to the standard automatic initialization mechanism.

Manual bootstrapping is the programmatic initialization of an AngularJS application by calling angular.bootstrap once the DOM is ready, rather than relying on the ng-app directive to start the framework automatically. This approach gives developers explicit control over initialization timing and makes it possible to run several independent AngularJS applications within a single page.

Read full answer

Applies toAngularJS 1.8.x

Automatic vs Manual Initialization

By default, AngularJS uses automatic initialization. After the framework script loads, it scans the DOM for the first ng-app directive, loads the module named by that directive, and compiles the DOM tree rooted at the element. Manual bootstrapping skips this scan entirely. The developer loads AngularJS and the application modules explicitly, waits for the DOM to be ready, and then invokes angular.bootstrap. This is useful when the framework must not start before external data, configuration, or asynchronous dependencies are available.

Aspect Automatic (ng-app) Manual (angular.bootstrap)
Trigger Framework scans the DOM at load time Developer calls the function explicitly
Root element The element carrying ng-app The DOM element passed as the first argument
Modules One module taken from the ng-app value An array of module names supplied by the caller
Initialization timing As soon as the script and DOM permit Whenever the developer chooses
Multiple applications Only the first ng-app is honored Each application is bootstrapped separately

The bootstrap API

The function signature is angular.bootstrap(element, modules, config).

  • element: The DOM element that becomes the application root, equivalent to the element that would normally carry ng-app.

  • modules: An array of module names whose providers and components are loaded into the injector.

  • config: An optional configuration object. The most common option is { strictDi: true }, which forces explicit dependency annotation and throws an error if a function is not annotated in a minification-safe way.

The call returns the injector instance, although most applications do not use it directly. It must be executed after the target element exists in the DOM, typically inside an angular.element(document).ready callback or from a script placed at the end of the body. When manual bootstrapping is used, no ng-app directive should appear in the same subtree, because it can trigger a second automatic bootstrap and cause conflicts.

When Manual Bootstrapping Is Preferable

  • Multiple applications on one page: AngularJS ignores every ng-app after the first. Manual bootstrapping lets distinct modules run in separate DOM trees, each with its own injector and scope hierarchy.

  • Asynchronous or dynamic module loading: Modules can be fetched by a script loader, an AMD/CommonJS bundle, or lazy-loaded scripts before the application starts.

  • Pre-rendering or external DOM manipulation: Another process, such as a server-side pre-renderer or a third-party widget, may create or modify markup before AngularJS compiles it.

  • Conditional initialization: The application can wait for feature detection, user authentication, configuration from an API, or a specific user action before starting.

  • Integration and end-to-end testing: Test harnesses can control exactly which modules load and where the root element is. Unit tests normally rely on angular.mock.module and inject rather than full bootstrapping.

Best Practices and Migration

  • Always annotate dependencies with array notation or the $inject property; manual bootstrapping does not make unsafe minification acceptable.

  • Bootstrap only after the DOM is ready, and keep one root element per bootstrap call.

  • Avoid mixing ng-app with manual bootstrap on the same page unless the ng-app element is outside every manually bootstrapped subtree.

  • AngularJS reached end of life on 31 December 2021; long-term support has ceased and security patches are no longer provided. For new projects, modern frameworks such as Angular (2+) are recommended. When maintaining legacy code, ngUpgrade allows AngularJS and Angular to run side by side during migration instead of building new features on the deprecated framework.

64

Explain the responsibility of the linking function in AngularJS custom directives and how it connects the data model to the view.

In AngularJS, the linking function is the execution phase where a directive registers DOM listeners, updates the view based on scope data, and establishes watchers. It serves as the bridge between the compiled template and the runtime scope, ensuring that user interactions and model changes stay synchronized within the digest cycle.

Read full answer

Applies toAngularJS 1.8.x

Core Responsibility

The linking function runs after the DOM template is cloned and inserted. Its primary job is to bind scope data to the DOM elements. Unlike the compile function, which manipulates the template DOM before cloning, the link function works on the live DOM instances. This is where you attach event handlers, initialize third-party libraries, or set up scope watchers using $scope.$watch.

Pre-link vs Post-link

A directive can define separate pre-link and post-link functions. The pre-link function executes before child elements are linked, making it unsuitable for DOM manipulation that relies on children. The post-link function (the default when specifying link directly) runs after all children are linked. Most DOM logic belongs in post-link because the DOM tree is fully constructed at that point.

Scope and Dependency Injection

Inside the link function, you receive the scope, element, attributes, and any required controllers. Dependencies injected into the directive factory, such as $timeout or $http, are available here. When injecting services, use array notation to ensure the code survives minification, which renames function parameters.

Directives vs Components

Since AngularJS 1.5, the .component() method is the preferred way to build UI blocks. Components use a controller with lifecycle hooks ($onInit, $onChanges) instead of a linking function. Use .directive() with a link function only when you need complex DOM manipulation or transclusion that components cannot handle. Note that AngularJS reached end of life on 31 December 2021, so new projects should use modern frameworks.

Best Practices and Pitfalls

  • Avoid heavy DOM manipulation: prefer CSS classes bound via scope instead of direct element changes.

  • Clean up resources: always remove event listeners and watchers in a $scope.$on('$destroy', ...) handler to prevent memory leaks.

  • Respect the digest cycle: never modify the DOM outside of Angular’s lifecycle without triggering a digest cycle manually via $scope.$apply().

  • Keep watchers low: limit the number of watchers to maintain performance during the digest cycle, using one-time bindings (::) where possible.

65

What is the process for building a custom AngularJS service that encapsulates calls to a backend API?

A custom API service in AngularJS is a singleton object registered on a module with angular.module(...).service() or .factory(), which wraps $http and exposes promise-returning methods such as getAll(), getById() or save() so that controllers and components never call the backend directly.

Read full answer

Applies toAngularJS 1.8.x with plain JavaScript (ES2015+). AngularJS reached end of life on 31 December 2021, so this pattern is for maintaining existing applications, not for starting new ones.

How It Works

  • Register the service on a module: angular.module('app', []).service('apiService', ['$http', function ($http) { ... }]).

  • Inject $http and return the promises it produces; callers chain .then() and .catch() because .success() and .error() were removed in AngularJS 1.6.

  • Keep URLs and HTTP verbs inside the service, and pass request data through the data property of the config object so AngularJS serializes it and applies the configured content type.

  • Inject the service by name into a controller or another component; AngularJS instantiates it once and shares that single instance.

Service, Factory and Provider

Recipe Returns Typical use
.service() A constructor instance (new is applied to your function) Stateful or class-like services, the most natural choice for an API client
.factory() Whatever the factory function returns Services that need private helpers or to return objects literally
.provider() A configurable object with a $get() function Services whose URL base must be configured during .config()

For a simple API wrapper, .service() is the clearest option; use .provider() only when the endpoint base must change at bootstrap time.

Code Example

angular.module('apiApp', [])

  // $http is injected with the array form so the code survives minification
  .service('userService', ['$http', function ($http) {
    // Base path is defined once, not repeated in every call
    var baseUrl = '/api/users';

    this.getAll = function () {
      return $http.get(baseUrl);
    };

    this.getById = function (id) {
      return $http.get(baseUrl + '/' + encodeURIComponent(id));
    };

    this.create = function (user) {
      return $http.post(baseUrl, user);
    };
  }])

  // A component consumes the service through dependency injection
  .component('userList', {
    templateUrl: 'user-list.html',
    controller: ['userService', function (userService) {
      var vm = this;

      vm.$onInit = function () {
        userService.getAll()
          .then(function (response) {
            vm.users = response.data;
          })
          .catch(function (response) {
            vm.error = 'Failed to load users (status ' + response.status + ')';
          });
      };

      vm.addUser = function (form) {
        // Only submit when the ng-form validation has passed
        if (form.$invalid) {
          return;
        }
        userService.create({ name: vm.newName })
          .then(function (response) {
            vm.users.push(response.data);
            vm.newName = '';
            form.$setPristine();
          })
          .catch(function (response) {
            vm.error = 'Create failed (status ' + response.status + ')';
          });
      };
    }]
  });

The matching template binds with controllerAs semantics, so it refers to $ctrl.users, $ctrl.newName and $ctrl.addUser($ctrl.userForm).

Best Practices and Common Pitfalls

  • Always use the array notation or $inject for dependency injection; without it, minification renames $http and the service breaks at runtime.

  • Return promises, not resolved values: a service that sets data on $scope from inside its own callback couples itself to a specific scope and is harder to reuse.

  • Handle errors in the caller too: let the service propagate rejections and surface them in the component, rather than swallowing them inside the service.

  • Encode or validate route parameters: encodeURIComponent(id) prevents malformed URLs when an identifier is user supplied.

  • Do not call $scope.$apply() after $http: AngularJS promises already trigger digest-cycle updates; calling it manually causes the “digest already in progress” error.

  • Prefer one service per resource area (userService, orderService) so controllers stay small and the same endpoint is not called through three different code paths.

66

Can you explain the purpose of the $compile service in AngularJS and describe typical scenarios where developers invoke it manually?

The $compile service in AngularJS parses HTML markup and links it to a scope, transforming static content into a live template with active data bindings. While AngularJS invokes this service automatically during initialization, developers may call it manually to render dynamic templates or instantiate directives programmatically at runtime.

Read full answer

Applies toAngularJS 1.8.x

How It Works

The service traverses the DOM to identify directives and returns a linking function. When invoked with a scope, this function binds the template to that scope, enabling interpolation and event handling. This two-phase process separates DOM manipulation from data binding, allowing efficient reuse of templates across different scopes.

Typical Use Cases

Manual compilation is useful for dynamic UI patterns where the HTML structure is determined by user actions or API responses:

  • Modal dialogs: compiling a template fetched from the server and attaching it to a new child scope.

  • Tab sets and dashboards: instantiating components defined via .component() or directives defined via .directive() without static markup in the main view.

  • Plugin systems: rendering third-party or user-configured markup that must participate in AngularJS data binding.

Digest Cycle Management

Manual compilation often occurs outside AngularJS’s automatic execution context, meaning bindings may remain stale until a digest cycle runs. If compilation happens within an Angular-managed event handler, the digest is active; otherwise, wrap the logic in $timeout, which invokes $apply() automatically. Avoid calling $scope.$apply() directly if a digest is already in progress, as this throws a “$digest already in progress” error.

Code Example

angular.module('app').controller('DynamicCtrl', ['$scope', '$compile', '$timeout',
  function($scope, $compile, $timeout) {
    $scope.loadTemplate = function(html) {
      // WARNING: html must come from a trusted source; never compile user input.
      // Sanitize untrusted HTML with a library such as DOMPurify before compiling.
      $timeout(function() {
        var element = angular.element('<div>' + html + '</div>');
        // $compile returns a linking function called with the scope
        var linked = $compile(element)($scope);
        angular.element('#container').append(linked);
      });
    };
  }
]);

Best Practices and Pitfalls

Compiling strings derived from user input creates severe cross-site scripting (XSS) vulnerabilities because $compile executes expressions within the string; only compile templates you fully trust. Each compiled binding adds a watcher, so limit dynamic compilation to necessary UI sections and utilize one-time bindings (::) where data is static. Finally, note that AngularJS reached end of life on 31 December 2021; reserve this pattern for legacy maintenance rather than new projects, and consider migration options such as rewriting in Angular or running both frameworks with ngUpgrade.

67

What mechanisms do AngularJS components use to exchange data with one another?

Components in AngularJS communicate primarily through explicit input and output bindings: a parent passes data into a child through one-way < (or attribute @) bindings, and the child sends events back to the parent through expression callbacks bound with &. For data that must be shared across many components or non-hierarchical parts of the application, AngularJS provides services (singletons injected into each component), $scope inheritance for directives that still use shared scopes, and event propagation via $emit/$broadcast on $scope or $rootScope.

Read full answer

Applies toAngularJS 1.8.x (end of life: long-term support ended 31 December 2021)

Parent-to-Child: Input Bindings

A component declares inputs in its bindings object. The recommended one-way binding < copies the value into the component’s controller at each digest and updates when the parent’s reference changes.

  • <: one-way binding for objects, arrays, functions and interpolated primitives. Changes to the parent’s property propagate down; mutations the child makes to an object or array are still visible to the parent because only the reference is copied.

  • @: string interpolation binding, useful for static configuration such as a template URL or a CSS class.

  • =: legacy two-way binding. It is still supported but discouraged in new component code because it hides data flow and makes components harder to reason about; use < plus a & callback instead.

Inputs are available on this (aliased as $ctrl by default) after the $onInit hook runs.

Child-to-Parent: Output Callbacks

A child cannot set a binding on its parent directly. Instead it calls a function passed down with &, which evaluates an expression in the parent’s scope. This mirrors the outputs of modern frameworks and keeps the child reusable.

  • The parent passes an expression, for example on-save="$ctrl.save(user)".

  • The child invokes it with a parameter map: this.onSave({ user: this.user }).

  • If the child only needs to signal an event, & still works well; for deeply nested trees, a service is usually simpler.

Sharing State with Services

Services are the idiomatic answer to communication between siblings, cousins, or components with no direct parent-child relationship. Register a service with angular.module(...).service() or .factory(), inject it into every component that needs it, and store shared state or business logic there. Because each component receives the same singleton, a change made by one is visible to the others. Combine the service with a promise or callback so consumers can react to changes, and wrap any asynchronous notification in $timeout or $q so the digest cycle runs.

Lifecycle Hooks

$onChanges(changesObj) fires when the value of a one-way or interpolated input changes, giving the child a chance to react or clone the incoming data before mutating it. $onInit runs once after the bindings are set and is the right place for initialisation logic. $postLink runs after the child’s DOM is linked, and $onDestroy is used to release resources such as event listeners or timers.

Events and Scope Inheritance

  • $scope.$emit sends an event upward to parent scopes; $scope.$broadcast sends it downward to child scopes. $rootScope.$broadcast reaches every scope and is a common way to signal global events such as login or logout.

  • Events are a legacy mechanism: they are hard to trace, they can leak listeners, and they do not fit components that use isolate scopes cleanly. Prefer services and & callbacks, and treat $emit/$broadcast as a last resort.

  • Directives that do not create an isolate scope still share their parent scope, so they can communicate through it. Components created with .component() always have an isolate scope, which is why explicit bindings are required.

Comparison of the Main Mechanisms

Mechanism Direction Best for Watchers added
< binding parent to child passing models and config into a component one per binding
& binding child to parent events, user actions, data the child produced one per binding
Service any direction shared state and logic across unrelated components none
$emit / $broadcast up / down the scope tree rare global notifications one per registered listener

Best Practices and Common Pitfalls

  • Prefer < and & over = so each component has a clear, testable contract; treat = as deprecated for new component code.

  • Do not mutate a bound object in place if the parent must not see the change: copy it in $onInit or $onChanges with angular.copy and emit the copy through the & callback.

  • Keep watcher counts low: use one-time bindings ({{::$ctrl.title}}) for static text and track by in ng-repeat so that re-rendering does not recreate DOM nodes unnecessarily.

  • Deregister listeners registered with $scope.$on and handlers added with element.on inside $onDestroy to avoid memory leaks.

  • Remember that AngularJS reached end of life on 31 December 2021: new work should target Angular or another supported framework, and existing applications can be migrated incrementally with ngUpgrade or rewritten in Angular.

68

How do the event propagation methods $emit, $broadcast, and $on differ within the AngularJS scope hierarchy?

In AngularJS, $scope event propagation relies on directionality: $emit dispatches events upward toward parent scopes, $broadcast dispatches downward to child scopes, and $on registers listeners for those events.

Read full answer

Applies toAngularJS 1.8.x

Propagation Directions Compared

Method Direction Receivers Typical use
$emit Upward, from current scope toward $rootScope Ancestor scopes only A child notifying a parent of an action
$broadcast Downward, from current scope to all descendants Every scope in the subtree A parent distributing data to children
$on No propagation; attaches a listener to one scope The scope it is registered on Reacting to events from ancestors or descendants

How Each Method Works

  • $emit: Sends an event name and optional data up through the scope hierarchy to the root scope. Only ancestor scopes receive the event, and propagation can be halted with event.stopPropagation().

  • $broadcast: Sends an event name and data down to all child scopes and their descendants. Every scope in the subtree receives the event unless a listener stops propagation.

  • $on: Registers a callback on the current scope to listen for a specific event name, capturing events emitted upward from children or broadcast downward from ancestors. It returns a deregistration function to remove the listener.

The Event Object

Listener callbacks receive an event object as the first argument, followed by any additional data passed during dispatch. - event.targetScope: The scope that originally triggered the emission or broadcast. - event.currentScope: The scope currently processing the listener callback. - event.stopPropagation(): Halts further propagation up the chain for $emit or down the tree for $broadcast. - event.preventDefault(): Sets defaultPrevented to true, allowing listeners to signal that an action should be cancelled.

Digest Cycle Impact

Both $emit and $broadcast trigger a digest cycle across the affected scopes, ensuring changes made inside listeners reflect in the view. If called from outside Angular’s context (e.g., a native DOM event handler), wrap them in $scope.$apply() to avoid synchronization issues. Excessive use creates performance bottlenecks, especially with deep scope hierarchies, so avoid calling $broadcast from within a watcher to prevent instability.

Best Practices and Migration

  • Prefer component bindings (< for inputs, & for outputs) over scope events in AngularJS 1.5+ components to reduce coupling.

  • AngularJS reached end of life on 31 December 2021; avoid introducing new scope event logic in production systems.

  • For modernization, choose between rewriting in Angular (2+) or running both frameworks simultaneously using ngUpgrade.

  • Broadcasting on $rootScope reaches every scope in the application; use this sparingly to avoid unintended side effects across modules.

69

In an AngularJS application, what approach would you use to let a component or controller subscribe to events emitted by a service?

AngularJS services are singleton objects without their own event registry, so you do not bind events directly to a service instance. Instead, the service publishes through $rootScope (or exposes its own pub/sub API built on it), and consumers subscribe with $rootScope.$on or $scope.$on, storing the returned deregistration function so the listener can be removed when the consumer is destroyed.

Read full answer

Applies toAngularJS 1.8.x

How AngularJS Events Work

  • $scope.$on(name, listener): registers a listener on a scope. Listeners registered on a component/controller scope are removed automatically when that scope is destroyed.

  • $rootScope.$broadcast(name, args): sends an event downward to all child scopes and their listeners.

  • $rootScope.$emit(name, args): sends an event upward from a scope to its parents, stopping at $rootScope unless event.stopPropagation() is called.

  • $rootScope.$on(name, listener): registers a listener that is not tied to any component scope, so it must be deregistered manually to avoid memory leaks.

  • Events are dispatched synchronously inside the digest cycle, and the listener receives an event object followed by any arguments passed to $broadcast/$emit.

Subscribing from a Component

The component-based style introduced in AngularJS 1.5 fits event subscription cleanly:

  1. Inject the service (or $rootScope) into the controller.

  2. Register the listener in the $onInit hook.

  3. Keep the deregistration function returned by $on.

  4. Call it from $onDestroy so the listener does not outlive the component.

For events that only affect a component’s bound input, prefer handling them in $onChanges when the data flows through bindings; use service events for cross-component or application-wide notifications.

Code Example

angular.module('app', [])

  // Service that owns data and publishes changes through $rootScope
  .factory('orderService', ['$rootScope', function ($rootScope) {
    var orders = [];

    return {
      list: function () {
        return orders;
      },
      add: function (order) {
        orders.push(order);
        // Broadcast to every scope listening for this event name
        $rootScope.$broadcast('order:added', order);
      },
      // Convenience wrapper: returns the deregistration function
      subscribe: function (listener) {
        return $rootScope.$on('order:added', listener);
      }
    };
  }])

  .component('orderList', {
    bindings: { customerId: '<' },
    controller: ['orderService', function (orderService) {
      var $ctrl = this;

      $ctrl.$onInit = function () {
        $ctrl.orders = [];

        // Subscribe once the controller is initialized
        $ctrl.off = orderService.subscribe(function (event, order) {
          if (order.customerId === $ctrl.customerId) {
            $ctrl.orders.push(order);
          }
        });
      };

      $ctrl.$onDestroy = function () {
        // $rootScope listeners are never removed automatically
        if (angular.isFunction($ctrl.off)) {
          $ctrl.off();
        }
      };
    }],
    template:
      '<ul>' +
      '<li ng-repeat="order in $ctrl.orders track by order.id">{{order.name}}</li>' +
      '</ul>'
  });

A controller using $scope directly can do the same without manual cleanup, using $inject so the code stays minification-safe:

function OrderController($scope, orderService) {
  $scope.orders = [];
  $scope.$on('order:added', function (event, order) {
    $scope.orders.push(order);
  });
}

OrderController.$inject = ['$scope', 'orderService'];

Best Practices and Common Pitfalls

  • Always deregister $rootScope.$on listeners: they survive route changes and component destruction, which is the most common source of leaks and duplicate handlers in AngularJS apps.

  • Prefer $scope.$on when a scope is available: the framework cleans it up for you.

  • Keep listeners cheap: they run inside the digest cycle, so heavy work blocks the UI; offload it and let the digest update the view.

  • Do not overuse global events: for a one-off request/response, return a promise from the service and chain .then()/.catch() instead of broadcasting an event.

  • Name events with a domain prefix such as order:added or auth:logout to avoid collisions as the application grows.

  • Watch for digest timing: listeners triggered outside AngularJS (for example, from a WebSocket callback) may need $scope.$apply() or $timeout so bindings update; the service shown above is safe because $broadcast is called from AngularJS code.

70

In AngularJS, what does it mean for a directive to have an isolated scope and how is it configured?

In AngularJS, an isolated scope is a directive scope that does not prototypically inherit from the parent scope, ensuring encapsulation and preventing accidental pollution of the parent model. It is configured by setting the scope property to an object literal in the directive definition object, which declares explicit data bindings between the parent scope and the directive.

Read full answer

Applies toAngularJS 1.8.x

Core Concept

By default, directives either share the parent scope (scope: false, the default) or create a child scope that prototypically inherits from it (scope: true). An isolated scope (scope: { ... }) breaks this inheritance chain, so the directive cannot read or write parent scope variables unless they are explicitly bound. This makes directives reusable and self-contained: their internal state cannot be accidentally modified by outside controllers, and they cannot silently mutate parent state outside the declared bindings. Because the directive’s data contract is explicit, an isolated-scope directive can be dropped into any part of the application without knowing which controllers surround it.

Binding Strategies

The scope object defines how data flows between the parent and the directive. Each key is the property name exposed inside the directive’s scope, and each value declares the binding type. Attribute names in HTML are written in kebab-case (e.g., user-id) while the scope definition uses camelCase (userId). - @ (attribute): Binds a local scope property to the interpolated value of the DOM attribute. The result is always a string, and AngularJS observes the attribute so the property updates whenever the interpolated value changes. - = (two-way): Establishes a two-way binding between the local property and the parent scope expression. Changes propagate in both directions; use it only when the directive genuinely needs to write back to the parent. - & (expression): Provides a way to evaluate an expression in the context of the parent scope, typically used to expose callback events from the directive. - < (one-way): Available since 1.5 and the preferred binding for directive inputs, because the directive cannot mutate the parent’s value. This is the default binding type in .component().

Scope Comparison

Feature Inherited Scope (scope: true) Isolated Scope (scope: {})
Parent Access Direct prototypal access Only via defined bindings
Reusability Low (depends on parent context) High (self-contained)
Risk Accidental variable collision Explicit contract only
Typical Usage Simple DOM manipulation, layout wrappers Reusable UI components and widgets

Code Example

The following directive uses minification-safe dependency injection (the array notation) and an isolated scope. The user-id attribute is bound one-way with <, and on-updated is an output expression bound with &. The $http promise is handled with .then() and .catch(); the .success()/.error() methods were removed in AngularJS 1.6.

angular.module('app')
  .directive('userCard', ['$http', '$log', function ($http, $log) {
    return {
      scope: {
        userId: '<',      // one-way input, e.g. <user-card user-id="ctrl.id">
        onUpdated: '&'    // output expression, e.g. on-updated="ctrl.refresh()"
      },
      template: '<div>{{ user.name }}</div>',
      link: function (scope) {
        // Isolated bindings are already available in the link function
        if (scope.userId) {
          $http.get('/api/users/' + encodeURIComponent(scope.userId))
            .then(function (response) {
              scope.user = response.data;
            })
            .catch(function (error) {
              $log.error('Failed to load user', error);
            });
        }
      }
    };
  }]);

Best Practices and Migration

AngularJS reached end of life on 31 December 2021 (long-term support ended), so new projects should be written in Angular (2+); existing applications can be migrated either by rewriting incrementally or by running both frameworks together with ngUpgrade. Within an AngularJS codebase, prefer .component() over .directive() whenever possible, since components enforce an isolated scope by default and support lifecycle hooks such as $onInit, $onChanges, $postLink and $onDestroy. In directives, register cleanup logic in the link function with scope.$on('$destroy', ...) to remove event listeners and cancel intervals or timeouts. Keep the number of watchers low by using one-time bindings ({{ ::value }}) for static content and track by in ng-repeat over large collections, and avoid deep-watching ($watch with a truthy third argument) large objects inside isolated scopes, since every extra watcher adds work to each digest cycle.

71

Describe what transclusion means in AngularJS and how it is utilized within directives or components.

Transclusion in AngularJS is the mechanism that allows a directive or component to wrap arbitrary content provided by the caller and insert it into a specific location within its own template using the ng-transclude directive. This enables the creation of reusable UI wrappers, such as modals or tabs, where the inner content retains access to the parent scope rather than the component’s isolate scope.

Read full answer

Applies toAngularJS 1.8.x

How Transclusion Works

When transclude: true is set in a directive or component definition, AngularJS captures the DOM elements contained within the element where the directive is applied. During compilation, these elements are stored and later inserted into the template wherever the ng-transclude attribute appears. This process preserves the original DOM structure and event bindings of the transcluded content.

  • Capture Phase: The compiler extracts the inner content of the host element.

  • Insertion Phase: The content is cloned and placed inside the template at the ng-transclude marker.

  • Scope Linking: By default, the transcluded content links to the scope outside the directive, not the directive’s isolate scope.

Component vs Directive Configuration

While traditional directives use the link function to manipulate transcluded content, components introduced in AngularJS 1.5 simplify this configuration. Both support transclusion, but components are preferred for UI building blocks.

Feature Directive (.directive) Component (.component)
Definition transclude: true transclude: true
Template template or templateUrl template or templateUrl
Scope Isolate scope (scope: {}) Always isolate scope (bindToController)
Style Legacy Recommended since 1.5

Scope Behavior and Nuances

A critical aspect of transclusion is scope inheritance. Even if a component uses an isolate scope, the transcluded content behaves as if it were still in the parent context. This means expressions inside the transcluded content evaluate against the parent controller’s properties.

  • If the component needs to expose data to the transcluded content, pass it via bindings to the parent controller instead of trying to share scope variables.

  • Using ng-transclude without arguments inserts all captured content.

  • Named transclusion creates slots: define transclude: { header: 'myCardHeader' } in the component, place <div ng-transclude="header"></div> in the template, and supply a matching element such as <my-card-header>...</my-card-header> in the host content.

Code Example

The following example demonstrates a card component that wraps user-provided content.

angular.module('app', [])
.component('myCard', {
  transclude: true, // enables content projection
  template: '<div class="card"><div class="card-header">Title</div><div class="card-body" ng-transclude></div></div>' // ng-transclude marks the insertion point
});
<my-card>
  <p>This content is transcluded into the card body.</p>
</my-card>

Best Practices and Maintenance

Transclusion is powerful but should be used judiciously to avoid performance issues and scope confusion.

  • Minimize Watchers: Transcluded content adds watchers to the digest cycle; keep templates simple.

  • Avoid Scope Pollution: Do not rely on transcluded content modifying the component’s internal state directly.

  • Migration Note: AngularJS reached end of life on 31 December 2021. For new projects, consider modern frameworks like Angular, React, or Vue. For existing apps, plan migration using ngUpgrade if moving to Angular (2+).

  • Security: Ensure transcluded content is trusted, as it executes expressions within the parent scope context.

72

How do you connect an AngularJS application to a RESTful API?

AngularJS connects to RESTful APIs through its $http service, a promise-based wrapper around the browser’s XMLHttpRequest that issues standard HTTP verbs (GET, POST, PUT, PATCH, DELETE) against JSON endpoints and consumes the responses asynchronously.

Read full answer

Applies toAngularJS 1.8.x (final release; long-term support ended 31 December 2021, so it should not be chosen for new projects).

How It Works

  • Inject $http and call its shorthand methods: $http.get(url, config), $http.post(url, data, config), $http.put(url, data, config), $http.patch(url, data, config) and $http.delete(url, config). The generic $http(config) form accepts method, url, data, params, headers and more.

  • Each call returns a promise; chain .then(function (response) {...}) for success and .catch(function (response) {...}) for errors. The legacy .success()/.error() callbacks were removed in AngularJS 1.6, so never use them.

  • The resolved value is a response object with data (the parsed JSON body), status, headers (a header accessor function), config and statusText.

  • AngularJS serialises JavaScript objects to JSON in the request body and sets Content-Type: application/json; responses with a JSON content type are parsed into objects for you.

  • The promise resolves inside a digest cycle, so assignments to $scope or a component’s this update the UI automatically. For non-Angular callbacks, wrap state changes in $scope.$apply() or, preferably, $timeout().

Consuming the Data in a Component

Keep HTTP logic in a single-instance service and inject it into a component controller built with angular.module(...).component(). Load data in the $onInit lifecycle hook, store it on this (exposed as $ctrl in the template), and cancel in-flight requests in $onDestroy if the component is destroyed first.

$http Versus $resource

Aspect $http $resource
Style Low-level, explicit requests Higher-level, URL-parameterised resource classes
Best for Custom endpoints, non-CRUD calls, full control over headers and transforms Classic CRUD endpoints with predictable URL patterns
Returns Promise resolving to a response object Resource objects with $promise and instance methods like $save()
Dependency Always available Requires the angular-resource module (ngResource)

$resource is convenient for strictly RESTful endpoints such as GET /api/users/:id, but it adds a dependency and hides request details. Many teams use $http plus a thin service wrapper for clarity.

Error Handling and Interceptors

  • Always attach a .catch() handler; without it, rejections from network failures or HTTP error statuses propagate silently.

  • Use interceptors ($httpProvider.interceptors) for cross-cutting concerns such as attaching an authentication token, redirecting on 401 Unauthorized, or logging. Register them in a config block: app.config(['$httpProvider', function ($httpProvider) { ... }]).

  • Set defaults application-wide through $httpProvider.defaults (for example a base Authorization header) or per request via the config object.

Best Practices and Common Pitfalls

  • Use array-style dependency injection — ['$http', function ($http) { ... }] — or attach .$inject = ['$http'] so the code survives minification.

  • Bind inputs one-way with '<' in component bindings and emit changes with '&' outputs; do not copy server state into shared two-way bindings.

  • Validate and encode anything rendered from the API; AngularJS escapes interpolations by default, so avoid ng-bind-html with untrusted data unless it has passed $sce sanitisation.

  • Send credentials or CSRF tokens via headers or cookies over HTTPS, never embedded in URL query strings.

  • Cancel requests on teardown with the timeout config option (a promise) to avoid “data arrives after the component is gone” bugs.

  • Keep the digest cheap: store lists on the component controller, not the root scope, and use track by in ng-repeat.

  • Do not build new projects on AngularJS: it is end of life. Existing apps can be migrated by rewriting in modern Angular or by running both frameworks side by side with ngUpgrade.

Code Example

// User data service encapsulating all REST calls
angular.module('app').factory('userService', ['$http', function ($http) {
  var baseUrl = 'https://api.example.com/users';

  return {
    // GET /users -> array of users; callers may pass extra config
    list: function (config) {
      return $http.get(baseUrl, config || {})
        .then(function (response) {
          return response.data; // unwrap the body for callers
        });
    },
    // POST /users with a JSON body
    create: function (user) {
      return $http.post(baseUrl, user)
        .then(function (response) {
          return response.data;
        });
    }
  };
}]);

// Component controller consuming the service
angular.module('app').component('userList', {
  templateUrl: 'user-list.html',
  controller: ['userService', '$q', function (userService, $q) {
    var ctrl = this;
    ctrl.users = [];
    ctrl.error = null;
    var canceler = $q.defer(); // resolves when the component is torn down

    ctrl.$onInit = function () {
      userService.list({ timeout: canceler.promise }) // request is cancellable
        .then(function (users) {
          ctrl.users = users; // AngularJS runs a digest, the view updates
        })
        .catch(function (response) {
          ctrl.error = 'Failed to load users (status ' + response.status + ')';
        });
    };

    ctrl.$onDestroy = function () {
      canceler.resolve(); // abort the request if the component is gone
    };

    ctrl.addUser = function (name) {
      userService.create({ name: name })
        .then(function (user) {
          ctrl.users.push(user);
        })
        .catch(function (response) {
          ctrl.error = 'Create failed (status ' + response.status + ')';
        });
    };
  }]
});
<!-- user-list.html: one-way bindings and track by keep the digest cheap -->
<ul>
  <li ng-repeat="user in $ctrl.users track by user.id">{{ user.name }}</li>
</ul>
<p ng-if="$ctrl.error" class="error">{{ $ctrl.error }}</p>
73

What guidelines should be followed when building AngularJS services to handle communication with backend REST APIs effectively?

Designing robust AngularJS services requires separating data access logic from controllers using factory providers that return promise-based methods via the $http service. This ensures testability, reusability, and consistent error handling across the application while maintaining minification-safe dependency injection.

Read full answer

Applies toAngularJS 1.8.x

Service Type Selection

In AngularJS 1.x, factory is the preferred method for creating API services compared to service or provider. A factory returns an object directly, allowing you to expose specific methods like get, save, or delete without instantiating a constructor with new. Use provider only if you need configuration during the app bootstrap phase, such as setting a dynamic API endpoint base URL before the service runs.

HTTP Communication and Promises

Always use $http for backend interactions, which returns standard JavaScript promises. The legacy .success() and .error() callbacks were removed in AngularJS 1.6; instead, chain .then() for success and .catch() for errors. This aligns with standard promise behavior and allows chaining multiple asynchronous operations. Ensure all HTTP methods return the promise so controllers can handle resolution or rejection appropriately.

Minification-Safe Dependency Injection

AngularJS relies on function parameter names to identify dependencies, which breaks when code is minified. To prevent injection errors in production, use array notation where dependencies are listed as strings before the function. This ensures $http and other services are resolved correctly regardless of variable renaming during the build process.

Error Handling and Security

Centralize error handling within the service or use an HTTP interceptor to manage global issues like authentication failures (401) or server errors (5xx). Never hardcode API secrets or authentication tokens directly in the service code; instead, retrieve them from a secure authentication service or storage. Always validate and sanitize data received from the backend before passing it to controllers or views to prevent XSS vulnerabilities.

Code Example

The following example demonstrates a minification-safe factory that fetches user data and handles errors using promises.

angular.module('app.services', [])
  .factory('UserService', ['$http', '$q', function ($http, $q) {
    var baseUrl = '/api/users';

    var getUser = function (id) {
      // Return the promise for chaining in the controller
      return $http.get(baseUrl + '/' + id)
        .then(function (response) {
          return response.data;
        })
        .catch(function (error) {
          // Log and rethrow or return a default
          console.error('Failed to fetch user:', error);
          return $q.reject(error);
        });
    };

    return {
      getUser: getUser
    };
  }]);

Best Practices and Pitfalls

Keep services stateless where possible to avoid memory leaks and unexpected behavior across controllers. Avoid placing business logic inside controllers; instead, delegate data manipulation to the service. Remember that AngularJS reached end of life on 31 December 2021, so new projects should consider modern frameworks, while legacy applications should focus on security patches and eventual migration via ngUpgrade or a full rewrite. Limit watchers in views that consume service data by using one-time bindings (::) where data does not change after initialization.

74

What strategies are employed to manage associations between RESTful resources within an AngularJS application?

Handling RESTful resource relations in AngularJS typically involves modeling nested URLs or linking entities via identifiers, using the $resource service (from the ngResource module) or manual $http calls. This lets the client mirror server-side associations such as one-to-many or parent-child structures while keeping data access separate from UI components.

Read full answer

Applies toAngularJS 1.8.x with the `ngResource` module (end of life: long-term support ended 31 December 2021).

Core Approaches

There are two primary strategies for representing relations: - Nested Resources: The URL reflects the hierarchy, such as /users/1/posts/2. This suits resources that cannot exist independently of their parent, and it makes ownership explicit in the request itself. - Linked Resources: The child resource contains a foreign key (for example userId) but is accessed via a flat URL such as /posts/2?userId=1. This is more flexible for querying across parents or when a child can belong to several parents.

AngularJS does not provide an ORM-like relationship model. Instead, relations are managed through URL parameterization in services and by normalizing data shape in controllers or components.

Managing Nested vs Linked Relations

When using $resource, you define URL parameters that map to object properties. For nested relations, include the parent identifier in the URL template (for example /users/:userId/posts/:postId) and pass the parent id when calling the action. For linked relations, define the child resource on its own URL and send the foreign key as a query parameter.

Complex relations often require logic that $resource cannot express, such as conditional endpoints, non-standard HTTP verbs, or response transformation. In those cases, inject $http into a service and construct the request manually. Because $http and $resource both return promises, use .then() and .catch(); the old .success() and .error() callbacks were removed in AngularJS 1.6.

Comparison

Aspect Nested Resources Linked Resources
URL shape /users/:userId/posts/:postId /posts/:postId with a userId query parameter
Best for Strictly owned children (a post belongs to one user) Shared or independently queryable children
Caching Distinct URL per parent, so cache keys are natural One URL per child; parent filtering happens server-side
Coupling Client must know the parent to reach the child Client can fetch a child directly

Best Practices and Pitfalls

  • Minification Safety: Always use the array notation for dependency injection (['$http', function($http) { ... }]) or attach $inject, so parameter names survive minification.

  • Promise Handling: $resource action methods return resource instances or arrays that also expose a $promise property; use $promise.then() when you need the data before acting on it.

  • Component Style: Keep relation logic in a service and expose results through a component’s one-way input bindings (<) and output callbacks (&), resolving data in $onInit or $onChanges.

  • Watcher Performance: Avoid deep-watching large relation graphs. Use one-time bindings (::) and track by in ng-repeat to keep the digest cycle cheap.

  • Security: Never rely on client-side URL construction for authorization. Validate every relation server-side so users can only reach resources they own.

  • End of Life: AngularJS reached end of life on 31 December 2021, so it should not be chosen for new projects. Existing applications can either be rewritten in Angular (2+) or run side by side with Angular using ngUpgrade; migrating to React or another framework requires a rewrite or a separate integration strategy rather than ngUpgrade.

75

What is the approach for adding internationalization (i18n) support to an AngularJS application?

Internationalization in AngularJS combines the framework’s built-in locale-aware filters for dates, numbers and currency with a third-party translation library such as angular-translate for text strings, because AngularJS does not ship a built-in translation service. You load locale rules, store translations in per-language JSON files, and switch the active language at runtime through the translation service.

Read full answer

Applies toAngularJS 1.8.x

Built-in Locale Features

AngularJS provides locale data through separate angular-locale_XX.js files (for example angular-locale_en-us.js) that you include in the page after the core script. Once loaded, the following filters automatically format values according to that locale: - date: formats dates using locale conventions for order, separators and month names. - number: formats numeric values with the correct decimal and grouping separators, with an optional fraction size. - currency: formats amounts with the locale’s currency symbol and position. - ng-pluralize: a directive that selects plural forms based on locale rules and a count.

These handle formatting only; they do not translate arbitrary text. Note that the bundled locale data has not been updated since AngularJS reached end of life, so newer locale conventions may be missing.

Third-Party Translation Libraries

For translating UI text, the mainstream options are: - angular-translate (pascalprecht.translate module): the most widely used library; stores translations as JSON, supports asynchronous file loading, interpolation, pluralization (with the separate messageformat handler) and both a translate filter and directive. - angular-gettext: uses standard GNU gettext .po catalog files, suited to teams already using gettext tooling. - Custom service: a small factory holding a translation dictionary plus a filter; viable for very small apps but you lose features like asynchronous loading and fallback languages.

AngularJS reached end of life with long-term support ending 31 December 2021. For new work the recommended path is migration to Angular, using ngUpgrade to run both frameworks side by side during the transition.

How the Pieces Fit Together

A typical i18n setup follows these steps: 1. Include the locale file for the default language in index.html. 2. Install and load the translation library module as a dependency of your app module. 3. Configure the translation provider with a loader that fetches per-language JSON files over HTTP. 4. Set a preferredLanguage and a fallbackLanguage so missing keys never render blank. 5. Wrap user-facing strings with the translation filter or directive, passing interpolation parameters where needed. 6. Provide a language switcher that calls the service’s language-change method and persists the choice, for example in localStorage.

Best Practices and Common Pitfalls

  • Minification safety: always use array notation ['$translate', function ($translate) {...}] or $inject, since provider and service names are strings.

  • Secure delivery: translation files load over HTTP, so serve them over HTTPS from a trusted origin; never embed secrets or sensitive data in translation files.

  • Missing keys: set a fallbackLanguage so missing translations do not render raw keys in production.

  • Watcher count: the translate filter runs on every digest cycle; for large pages prefer the translate directive (`<h1 translate>WELCOME</h1>`) or one-time binding :: where the language does not change, to keep the digest cycle fast.

  • Dynamic HTML: translations containing HTML should use the translate-compile directive only for trusted content, since compiling HTML introduces XSS risk; validate and sanitize any user-supplied interpolation values.

  • Locale vs translation: remember that locale files format values but do not translate strings - you need both layers for full i18n.

76

Which libraries and built-in modules support internationalization and localization in an AngularJS application?

The main options are AngularJS’s own angular-i18n locale files, which localize the built-in date, number and currency filters through the $locale service, plus third-party modules such as angular-translate for UI message translation and angular-dynamic-locale for switching locales at runtime.

Read full answer

Applies toAngularJS 1.8.x

Built-in Localization Support

AngularJS ships with formatting that is locale-aware out of the box:

  • angular-i18n: a separate npm package (formerly the i18n folder of the AngularJS repository) that provides one file per locale, e.g. angular-locale_en-us.js or angular-locale_de-de.js. Loading a locale file reconfigures $locale so the date, number, currency and lowercase/uppercase filters follow that locale’s rules.

  • $locale service: exposes locale metadata (id, pluralization rules, number and datetime formats) that the built-in filters consume.

  • ngPluralize directive: handles simple pluralization based on the current locale’s plural rules.

  • angular-message-format module (angular-message-format.js): adds MessageFormat support to bindings and ngPluralize-style messages, useful for plural and gender-aware strings.

The built-in support does not translate application text; it only formats data. Also, changing the loaded locale file normally requires a page reload, which is why dynamic-locale modules exist.

Common Third-Party Modules

Module Purpose Notes
angular-translate Translates UI strings via $translate, the translate filter and the translate directive Most widely used solution; supports JSON translation tables, async loaders, interpolation, sanitization and MessageFormat
angular-dynamic-locale Changes $locale at runtime with tmhDynamicLocaleService.set('en-us') Loads and caches locale files on demand so date/number formatting follows the selected language without a reload
angular-sanitize Sanitizes HTML in translations Required by angular-translate‘s sanitize strategies to avoid injecting unescaped markup
angular-message-format MessageFormat interpolation for plurals and gender Used together with angular-translate‘s MessageFormat interpolation for complex translated sentences
angular-translate-loader-partial / $translatePartialLoader Loads only the translation tables a view needs Keeps the initial bundle small in large applications

How They Fit Together

A typical setup keeps formatting and message translation separate: load angular-i18n locale files (directly or through angular-dynamic-locale) so dates, numbers and currencies render correctly, and use angular-translate for labels, messages and any text the user reads. angular-translate reads translation tables keyed by string IDs, can load them synchronously or through loaders such as $translatePartialLoader, and resolves the active language from the browser locale, a stored user preference or an explicit preferredLanguage setting.

Best Practices and Caveats

  • Store translations as JSON keyed by locale and string ID; never hard-code user-facing text in controllers or templates.

  • Load non-default locales asynchronously so the application only pays for the locales it uses.

  • Enable a sanitize strategy for HTML translations and pair it with angular-sanitize; escaped or unsafe HTML strategies are a known XSS vector when translations come from an untrusted source.

  • Use one-time bindings (:: 'KEY' | translate) for static translated text to avoid adding watchers to the digest cycle.

  • Remember that AngularJS reached end of life on 31 December 2021: these modules receive little or no maintenance, so for new work prefer a supported framework, and for existing apps plan migration to Angular (a rewrite, or running both frameworks together with ngUpgrade).

77

What steps are involved in preparing and optimizing an AngularJS application for a production deployment?

A production build of an AngularJS app concatenates, minifies and optimizes the JavaScript, HTML templates and CSS so the app downloads fast and runs with development-time overhead removed, while keeping dependency injection safe under minification. It is as much about configuration (disabling debug data, enabling strict DI) as it is about bundling.

Read full answer

Applies toAngularJS 1.8.x, the final release (the framework reached end of life on 31 December 2021, so it should not be chosen for new projects)

How It Works

  1. Install the framework and build tools through npm (AngularJS ships as the angular npm package; the CDN files are also fine).

  2. Make every injectable minification-safe, either by writing the array notation ['$http', function ($http) {...}] manually or by running ng-annotate in the build pipeline.

  3. Concatenate source files into one or few bundles to reduce HTTP requests.

  4. Compile HTML templates into AngularJS’s $templateCache so views are shipped inside the JavaScript bundle.

  5. Minify and gzip JavaScript and CSS, and add long-lived cache headers with cache-busting file names.

  6. Bootstrap the app with strict DI and with debug info disabled, then serve everything over HTTPS.

Key Optimizations

  • $compileProvider.debugInfoEnabled(false): removes the scope and controller references AngularJS attaches to DOM nodes during development, which measurably speeds up binding and digest cycles.

  • $logProvider.debugEnabled(false): silences $log.debug calls so logging noise and its cost disappear in production.

  • ng-strict-di (or angular.bootstrap(el, ['myApp'], {strictDi: true})): throws an error whenever an injectable is missing its annotations, so a minification bug is caught at startup instead of breaking a feature at runtime.

  • $templateCache: pre-loaded templates avoid a network request per view and remove template flicker.

  • Digest-cycle hygiene: keep the watcher count low with one-time bindings ({{ ::value }}) and track by in ng-repeat; this pays off far more than minification on large apps.

Aspect Development Production
Debug info (debugInfoEnabled) enabled disabled
Strict DI optional enabled
Templates loaded via HTTP per view pre-bundled in $templateCache
JavaScript raw, per-file concatenated, annotated, minified, gzipped
Error handling unwrapped errors still surfaced, but with debug data off

Code Example

Production configuration and bootstrap:

// app.js
angular.module('myApp', ['myApp.templates'])
  .config(['$compileProvider', '$logProvider',
    function ($compileProvider, $logProvider) {
      $compileProvider.debugInfoEnabled(false); // drop scope/controller data on DOM nodes
      $logProvider.debugEnabled(false);         // silence $log.debug in production
    }
  ]);

// bootstrap.js — strict DI catches un-annotated injectables at startup
document.addEventListener('DOMContentLoaded', function () {
  angular.bootstrap(document.documentElement, ['myApp'], { strictDi: true });
});

Build pipeline (npm packages: gulp, gulp-concat, gulp-ng-annotate, gulp-uglify, gulp-htmlmin, gulp-angular-templatecache):

// gulpfile.js
var gulp = require('gulp');
var concat = require('gulp-concat');
var ngAnnotate = require('gulp-ng-annotate');
var uglify = require('gulp-uglify');
var htmlmin = require('gulp-htmlmin');
var templateCache = require('gulp-angular-templatecache');

function templates() {
  return gulp.src('src/**/*.html')
    .pipe(htmlmin({ collapseWhitespace: true }))
    .pipe(templateCache('templates.js', {
      module: 'myApp.templates',
      standalone: true
    }))
    .pipe(gulp.dest('dist/js'));
}

function scripts() {
  return gulp.src(['src/app.js', 'src/**/*.js', 'dist/js/templates.js'])
    .pipe(ngAnnotate())      // add array notation before minification
    .pipe(concat('app.min.js'))
    .pipe(uglify())
    .pipe(gulp.dest('dist/js'));
}

gulp.task('build', gulp.series(templates, scripts));

Best Practices and Common Pitfalls

  • Never minify without annotating. Minification renames function parameters, which breaks AngularJS’s implicit DI; run ng-annotate (or write the array notation by hand) before uglify, and keep strictDi on so a missed injection fails loudly.

  • Do not rely on debug data in production. With debugInfoEnabled(false), tools that read angular.element(el).scope() stop working; if you need to debug a live build, temporarily re-enable it or call angular.reloadWithDebugInfo() from the console.

  • Cache-bust your bundles. Append a content hash to app.min.js and set far-future Cache-Control headers, otherwise browsers keep serving a stale bundle after a release.

  • Serve over HTTPS with gzip/Brotli and consider a Content Security Policy header; AngularJS templates use ng- attributes rather than inline event handlers, which makes a strict CSP practical.

  • Remember the framework is end of life. For existing apps, keep builds and browsers patched; for new work, the realistic options are rewriting in modern Angular or running AngularJS side by side with it through ngUpgrade, which lets you migrate components incrementally.

78

Walk through the steps and best practices for deploying an AngularJS application to a production server.

Deploying an AngularJS application requires asset optimization and server configuration to support client-side routing, while acknowledging the framework’s end-of-life status. The process typically involves bundling JavaScript and CSS, minifying code safely for dependency injection, and configuring the web server to serve index.html for unknown routes.

Read full answer

Applies toAngularJS 1.8.x

Build Pipeline and Minification

Production deployments must bundle files to reduce HTTP requests and minify them to reduce payload size. Tools like Gulp, Webpack, or Grunt are commonly used. Minification renames variables, which breaks AngularJS dependency inference unless annotations are preserved. You must use array notation or $inject to ensure controllers and services resolve correctly after minification. Adding ng-strict-di to the root element during development and testing makes the injector throw an error on any un-annotated component, so minification failures are caught before deployment. One-time bindings (::) should be used in templates where data does not change to reduce watcher count during the digest cycle.

Server Configuration for Routing

If using HTML5 mode ($locationProvider.html5Mode(true)), the application needs a <base href="/"> tag in index.html (or html5Mode({ enabled: true, requireBase: false })), and the server must route all unknown paths to index.html. Without this, deep links return 404 errors on page refresh. Static file servers require specific rewrite rules to fall back to the application entry point. Ensure MIME types are correct for .json and .js files. If using a CDN, configure CORS headers appropriately. The server should also serve gzipped or Brotli-compressed assets to improve load times.

Code Example

The following demonstrates a minification-safe component using array notation and promise-based $http calls, alongside the minimal HTML required to bootstrap the bundled application.

angular.module('app')
  .component('mainApp', {
    controller: ['$http', function ($http) {
      var $ctrl = this;
      $ctrl.$onInit = function () {
        $http.get('/api/data').then(function (response) {
          $ctrl.data = response.data;
        });
      };
    }]
  });
<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
  <meta charset="utf-8">
  <!-- Required when $locationProvider.html5Mode(true) is enabled -->
  <base href="/">
  <script src="dist/app.bundle.js"></script>
</head>
<body>
  <main-app></main-app>
</body>
</html>

Security, Debugging and End of Life

Disable debug tools in production by configuring $compileProvider.debugInfoEnabled(false) in a config block, which prevents attackers from inspecting scope data via browser extensions and improves digest performance. AngularJS reached end of life on 31 December 2021, meaning no security patches or bug fixes are provided. Deploying new AngularJS projects is strongly discouraged. For existing applications, plan a migration to Angular (2+) or another modern framework. Options include a full rewrite or running both frameworks side by side with ngUpgrade. Monitor security advisories closely, as vulnerabilities discovered after the EOL date will not be patched by the core team.

79

Which build systems and bundlers are commonly employed when developing AngularJS applications?

AngularJS applications are typically assembled with task runners such as Gulp or Grunt and module bundlers such as Webpack, Browserify, or Parcel, which concatenate and minify JavaScript, cache templates, compile styles, and serve assets. Because AngularJS reached end of life on 31 December 2021, these toolchains are now used mainly to maintain legacy applications rather than to start new ones.

Read full answer

Applies toAngularJS 1.8.x

Task Runners vs Module Bundlers

  • Gulp and Grunt: orchestrate explicit file operations—copying HTML, compiling Sass, concatenating scripts, and running tests. They do not infer a dependency graph, so script ordering must be declared manually or through injection plugins.

  • Webpack, Browserify, and Parcel: begin from entry JavaScript files and statically resolve require or import references. They emit bundled assets and can also process CSS, images, and templates.

  • Choosing between them: a legacy AngularJS app that loads scripts globally usually requires the smallest change with a task runner. A codebase already using CommonJS or ES modules benefits from a bundler. Because AngularJS modules are registered at runtime through angular.module, bundlers cannot tree-shake AngularJS module definitions the way they can plain ES modules.

Minification-Safe Dependency Injection

AngularJS dependency injection derives dependency names from function parameter names, so minification breaks it unless the names are preserved. - Array notation: ['$http', function ($http) { ... }] keeps dependency names inside string literals. - $inject property: assigning MyController.$inject = ['$http'] provides the same protection. - Build-side annotation: tools such as ng-annotate can insert array notation automatically before minification, reducing manual boilerplate. - Minifier behavior: standard JavaScript minifiers do not mangle string literals, so dependency arrays are safe by default; the real risk is parameter renaming when array notation is missing.

Template Caching and Asset Bundling

Templates referenced with templateUrl normally cause HTTP requests at runtime. Build tools inline them into $templateCache so the application can run from a single bundle or offline. - Gulp plugins such as gulp-angular-templatecache compile HTML files into JavaScript modules that populate the cache during the build. - Webpack loaders can embed templates and register them with $templateCache while bundling. - Caching also avoids CORS or 404 problems when template paths change after deployment.

Common Toolchain Comparison

Tool Type Typical AngularJS role Best suited for
Gulp Task runner Minify, concatenate, cache templates, live reload Legacy global-script apps needing incremental tasks
Grunt Task runner Configuration-driven file processing Teams preferring declarative task configuration
Webpack Module bundler Bundle JS/CSS/templates, code splitting, source maps Apps using CommonJS/ES modules and modern assets
Browserify Module bundler Bundle CommonJS modules for the browser Smaller legacy projects migrating from script tags
npm scripts Build orchestrator Call CLI tools directly Simple pipelines without a dedicated runner

Best Practices and Migration

  • Reduce runtime watchers with one-time bindings (::) and track by in ng-repeat; no build tool can optimize the digest cycle.

  • Use $http promises with .then() and .catch(); .success() and .error() were removed in AngularJS 1.6.

  • Emit source maps so minified stack traces map back to original files.

  • Stay with plain JavaScript; adding TypeScript to an unmigrated AngularJS 1.8 application adds complexity without framework-level support.

  • Plan migration through ngUpgrade or a full rewrite, because no security patches are released after the end-of-life date.

80

What techniques and tools should you use to troubleshoot and debug an AngularJS application?

Debugging an AngularJS application combines standard browser developer tools with framework-specific techniques like the $log service, the debugger statement, and a solid understanding of the digest cycle. The most effective workflow uses Chrome or Firefox DevTools alongside AngularJS’s built-in error reporting and strict dependency injection mode to locate problems quickly.

Read full answer

Applies toAngularJS 1.8.x (note: AngularJS reached end of life on 31 December 2021, so this applies to maintaining existing apps, not new projects)

Browser Developer Tools

  • Console: View AngularJS error messages, which include error codes like $injector:modulerr that map to detailed documentation pages.

  • Sources panel: Set breakpoints in your JavaScript files, step through controllers and component lifecycle hooks, and inspect the call stack.

  • Network tab: Inspect $http requests and responses to verify API calls are being made correctly.

  • Elements panel: Check that bindings rendered correctly in the DOM and inspect applied CSS classes.

AngularJS-Specific Debugging Aids

  • ng-strict-di: Add this directive to your ng-app element to catch missing explicit dependency annotation, which prevents minification-breakage.

  • angular.reloadWithDebugInfo(): Call this in the console to reload the page with debug information enabled, exposing scope data on DOM elements.

  • Batarang: The official Chrome extension is deprecated and discontinued; rely on DevTools instead.

  • debugger statement: Insert it in any controller or hook to pause execution when DevTools is open.

Logging with $log

Use the $log service instead of console.log so logging can be decorated or disabled in production. It provides $log.log(), $log.info(), $log.warn(), $log.error(), and $log.debug().

Understanding the Digest Cycle

Many bugs stem from change detection. If you modify data outside AngularJS (e.g., in a third-party callback), wrap it in $scope.$apply() or use $timeout, which triggers a digest automatically. Keep watcher counts low with one-time bindings (::) and track by in ng-repeat to avoid performance issues that look like bugs.

Common Pitfalls and Best Practices

  • Digest already in progress: Calling $scope.$apply() inside an existing digest throws an error; prefer $timeout or check $rootScope.$$phase.

  • Memory leaks: Unregister event listeners and cancel timers in $onDestroy.

  • Silent failures: Always add a .catch() handler to $http promises; the old .success()/.error() methods were removed in 1.6.

  • Production logging: Disable debug info and $log.debug() output before deploying.

  • Migration context: For new work, plan a migration to Angular using ngUpgrade to run both frameworks side by side, or a full rewrite.

81

Which debugging approaches are typically used when troubleshooting AngularJS applications?

When an AngularJS application misbehaves, the fastest route to the cause is to combine browser developer tools with framework-specific hooks: the $log service, the digest cycle’s built-in error messages, and the scope and injector access that angular.element exposes from the console.

Read full answer

Applies toAngularJS 1.8.x, using JavaScript (ES5 or ES2015+) in the browser

Browser Developer Tools

The browser’s DevTools remain the first stop, used in AngularJS-specific ways:

  • Console: uncaught exceptions inside controllers, services and watchers appear here, already wrapped by AngularJS’s $exceptionHandler, which adds a stack trace.

  • Sources panel: set breakpoints inside a controller or service; because AngularJS calls watchers and callbacks during the digest cycle, hitting a breakpoint shows the call stack back into $digest, which reveals which binding triggered the code.

  • Network tab: every $http call is an XHR, so requests, responses, headers and timing are visible here; this is where failed API calls, missing JSON payloads or XSRF cookie problems show up.

  • Elements panel: inspect rendered DOM to confirm whether a directive was compiled and whether ng-class, ng-show or ng-if produced the expected markup.

Framework-Aware Logging with $log

Prefer the $log service over raw console.log in application code: it is injectable, mockable in unit tests, and respects $logProvider.debugInfoEnabled(). Use $log.debug() for verbose diagnostics and $log.error() for failures, so logging can be turned down in production. For errors that escape application code, register a custom $exceptionHandler decorator to send stack traces to a monitoring service while still logging to the console.

Inspecting Scopes, Injectors and Services from the Console

AngularJS exposes live internals through angular.element, which is useful for ad-hoc exploration without touching source files:

  • Scope: angular.element(element).scope() returns the scope bound to a DOM node, letting you read and set properties and then call $scope.$apply() to see the UI react. With jQuery integrated, any selector works; with jqLite alone, target elements that jqLite can find.

  • Injector: angular.element(document).injector() returns the application injector, and .get('serviceName') retrieves any service, factory or value — ideal for invoking a service method manually to isolate a bug.

  • Debug data: scope and binding information is attached to the DOM when $compileProvider.debugInfoEnabled(true) (the default); disabling it in production improves performance but removes this console access.

Technique Best for Caveat
DevTools breakpoints Logic errors in controllers/services Digest-cycle stacks can be deep
$log Diagnosable, testable logging Must be enabled and filtered by level
angular.element(...).scope() Inspecting live bindings Removed when debug info is disabled
Injector .get() Calling services in isolation Not available outside ng-app
Network tab $http and XSRF issues Only shows actual requests made

Debugging the Digest Cycle

Many AngularJS bugs are digest-cycle bugs, and the framework describes them clearly:

  • $apply already in progress: code inside AngularJS called $scope.$apply() manually; wrap external updates in $timeout instead, which schedules the digest safely.

  • Infinite $digest / infdig: a watcher keeps changing a value it observes, often a binding that returns a new array or object each time; stabilise it with track by in ng-repeat or by caching the computed value.

  • Missing updates: changes from non-AngularJS code (plain timers, legacy jQuery plugins) need $scope.$apply() or $timeout to reach the view.

  • Performance: slow digests usually mean too many watchers; reduce them with one-time bindings ({{ ::value }}), track by, and replacing large ng-repeat lists with pagination or virtualisation.

Testing and Network Debugging

Reproducing bugs in isolation is often faster than debugging the running app:

  • Unit tests with ngMock: inject the module under test, stub $http backend calls with $httpBackend.expectGET(...), and assert service behaviour — this separates view bugs from data bugs.

  • $httpBackend in tests: also surfaces malformed request URLs and wrong HTTP methods before they reach the browser.

  • Interceptors: register a $httpProvider.interceptors response interceptor to log every failing response centrally, which is more reliable than scattering error handlers.

  • End-to-end tests: Protractor was the historical AngularJS E2E tool but is deprecated and end-of-life; teams maintaining or extending test suites should choose a current framework such as Cypress or Playwright.

Tooling and Best Practices

  • AngularJS Batarang, the long-standing Chrome extension for scope and dependency profiling, is outdated and largely incompatible with modern browsers; use angular.element from the console or the standalone ng-inspector panel instead.

  • Enable ng-strict-di during development to catch minification-unsafe dependency injection early — the array or $inject annotation form is required for production builds.

  • Keep controllers thin and logic in testable services: bugs in services are far easier to isolate with unit tests than bugs embedded in watchers or template expressions.

  • Note that AngularJS itself reached end of life on 31 December 2021, so bugs fixed only by framework patches will not be fixed; for long-lived applications, plan migration to modern Angular, either as a rewrite or incrementally with ngUpgrade running both frameworks side by side.

82

What are some commonly used third-party libraries and plugins that integrate well with AngularJS?

AngularJS developers relied on a mature ecosystem of third-party libraries and plugins to extend the framework beyond its core, with ui-router for state-based routing, Angular Material for UI components, and utility libraries such as Moment.js and angular-translate being the most commonly discussed in interviews.

Read full answer

Applies toAngularJS 1.8.x (final release)

Navigation and Routing

AngularJS ships with the built-in ngRoute module, but the community widely adopted ui-router for anything beyond simple navigation:

  • ui-router: state-based routing with nested states, multiple named views, and resolve blocks that preload data before a state activates. It is the de facto standard for complex applications where the URL structure does not map cleanly to a single controller and template swap.

  • ngRoute: still adequate for small applications with a flat set of top-level views.

Feature ngRoute ui-router
Routing model URL to single view State to one or more named views
Nested views Not supported Nested states and views
Data preloading resolve on route resolve per state
Use case Simple apps Complex, hierarchical apps

UI Component Suites

  • Angular Material: an implementation of Google’s Material Design as AngularJS directives, offering accessible dialogs, date pickers, toolbars, grids and form controls. It is the most complete component suite for AngularJS apps.

  • UI Bootstrap (angular-ui-bootstrap): directives that wrap Bootstrap components such as modals, tooltips and datepickers. Note that this package is deprecated; for Bootstrap 4 layouts the maintained successor is UI Bootstrap 4 (ui-bootstrap4).

  • AngularUI: a family of smaller utility directives (e.g. ui.sortable, ui.calendar) that wrap jQuery plugins, useful when a full design system is not wanted.

Data Handling and Utilities

  • Restangular: an abstraction over $http for RESTful endpoints that reduces boilerplate by returning element and collection objects with bound methods (get, post, put, remove). It is a common interview alternative to hand-written $http calls.

  • angular-translate: the standard internationalization library, supporting asynchronous loading of translation tables, interpolation and pluralization.

  • Moment.js: widely used for date parsing, formatting and manipulation. It is in maintenance mode — the maintainers recommend it for existing apps but suggest lighter modern alternatives for new work.

  • Lodash / Underscore: general-purpose utilities for collection and object manipulation, frequently paired with AngularJS codebases.

End-of-Life Considerations

AngularJS reached end of life when long-term support ended on 31 December 2021, so version 1.8.x receives no further security patches or bug fixes. These libraries are therefore relevant mainly for maintaining legacy applications; new projects should be built in Angular (2+) or another modern framework. When the question of migration arises, the two standard options are a full rewrite in Angular or running both frameworks side by side with ngUpgrade for a gradual migration.

Best Practices and Pitfalls

  • Verify compatibility: confirm any plugin explicitly supports AngularJS 1.8.x, since many community packages stopped being maintained when the framework reached EOL.

  • Prefer components over directives: use angular.module(...).component() with one-way '<' bindings and controllerAs ($ctrl) rather than hand-written .directive() definitions.

  • Write minification-safe code: use array notation ['$http', function ($http) {...}] or the $inject property so dependency names survive build-time minification.

  • Keep watchers low: use one-time bindings ({{ ::value }}) and track by in ng-repeat to reduce digest-cycle work.

  • Check licenses and bundle size: jQuery-dependent plugins in particular can add significant weight; prefer AngularJS-native directives where possible.

83

In what ways can AngularJS be combined with external libraries or other web frameworks within a single application?

AngularJS integrates with external tools primarily through directives and services, which encapsulate third-party logic while bridging the gap between external change detection and AngularJS’s digest cycle. Since AngularJS reached end of life on 31 December 2021, these patterns are typically used for maintaining legacy systems or during migration rather than new development.

Read full answer

Applies toAngularJS 1.8.x

Encapsulating External Libraries

To use a library like jQuery or a charting tool, wrap it inside a directive or component. This isolates DOM manipulation from the rest of the application. When the external library modifies data or triggers events outside AngularJS’s context, you must manually trigger change detection. - Directives: Use .directive() when you need complex DOM manipulation or transclusion that components cannot handle easily. - Components: Prefer .component() for UI wrappers, utilizing the $postLink lifecycle hook for DOM initialization. - Digest Cycle: Call $scope.$apply() or use $timeout to ensure AngularJS recognizes changes made by external code. When external events trigger changes outside AngularJS, wrap the update in $timeout, which triggers a digest cycle implicitly and avoids “digest already in progress” errors. - jqLite Caveat: $element inside a directive or component controller is an AngularJS jqLite object unless jQuery is loaded before AngularJS; jQuery plugins require the full jQuery build loaded first.

Dependency Injection and Services

Wrap library functionality in services to make them testable and injectable. Ensure dependency injection is minification-safe by using array notation. - Service Wrapper: Create a service that initializes the library and exposes methods. - Minification Safety: Use ['$dep', function($dep) {...}] syntax so variable names survive uglification. - Compatibility: A service wrapping $http or a storage library should return promises compatible with AngularJS’s $q.

Hybrid Applications with ngUpgrade

For migrating to Angular (2+), ngUpgrade allows both frameworks to run in the same application. This is the recommended path for large-scale modernization rather than indefinite maintenance. - UpgradeModule: Bootstraps the AngularJS app inside an Angular module. - Downgrade/Upgrade: AngularJS components can be used in Angular templates via downgradeComponent, and vice versa. - Lifecycle: Both change detection systems run side-by-side, requiring careful zone management.

Best Practices and Pitfalls

Maintaining performance and stability is critical when mixing frameworks. - Watcher Limit: Keep watcher counts low; use one-time bindings (::) where data does not change. - DOM Access: Never manipulate DOM directly inside controllers; keep it within directives/components. - Memory Cleanup: Remove event listeners and timers in $onDestroy to prevent leaks during integration. - Security: Avoid injecting external HTML without sanitization; use $sce to trust explicit content.

84

How do you configure a route so that its data is fetched before the route activates, using the resolve option in AngularJS?

The resolve option lets you declare a map of dependencies that AngularJS prepares before it activates a route: each value (or the promise it returns) must resolve successfully, and the resolved values are then injected into the route’s controller.

Read full answer

Applies toAngularJS 1.8.x with `ngRoute` (the same concept exists in `ui-router`, with different event names). Note that AngularJS reached end of life on 31 December 2021, so this applies to maintaining existing applications, not starting new ones.

How It Works

  • You add a resolve property to a route definition in a config block that injects $routeProvider.

  • Each key in resolve becomes an injectable name for the route’s controller; each value is a function (or an array for minification-safe injection) that returns a value or a promise.

  • AngularJS waits for all promises to settle before loading the template and instantiating the controller, so the controller always receives ready-to-use data.

  • If any resolve promise is rejected, the navigation is cancelled and a $routeChangeError event is broadcast on $rootScope; the template and controller for that route are not created.

Key Features

  • Declarative data loading: the route configuration states what the route needs, rather than the controller triggering loads itself.

  • Promise-aware: $http calls, $resource promises and $q.all() compositions all work as resolve values.

  • Controller injection: resolved values are injected by name, exactly like $scope, $http or any other service.

  • Minification-safe: use the array annotation ['$http', function ($http) { ... }] or attach a $inject property, because the parameter names of resolve functions are also mangled by minifiers.

  • Per-route caching scope: resolved values live for the lifetime of the current route; navigating away and back re-runs the resolve functions.

Code Example

angular.module('app', ['ngRoute'])

  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider
      .when('/items', {
        templateUrl: 'items.html',
        controller: 'ItemsController',
        controllerAs: '$ctrl',
        // Resolved values are injected into the controller by their key names.
        resolve: {
          items: ['$http', function ($http) {
            return $http.get('/api/items').then(function (response) {
              return response.data;
            });
          }]
        }
      })
      .otherwise({ redirectTo: '/items' });
  }])

  .controller('ItemsController', ['items', function (items) {
    // `items` is the resolved array; no loading flag is needed here.
    this.items = items;
  }]);
// Handle failed navigation at the application level.
angular.module('app').run(['$rootScope', '$location', function ($rootScope, $location) {
  $rootScope.$on('$routeChangeError', function (event, current, previous, rejection) {
    // Log or redirect; the failed route's view is not rendered.
    $location.path('/error');
  });
}]);

Advantages and Limitations

Aspect Explanation
Cleaner controllers Controllers receive data instead of managing loading state
Better UX sequencing Templates can render immediately because data is already available
Centralised error handling One $routeChangeError handler can cover all failed loads
Slower navigation The view does not appear until every promise resolves, which can feel unresponsive for slow endpoints
No built-in caching Resolves re-run on each activation unless you cache inside a service

Best Practices and Common Pitfalls

  • Keep resolves fast: fetch only what the initial view needs; defer secondary data to the controller so navigation is not blocked.

  • Always handle rejection: listen for $routeChangeError ($stateChangeError with ui-router) to show feedback instead of leaving the user on a blank page.

  • Use array annotation: resolve: { items: ['$http', function ($http) { ... }] } keeps injection working after minification.

  • Prefer services over inline logic: put $http calls in a service and call it from the resolve function, so the logic is reusable and testable.

  • Don’t assume synchronous results: returning a value without a promise works, but mixing plain values with promises can hide the fact that the route still waits for the slowest one.

  • Watch the digest cycle: resolve functions run inside AngularJS, so use $http (which triggers $apply automatically) rather than raw callbacks that need manual $scope.$apply.

85

Which lifecycle events does the router broadcast during navigation in AngularJS, and when does each one fire?

Route lifecycle events are events broadcast on $rootScope by the router while the application moves from one route to another. They let you run logic before route dependencies are loaded, after a route has loaded successfully, when loading fails, or when only query-string parameters change. They are router-level events, distinct from component lifecycle hooks such as $onInit or $onDestroy.

Read full answer

Applies toAngularJS 1.8.x with `ngRoute` (`angular-route` 1.8.x); UI-Router notes apply to `@uirouter/angularjs` 1.x.

What Are Route Lifecycle Events

  • Broadcast events: subscribe with $rootScope.$on('$routeChangeStart', function (event, next, current) { ... }); the handler receives the event object plus the incoming and outgoing route objects.

  • Route objects: next and current are the matched route definitions, including templateUrl, controller, resolve, params and custom properties — not DOM elements.

  • Not component lifecycle: controllers and components still use their own hooks; route events describe navigation between routes.

ngRoute Navigation Sequence

  1. The URL changes and $location broadcasts $locationChangeStart; this is the documented event for cancelling navigation with event.preventDefault().

  2. If not prevented, $locationChangeSuccess fires and ngRoute matches the new URL against the route configuration.

  3. $routeChangeStart is broadcast with the next and current routes, then the template and any resolve dependencies start loading.

  4. When every resolve promise and the template load successfully, $routeChangeSuccess is broadcast; the ngView directive then instantiates the controller and compiles the view.

  5. If a resolve promise rejects or the template fails, $routeChangeError is broadcast with the rejection reason.

  6. When reloadOnSearch is false and only $location.search() changes, $routeUpdate fires instead of a full route change, so the controller instance is reused.

Event Reference

Event When it fires Typical use
$routeChangeStart After the URL matches a route, before templates and resolve dependencies load Authentication and permission checks, analytics, showing a loading indicator
$routeChangeSuccess After all resolve promises and the template load successfully Setup that depends on resolved data, starting timers or subscriptions
$routeChangeError When a resolve promise rejects or the template fails Show an error view, redirect to a fallback route, log the failure
$routeUpdate Query-string changed while reloadOnSearch is false React to filters or pagination without re-instantiating the controller
$viewContentLoaded Emitted on the ngView scope after its content is compiled DOM-dependent setup for the newly loaded view

UI-Router Differences

UI-Router has analogous state events such as $stateChangeStart, $stateChangeSuccess, $stateChangeError and $stateNotFound. In @uirouter/angularjs 1.x these legacy state events are deprecated in favour of transition hooks: use $transitions.onBefore(), $transitions.onStart(), $transitions.onSuccess() and $transitions.onError() instead. The hook callbacks receive a Transition object, which is more expressive than the older event payload and is the officially recommended API.

Best Practices and Common Pitfalls

  • Keep listeners lightweight: route events fire on every navigation, so avoid expensive synchronous work; put asynchronous work in resolve blocks.

  • Cancel at the right place: to prevent navigation outright, use $locationChangeStart with event.preventDefault(); inside $routeChangeStart, the reliable pattern is to redirect with $location.path() rather than to assume cancellation.

  • Clean up listeners: $rootScope.$on() returns a deregistration function; call it when the registering scope is destroyed to avoid leaks and duplicate handlers.

  • Do not confuse route events with digest timing: code that runs in these handlers should still use services such as $timeout or $q if it needs to defer changes safely.

  • Handle failures explicitly: a rejected resolve triggers $routeChangeError, so provide a fallback route or error state instead of leaving the previous view in an inconsistent state.

86

Describe how the require property is used when defining directives in AngularJS.

The require property in an AngularJS directive declaration tells the framework to inject the controller instance of another directive into the current directive’s link function, or bind it onto a component controller. This lets directives communicate through an explicit API instead of relying on scope inheritance or direct DOM manipulation.

Read full answer

Applies toAngularJS 1.8.x

What require Does

  • Controller injection: require looks up the controller registered by another directive via its controller property and passes it as the fourth argument to the link function, after scope, element, and attrs.

  • Directive-to-directive coupling: Unlike dependency injection, which provides singleton services, require wires up directive instances at compile/link time, so a child directive can call methods or read state exposed by a parent directive’s controller.

  • No scope pollution: Communication happens through the controller object, keeping the shared contract explicit instead of scattering data across $scope.

Require Prefixes

The string value of require can include prefixes that change where AngularJS searches for the controller and whether it is mandatory.

Prefix Example Behavior
(none) 'panelGroup' Controller must be on the same element; throws if missing
? '?panelGroup' Optional on the same element; null if not found
^ '^panelGroup' Search the same element and its parents; throws if missing
^? '^?panelGroup' Search parents, optional; null if not found
^^ '^^panelGroup' Search only ancestor elements, not the element itself (1.5+)
^^? '^^?panelGroup' Ancestors only, optional (1.5+)

Component Style Since 1.5

Since AngularJS 1.5, the recommended approach is angular.module(...).component(). Components support require as a string or an object. Required controllers are bound as properties on the component’s controller and are available in $onInit.

angular.module('app').component('panelItem', {
  bindings: {
    panelId: '<'
  },
  require: {
    panelGroup: '^^panelGroup'
  },
  controller: function () {
    var ctrl = this;
    ctrl.$onInit = function () {
      if (ctrl.panelGroup) {
        ctrl.panelGroup.addPanel(ctrl.panelId);
      }
    };
  }
});
<div panel-group>
  <div panel-item panel-id="'one'"></div>
  <div panel-item panel-id="'two'"></div>
</div>

Directive Code Example

A common pattern is a container directive exposing an API that nested directives consume.

angular.module('app').directive('panelGroup', [function () {
  return {
    controller: function () {
      var panels = [];
      this.addPanel = function (id) {
        panels.push(id);
      };
      this.getPanelCount = function () {
        return panels.length;
      };
    }
  };
}]);

angular.module('app').directive('panelItem', [function () {
  return {
    require: '^panelGroup',
    link: function (scope, element, attrs, groupCtrl) {
      // groupCtrl is the controller of the ancestor panelGroup directive
      groupCtrl.addPanel(attrs.panelId);
    }
  };
}]);
<div panel-group>
  <div panel-item panel-id="one"></div>
  <div panel-item panel-id="two"></div>
</div>

Multiple Requires

  • In a directive, pass an array: require: ['^panelGroup', '?^panelSummary']. The fourth link argument is an array of controllers in the same order, e.g. [groupCtrl, summaryCtrl].

  • In a component, pass an object: require: { group: '^panelGroup', summary: '?^panelSummary' }. Each required controller is bound to the named property on the component controller.

  • Object keys in a component are arbitrary names you choose; the prefix strings still use ^, ?, and ^^.

Best Practices and Pitfalls

  • Prefer components: Use .component() with one-way bindings ('<') for inputs and '&' for outputs; use .directive() with require only when a component cannot express the parent-child API you need.

  • Guard optional requires: Always check for null when using ?, ^?, or ^^?, otherwise calling a method on null throws at runtime.

  • Keep controllers thin: Expose only the methods child directives need; do not put business logic or $scope manipulation in a required controller.

  • Minification safety: Use array notation ['$compile', function ($compile) {...}] or the $inject property for any injected services.

  • Avoid circular requires: Two directives requiring each other on the same element cause a compile error; structure the relationship as parent-to-child instead.

  • End of life: AngularJS reached end of life on 31 December 2021, so for new projects consider Angular, React, or another maintained framework; for migration, options include rewriting in Angular or running both with ngUpgrade.

87

How can you validate user input in AngularJS using directives?

AngularJS validates input declaratively by combining ng-model with built-in validator directives such as required, and you can extend this with custom directives that hook into the ngModelController and register their own validators.

Read full answer

Applies toAngularJS 1.8.x (the final 1.x release; AngularJS reached end of life on 31 December 2021, so use it only for maintaining existing apps, not new projects).

How Built-in Validation Works

AngularJS form validation depends on three pieces working together:

  • ng-model: binds the input to a scope property and exposes the ngModelController, which stores the value and its validity state.

  • Form controller: an element with ng-form (or a named <form>) gets a FormController published on the scope under its name attribute. Each named input appears on it, so myForm.email.$valid is readable in markup or controllers.

  • CSS classes: AngularJS adds classes such as ng-valid, ng-invalid, ng-pristine, ng-dirty, ng-touched and ng-untouched, so you can style feedback without writing the logic yourself.

State flags you will use constantly are $pristine/$dirty (has the value changed?), $touched/$untouched (has the field been blurred?) and $valid/$invalid.

Built-in Validators

Directive / attribute Purpose
required / ng-required Value must be non-empty; ng-required lets you control this with an expression
ng-minlength / ng-maxlength Minimum and maximum text length
ng-pattern Value must match a regular expression
type="email", type="url", type="number" Browser input types that AngularJS also validates
ng-min / ng-max Numeric bounds for type="number"
ng-model-options Tuning such as updateOn: 'blur' or debounce, which controls when validation runs

Error details live in formName.fieldName.$error, a map keyed by validator name, which the ngMessages module (from angular-messages.js) renders cleanly.

Writing a Custom Validation Directive

Use .directive() only for things a .component() cannot do; validation attributes are exactly that case. The standard pattern since 1.3 is to require the ngModel controller and add to its $validators object:

  • require: 'ngModel' gives your directive the fourth injection argument, the ngModelController.

  • ngModel.$validators.yourName = function (modelValue, viewValue) {...} returns true for valid. These run on every digest, so keep them cheap and synchronous.

  • ngModel.$asyncValidators.yourName is for server-side checks; it returns a promise and sets ng-pending until it resolves.

  • For raw input cleanup, push into ngModel.$parsers (view to model) and ngModel.$formatters (model to view); return undefined from a parser to keep an invalid value out of the model.

  • Call ngModel.$setValidity('yourName', false) inside parsers when you reject a value manually.

Code Example

<!-- Requires angular.js and angular-messages.js to be loaded -->
<form name="userForm" ng-submit="$ctrl.submit()" novalidate>
  <label>Email
    <input type="email" name="email" ng-model="$ctrl.user.email"
           required ng-minlength="5" ng-model-options="{ updateOn: 'blur' }">
  </label>
  <div ng-messages="userForm.email.$error" ng-if="userForm.email.$touched">
    <span ng-message="required">Email is required.</span>
    <span ng-message="email">Enter a valid email address.</span>
    <span ng-message="minlength">Too short.</span>
  </div>

  <label>Username
    <input type="text" name="username" ng-model="$ctrl.user.username"
           required unique-username>
  </label>
  <div ng-messages="userForm.username.$error" ng-if="userForm.username.$touched">
    <span ng-message="required">Username is required.</span>
    <span ng-message="uniqueUsername">Already taken.</span>
  </div>

  <button ng-disabled="userForm.$invalid">Save</button>
</form>
angular.module('app', ['ngMessages'])

  .component('userEditor', {
    controllerAs: '$ctrl',
    controller: ['$scope', 'UserService', function ($scope, UserService) {
      this.user = { email: '', username: '' };

      this.submit = function () {
        // A named form is published on the scope, so read it from $scope, not `this`
        if ($scope.userForm.$invalid) {
          return;
        }
        UserService.save(this.user);
      };
    }]
  })

  // Synchronous validator: no spaces allowed
  .directive('uniqueUsername', function () {
    return {
      restrict: 'A',
      require: 'ngModel',
      link: function (scope, element, attrs, ngModel) {
        ngModel.$validators.uniqueUsername = function (modelValue) {
          // Empty is handled by 'required'; don't double-report it here
          if (ngModel.$isEmpty(modelValue)) {
            return true;
          }
          return !/\s/.test(modelValue);
        };
      }
    };
  });

Best Practices and Common Pitfalls

  • Let the framework do the work: bind with ng-model, read state from the form controller, and avoid manually inspecting the DOM for values or errors.

  • Remember where form controllers live: a named form is published on the scope, so with controllerAs you reach it in your controller as $scope.formName, not this.formName.

  • Validate on the server too: client-side validation in AngularJS is for UX only; it can be bypassed, and AngularJS 1.8.x no longer receives security patches.

  • Prefer $validators over $parsers for pure validity checks; parsers are for transforming or rejecting the raw view value.

  • Keep async validators lightweight and debounce them, since each digest can re-trigger them.

  • Reset state deliberately with formName.$setPristine() and $setUntouched() after a successful save, otherwise stale error styling persists.

  • Watch the digest loop: heavy validators inside ng-repeat or large forms add watchers; use track by and one-time bindings (::) where the markup allows.

88

Explain how scope inheritance behaves in directives and when each scope configuration should be used.

In AngularJS 1.8.x, a directive’s scope option controls whether it shares the parent scope, creates a new child scope that prototypically inherits from the parent, or uses a completely isolated scope with no inheritance. Understanding this choice is essential because it determines how data bindings flow and whether writes from the directive affect the parent controller’s state.

Read full answer

Applies toAngularJS 1.8.x (AngularJS reached end of life on 31 December 2021; for new projects use Angular or another supported framework)

The Three Scope Configurations

  • scope: false (default): The directive uses the parent scope directly; any read or write affects the parent controller’s scope. This option is not available on components: angular.module(...).component() always creates an isolated scope.

  • scope: true: AngularJS creates a new child scope that prototypically inherits from the parent. Reads fall through to the parent if the property is not found on the child, but writes create or shadow properties on the child scope.

  • scope: {}: An isolated scope with no prototypal relationship to the parent. Properties must be explicitly bound via the directive definition object using < (one-way), = (two-way), @ (string), or & (expression) bindings.

How Prototypal Inheritance Causes Bugs

The classic pitfall occurs with scope: true and ng-model bound to a primitive:

  • Reading {{userName}} works because JavaScript walks up the prototype chain to the parent.

  • Writing via ng-model="userName" creates a new userName property on the child scope, shadowing the parent’s value. The parent controller never sees the change.

This is why the “dot rule” exists: always bind to an object property like ng-model="user.name". Since user is an object reference shared through the prototype chain, assigning to user.name mutates the same object the parent holds.

Comparison Table

Configuration Inherits from parent Writes affect parent Typical use
scope: false Yes, same scope object Yes Simple DOM decoration, no own state
scope: true Yes, new child scope No (shadows primitives) Directives needing local state but reading parent data
scope: {} No Only via explicit bindings Reusable components; the default for .component()

Best Practices and Common Pitfalls

  • Prefer .component() with isolated scope for any reusable UI piece; it avoids accidental parent-state mutation and matches the AngularJS 1.5+ component-based style.

  • Use .directive() with scope: true or scope: false only for DOM manipulation logic that genuinely needs parent context, such as a focus helper or a third-party widget wrapper.

  • Never bind ng-model to a primitive on an inherited scope; wrap values in an object (user.name) or use an isolated scope with explicit bindings.

  • Keep watchers low: isolated scope boundaries keep watcher ownership clear, and one-time bindings (::) plus track by in ng-repeat cut digest-cycle cost.

  • Remember lifecycle hooks: with .component(), use $onInit, $onChanges, and $onDestroy; with .directive(), use link and optionally controller with the same hooks.

  • Plan for migration: since AngularJS is end-of-life, isolate scope boundaries now make future migration to Angular (or running both via ngUpgrade) significantly easier.

90

Explain what the compile function and the pre-link and post-link functions do in an AngularJS directive.

In AngularJS directives, the compile function transforms a directive’s template once before any scope is attached, while the pre-link and post-link functions attach behavior to each directive instance, with post-link being the safe place for DOM manipulation because child elements are already linked there.

Read full answer

Applies toAngularJS 1.8.x (end of life; long-term support ended 31 December 2021)

The Compile Phase

The compile function runs once per directive template during the compilation phase, before scopes are created or attached. It receives the template element tElement, template attributes tAttrs, and a transcludeFn. Its job is template-level DOM transformation that is identical for every instance, such as adding classes, restructuring markup, or normalizing attributes. Because no scope exists yet, you cannot set up watchers or read scope values here. The compile function returns either a single link function (which becomes the post-link function) or an object with pre and post properties.

Pre-Linking vs Post-Linking

The link phase runs once per directive instance, after the scope is attached. A link function receives scope, element, attrs, controller, and transcludeFn.

Aspect Pre-Link Post-Link
Execution timing Before child elements are linked After child elements are linked
DOM traversal direction Parent to child Child to parent
Safe for DOM changes No (children not ready) Yes
Typical use Rare; nested controller setup Event listeners, watchers, DOM work
Default when returning a function No Yes

Execution Order

For a nested structure, AngularJS walks the DOM in a specific order:

  1. Compile runs top-down once per template.

  2. Pre-link functions run top-down (parent before children).

  3. Post-link functions run bottom-up (children before parent).

This ordering is why post-link is the reliable place to work with child elements: by the time a parent’s post-link runs, every child directive and its scope is fully initialized.

When to Use Each

  • Use compile for one-time template transformations shared across all instances, which keeps the number of watchers low and improves performance.

  • Use pre-link only in rare cases where a parent directive must set up state before its children link, such as certain nested form or controller scenarios.

  • Use post-link for almost everything else: registering event handlers with element.on, setting up $watch or $observe, and manipulating the DOM based on scope values.

  • Prefer .component() (available since 1.5) for most UI logic; write a .directive() with compile/link only when you need direct DOM control that a component cannot provide.

Common Pitfalls

  • Doing scope work in compile: no scope exists yet, so reading or watching scope values there fails or captures the wrong value.

  • Manipulating children in pre-link: child elements are not linked, so their behavior and scopes are not ready.

  • Forgetting $apply: event handlers attached with element.on run outside the digest cycle, so wrap scope changes in scope.$apply (or prefer ng-click instead).

  • Returning both a link function and a link object: if compile returns a function, it is used as the post-link function and any pre/post object is ignored.

  • Using .directive() when .component() suffices: components give you $onInit, $onChanges, and one-way bindings with less boilerplate; reserve compile/link for low-level DOM needs.

91

What strategies and patterns do you use to handle application state in large-scale AngularJS projects?

In large AngularJS applications, state is best managed through dedicated singleton services that act as a single source of truth, combined with a component-based architecture using one-way data flow and a router for URL-driven state. This keeps state predictable, testable, and decoupled from individual controllers. Note that AngularJS reached end of life with long-term support ending 31 December 2021, so these patterns are most relevant for maintaining existing applications rather than starting new ones.

Read full answer

Applies toAngularJS 1.8.x

Core Pattern: State Services as Single Source of Truth

The foundation is a service (created with .service() or .factory()) that owns the state and exposes methods to read and mutate it. Components inject this service and bind to its data.

  • Singleton lifetime: AngularJS services are singletons, so every component sees the same state instance.

  • Explicit mutation methods: Instead of letting components write directly to shared objects, expose methods like updateUser() or loadItems() that encapsulate the change logic.

  • Promise-based async: Use $http with .then() and .catch(); the old .success() and .error() shortcuts were removed in 1.6.

  • Minification-safe injection: Always define dependencies with the array notation (['$http', function ($http) {...}]) or $inject, since large apps are always minified in production.

One-Way Data Flow Through Components

Since AngularJS 1.5, use angular.module(...).component() with controllerAs: '$ctrl'. Pass state down with one-way bindings ('<') and send user actions up with output bindings ('&'). This makes the direction of data explicit and reduces accidental mutations.

Binding Symbol Purpose
One-way input '<' Parent passes data down; child should not mutate it
Output callback '&' Child notifies parent of an action
Two-way '=' Avoid in large apps; creates hidden coupling
Attribute string '@' For static strings only

Routing and URL State

For navigation state, use a router such as ui-router, the most popular third-party router for AngularJS (the framework’s built-in alternative is ngRoute), rather than storing route state in a service. URL parameters and the router’s state object represent the authoritative navigation state, which is bookmarkable and back-button friendly.

What to Avoid

  • $rootScope.$broadcast / $emit event buses: These create hidden dependencies and make state changes hard to trace. Use service methods or component callbacks instead.

  • Direct $scope sharing: Prefer $ctrl bindings over scope inheritance chains.

  • Mutating one-way inputs: If a child needs to change data, call an output binding ('&') and let the parent (or the state service) perform the update.

Best Practices and Common Pitfalls

  • Keep watchers low: Use track by in ng-repeat and one-time bindings (::) for static data to reduce digest-cycle work.

  • Trigger digests correctly: Code running outside AngularJS (third-party callbacks, timers) needs $scope.$apply() or $timeout() to be noticed; $timeout applies automatically.

  • Consider ngRedux for very large apps: If state becomes complex, the third-party ngRedux library brings Redux-style immutable updates and devtools to AngularJS 1.x.

  • Plan migration: For new work, consider migrating to Angular (2+) using ngUpgrade to run both frameworks side by side, since AngularJS no longer receives security updates.

92

How do the built-in $route service and the $stateProvider from UI Router differ when handling navigation and view management in AngularJS?

The fundamental difference is that $route (via ngRoute) maps URLs directly to views in a flat structure, while $stateProvider (via ui-router) manages hierarchical application states that may or may not correspond directly to URL changes. $route is suitable for simple applications requiring basic page switching, whereas $stateProvider supports nested views, multiple named views per state, and complex parameter handling essential for large-scale interfaces.

Read full answer

Applies toAngularJS 1.8.x with ui-router 1.0.x

Core Architecture and Philosophy

  • $route (ngRoute): URL-centric. Each route binds a URL pattern to a template and controller. The view hierarchy is flat; there is no built-in support for nesting views.

  • $stateProvider (ui-router): State-centric. A state represents a place in the application in terms of overall UI and data. States can nest, allowing child views to inherit controllers and resolved data from parent states.

Configuration and Syntax

  • Configuration happens during the app’s config phase using providers.

  • $routeProvider: Uses .when(path, config) to define routes and .otherwise(config) for fallbacks.

  • $stateProvider: Uses .state(name, config) to define named states with optional url properties.

  • Dependency injection: Both require minification-safe array notation (['$dep', fn]) or $inject when injected into config blocks.

Advanced Capabilities

  • Nested views: ui-router supports parent.child states, enabling shared layouts and incremental view updates. ngRoute requires manual composition or full template swaps.

  • Multiple named views: ui-router supports views: { 'header': {...}, 'body': {...} } within a single state. ngRoute supports only one ng-view per page.

  • Parameters: ui-router handles complex parameter objects, query parameters, and parameters that do not appear in the URL more flexibly.

  • Transitions: ui-router provides transition hooks (onEnter, onExit, and the $transitions service), whereas ngRoute relies on $routeChangeStart and $routeChangeSuccess events.

Comparison Summary

Feature ngRoute ($route) ui-router ($stateProvider)
Model URL → view State → UI and data
View hierarchy Flat Hierarchical
Named views Single ng-view Multiple per state
Parameters Simple, URL-only URL, query, and non-URL
Change hooks $routeChange* events onEnter/onExit, $transitions
Origin Built-in ngRoute module Third-party ui-router module

Selection Guidance

Choose ngRoute when the application is small, has a single main view, and requires minimal dependencies. Choose ui-router when the UI involves complex workflows, dashboards with multiple panels, or deep nesting where preserving state between navigation steps is necessary. Note that AngularJS reached end of life on 31 December 2021; for new projects, consider migrating to Angular or another modern framework instead of investing in either routing solution.

Best Practices and Pitfalls

  • Minification safety: Always use array notation ['$dep', function($dep){}] or the $inject property to prevent dependency injection errors after minification.

  • Component style: Prefer binding components (component: 'name') over template + controller pairs where possible, aligning with AngularJS 1.5+ standards.

  • Watchers: Excessive nesting in ui-router can increase watcher counts. Use one-time bindings (::) and track by in ng-repeat to reduce digest cycle load.

  • Avoid mixing: Do not combine ngRoute and ui-router in the same application; they compete for URL control and can cause infinite digest loops.

93

What mechanisms are available for persisting data in AngularJS applications, and how are they typically used?

AngularJS itself has no built-in persistence layer; data persistence is handled by combining browser storage APIs for local caching with server communication through the $http service or dedicated libraries. The framework provides the tooling to read and write data, but where and how long that data survives depends on the storage backend you choose. Note that AngularJS reached end of life (long-term support ended 31 December 2021), so these mechanisms are primarily for maintaining existing applications rather than starting new ones.

Read full answer

Applies toAngularJS 1.8.x

How Persistence Works in AngularJS

AngularJS is a client-side MVC framework, so persistence happens outside the framework core. A typical application persists data in one of three places:

  • Server-side database: The most common approach. AngularJS sends data over HTTPS to a REST API using $http or $resource, and the backend stores it in a database. The client holds only a transient copy.

  • Browser storage: For offline support, caching, or remembering user preferences, data is saved locally using localStorage, sessionStorage, IndexedDB, or cookies via the ngCookies module’s $cookies service.

  • Third-party sync libraries: Libraries such as angular-cache, ngStorage, or angularfire (for Firebase) wrap storage and add features like expiration, synchronization, and query support.

Client-Side Storage Options

Storage Lifetime Capacity Best For
$cookies (ngCookies) Until expiry or browser close ~4 KB per cookie Small tokens, short-lived flags
sessionStorage Current tab session only ~5-10 MB Temporary form state
localStorage Until explicitly cleared ~5-10 MB User preferences, cached data
IndexedDB Until explicitly cleared Large, structured data Offline apps, large datasets

For new features, prefer localStorage or IndexedDB over cookies, since cookies are sent with every HTTP request and have strict size limits. Never store sensitive data like passwords or full authentication tokens in any client-side storage, because JavaScript running in the browser can read it.

Server Communication with $http

The standard pattern is a minification-safe service that wraps $http calls and returns promises. In AngularJS 1.8.x, $http returns promises, so use .then() and .catch(); the old .success() and .error() shortcut methods were removed in 1.6 and must not be used. Always send data to an HTTPS endpoint and let the server validate and authorize every write.

Best Practices and Pitfalls

  • Keep persistence logic out of controllers: Put all storage and $http code in a service or factory, then inject it into a component controller.

  • Use component-style architecture: Define UI with angular.module(...).component() using controllerAs: '$ctrl', one-way bindings ('<') for inputs, and '&' for outputs. Call the persistence service from $onInit or from an action handler.

  • Handle promise rejections: Always attach a .catch() handler; unhandled rejections can leave the UI in an inconsistent state.

  • Avoid storing secrets client-side: Authentication tokens belong in secure, httpOnly cookies set by the server, not in localStorage.

  • Plan for end of life: Because AngularJS is no longer supported, new projects should use a current framework. For existing apps, migration options are rewriting in Angular (2+) or running both frameworks together with ngUpgrade.

94

Which approaches can be used to persist application state in AngularJS when the browser refreshes the page?

To preserve application state across page reloads in AngularJS, developers rely on client-side storage mechanisms — Web Storage (localStorage and sessionStorage) or cookies — usually wrapped in a service so the rest of the application accesses them through a single, consistent API. Because AngularJS controllers and their scopes are recreated on every reload, state must be serialized and written to one of these stores before the refresh happens and read back during component initialization.

Read full answer

Applies toAngularJS 1.8.x

Storage Mechanisms

The three standard options differ in lifetime, capacity and how they interact with the server:

  • localStorage: survives browser restarts and is cleared only by explicit user or application action, so it suits long-lived data such as user preferences, UI layout or cached reference data.

  • sessionStorage: scoped to the browser tab and cleared when the tab closes, making it a good fit for temporary workflow state such as a multi-step form in progress.

  • Cookies: sent automatically with every matching HTTP request, which makes them the right choice for data the server must read on the next request, but they are much smaller and should not be used as a general client-side cache.

Mechanism Lifetime Approximate size limit Sent to the server Typical AngularJS access
localStorage Until cleared ~5 MB per origin No $window.localStorage
sessionStorage Until tab closes ~5 MB per origin No $window.sessionStorage
Cookies Until expiry ~4 KB per cookie Yes $cookies (ngCookies module)

AngularJS Integration

Web Storage is accessed through the injected $window service rather than the global window object, which keeps the code testable. For cookies, AngularJS provides the $cookies service from the ngCookies module (angular-cookies.js), with $cookies.put and $cookies.get for strings and $cookies.getObject/$cookies.putObject for objects. Whatever the mechanism, the store should be hidden behind a factory or service so components call get/set methods instead of touching storage directly.

State is typically restored in the $onInit lifecycle hook and written back when it changes — for example from an ng-change handler on an input, or from $onChanges/$onDestroy when a component’s inputs change or it is torn down. Web Storage only stores strings, so objects must be serialized with JSON.stringify and read back with JSON.parse, wrapped in error handling because corrupted or manually edited data will throw. If a component reacts to the browser’s storage events (fired when another tab changes the store), those events arrive outside the digest cycle, so scope updates must be wrapped in $scope.$apply or, preferably, $timeout to avoid digest errors.

Security and Best Practices

  • Never store secrets: passwords, refresh tokens or other sensitive data must not be kept in client-side storage, because any XSS attack on the page can read it; authentication state belongs in an HttpOnly, Secure, SameSite cookie set by the server.

  • Expect end-of-life constraints: AngularJS reached end of life on 31 December 2021, so these patterns are for maintaining legacy applications; new work should use a supported framework, and migration options are a rewrite in Angular or running AngularJS and Angular side by side with ngUpgrade.

  • Keep watchers low: avoid deep $watch on large persisted objects and prefer one-time bindings (::) for data that does not change after initialization.

  • Store the minimum: persist only what is needed to restore the view, keep the serialized payload small, and version the storage key so a new application release can migrate or discard stale data cleanly.

95

What practices should developers follow and avoid when building applications with AngularJS?

The essential AngularJS best practices revolve around using the component-based architecture introduced in 1.5, writing minification-safe dependency injection, handling promises correctly, and keeping the digest cycle efficient by limiting watchers. Note that AngularJS reached end of life with long-term support ending 31 December 2021, so these practices apply to maintaining existing applications rather than starting new projects, where migration to Angular or another framework is recommended.

Read full answer

Applies toAngularJS 1.8.x

Architecture and Component Style

Since AngularJS 1.5, prefer angular.module(...).component() over .directive() for building the UI. Components give you a clear template/controller separation, isolated bindings, and lifecycle hooks.

  • Use .component(): Define reusable UI pieces with bindings, a controller, and a template or templateUrl.

  • controllerAs syntax: Use $ctrl (the default alias for components since 1.5) in templates instead of $scope.

  • One-way bindings: Pass data in with '<' to avoid accidental two-way digest loops; use '=' only when true two-way binding is required.

  • Outputs with '&': Communicate to parents via expression bindings rather than $emit/$broadcast, which are discouraged for component-to-component communication.

  • Lifecycle hooks: Implement $onInit for setup, $onChanges for input changes, $postLink for DOM work, and $onDestroy for cleanup.

  • Reserve .directive(): Use it only when you need transclusion-heavy behavior, custom DOM manipulation that a component cannot express, or structural directives.

Dependency Injection and $http

Always write DI in a minification-safe form so uglification does not break parameter names.

  • Array notation: ['$http', function ($http) { ... }] or the $inject property on the controller function.

  • Promise handling: $http returns promises; chain with .then() and .catch(). The .success()/.error() shortcuts were removed in 1.6 and must not be used.

  • Avoid $scope in controllers: Push state onto this ($ctrl) and let the view read from it.

Change Detection and Performance

AngularJS runs a digest cycle that checks every watcher, so unbounded watchers cause slow rendering.

  • One-time binding ::: Use {{::value}} for data that never changes after first render.

  • track by in ng-repeat: Always add track by with a stable id to avoid re-creating DOM nodes.

  • Use $timeout: Wrap third-party callbacks with $timeout (which triggers $apply internally) instead of calling $scope.$apply() manually.

  • Limit watchers: Avoid heavy expressions inside bindings and filters that run on every digest.

Do Don’t
.component() with $ctrl $scope-heavy controllers
'<' one-way bindings '=' two-way everywhere
.then()/.catch() on $http .success()/.error() (removed in 1.6)
track by and :: Untracked ng-repeat lists
$timeout for async wraps Raw setTimeout without $apply

Common Pitfalls

  • Forgetting minification safety: Plain function ($http) {} breaks after build tools rename parameters.

  • Using removed APIs: .success()/.error() no longer exist in 1.8; always use .then()/.catch() on the promise returned by $http.

  • Digest in progress errors: Calling $scope.$apply() inside something already in a digest throws; prefer $timeout or $evalAsync.

  • Overusing $emit/$broadcast: Scope events still work in 1.8 but are harder to trace; prefer '&' bindings for component outputs.

  • Memory leaks: Always clean up timers, intervals, and external listeners in $onDestroy.

  • Starting new work in AngularJS: For greenfield projects, choose Angular or another supported framework; use ngUpgrade only to run both frameworks side by side during a gradual migration.

96

Identify and explain common anti-patterns that developers should avoid when maintaining or building applications with AngularJS.

AngularJS development often suffers from anti-patterns that degrade performance and maintainability, such as manipulating the DOM directly in controllers or overusing the scope hierarchy. Recognizing these pitfalls is crucial for keeping applications stable, especially since AngularJS reached end of life on 31 December 2021 and should not be used for new projects.

Read full answer

Applies toAngularJS 1.8.x

Scope and Watcher Overload

Every interpolation and two-way binding registers a watcher that participates in the digest cycle. Placing many {{ }} expressions inside ng-repeat, or creating watchers inside loops, makes dirty checking expensive and can cause visible lag. Use one-time bindings (::) for values that never change after initialization, and supply a track by expression in ng-repeat so Angular can reuse DOM elements instead of recreating watchers. Avoid calling $scope.$digest() or $scope.$apply() manually unless you are integrating non-Angular code; an $apply already in progress throws an error, while a missed $apply leaves the view stale.

DOM Manipulation and Component Structure

Controllers and component controllers should hold state and business logic only. Reading or writing the DOM with jQuery or document.getElementById inside a controller breaks separation of concerns and makes unit testing difficult. DOM work belongs in a directive or in the component $postLink lifecycle hook, after the template is rendered. Prefer .component() with controllerAs: '$ctrl' (the default) over .directive() with manual scope inheritance, because prototypal inheritance can shadow properties and produce surprising binding behavior.

Dependency Injection and Promise Handling

AngularJS infers dependency names from function parameter names, so minification breaks injection unless dependencies are declared explicitly. Use array notation—['$http', function ($http) {...}]—or a static $inject property. The $http service once offered .success() and .error() callbacks, but those were removed in AngularJS 1.6; using them in 1.8.x throws a runtime error. Always consume $http with standard promise methods: .then() for success and .catch() for failures.

Migration and End of Life Considerations

Long-term support for AngularJS ended on 31 December 2021, so no security patches are published. Existing applications should be isolated behind modern gateways and kept on supported browsers. Migration paths are a full rewrite in Angular (2+) or a hybrid transition with ngUpgrade, which bootstraps both frameworks side by side. Avoid adding new features to an AngularJS codebase unless there is no viable alternative.

Best Practices Summary

  • Components: Use .component() for UI elements and rely on $onInit, $onChanges, $postLink, and $onDestroy.

  • Watchers: Reduce watcher count with :: one-time bindings and track by in repeated lists.

  • Dependency injection: Always declare dependencies with array notation or $inject so minification is safe.

  • Promises: Replace .success() / .error() with .then() / .catch().

  • State management: Keep shared state in services instead of polluting $rootScope.

  • Security: Validate and encode user input; AngularJS auto-escapes interpolation but legacy expressions and custom directives can still expose XSS risks.

Sign in to keep reading

You've opened 2 full answers. Sign in to read all 96 AngularJS answers, mark what you know and pick up where you left off.

All Web and Mobile Development subjects