AngularJS Interview Questions (Free Preview)
Free sample of 15 from 44 questions available
Directives
What is the difference between ng-show/ng-hide and ng-if?
The 30-Second Answer:
ng-show and ng-hide toggle the CSS display property to show or hide elements while keeping them in the DOM, whereas ng-if completely removes or adds elements to the DOM based on the condition. This makes ng-if more performant for heavy elements but comes with the cost of destroying and recreating scope.
The 2-Minute Answer (If They Want More):
The choice between ng-show/ng-hide and ng-if impacts both performance and behavior. With ng-show/ng-hide, the element and its children are always present in the DOM, just hidden via CSS (display: none). This means all watchers, event listeners, and scope properties remain active even when hidden. For simple elements that toggle frequently, this is efficient since there's no DOM manipulation overhead.
In contrast, ng-if creates or destroys the DOM element and its associated scope each time the condition changes. When the condition becomes false, ng-if removes the element from the DOM and destroys its scope, cleaning up all watchers and event listeners. When the condition becomes true again, it creates a new element with a new child scope. This makes ng-if ideal for heavy components with many watchers or complex initialization logic, as it frees up resources when hidden.
Another critical difference is scope inheritance. ng-if creates a child scope, which can lead to scope inheritance issues if you're not careful with primitive values. Elements hidden with ng-show/ng-hide remain in the same scope, avoiding these pitfalls.
Form validation also behaves differently: hidden fields with ng-show/ng-hide still participate in form validation, while ng-if removed fields are excluded from the form's validation state. Choose ng-show/ng-hide for simple, frequently toggled UI elements and ng-if for resource-intensive components that are rarely displayed or contain heavy initialization logic.
Code Example:
<div ng-app="myApp" ng-controller="ToggleCtrl">
<button ng-click="toggle()">Toggle Display</button>
<!-- ng-show/ng-hide: Element stays in DOM, just hidden via CSS -->
<div ng-show="isVisible" class="show-hide-example">
<p>Shown with ng-show (always in DOM)</p>
<input type="text" ng-model="showHideValue">
<!-- This element exists in DOM even when hidden -->
</div>
<!-- ng-if: Element completely removed from DOM when false -->
<div ng-if="isVisible" class="ng-if-example">
<p>Shown with ng-if (removed from DOM when false)</p>
<input type="text" ng-model="ngIfValue">
<!-- This element doesn't exist in DOM when isVisible is false -->
</div>
<!-- Demonstrating scope differences -->
<div ng-if="isVisible">
<!-- ng-if creates a child scope, need $parent or object notation -->
<button ng-click="$parent.counter = $parent.counter + 1">
Increment (ng-if scope)
</button>
</div>
<p>Counter: {{counter}}</p>
</div>
angular.module('myApp', [])
.controller('ToggleCtrl', function($scope) {
$scope.isVisible = true;
$scope.counter = 0;
$scope.toggle = function() {
$scope.isVisible = !$scope.isVisible;
// When toggling to false with ng-if:
// - Element is removed from DOM
// - Child scope is destroyed
// - Watchers are cleaned up
// With ng-show, element stays in DOM with display:none
};
});
Comparison Table:
| Feature | ng-show/ng-hide | ng-if |
|---|---|---|
| DOM Presence | Always in DOM | Added/removed from DOM |
| CSS Behavior | display: none/block |
Element created/destroyed |
| Scope | Same scope | Creates child scope |
| Performance (Toggle) | Fast (CSS only) | Slower (DOM manipulation) |
| Performance (Hidden) | Uses memory/watchers | Frees resources |
| Form Validation | Fields still validated | Fields excluded when removed |
| Initialization | Runs once | Runs each time shown |
| Use Case | Frequently toggled simple elements | Heavy components, conditional rendering |
References:
- AngularJS ng-show Documentation
- AngularJS ng-if Documentation
- Understanding ng-if vs ng-show - Stack Overflow
What is ng-repeat and how does it work?
The 30-Second Answer:
ng-repeat is a directive that instantiates a template once for each item in a collection, creating a new child scope for each iteration. It's AngularJS's primary tool for rendering lists and automatically updates the DOM when the underlying collection changes through data binding.
The 2-Minute Answer (If They Want More):
ng-repeat is one of the most commonly used directives in AngularJS, enabling you to dynamically generate DOM elements based on arrays or objects. Each iteration creates a child scope that prototypes from the parent scope and includes special properties like $index, $first, $last, $middle, $even, and $odd that help with conditional rendering and styling.
The directive watches the collection for changes and efficiently updates the DOM when items are added, removed, or reordered. By default, ng-repeat tracks items by identity, which can cause issues when you have duplicate values or want to optimize performance. To address this, you can use the track by expression to specify a unique identifier for each item, which improves performance and prevents unnecessary DOM manipulation.
ng-repeat also supports filtering and sorting through AngularJS's filter syntax, allowing you to pipe the collection through filters directly in the template. You can iterate over object properties using (key, value) in object syntax, and limit the number of displayed items with the limitTo filter.
One important consideration is performance: ng-repeat creates a watcher for each item in the collection, so large lists can impact performance. For very large datasets (thousands of items), consider using virtual scrolling solutions or pagination. Additionally, since each iteration creates a new scope, you need to be mindful of scope inheritance issues when modifying primitive values from within the repeated template.
Code Example:
<div ng-app="myApp" ng-controller="ListCtrl">
<!-- Basic ng-repeat with array -->
<ul>
<li ng-repeat="item in items">
{{$index + 1}}. {{item}}
<span ng-if="$first">(First)</span>
<span ng-if="$last">(Last)</span>
</li>
</ul>
<!-- ng-repeat with objects and track by for performance -->
<div ng-repeat="user in users track by user.id">
<p>{{user.name}} - {{user.email}}</p>
<!-- track by user.id prevents DOM recreation when array is replaced -->
</div>
<!-- ng-repeat with filtering and sorting -->
<input type="text" ng-model="searchText" placeholder="Search users">
<ul>
<li ng-repeat="user in users | filter:searchText | orderBy:'name'">
{{user.name}}
</li>
</ul>
<!-- ng-repeat with objects (iterating over properties) -->
<div ng-repeat="(key, value) in settings">
<strong>{{key}}:</strong> {{value}}
</div>
<!-- ng-repeat with limitTo filter -->
<ul>
<li ng-repeat="item in longList | limitTo:10">
{{item}}
</li>
</ul>
<!-- Using special properties for styling -->
<table>
<tr ng-repeat="row in tableData"
ng-class="{'even-row': $even, 'odd-row': $odd}">
<td>{{$index}}</td>
<td>{{row.name}}</td>
</tr>
</table>
</div>
angular.module('myApp', [])
.controller('ListCtrl', function($scope) {
// Simple array
$scope.items = ['Angular', 'React', 'Vue'];
// Array of objects - use track by for better performance
$scope.users = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com' }
];
// Object iteration
$scope.settings = {
theme: 'dark',
language: 'en',
notifications: true
};
// Large list example
$scope.longList = Array.from({ length: 100 }, (_, i) => `Item ${i + 1}`);
// Table data with even/odd styling
$scope.tableData = [
{ name: 'Row 1' },
{ name: 'Row 2' },
{ name: 'Row 3' }
];
});
References:
↑ Back to topWhat is the difference between restrict options (E, A, C, M)?
The 30-Second Answer:
The restrict option determines how a directive can be invoked in HTML: 'E' for element (<my-directive>), 'A' for attribute (<div my-directive>), 'C' for class (<div class="my-directive">), and 'M' for comment (<!-- directive: my-directive -->). You can combine options like 'EA' to allow both element and attribute usage.
The 2-Minute Answer (If They Want More):
The restrict option is a crucial configuration in custom directives that controls how developers can use your directive in HTML templates. Each option serves different use cases and follows different semantic patterns. The default value if not specified is 'EA', allowing both element and attribute usage.
'E' (Element) is best for directives that represent standalone components or widgets with their own template, like <user-card> or <date-picker>. These directives typically have isolated scopes and encapsulate complete UI components. Use element restriction when the directive is semantically a component rather than a behavior modifier. The downside is that older browsers (IE8 and below) require special handling for custom elements.
'A' (Attribute) is the most common and flexible option, perfect for directives that add behavior to existing elements or enhance them with additional functionality. Examples include <input ng-model="name"> or <button ng-click="save()">. Attribute directives work across all browsers and are ideal for decorating existing HTML elements with additional behavior without changing their semantic meaning.
'C' (Class) is rarely used in practice because using classes for directive activation can conflict with CSS styling purposes and makes the code less clear. However, it can be useful when you need to apply directives to elements where you cannot modify the markup directly, or for styling-related directives.
'M' (Comment) is the least common and primarily exists for legacy compatibility. It allows directives to be applied via HTML comments like <!-- directive: my-directive -->. This was useful in very old browsers or when you couldn't modify existing HTML, but it's generally avoided in modern development because it's not intuitive and hard to maintain.
In practice, most custom directives use either 'E' for components or 'A' for behaviors, or 'EA' to provide flexibility. The choice should reflect the semantic purpose: if it's a thing (component), use 'E'; if it adds behavior to a thing, use 'A'.
Code Example:
angular.module('myApp', [])
// Element directive (E) - Used as a custom HTML element
.directive('userProfile', function() {
return {
restrict: 'E', // Only allow <user-profile></user-profile>
scope: {
user: '='
},
template: `
<div class="profile">
<h2>{{user.name}}</h2>
<p>{{user.bio}}</p>
</div>
`
};
})
// Attribute directive (A) - Used as an HTML attribute
.directive('highlight', function() {
return {
restrict: 'A', // Only allow <div highlight></div>
link: function(scope, element, attrs) {
element.css({
'background-color': attrs.highlight || 'yellow',
'padding': '10px'
});
}
};
})
// Class directive (C) - Used as a CSS class
.directive('errorMessage', function() {
return {
restrict: 'C', // Only allow <div class="error-message"></div>
link: function(scope, element) {
element.css({
'color': 'red',
'font-weight': 'bold'
});
}
};
})
// Comment directive (M) - Used as an HTML comment
.directive('sectionBreak', function() {
return {
restrict: 'M', // Only allow <!-- directive: section-break -->
replace: true,
template: '<hr class="section-divider">'
};
})
// Combined restrictions (EA) - Most flexible approach
.directive('loader', function() {
return {
restrict: 'EA', // Allow both <loader></loader> and <div loader></div>
template: '<div class="spinner">Loading...</div>',
link: function(scope, element, attrs) {
scope.$watch(attrs.loading, function(isLoading) {
if (isLoading) {
element.show();
} else {
element.hide();
}
});
}
};
})
// Practical example showing when to use each type
.directive('modalDialog', function() {
return {
restrict: 'E', // Component - semantically a "thing"
transclude: true,
scope: {
title: '@',
onClose: '&'
},
template: `
<div class="modal-backdrop">
<div class="modal-content">
<h2>{{title}}</h2>
<div ng-transclude></div>
<button ng-click="onClose()">Close</button>
</div>
</div>
`
};
})
.directive('clickOutside', function($document) {
return {
restrict: 'A', // Behavior modifier - adds functionality
link: function(scope, element, attrs) {
var clickHandler = function(event) {
if (!element[0].contains(event.target)) {
scope.$apply(attrs.clickOutside);
}
};
$document.on('click', clickHandler);
scope.$on('$destroy', function() {
$document.off('click', clickHandler);
});
}
};
});
// HTML usage examples
/*
<!-- Element directive (E) -->
<user-profile user="currentUser"></user-profile>
<!-- Attribute directive (A) -->
<div highlight="lightblue">This text is highlighted</div>
<button ng-click="save()">Save</button>
<!-- Class directive (C) - less common -->
<div class="error-message">Something went wrong!</div>
<!-- Comment directive (M) - rarely used -->
<!-- directive: section-break -->
<!-- Combined (EA) - can be used either way -->
<loader loading="isLoading"></loader>
<!-- OR -->
<div loader loading="isLoading"></div>
<!-- Practical component example (E) -->
<modal-dialog title="Confirm Action" on-close="closeModal()">
<p>Are you sure you want to proceed?</p>
</modal-dialog>
<!-- Practical behavior example (A) -->
<div class="dropdown-menu" click-outside="closeDropdown()">
Menu content here
</div>
*/
Comparison Table:
| Restrict | Syntax | Use Case | Example | Browser Support | Common Usage |
|---|---|---|---|---|---|
| E | <my-dir> |
Standalone components, widgets | <date-picker>, <user-card> |
May need shim for IE8- | Component directives |
| A | <div my-dir> |
Behavior modifiers, enhancements | <input ng-model>, <div tooltip> |
Universal | Most common, default choice |
| C | <div class="my-dir"> |
Style-related directives | <div class="error"> |
Universal | Rarely used, conflicts with CSS |
| M | <!-- directive: my-dir --> |
Legacy compatibility | <!-- directive: break --> |
Universal | Very rare, hard to maintain |
| EA | Both element & attribute | Flexible usage | <loader> or <div loader> |
Element may need shim | Good for libraries |
Decision Guide:
- Use E when: Creating a component that represents a "thing" (noun)
- Use A when: Adding behavior or enhancing existing elements (verb/adjective)
- Use C when: You have a very specific styling use case (rare)
- Use M when: Dealing with legacy constraints (very rare)
- Use EA when: Building library directives for maximum flexibility
References:
- AngularJS Directive Restrict Options
- AngularJS Guide: Directive Normalization
- Best Practices for Directive Restrict Options
Services and Dependency Injection
What is the difference between service, factory, and provider?
The 30-Second Answer:
Service, factory, and provider are three different ways to create services in AngularJS. A factory returns a value/object, a service is instantiated with new (constructor function), and a provider is the most configurable option that can be configured during the config phase. All three ultimately create singleton services, but differ in syntax and flexibility.
The 2-Minute Answer (If They Want More): The factory is the most commonly used pattern. It's a function that returns an object, literal value, or constructor function. You have complete control over what gets returned, making it flexible and straightforward. Factories are ideal for creating objects with private variables using closures.
The service is a constructor function that AngularJS instantiates with the new keyword. Properties and methods are attached to this, and AngularJS automatically returns the instance. Services are great when you're comfortable with JavaScript constructor patterns and want a more traditional OOP approach.
The provider is the most powerful and complex option. It's the only service type that can be injected into the config() function, allowing configuration before the application starts. Providers must have a $get() method that returns the actual service instance. Use providers when you need to configure service behavior at the module level, such as setting API endpoints or feature flags.
Under the hood, everything is a provider - service() and factory() are just syntactic sugar that create providers automatically. For most use cases, stick with factory or service. Only use provider when you genuinely need pre-configuration capabilities.
Code Example:
angular.module('myApp', [])
// FACTORY: Returns an object with methods
.factory('UserFactory', function($http) {
var users = [];
return {
getUsers: function() {
return $http.get('/api/users').then(function(response) {
users = response.data;
return users;
});
},
addUser: function(user) {
users.push(user);
}
};
})
// SERVICE: Constructor function, use 'this'
.service('UserService', function($http) {
var users = [];
this.getUsers = function() {
return $http.get('/api/users').then(function(response) {
users = response.data;
return users;
});
};
this.addUser = function(user) {
users.push(user);
};
})
// PROVIDER: Configurable, must have $get method
.provider('UserProvider', function() {
var apiEndpoint = '/api/users'; // Default
// Configuration method (available in config phase)
this.setApiEndpoint = function(endpoint) {
apiEndpoint = endpoint;
};
// $get is called to create the actual service
this.$get = function($http) {
var users = [];
return {
getUsers: function() {
return $http.get(apiEndpoint).then(function(response) {
users = response.data;
return users;
});
},
addUser: function(user) {
users.push(user);
}
};
};
})
// Configuring the provider before app starts
.config(function(UserProviderProvider) {
// Note: Provider name + "Provider" suffix
UserProviderProvider.setApiEndpoint('/api/v2/users');
})
.controller('MainController', function($scope, UserFactory, UserService, UserProvider) {
// All three can be injected and used the same way
UserFactory.getUsers().then(function(users) {
$scope.users = users;
});
});
Comparison Table:
| Feature | Factory | Service | Provider |
|---|---|---|---|
| Syntax | Returns object/value | Constructor with this |
Must have $get() method |
| Instantiation | Called as function | Called with new |
$get() called as function |
| Configurable | No | No | Yes (in config phase) |
| Complexity | Simple | Simple | Complex |
| Use Case | Most common, flexible | OOP style | Need pre-configuration |
| Returns | Whatever you return | Instance created with new |
Whatever $get() returns |
References:
- AngularJS Developer Guide - Providers
- Stack Overflow - Service vs Factory vs Provider
- Todd Motto - Factory vs Service
Routing
What is the difference between ng-route and ui-router?
The 30-Second Answer: ng-route is AngularJS's official URL-based routing module with simple route-to-template mapping, while ui-router is a third-party state-based routing library that supports nested views, multiple named views, and more advanced routing scenarios. ui-router is more powerful and flexible but has a steeper learning curve.
The 2-Minute Answer (If They Want More): The fundamental difference lies in their approach to navigation: ng-route thinks in terms of routes (URL-centric), while ui-router thinks in terms of states (application-state-centric). With ng-route, you define URL patterns and map them to templates and controllers. With ui-router, you define application states that happen to have URLs associated with them, which is a subtle but important distinction.
ng-route has a one-to-one relationship between URLs and views—each route displays exactly one template in the ng-view directive. ui-router supports multiple named views, allowing you to compose complex layouts where different parts of the page update independently. For example, you might have a header view, sidebar view, and content view all updating based on the current state.
ui-router excels at nested routing, where child states inherit from parent states. This makes it much easier to build hierarchical navigation structures like master-detail interfaces or multi-step wizards. ng-route requires workarounds to achieve similar functionality, often resulting in code duplication.
Another key difference is state management. ui-router's $stateProvider offers features like state inheritance, abstract states (non-navigable parent states), state parameters separate from URL parameters, and resolve functions that load data before the state activates. ng-route has basic resolve support but lacks the sophisticated state management capabilities.
For simple applications with straightforward routing needs, ng-route is sufficient and requires less boilerplate. However, most production AngularJS applications use ui-router because it scales better as routing requirements grow more complex. The migration effort from ng-route to ui-router is relatively straightforward since the concepts are similar.
Code Example:
// ============================================
// ng-route Example
// ============================================
var app = angular.module('myApp', ['ngRoute']);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/users', {
templateUrl: 'users-list.html',
controller: 'UsersListController'
})
.when('/users/:id', {
templateUrl: 'user-detail.html',
controller: 'UserDetailController'
});
}]);
// HTML: Single ng-view directive
// <div ng-view></div>
// ============================================
// ui-router Equivalent
// ============================================
var app = angular.module('myApp', ['ui.router']);
app.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
// Parent state with abstract flag
.state('users', {
url: '/users',
templateUrl: 'users-list.html',
controller: 'UsersListController'
})
// Nested child state - inherits from parent
.state('users.detail', {
url: '/:id', // Combined with parent: /users/:id
templateUrl: 'user-detail.html',
controller: 'UserDetailController'
});
$urlRouterProvider.otherwise('/users');
}]);
// HTML: Named ui-view directive
// <div ui-view></div>
// ============================================
// ui-router Advanced Features
// ============================================
app.config(['$stateProvider', function($stateProvider) {
$stateProvider
// Multiple named views in a single state
.state('dashboard', {
url: '/dashboard',
views: {
// Named view for header
'header': {
templateUrl: 'dashboard-header.html',
controller: 'DashboardHeaderCtrl'
},
// Named view for sidebar
'sidebar': {
templateUrl: 'dashboard-sidebar.html',
controller: 'DashboardSidebarCtrl'
},
// Named view for main content
'content': {
templateUrl: 'dashboard-content.html',
controller: 'DashboardContentCtrl'
}
}
})
// Abstract state (cannot navigate directly to it)
.state('admin', {
abstract: true,
url: '/admin',
template: '<ui-view/>', // Child states render here
resolve: {
// Load admin privileges before any child state activates
adminAuth: ['AuthService', function(AuthService) {
return AuthService.requireAdmin();
}]
}
})
// Child state inherits adminAuth resolve
.state('admin.users', {
url: '/users', // Full URL: /admin/users
templateUrl: 'admin-users.html',
controller: 'AdminUsersController'
});
}]);
// HTML for multiple named views:
// <div ui-view="header"></div>
// <div ui-view="sidebar"></div>
// <div ui-view="content"></div>
Comparison Table:
| Feature | ng-route | ui-router |
|---|---|---|
| Routing Model | URL-based routes | State-based routing |
| Multiple Views | No (single ng-view) | Yes (named ui-views) |
| Nested Routes | Limited support | Full support with inheritance |
| Abstract States | No | Yes |
| State Parameters | Via URL only | URL + non-URL parameters |
| Resolve Functions | Basic support | Advanced with inheritance |
| Learning Curve | Simple | Moderate |
| Bundle Size | ~4KB | ~25KB |
| Official Support | AngularJS team | Third-party (ui-router team) |
| Best For | Simple apps | Complex apps with nested navigation |
References:
- ui-router Official Documentation
- AngularJS ngRoute vs ui-router Comparison
- Stack Overflow: When to use ui-router over ngRoute
Filters
What is the performance impact of filters?
The 30-Second Answer: Filters run on every digest cycle, which can significantly impact performance if they perform expensive operations or are used extensively. Stateless, pure filters with simple logic are relatively fast, but complex filters with loops, DOM manipulation, or external dependencies can cause performance bottlenecks and should be minimized or replaced with pre-computed properties.
The 2-Minute Answer (If They Want More): The primary performance concern with AngularJS filters is that they execute during every digest cycle, which can happen many times per second in response to user interactions, HTTP responses, or timeout events. Even if the input data hasn't changed, AngularJS may re-evaluate filters to check for changes, making the frequency of execution a critical performance factor.
Filters that perform expensive operations—such as filtering or sorting large arrays, making calculations on every item, or performing string manipulations—can cause noticeable lag, especially when used in ng-repeat directives with large datasets. Each iteration of ng-repeat can trigger filter evaluation, multiplying the performance impact.
To optimize filter performance, follow these best practices: keep filter logic simple and efficient, avoid DOM manipulation or HTTP requests inside filters, use track by with ng-repeat to minimize re-rendering, and consider pre-computing filtered results in your controller for complex operations. For very large datasets, implement pagination or virtual scrolling instead of filtering the entire collection.
The orderBy and filter filters are particularly notorious for performance issues with large arrays because they process every element. If you're filtering or sorting thousands of items, consider doing this work in your controller once and caching the results, only recomputing when the source data actually changes. For real-time filtering with large datasets, debouncing user input can also significantly reduce the number of filter executions.
Code Example:
<div ng-app="myApp" ng-controller="PerformanceCtrl">
<!-- POOR PERFORMANCE: Filter runs on every digest -->
<h3>Inefficient Approach (Avoid)</h3>
<input type="text" ng-model="searchText">
<ul>
<!-- This filter runs on EVERY digest cycle, even if searchText hasn't changed -->
<li ng-repeat="item in largeDataset | filter:searchText | orderBy:'name'">
{{ item.name }} - {{ item.value | expensiveFilter }}
</li>
</ul>
<!-- BETTER PERFORMANCE: Pre-computed in controller -->
<h3>Optimized Approach (Preferred)</h3>
<input type="text" ng-model="search" ng-change="filterItems()">
<ul>
<!-- Using track by to minimize DOM re-rendering -->
<li ng-repeat="item in filteredItems track by item.id">
{{ item.name }} - {{ item.formattedValue }}
</li>
</ul>
<!-- PERFORMANCE COMPARISON -->
<h3>Performance Metrics</h3>
<p>Filter executions: {{ filterExecutionCount }}</p>
<p>Last filter time: {{ lastFilterTime }}ms</p>
<!-- ONE-TIME BINDING for static data -->
<h3>One-Time Binding (Best for Static Data)</h3>
<ul>
<!-- :: syntax prevents re-evaluation after initial binding -->
<li ng-repeat="item in ::staticItems">
{{ ::item.name }} - {{ ::item.value | currency }}
</li>
</ul>
</div>
<script>
angular.module('myApp', [])
// Example of a potentially expensive filter
.filter('expensiveFilter', function() {
var executionCount = 0;
return function(input) {
executionCount++;
console.warn('expensiveFilter executed:', executionCount, 'times');
// Simulate expensive operation
var result = input;
for (var i = 0; i < 1000; i++) {
result = result + Math.random();
}
return result.toString().substring(0, 10);
};
})
// Optimized custom filter - memoized
.filter('optimizedFilter', function() {
var cache = {};
return function(input, param) {
// Create cache key from inputs
var cacheKey = input + '_' + param;
// Return cached result if available
if (cache[cacheKey]) {
return cache[cacheKey];
}
// Perform expensive operation
var result = performExpensiveOperation(input, param);
// Cache the result
cache[cacheKey] = result;
return result;
};
function performExpensiveOperation(input, param) {
// Your expensive logic here
return input.toUpperCase();
}
})
.controller('PerformanceCtrl', function($scope, $timeout, $filter) {
// Generate large dataset
$scope.largeDataset = [];
for (var i = 0; i < 1000; i++) {
$scope.largeDataset.push({
id: i,
name: 'Item ' + i,
value: Math.random() * 100,
category: ['A', 'B', 'C'][i % 3]
});
}
$scope.staticItems = $scope.largeDataset.slice(0, 10);
// Track filter performance
$scope.filterExecutionCount = 0;
// INEFFICIENT: Filtering in template
// The filter runs on every digest cycle
// EFFICIENT: Pre-computed filtering in controller
$scope.filteredItems = $scope.largeDataset;
$scope.filterItems = function() {
var startTime = performance.now();
if (!$scope.search) {
$scope.filteredItems = $scope.largeDataset;
} else {
// Use $filter service for programmatic filtering
$scope.filteredItems = $filter('filter')($scope.largeDataset, $scope.search);
$scope.filteredItems = $filter('orderBy')($scope.filteredItems, 'name');
}
$scope.filterExecutionCount++;
$scope.lastFilterTime = (performance.now() - startTime).toFixed(2);
};
// Debounced version for even better performance
var debounceTimeout;
$scope.debouncedFilter = function() {
if (debounceTimeout) {
$timeout.cancel(debounceTimeout);
}
debounceTimeout = $timeout(function() {
$scope.filterItems();
}, 300); // Wait 300ms after user stops typing
};
// BEST PRACTICE: Compute once, reuse
$scope.preComputedData = $scope.largeDataset.map(function(item) {
return {
id: item.id,
name: item.name,
// Pre-format values instead of using filters in template
formattedValue: $filter('currency')(item.value, '$', 2)
};
});
// Example of watching for data changes efficiently
$scope.$watchCollection('largeDataset', function(newData) {
// Only recompute when source data actually changes
if (newData) {
$scope.filterItems();
}
});
})
// Performance monitoring directive
.directive('filterPerformance', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var filterExecutions = 0;
// Monitor digest cycles
scope.$watch(function() {
filterExecutions++;
console.log('Digest cycle count:', filterExecutions);
});
}
};
});
</script>
<!-- PERFORMANCE TIPS SUMMARY -->
<!--
1. AVOID: Multiple filters in ng-repeat with large datasets
BAD: ng-repeat="item in items | filter:search | orderBy:sort"
GOOD: ng-repeat="item in filteredItems track by item.id"
2. AVOID: Complex logic inside filters
BAD: {{ value | complexCalculation }}
GOOD: Pre-compute in controller: {{ item.preComputedValue }}
3. USE: One-time binding (::) for static data
{{ ::staticValue | date }}
4. USE: track by with ng-repeat to minimize DOM manipulation
ng-repeat="item in items track by item.id"
5. USE: Debouncing for user input
ng-change="debouncedSearch()" instead of instant filtering
6. USE: Pagination or virtual scrolling for large datasets
Instead of filtering thousands of items, show 50 at a time
7. CONSIDER: Memoization/caching within filters for expensive operations
8. MONITOR: Use browser dev tools to profile digest cycle performance
-->
</script>
References:
↑ Back to topDigest Cycle and Performance
What is $watch and what are its performance implications?
The 30-Second Answer:
$watch registers a listener on a scope expression that gets evaluated during every digest cycle. When the watched value changes, the listener function executes. The performance implication is significant: each watcher runs on every digest cycle, so having hundreds or thousands of watchers can cause noticeable performance degradation and UI lag.
The 2-Minute Answer (If They Want More):
$watch is the foundation of AngularJS's data binding system. Every time you use ng-bind, ng-model, ng-repeat, or any other directive that displays or binds data, AngularJS creates watchers behind the scenes. You can also manually create watchers to observe changes to scope properties or expressions.
During each digest cycle, every single watcher is evaluated. For simple properties, this is a straightforward comparison. But for complex objects or arrays, AngularJS performs deep equality checking by default (value-based comparison), which can be expensive. The more watchers you have and the more complex they are, the slower your digest cycles become.
Performance implications become critical when you have:
- Large
ng-repeatlists (each item may have multiple watchers) - Complex watch expressions (functions that do heavy computation)
- Deep object watches (comparing entire object trees)
- Watchers that trigger other changes (cascading digest cycles)
Common performance issues arise when applications have 2,000+ watchers. Mobile devices struggle even earlier. You can check watcher count using browser extensions or custom scripts. Optimization strategies include using one-time binding (::), reducing watchers in ng-repeat, using track by, watching specific properties instead of entire objects, and debouncing watch expressions.
There are three types of watchers: reference watch (default for primitives), equality watch (deep comparison), and collection watch (array/object changes). Each has different performance characteristics, with equality watches being the most expensive.
Code Example:
angular.module('myApp', [])
.controller('WatchController', function($scope, $timeout) {
// Basic $watch - watches a simple property
$scope.name = 'John';
$scope.$watch('name', function(newValue, oldValue) {
if (newValue !== oldValue) {
console.log('Name changed from', oldValue, 'to', newValue);
}
});
// Watching an expression (watch function)
$scope.firstName = 'John';
$scope.lastName = 'Doe';
$scope.$watch(
function() {
return $scope.firstName + ' ' + $scope.lastName;
},
function(newValue, oldValue) {
$scope.fullName = newValue;
}
);
// EXPENSIVE: Deep equality watch (third parameter true)
$scope.user = { name: 'John', age: 30, address: { city: 'NYC' } };
$scope.$watch('user', function(newValue, oldValue) {
if (newValue !== oldValue) {
console.log('User object changed (deep comparison)');
}
}, true); // This does deep object comparison - SLOW!
// BETTER: Watch specific property instead of entire object
$scope.$watch('user.name', function(newValue, oldValue) {
if (newValue !== oldValue) {
console.log('User name changed');
}
});
// Collection watch - watches array/object for additions/removals
$scope.items = [1, 2, 3];
$scope.$watchCollection('items', function(newValue, oldValue) {
console.log('Items array changed');
});
// Deregistering a watcher to improve performance
var deregister = $scope.$watch('temporaryData', function(newValue) {
// Do something
});
// Remove watcher when no longer needed
$timeout(function() {
deregister(); // Stops watching and improves performance
}, 5000);
// Performance-friendly watch with debouncing
var watchDebounce;
$scope.$watch('searchQuery', function(newValue) {
$timeout.cancel(watchDebounce);
watchDebounce = $timeout(function() {
// Expensive operation (API call, filtering, etc.)
$scope.performSearch(newValue);
}, 300); // Wait 300ms after user stops typing
});
// Utility to count watchers (for debugging)
$scope.getWatcherCount = function() {
var root = angular.element(document.getElementsByTagName('body'));
var watchers = [];
var f = function(element) {
angular.forEach(['$scope', '$isolateScope'], function(scopeProperty) {
if (element.data() && element.data().hasOwnProperty(scopeProperty)) {
angular.forEach(element.data()[scopeProperty].$$watchers, function(watcher) {
watchers.push(watcher);
});
}
});
angular.forEach(element.children(), function(childElement) {
f(angular.element(childElement));
});
};
f(root);
return watchers.length;
};
});
Mermaid Diagram (if helpful for visualization):
flowchart TD
A[Digest Cycle Starts] --> B[Iterate through all watchers]
B --> C{Watch Type?}
C -->|Reference Watch| D[Compare references]
C -->|Equality Watch| E[Deep compare objects]
C -->|Collection Watch| F[Compare array/object length & items]
D --> G{Changed?}
E --> G
F --> G
G -->|Yes| H[Execute listener function]
G -->|No| I[Skip to next watcher]
H --> I
I --> J{More watchers?}
J -->|Yes| B
J -->|No| K[Digest cycle complete]
style E fill:#ff9999
style H fill:#99ff99
References:
- AngularJS API: $rootScope.Scope.$watch
- AngularJS Performance: Speeding up ng-repeat
- 11 Tips to Improve AngularJS Performance
AngularJS Fundamentals
What is AngularJS and how does it differ from Angular (2+)?
The 30-Second Answer: AngularJS (Angular 1.x) is a JavaScript-based MVC framework released by Google in 2010 for building dynamic web applications. Angular (2+) is a complete TypeScript-based rewrite with a component-based architecture, better performance, and mobile support, representing a fundamental paradigm shift rather than an incremental update.
The 2-Minute Answer (If They Want More): AngularJS introduced revolutionary concepts like two-way data binding, dependency injection, and directives to the front-end world. It uses controllers, $scope, and a digest cycle for change detection. The framework was designed when JavaScript and browser capabilities were more limited, leading to architectural decisions that became bottlenecks as applications grew.
Angular (2+) was built from scratch to address these limitations. It adopted TypeScript for better tooling and type safety, replaced controllers with components, eliminated $scope in favor of component properties, and introduced a zone-based change detection system. The new architecture is modular, tree-shakeable, and optimized for modern build tools.
The migration from AngularJS to Angular isn't straightforward—they're fundamentally different frameworks. AngularJS follows MVC/MVW patterns while Angular embraces component-based architecture. AngularJS uses JavaScript expressions in templates; Angular uses TypeScript and a more powerful template syntax. This is why the Angular team provides tools like ngUpgrade for gradual migration rather than simple upgrade paths.
Understanding both is valuable because many legacy enterprise applications still run on AngularJS, while new development uses Angular. The terminology distinction matters: "AngularJS" refers to 1.x versions, while "Angular" refers to 2+ versions.
Code Example:
// AngularJS (1.x) - Controller-based approach
angular.module('myApp', [])
.controller('UserController', ['$scope', function($scope) {
// Data binding through $scope
$scope.user = {
name: 'John Doe',
email: 'john@example.com'
};
// Methods attached to $scope
$scope.updateUser = function() {
$scope.user.lastUpdated = new Date();
};
}]);
// HTML Template for AngularJS
// <div ng-controller="UserController">
// <input ng-model="user.name">
// <p>{{user.name}}</p>
// </div>
// Angular (2+) - Component-based approach
import { Component } from '@angular/core';
@Component({
selector: 'app-user',
template: `
<input [(ngModel)]="user.name">
<p>{{user.name}}</p>
`
})
export class UserComponent {
// Component properties replace $scope
user = {
name: 'John Doe',
email: 'john@example.com'
};
// Methods are class methods
updateUser() {
this.user.lastUpdated = new Date();
}
}
Mermaid Diagram:
flowchart LR
A[AngularJS 1.x] --> B[JavaScript]
A --> C[Controllers + $scope]
A --> D[Digest Cycle]
A --> E[Directives]
F[Angular 2+] --> G[TypeScript]
F --> H[Components]
F --> I[Zone.js Detection]
F --> J[Enhanced Directives]
style A fill:#dd1b16
style F fill:#0078d4
References:
- AngularJS Official Documentation
- Angular Official Documentation
- Angular Versions Comparison - Angular Blog
What is the MVC/MVW architecture in AngularJS?
The 30-Second Answer: AngularJS implements an MVC (Model-View-Controller) pattern, though it's often called MVW (Model-View-Whatever) because the framework is flexible about the "controller" role. The Model represents data, the View is the HTML template, and the Controller (or Whatever) contains the business logic that connects them through $scope.
The 2-Minute Answer (If They Want More): In traditional MVC, the Model holds application data and business logic, the View displays the UI, and the Controller mediates between them. AngularJS adapts this pattern for front-end development in a unique way that sparked debate about whether it's "true MVC," hence the MVW terminology coined by the AngularJS team.
The Model in AngularJS is plain JavaScript objects—there's no special model class. Any data you attach to $scope becomes part of the model. The View is the HTML template with AngularJS directives and expressions. The Controller is a JavaScript function that initializes the $scope, attaches data and methods to it, and handles user interactions.
What makes AngularJS's implementation distinctive is two-way data binding. Changes in the View automatically update the Model, and Model changes automatically update the View, without explicit event handlers. The $scope object acts as the glue, serving as the "viewmodel" in MVVM terminology, which is why "Whatever" became part of the description.
Services and factories handle reusable business logic and data access, keeping controllers focused on view-specific logic. This separation of concerns makes AngularJS applications more maintainable and testable, though improper use of $scope and controllers can lead to tight coupling if developers aren't careful.
Code Example:
// Model - Plain JavaScript objects
var userData = {
users: [
{ id: 1, name: 'Alice', role: 'Developer' },
{ id: 2, name: 'Bob', role: 'Designer' }
]
};
// Service - Business logic and data access
angular.module('myApp', [])
.service('UserService', function() {
this.users = userData.users;
this.addUser = function(user) {
user.id = this.users.length + 1;
this.users.push(user);
};
this.removeUser = function(id) {
this.users = this.users.filter(u => u.id !== id);
};
})
// Controller - Connects Model and View
.controller('UserController', ['$scope', 'UserService',
function($scope, UserService) {
// Initialize scope with data from service
$scope.users = UserService.users;
$scope.newUser = {};
// Methods for view interactions
$scope.addUser = function() {
UserService.addUser($scope.newUser);
$scope.newUser = {}; // Reset form
};
$scope.removeUser = function(id) {
UserService.removeUser(id);
};
}
]);
// View - HTML Template
// <div ng-controller="UserController">
// <input ng-model="newUser.name" placeholder="Name">
// <input ng-model="newUser.role" placeholder="Role">
// <button ng-click="addUser()">Add User</button>
//
// <ul>
// <li ng-repeat="user in users">
// {{user.name}} - {{user.role}}
// <button ng-click="removeUser(user.id)">Remove</button>
// </li>
// </ul>
// </div>
Mermaid Diagram:
flowchart TD
V[View - HTML Template] <-->|Two-way Binding| S[$scope]
S <--> C[Controller - Business Logic]
C <--> M[Model - Data Objects]
C <--> SRV[Services - Shared Logic]
SRV <--> M
style V fill:#e1f5ff
style C fill:#fff4e1
style M fill:#f0ffe1
style S fill:#ffe1f5
style SRV fill:#e1e1ff
References:
- Understanding MVC in AngularJS - AngularJS Developer Guide
- MVW Pattern Explanation - AngularJS GitHub
- Model-View-Whatever - Martin Fowler
What is $scope in AngularJS?
The 30-Second Answer: $scope is an object that acts as the glue between the controller and the view, providing the context for expressions in templates. It contains the data model and methods that the view can access, and it's where AngularJS's two-way data binding magic happens through the digest cycle.
The 2-Minute Answer (If They Want More):
Every AngularJS controller and directive creates a $scope object that serves as the execution context for expressions in templates. When you write {{user.name}} in HTML, AngularJS looks for user.name on the current $scope. When you use ng-click="saveUser()", it calls the saveUser function from $scope.
$scope objects form a hierarchical structure mirroring the DOM structure—child scopes inherit properties from parent scopes through prototypal inheritance. This means a child controller can access properties from its parent's $scope, but modifications create a new property on the child scope rather than modifying the parent (unless dealing with object properties).
The $scope is at the heart of AngularJS's change detection system. When you modify a $scope property, AngularJS's digest cycle compares the new value with the previous value for all watched expressions. If changes are detected, the view updates automatically. You can watch for changes using $scope.$watch() and manually trigger digests with $scope.$apply() when integrating non-AngularJS code.
Best practices include keeping $scope thin—use it only for view-related data and methods. Business logic belongs in services. Avoid deep watching when possible for performance, and be mindful of the scope hierarchy to prevent unexpected inheritance issues. Many developers prefer the "controller as" syntax introduced in AngularJS 1.2, which reduces reliance on $scope and makes templates more explicit.
Code Example:
angular.module('myApp', [])
.controller('ParentController', ['$scope', function($scope) {
// Data on parent scope
$scope.parentName = 'Parent Controller';
$scope.sharedData = { count: 0 };
// Method on parent scope
$scope.incrementCount = function() {
$scope.sharedData.count++;
};
// Watch for changes
$scope.$watch('sharedData.count', function(newValue, oldValue) {
if (newValue !== oldValue) {
console.log('Count changed from', oldValue, 'to', newValue);
}
});
}])
.controller('ChildController', ['$scope', function($scope) {
// Child has access to parent scope through inheritance
$scope.childName = 'Child Controller';
// Accessing parent's method
$scope.incrementFromChild = function() {
// This modifies parent's object (reference shared)
$scope.sharedData.count++;
};
// This would create a new property on child scope
// NOT modify parent's primitive
$scope.overrideParent = function() {
$scope.parentName = 'Overridden'; // Creates new child property
};
}]);
// Alternative: "Controller As" syntax (better practice)
angular.module('myApp')
.controller('UserController', function() {
// 'this' instead of $scope
var vm = this;
vm.user = {
name: 'John Doe',
email: 'john@example.com'
};
vm.saveUser = function() {
// Save logic here
console.log('Saving user:', vm.user);
};
});
// HTML using "Controller As"
// <div ng-controller="UserController as userCtrl">
// <input ng-model="userCtrl.user.name">
// <p>{{userCtrl.user.name}}</p>
// <button ng-click="userCtrl.saveUser()">Save</button>
// </div>
// Manual digest cycle trigger (for non-Angular code)
angular.module('myApp')
.controller('IntegrationController', ['$scope', '$timeout',
function($scope, $timeout) {
$scope.message = 'Initial';
// Using vanilla setTimeout requires manual $apply
setTimeout(function() {
$scope.$apply(function() {
$scope.message = 'Updated outside Angular';
});
}, 1000);
// $timeout is Angular-aware, no $apply needed
$timeout(function() {
$scope.message = 'Updated with $timeout';
}, 2000);
}
]);
Mermaid Diagram:
flowchart TD
R[$rootScope] --> P1[$scope Parent]
P1 --> C1[$scope Child 1]
P1 --> C2[$scope Child 2]
C1 --> GC1[$scope Grandchild]
P1 -.->|Prototypal<br/>Inheritance| C1
P1 -.->|Prototypal<br/>Inheritance| C2
C1 -.->|Prototypal<br/>Inheritance| GC1
V[View/Template] <-->|Two-way<br/>Binding| P1
V2[Child View] <-->|Two-way<br/>Binding| C1
style R fill:#ff6b6b
style P1 fill:#4ecdc4
style C1 fill:#45b7d1
style C2 fill:#45b7d1
style GC1 fill:#96ceb4
References:
- AngularJS $scope Documentation
- Understanding Scopes - AngularJS Wiki
- Scope Hierarchies and Inheritance
What is $rootScope and how does it differ from $scope?
The 30-Second Answer: $rootScope is the top-level scope that exists for the entire application lifecycle and is the parent of all other scopes. While $scope is specific to a controller or directive and has a limited lifecycle, $rootScope is global and persists throughout the application, making it useful for application-wide data and events but risky if overused.
The 2-Minute Answer (If They Want More): Every AngularJS application has exactly one $rootScope, created when the application bootstraps. All other scopes are descendants of $rootScope through prototypal inheritance. Think of it as the global scope for your AngularJS application—any property or method on $rootScope is accessible from any scope in your application.
The key differences lie in lifecycle and scope. $scope objects are created when controllers or directives instantiate and destroyed when they're removed from the DOM. $rootScope exists from application start to finish. This makes $rootScope tempting for storing global state, but that's often an anti-pattern that leads to tight coupling and maintenance nightmares.
$rootScope's most legitimate use case is event broadcasting and emitting. You can use $rootScope.$broadcast() to send events down the scope hierarchy or $rootScope.$emit() for events up the hierarchy. This enables cross-controller communication without direct dependencies. However, even this should be used sparingly—services are usually a better choice for sharing state.
Performance is another consideration. Watchers on $rootScope persist throughout the application, consuming resources even when not needed. Properties on $rootScope pollute the global namespace within your Angular app. Best practice is to avoid $rootScope except for truly application-wide concerns like authentication state or global configuration, and even then, consider using a service with $rootScope only for event communication.
Code Example:
angular.module('myApp', [])
.run(['$rootScope', function($rootScope) {
// Application-wide configuration (use sparingly)
$rootScope.appName = 'My Application';
$rootScope.appVersion = '1.0.0';
// Global event listener
$rootScope.$on('userLoggedIn', function(event, user) {
console.log('User logged in:', user.name);
$rootScope.currentUser = user;
});
// Global error handler
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
console.error('Navigation error:', error);
});
}])
.controller('LoginController', ['$scope', '$rootScope',
function($scope, $rootScope) {
$scope.user = {};
$scope.login = function() {
// After successful login, broadcast event
var user = { name: $scope.user.name, id: 123 };
// Broadcast event down the scope tree
$rootScope.$broadcast('userLoggedIn', user);
};
}
])
.controller('HeaderController', ['$scope', '$rootScope',
function($scope, $rootScope) {
// Child scope has access to $rootScope properties
$scope.appName = $rootScope.appName;
// Listen for login event
$scope.$on('userLoggedIn', function(event, user) {
$scope.currentUser = user;
$scope.isLoggedIn = true;
});
}
])
.controller('ProfileController', ['$scope', '$rootScope',
function($scope, $rootScope) {
// Access user from $rootScope (NOT recommended)
$scope.user = $rootScope.currentUser;
// Better approach: use a service
// $scope.user = AuthService.getCurrentUser();
}
]);
// BETTER PRACTICE: Use a service instead of $rootScope for state
angular.module('myApp')
.service('AuthService', ['$rootScope', function($rootScope) {
var currentUser = null;
this.login = function(user) {
currentUser = user;
// Still use $rootScope for events
$rootScope.$broadcast('userLoggedIn', user);
};
this.getCurrentUser = function() {
return currentUser;
};
this.logout = function() {
currentUser = null;
$rootScope.$broadcast('userLoggedOut');
};
}])
.controller('BetterProfileController', ['$scope', 'AuthService',
function($scope, AuthService) {
// Get data from service, not $rootScope
$scope.user = AuthService.getCurrentUser();
// Listen for events on $scope (automatically cleaned up)
$scope.$on('userLoggedOut', function() {
$scope.user = null;
});
}
]);
// Event propagation example
angular.module('myApp')
.controller('EventDemoController', ['$scope', '$rootScope',
function($scope, $rootScope) {
// $emit: sends event UP the scope chain to $rootScope
$scope.sendEventUp = function() {
$scope.$emit('eventGoingUp', { data: 'from child' });
};
// $broadcast: sends event DOWN to all child scopes
$scope.sendEventDown = function() {
$scope.$broadcast('eventGoingDown', { data: 'from parent' });
};
// Listen on $rootScope (careful: must manually unregister)
var deregister = $rootScope.$on('someEvent', function(event, data) {
console.log('Event received:', data);
});
// Clean up listener when controller is destroyed
$scope.$on('$destroy', function() {
deregister();
});
}
]);
Mermaid Diagram:
flowchart TD
RS[$rootScope<br/>Global - Persists entire app lifecycle]
RS --> S1[$scope Controller1<br/>Created when controller instantiates]
RS --> S2[$scope Controller2<br/>Created when controller instantiates]
S1 --> S1C1[$scope Directive1]
S1 --> S1C2[$scope Directive2]
S2 --> S2C1[$scope Directive3]
RS -.->|$broadcast<br/>Event flows DOWN| S1
RS -.->|$broadcast<br/>Event flows DOWN| S2
S1 -.->|$emit<br/>Event flows UP| RS
S2 -.->|$emit<br/>Event flows UP| RS
D[DOM Removed] -.->|Destroys| S1
D -.->|Destroys| S1C1
RS -.->|Survives| RS
style RS fill:#ff6b6b,color:#fff
style S1 fill:#4ecdc4
style S2 fill:#4ecdc4
style S1C1 fill:#96ceb4
style S1C2 fill:#96ceb4
style S2C1 fill:#96ceb4
style D fill:#ffd93d
References:
- AngularJS $rootScope API Documentation
- Scope Lifecycle and Events
- Best Practices: Avoiding $rootScope Abuse
What is two-way data binding in AngularJS?
The 30-Second Answer: Two-way data binding is AngularJS's mechanism that automatically synchronizes data between the model ($scope) and the view (HTML template). When you update the model, the view updates automatically, and when a user modifies the view (like typing in an input), the model updates automatically—no manual DOM manipulation or event handlers required.
The 2-Minute Answer (If They Want More): Two-way data binding is one of AngularJS's most revolutionary features that dramatically reduced boilerplate code in web applications. In traditional JavaScript, updating the UI meant manually manipulating the DOM, and capturing user input required event handlers. AngularJS eliminates this through its digest cycle and dirty checking mechanism.
When you use directives like ng-model, AngularJS establishes a two-way connection between the view element and a $scope property. Behind the scenes, AngularJS creates watchers for all bound expressions. During the digest cycle (triggered by AngularJS events like clicks, HTTP responses, or timeouts), it checks if watched values have changed by comparing current values with previous values.
If changes are detected in the model, AngularJS updates the view. If changes occur in the view (like user input), AngularJS updates the model and runs the digest cycle again to catch any cascading changes. The digest cycle loops until no more changes are detected or hits a maximum iteration limit (default 10), which prevents infinite loops.
While elegant, two-way binding has performance implications. Each watcher costs resources, and too many watchers (generally over 2,000) can cause performance degradation. Modern Angular (2+) moved to one-way data flow with explicit event handling for better performance and predictability. Understanding two-way binding helps explain why AngularJS can feel "magical" but also why large applications required careful optimization.
Code Example:
angular.module('myApp', [])
.controller('DataBindingController', ['$scope', function($scope) {
// Model - Plain JavaScript object
$scope.user = {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com'
};
// Computed property (updated automatically via digest cycle)
$scope.getFullName = function() {
return $scope.user.firstName + ' ' + $scope.user.lastName;
};
// Watch for changes manually
$scope.$watch('user.firstName', function(newValue, oldValue) {
if (newValue !== oldValue) {
console.log('First name changed from', oldValue, 'to', newValue);
}
});
// Deep watch for object changes
$scope.$watch('user', function(newValue, oldValue) {
if (newValue !== oldValue) {
console.log('User object changed');
}
}, true); // Third parameter enables deep watching
// Method that updates model (view updates automatically)
$scope.updateUser = function() {
$scope.user.firstName = 'Jane';
// No need to manually update view - digest cycle handles it
};
// List example with ng-repeat
$scope.items = [
{ id: 1, name: 'Item 1', checked: false },
{ id: 2, name: 'Item 2', checked: true },
{ id: 3, name: 'Item 3', checked: false }
];
$scope.addItem = function() {
var newId = $scope.items.length + 1;
$scope.items.push({
id: newId,
name: 'Item ' + newId,
checked: false
});
// View updates automatically to show new item
};
$scope.removeItem = function(id) {
$scope.items = $scope.items.filter(item => item.id !== id);
// View updates automatically to remove item
};
// Integration with non-Angular code requires $apply
$scope.updateFromOutside = function() {
setTimeout(function() {
// Wrap in $apply to trigger digest cycle
$scope.$apply(function() {
$scope.user.firstName = 'Updated';
});
}, 1000);
};
}]);
// HTML Template demonstrating two-way binding
/*
<div ng-controller="DataBindingController">
<!-- Input changes update model, model changes update input -->
<input type="text" ng-model="user.firstName" placeholder="First Name">
<input type="text" ng-model="user.lastName" placeholder="Last Name">
<input type="email" ng-model="user.email" placeholder="Email">
<!-- Display updates automatically when model changes -->
<p>Full Name: {{getFullName()}}</p>
<p>Email: {{user.email}}</p>
<!-- Button click updates model, which updates view -->
<button ng-click="updateUser()">Update to Jane</button>
<!-- Checkbox demonstrates two-way binding with boolean -->
<div ng-repeat="item in items">
<input type="checkbox" ng-model="item.checked">
<span ng-class="{completed: item.checked}">{{item.name}}</span>
<button ng-click="removeItem(item.id)">Remove</button>
</div>
<button ng-click="addItem()">Add Item</button>
<!-- Select dropdown with two-way binding -->
<select ng-model="user.country">
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
</select>
<p>Selected Country: {{user.country}}</p>
</div>
*/
// Performance optimization: one-time binding (AngularJS 1.3+)
angular.module('myApp')
.controller('OptimizedController', ['$scope', function($scope) {
$scope.staticData = 'This never changes';
$scope.dynamicData = 'This changes';
}]);
// In template:
// {{::staticData}} <- One-time binding, watcher removed after first digest
// {{dynamicData}} <- Regular binding, watcher persists
Mermaid Diagram:
flowchart LR
subgraph View
I[Input Element<br/>ng-model='user.name']
D[Display Element<br/>{{user.name}}]
end
subgraph Model
S[$scope.user.name]
end
subgraph Digest Cycle
W[Watchers]
DC[Dirty Check]
U[Update DOM]
end
I -->|User types| S
S -->|Model changes| W
W -->|Check changes| DC
DC -->|Changes detected| U
U -->|Update| I
U -->|Update| D
S -.->|Programmatic update| W
style I fill:#e1f5ff
style D fill:#e1f5ff
style S fill:#f0ffe1
style W fill:#ffe1f5
style DC fill:#fff4e1
style U fill:#e1e1ff
Mermaid Diagram (Digest Cycle Process):
sequenceDiagram
participant User
participant View
participant Scope as $scope
participant Digest as Digest Cycle
User->>View: Types in input (ng-model)
View->>Scope: Update model property
Scope->>Digest: Trigger $digest()
loop Dirty Checking
Digest->>Digest: Compare old vs new values
Digest->>Scope: Check all watchers
Digest->>View: Update DOM if changed
end
Digest->>View: Render final state
View->>User: Display updated UI
Note over Digest: Max 10 iterations to prevent<br/>infinite digest loops
References:
- AngularJS Data Binding Documentation
- Understanding the Digest Cycle
- Watchers and Performance Optimization
Controllers
What is a controller in AngularJS?
The 30-Second Answer: A controller in AngularJS is a JavaScript constructor function that augments the Angular scope, providing the business logic and data for views. Controllers define behavior, initialize state, and handle user interactions by attaching properties and methods to the $scope object (or to the controller instance itself using "controller as" syntax).
The 2-Minute Answer (If They Want More): Controllers are the heart of AngularJS applications, acting as the glue between your views and models. When AngularJS instantiates a controller, it creates a new child scope and injects it as the $scope parameter. This scope becomes the execution context for expressions in the view template.
The primary responsibility of a controller is to set up the initial state of the scope and add behavior to it. This includes initializing data models, defining functions that respond to user events, and orchestrating data flow between services and the view. Controllers should be thin, focusing on view-specific logic while delegating business logic to services.
It's important to note that controllers are instantiated when they're needed and destroyed when they're not, making them transient by nature. This lifecycle means you shouldn't use controllers for sharing state across different parts of your application—that's what services are for. Each controller instance gets its own isolated scope, ensuring proper encapsulation.
Modern AngularJS development favors the "controller as" syntax over traditional $scope injection, as it provides clearer code and better aligns with component-based architectures that eventually led to Angular 2+.
Code Example:
// Traditional controller with $scope injection
angular.module('myApp', [])
.controller('UserController', ['$scope', 'UserService', function($scope, UserService) {
// Initialize scope data
$scope.user = {
name: '',
email: ''
};
// Add behavior to scope
$scope.saveUser = function() {
UserService.save($scope.user)
.then(function(response) {
$scope.message = 'User saved successfully!';
})
.catch(function(error) {
$scope.error = 'Failed to save user';
});
};
// Watch for changes
$scope.$watch('user.email', function(newValue, oldValue) {
if (newValue !== oldValue) {
$scope.emailChanged = true;
}
});
}]);
References:
↑ Back to topTesting and Migration
What is ngMock and how is it used in testing?
The 30-Second Answer:
ngMock is AngularJS's testing module that provides utilities for injecting and mocking dependencies during unit tests. It includes the module() and inject() functions for loading modules and injecting services, plus mocks like $httpBackend for simulating HTTP requests and $timeout for controlling asynchronous code execution.
The 2-Minute Answer (If They Want More): The angular-mocks library (ngMock) is essential for testing AngularJS applications because it enhances the dependency injection system to work seamlessly in a testing environment. When I include angular-mocks.js in my test setup, it automatically replaces certain services with mock versions that give me more control during testing.
The most commonly used features are module() for loading AngularJS modules before tests, and inject() for injecting dependencies into test functions. The inject function uses a clever naming convention where I can wrap parameter names with underscores (like _$rootScope_) to avoid naming conflicts with local variables while still getting the actual service injected.
One of the most powerful mocks is $httpBackend, which intercepts HTTP requests and allows me to define expected requests and their responses without making real network calls. This makes tests fast, deterministic, and independent of external services. I use expectGET(), expectPOST(), etc., to define expectations, and flush() to resolve pending requests synchronously.
Other useful mocks include $timeout and $interval which let me control time in tests by manually flushing pending timers, and various spies and mock implementations that make it easy to verify function calls and control their behavior. The ngMock module also enhances $log to store log messages so I can assert on what gets logged during test execution.
Code Example:
describe('ngMock Examples', function() {
var $httpBackend, $timeout, $log, apiService;
// Load the app module and ngMock
beforeEach(module('myApp'));
// Inject dependencies using ngMock's inject function
beforeEach(inject(function(_$httpBackend_, _$timeout_, _$log_, _apiService_) {
// Underscore wrapping allows using same name for local variable
$httpBackend = _$httpBackend_;
$timeout = _$timeout_;
$log = _$log_;
apiService = _apiService_;
}));
afterEach(function() {
// Verify all expected HTTP requests were made
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
// Example 1: Using $httpBackend to mock HTTP requests
describe('$httpBackend mock', function() {
it('should mock GET request', function() {
var result;
// Define expected request and response
$httpBackend.expectGET('/api/users/1')
.respond(200, {id: 1, name: 'John'});
apiService.getUser(1).then(function(data) {
result = data;
});
// Flush pending HTTP requests
$httpBackend.flush();
expect(result.name).toBe('John');
});
it('should mock POST request with data', function() {
var newUser = {name: 'Jane', email: 'jane@example.com'};
// Expect POST with specific data
$httpBackend.expectPOST('/api/users', newUser)
.respond(201, {id: 2, name: 'Jane', email: 'jane@example.com'});
var result;
apiService.createUser(newUser).then(function(data) {
result = data;
});
$httpBackend.flush();
expect(result.id).toBe(2);
});
it('should handle HTTP errors', function() {
var error;
$httpBackend.expectGET('/api/users/999')
.respond(404, {error: 'Not found'});
apiService.getUser(999).catch(function(err) {
error = err;
});
$httpBackend.flush();
expect(error.status).toBe(404);
});
});
// Example 2: Using $timeout mock to control async code
describe('$timeout mock', function() {
it('should control timer execution', function() {
var called = false;
$timeout(function() {
called = true;
}, 1000);
// Timer hasn't executed yet
expect(called).toBe(false);
// Flush all pending timers
$timeout.flush();
// Now it has executed
expect(called).toBe(true);
});
it('should flush specific delay', function() {
var calls = [];
$timeout(function() { calls.push('first'); }, 100);
$timeout(function() { calls.push('second'); }, 200);
// Flush only 100ms worth of timers
$timeout.flush(100);
expect(calls).toEqual(['first']);
// Flush remaining timers
$timeout.flush();
expect(calls).toEqual(['first', 'second']);
});
});
// Example 3: Using $log mock to verify logging
describe('$log mock', function() {
it('should capture log messages', function() {
$log.debug('Debug message');
$log.info('Info message');
$log.warn('Warning message');
$log.error('Error message');
expect($log.debug.logs).toContain(['Debug message']);
expect($log.info.logs).toContain(['Info message']);
expect($log.warn.logs.length).toBe(1);
expect($log.error.logs[0]).toEqual(['Error message']);
});
});
// Example 4: Creating custom mocks
describe('Custom mocks', function() {
it('should mock a service dependency', function() {
// Create a mock service
var mockUserService = {
getCurrentUser: jasmine.createSpy('getCurrentUser').and.returnValue({
id: 1,
name: 'Test User'
})
};
// Inject the mock using module
module(function($provide) {
$provide.value('userService', mockUserService);
});
inject(function(userService) {
var user = userService.getCurrentUser();
expect(user.name).toBe('Test User');
expect(mockUserService.getCurrentUser).toHaveBeenCalled();
});
});
});
// Example 5: Using whenGET for non-strict matching
describe('whenGET vs expectGET', function() {
it('should use whenGET for optional requests', function() {
// whenGET doesn't fail if request isn't made
$httpBackend.whenGET('/api/config').respond({setting: 'value'});
// This won't fail even though we don't make the request
// (useful for setup that might or might not happen)
});
});
});
// Karma configuration example (karma.conf.js)
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js', // Include ngMock
'app/**/*.js',
'test/**/*.spec.js'
],
browsers: ['Chrome'],
singleRun: true
});
};
References:
- AngularJS ngMock Module Documentation
- AngularJS $httpBackend Mock
- Testing AngularJS with Jasmine and Karma
Forms and Validation
How does form validation work in AngularJS?
The 30-Second Answer:
AngularJS provides built-in form validation through directives and the form controller. When you create a form, AngularJS automatically tracks the state of each input field and the overall form, providing properties like $valid, $invalid, $dirty, and $pristine that you can use to show validation errors and control form submission.
The 2-Minute Answer (If They Want More):
AngularJS form validation works by creating a form controller that tracks the validation state of all inputs within the form. Each input with ng-model gets its own controller that tracks validation states like whether it's been touched, modified, valid, or invalid.
The framework applies validation rules through HTML5 attributes (required, min, max, pattern) and AngularJS-specific directives (ng-required, ng-minlength, ng-maxlength, ng-pattern). As users interact with the form, AngularJS updates CSS classes (ng-valid, ng-invalid, ng-dirty, ng-pristine, ng-touched, ng-untouched) that you can use for styling.
The form controller exposes validation properties on the scope, allowing you to conditionally display error messages, disable submit buttons, or prevent form submission. You can access individual field validation states via formName.fieldName.$valid and overall form state via formName.$valid.
This two-way binding between the model and view means validation happens in real-time as users type, providing immediate feedback. You can also create custom validators using directives or the $validators pipeline for complex validation logic.
Code Example:
<!-- HTML Form with Validation -->
<form name="userForm" ng-submit="submitForm()" novalidate>
<!-- Email field with validation -->
<div>
<label>Email:</label>
<input type="email"
name="email"
ng-model="user.email"
required
ng-pattern="/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/">
<!-- Show error messages based on validation state -->
<span ng-show="userForm.email.$invalid && userForm.email.$touched" class="error">
<span ng-show="userForm.email.$error.required">Email is required.</span>
<span ng-show="userForm.email.$error.pattern">Invalid email format.</span>
</span>
</div>
<!-- Username with length validation -->
<div>
<label>Username:</label>
<input type="text"
name="username"
ng-model="user.username"
required
ng-minlength="3"
ng-maxlength="20">
<span ng-show="userForm.username.$invalid && userForm.username.$dirty" class="error">
<span ng-show="userForm.username.$error.required">Username is required.</span>
<span ng-show="userForm.username.$error.minlength">Username must be at least 3 characters.</span>
<span ng-show="userForm.username.$error.maxlength">Username cannot exceed 20 characters.</span>
</span>
</div>
<!-- Submit button disabled when form is invalid -->
<button type="submit" ng-disabled="userForm.$invalid">Submit</button>
<!-- Show form validation summary -->
<div ng-show="userForm.$invalid && userForm.$submitted">
Please fix the errors above before submitting.
</div>
</form>
// Controller handling form submission
angular.module('myApp', [])
.controller('FormController', function($scope) {
$scope.user = {};
$scope.submitForm = function() {
// Check if form is valid
if ($scope.userForm.$valid) {
console.log('Form submitted:', $scope.user);
// Process form data (API call, etc.)
} else {
// Mark all fields as touched to show validation errors
angular.forEach($scope.userForm.$error, function(field) {
angular.forEach(field, function(errorField) {
errorField.$setTouched();
});
});
}
};
});
/* CSS for validation states */
.ng-invalid.ng-touched {
border-color: red;
}
.ng-valid.ng-touched {
border-color: green;
}
.error {
color: red;
font-size: 12px;
}
References:
↑ Back to top