FREE PREVIEW

You're viewing a free preview

This is a sample of 15 questions from our full collection of 44 interview questions.

Unlock all 44 questions with detailed explanations and code examples

Get Full Access

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:

↑ Back to top

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 top

What 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:

↑ Back to top

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:

↑ Back to top

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:

↑ Back to top

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 top

Digest 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-repeat lists (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:

↑ Back to top

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:

↑ Back to top

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:

↑ Back to top

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:

↑ Back to top

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:

↑ Back to top

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:

↑ Back to top

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 top

Testing 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:

↑ Back to top

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

Want more questions?

You've seen 15 sample questions. Unlock all 44 En interview questions with detailed explanations, code examples, and expert insights.

44+ questions
Code examples
Expert explanations
Instant access
Unlock Full Access