Skip to content

AngularJS 치트시트

JavaScript-based front-end web framework (Angular 1.x).

01

Getting Started

Setup & Bootstrap

AngularJS (1.x) is included via a script tag. ng-app bootstraps the application and ng-controller wires a controller to a DOM subtree. Double curly braces {{ }} are Angular expressions that render model data.

angularjs
<!-- include AngularJS 1.8.x -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>

<body ng-app="myApp">
  <div ng-controller="MainCtrl">
    {{ message }}
  </div>
</body>

<script>
  var app = angular.module('myApp', []);
  app.controller('MainCtrl', function($scope) {
    $scope.message = 'Hello AngularJS!';
  });
</script>

ng-app & Auto-Bootstrap

ng-app with a module name auto-bootstraps AngularJS on DOMContentLoaded. ng-strict-di forces explicit dependency annotations, catching minification bugs early in development. Only one ng-app is auto-bootstrapped per page.

angularjs
<!-- ng-app on <html> makes the whole page an Angular app -->
<html ng-app="myApp" ng-strict-di>
  <head><title>App</title></head>
  <body>
    <div>{{ 1 + 2 }}</div>
  </body>
</html>

<!-- ng-strict-di enforces dependency injection annotations -->
<!-- (errors if a service uses implicit DI without annotation) -->

Manual Bootstrap

Use angular.bootstrap() when you need to delay startup (e.g., waiting for async module loading or a fetched config). You can bootstrap multiple Angular apps on one page this way, each on its own root element.

angularjs
<!-- HTML has NO ng-app attribute -->
<div id="appRoot">
  {{ message }}
</div>

<script>
  angular.element(document).ready(function() {
    angular.bootstrap(document.getElementById('appRoot'), ['myApp']);
  });
</script>

First Data Binding

With ng-app and no module name, Angular runs in 'auto' mode with the default ng module. ng-model binds the input to a property named 'name' on the implicit scope, and {{ }} renders it live. This is two-way data binding in its simplest form.

angularjs
<div ng-app>
  <label>Your name:
    <input type="text" ng-model="name">
  </label>
  <p>Hello {{ name || 'stranger' }}!</p>
</div>

Module & Controller Basics

angular.module('name', [deps]) creates a module; angular.module('name') retrieves an existing one. The inline array annotation ['$scope', function($scope){}] keeps the controller minification-safe. Always pass dependencies explicitly to the module when creating it.

angularjs
var app = angular.module('myApp', []); // create module
//          ^name   ^dependencies

app.controller('GreetCtrl', ['$scope', function($scope) {
  $scope.user = { name: 'Ada' };
  $scope.greet = function() {
    return 'Hi ' + $scope.user.name;
  };
}]);
02

Modules

Creating vs Retrieving a Module

The two-argument form angular.module(name, deps) creates a new module. The one-argument form angular.module(name) retrieves a previously defined one. Accidentally passing [] twice silently wipes the module's registrations.

angularjs
// CREATE a module (second arg = dependency array)
var app = angular.module('myApp', []);

// RETRIEVE an existing module (NO second arg)
// var app = angular.module('myApp');

// common mistake: passing [] again overwrites the module

Module Dependencies

The dependency array lists modules whose providers are injected into this one. Built-in modules like ngRoute are optional and must be explicitly listed (and their script loaded). Sub-modules let you split a large app into reusable pieces.

angularjs
var app = angular.module('myApp', [
  'ngRoute',          // built-in routing
  'ngAnimate',        // animation hooks
  'ui.router',        // 3rd-party router
  'myApp.common',     // your own sub-module
]);

Config Block

config() runs during provider registration, before any service instance is created. It is the only place you can inject and configure Providers (e.g., $routeProvider, $locationProvider). Use it to set up routes, enable HTML5 mode, or register custom validators.

angularjs
app.config(['$routeProvider', function($routeProvider) {
  $routeProvider
    .when('/home', { templateUrl: 'home.html' })
    .when('/about', { templateUrl: 'about.html' })
    .otherwise({ redirectTo: '/home' });
}]);

Run Block

run() executes after the injector is created and all providers are configured — the app's 'main' entry point. Inject instances (not providers). It's commonly used to register global $rootScope listeners for route changes or authentication checks.

angularjs
app.run(['$rootScope', 'AuthService', function($rootScope, AuthService) {
  $rootScope.$on('$routeChangeStart', function(event, next) {
    if (!AuthService.isAuthenticated()) {
      event.preventDefault();
    }
  });
}]);

Constants & Values

constant() registers a value that can be injected into config() blocks too — ideal for configuration that providers need. value() is injectable only into run-phase constructs (services, controllers). Use constant for config that must be available early.

angularjs
// constant: available in BOTH config and run phases
app.constant('API_BASE', 'https://api.example.com');

// value: available only in run phase (injectable as instance)
app.value('defaultPageSize', 20);

// usage in a service:
app.factory('UserSvc', ['API_BASE', function(API_BASE) {
  return { all: function() { /* use API_BASE */ } };
}]);
03

Controllers

Basic Controller

Controllers set up the initial state of $scope and add behavior (functions). Keep controllers thin — business logic belongs in services. The controller constructor runs each time a new instance of the controller is needed (e.g., per ng-controller usage).

angularjs
app.controller('TodoCtrl', ['$scope', function($scope) {
  $scope.todos = [
    { text: 'Learn AngularJS', done: true },
    { text: 'Build an app', done: false }
  ];

  $scope.addTodo = function() {
    $scope.todos.push({ text: $scope.todoText, done: false });
    $scope.todoText = '';
  };

  $scope.remaining = function() {
    return $scope.todos.filter(function(t) { return !t.done; }).length;
  };
}]);

Controller As Syntax

controllerAs publishes the controller instance on the scope under an alias (main). Use this instead of $scope for properties and methods. This avoids $scope inheritance pitfalls and makes it explicit which controller a property comes from in nested views.

angularjs
<div ng-controller="MainCtrl as main">
  <p>{{ main.title }}</p>
  <button ng-click="main.save()">Save</button>
</div>

<script>
app.controller('MainCtrl', function() {
  this.title = 'Controller As';
  this.save = function() { /* ... */ };
});
</script>

Minification-Safe DI

Minifiers rename function parameters, breaking Angular's name-based dependency inference. The inline array annotation lists service names as strings before the function, so the names survive minification. Always use this annotation or ng-annotate / $inject.

angularjs
// SAFE: inline array annotation (parameter names are string literals)
app.controller('SafeCtrl', ['$scope', '$http', function($scope, $http) {
  $http.get('/api/items').then(function(res) { $scope.items = res.data; });
}]);

// UNSAFE: implicit DI breaks when minified
app.controller('UnsafeCtrl', function($scope, $http) { /* ... */ });

$inject Property Annotation

An alternative to the inline array: attach an $inject array of service names to the constructor function. This keeps the function declaration readable and reusable. Both styles are equivalent — pick one and be consistent (or automate with ng-annotate).

angularjs
function UserCtrl($scope, UserService) {
  UserService.list().then(function(users) {
    $scope.users = users;
  });
}
// explicit dependency list on the function
UserCtrl.$inject = ['$scope', 'UserService'];

app.controller('UserCtrl', UserCtrl);

Nested Controllers & Inheritance

Child scopes prototypally inherit from parent scopes, so a child controller reads parent properties. However, assigning a primitive on the child creates a new local property that shadows the parent rather than updating it — a common gotcha. Prefer objects (dot notation) or controllerAs to avoid this.

angularjs
<div ng-controller="ParentCtrl">
  <p>{{ shared }}</p>      <!-- sees parent's 'shared' -->
  <div ng-controller="ChildCtrl">
    <p>{{ shared }}</p>    <!-- inherits, can shadow -->
    <p>{{ childOnly }}</p>
  </div>
</div>

app.controller('ParentCtrl', ['$scope', function($scope) {
  $scope.shared = 'from parent';
}]);
app.controller('ChildCtrl', ['$scope', function($scope) {
  $scope.childOnly = 'child only';
  $scope.shared = 'overridden by child'; // shadows parent
}]);

Controller Cleanup ($destroy)

Listen for the $destroy event on $scope to run cleanup when the controller's scope is torn down (e.g., navigating away). Cancel $interval/$timeout timers, unbind window/listeners, and release references to prevent memory leaks. Angular auto-cleans its own scope watchers.

angularjs
app.controller('TimerCtrl', ['$scope', '$interval', function($scope, $interval) {
  var timer = $interval(function() { $scope.now = Date.now(); }, 1000);

  $scope.$on('$destroy', function() {
    $interval.cancel(timer); // free the resource
  });
}]);
04

$scope

$scope Basics

$scope is the glue between a controller and the view — it holds the model and behavior the template binds to. Angular creates a new scope for each controller; expressions in the template are evaluated against that scope (and its parents).

angularjs
app.controller('MsgCtrl', ['$scope', function($scope) {
  $scope.message = 'Hi';          // model on scope
  $scope.count = 0;
  $scope.inc = function() {
    $scope.count++;
  };
}]);

// In HTML: {{ message }}, {{ count }}, ng-click="inc()"

Scope Inheritance (Prototypal)

Child scopes inherit parent scope via JavaScript's prototype chain. Reading a parent property works, but assigning a primitive (e.g., $scope.name = 'x') on a child creates a local copy that shadows the parent. Bind to objects (user.name) so writes propagate up the chain.

angularjs
<!-- primitive on parent: child writes a NEW copy -->
<div ng-controller="ParentCtrl">
  <div ng-controller="ChildCtrl">
    <input ng-model="name"> <!-- shadows parent.name -->
  </div>
</div>

<!-- object avoids the shadowing gotcha -->
app.controller('ParentCtrl', ['$scope', function($scope) {
  $scope.user = { name: 'Ada' }; // child edits user.name -> updates parent
}]);

$rootScope

$rootScope is the top of the scope hierarchy; every other scope descends from it. Properties placed here are visible in all templates. Use it sparingly for truly global values (app name, session flag) — overuse creates hidden coupling and makes state hard to trace.

angularjs
app.run(['$rootScope', function($rootScope) {
  $rootScope.appName = 'MyApp';     // available everywhere
  $rootScope.version = '1.0';
}]);

<!-- any template -->
<footer>© {{ appName }} {{ version }}</footer>

Scope Events: $emit / $broadcast / $on

$emit fires an event that travels upward to $rootScope; $broadcast travels downward to all child scopes. $on registers a listener and returns a de-registration function. Pass data via the second argument. Unbind listeners on $destroy to avoid leaks.

angularjs
// travel UP toward $rootScope
$scope.$emit('userLoggedOut', { userId: 7 });

// travel DOWN to all descendant scopes
$scope.$broadcast('itemAdded', { id: 99 });

// listen for an event
var unbind = $scope.$on('itemAdded', function(event, args) {
  console.log('item', args.id);
});
// stop listening when scope is destroyed
$scope.$on('$destroy', unbind);

$new, $id, $parent

$new(isolated, parent) creates a child (or isolate) scope; $id is a unique numeric id; $parent references the parent scope. These are mostly used inside directives. Always call $destroy() on manually-created scopes to remove watchers and avoid memory leaks.

angularjs
var child = $scope.$new();        // create a child scope
child.extra = 'child data';
console.log(child.$id);          // unique scope id
console.log(child.$parent === $scope); // true
// ...
child.$destroy();                // remove from hierarchy

Isolate Scope (Directive Preview)

Setting scope: {} in a directive creates an isolate scope that does NOT prototypally inherit from its parent — essential for reusable components. The parent communicates only through declared bindings (@, =, &). This prevents components from accidentally reading or polluting surrounding scope.

angularjs
app.directive('userCard', function() {
  return {
    scope: {              // ISOLATE scope: does NOT inherit parent
      user: '=',          // two-way binding
      onKick: '&'         // expression binding
    },
    template: '<p>{{ user.name }}</p>'
  };
});

<!-- usage -->
<user-card user="currentUser" on-kick="removeUser()"></user-card>
05

Expressions

Basic Expressions

Angular expressions are JavaScript-like but evaluated against $scope (not window). They support arithmetic, string concat, member access, and ternaries. Unlike JS, undefined/null are shown as empty string (no 'undefined' text). The optional-chaining operator is NOT supported.

angularjs
<p>{{ 1 + 2 }}</p>                <!-- 3 -->
<p>{{ 'Hello, ' + name }}</p>
<p>{{ user.name }}</p>
<p>{{ items.length }} items</p>
<p>{{ obj?.nested }}</p>           <!-- AngularJS does NOT support ?. -->

Expression vs JavaScript

Expressions are more limited than JS: no statements (if/for/while), no new/throw, no commas, no function definitions, no bitwise on some versions. They are evaluated with $scope as context and silently swallow errors (logged as $exceptionHandler). This keeps the template safe and declarative.

angularjs
<!-- ALLOWED in expressions -->
{{ a + b }}
{{ cond ? 'yes' : 'no' }}
{{ user.getName() }}
{{ [1,2,3].length }}

<!-- NOT allowed: control flow, new, throw, comma operator -->
{{ if (x) { ... } }}     <!-- SyntaxError -->
{{ throw new Error() }}  <!-- not allowed -->

One-time Binding (::)

The :: prefix creates a one-time binding: Angular evaluates the expression and, once it is defined (non-undefined), removes the watcher. This drastically reduces the number of watchers in the digest cycle, improving performance for data that doesn't change after initial load.

angularjs
<!-- AngularJS 1.3+ -->
<p>{{ ::user.name }}</p>           <!-- evaluated once, then unwatched -->
<li ng-repeat="item in ::items">{{ ::item.label }}</li>

<!-- good for static data that never changes -->
<h1>{{ ::conferenceTitle }}</h1>

$eval and $parse

$scope.$eval(expr) parses and runs an expression string against the scope. $parse compiles an expression once into a reusable getter/setter function — far more efficient than re-evaluating a string repeatedly. Use $parse inside directives/services that consume expression bindings.

angularjs
// evaluate an expression against $scope
var result = $scope.$eval('1 + 2 * count');   // 1 + 2*count
// $scope.$eval(expr, locals)

// compile once, reuse: more efficient for repeated eval
var getter = $parse('user.name');
var name = getter($scope);
getter.assign($scope, 'Ada'); // two-way: sets user.name

Filters in Expressions

The pipe | applies a filter to the expression; multiple filters chain left-to-right. Arguments are passed with a colon. Filters are convenient but re-run each digest — for large lists, filter in the controller via $filter or a computed property for better performance.

angularjs
<p>{{ price | currency }}</p>             <!-- $1.99 -->
<p>{{ today | date:'yyyy-MM-dd' }}</p>    <!-- 2025-01-01 -->
<p>{{ name | uppercase }}</p>
<p>{{ items | filter:query | orderBy:'name' }}</p>

<!-- chained with |, arguments after : -->

ng-bind & ng-cloak

Before Angular bootstraps, the browser shows raw {{ }} text ('flash of uncompiled content'). ng-bind writes the value into the element's text only after Angular runs, avoiding the flash. ng-cloak combined with a CSS rule hides elements until Angular has compiled them.

angularjs
<!-- raw {{ }} may flash before Angular compiles -->
<p>{{ message }}</p>

<!-- ng-bind avoids the flash -->
<p ng-bind="message"></p>

<!-- ng-cloak hides elements until compiled -->
<style>[ng-cloak] { display: none; }</style>
<p ng-cloak>{{ message }}</p>
06

Built-in Directives

ng-model (Two-Way Binding)

ng-model binds form inputs to a scope property with two-way sync: input changes update the model, and model changes update the input. It works on input, textarea, select, and checkbox/radio. Always bind to a dotted property (user.name) to avoid primitive-shadowing issues.

angularjs
<input type="text" ng-model="user.name">
<p>Hello, {{ user.name }}</p>

<!-- checkbox binds to boolean -->
<input type="checkbox" ng-model="agree"> I agree

<!-- select binds the chosen option's value -->
<select ng-model="color">
  <option value="r">Red</option>
  <option value="g">Green</option>
</select>

ng-repeat

ng-repeat clones the element for each item in a collection. $index, $first, $last, $middle, $even, $odd are exposed. Always use track by to give Angular a stable identity so it can reuse DOM nodes when the array changes — this avoids re-rendering everything and fixes duplicate-key errors.

angularjs
<ul>
  <li ng-repeat="item in items track by item.id">
    {{ $index }}: {{ item.name }}
    <span ng-if="$first">(first)</span>
    <span ng-if="$last">(last)</span>
  </li>
</ul>

<!-- ng-repeat over object -->
<li ng-repeat="(key, value) in config">{{ key }} = {{ value }}</li>

ng-if / ng-show / ng-hide

ng-if conditionally inserts/removes the element (and its child scopes) from the DOM — expensive to toggle but cheap when hidden. ng-show/ng-hide only flip a CSS class, keeping the element in the DOM — cheap to toggle but the element is always compiled. Prefer ng-if for rarely-shown content.

angularjs
<!-- ng-if: removes/adds element from DOM -->
<div ng-if="user.isAdmin">Admin tools</div>

<!-- ng-show/ng-hide: toggles display:none only -->
<div ng-show="isLoading">Loading...</div>
<div ng-hide="isLoggedIn">Please log in</div>

ng-class & ng-style

ng-class accepts an object (truthy keys become classes), an array of class names, or a string. ng-style takes an object mapping CSS property names to values. Both re-evaluate each digest, so for static styling prefer a plain class attribute — use these for state-driven styling.

angularjs
<!-- object form: keys are classes, truthy values apply them -->
<div ng-class="{ active: tab === 1, disabled: !enabled }"></div>

<!-- array form -->
<div ng-class="[styleA, styleB]"></div>

<!-- ng-style: object of CSS properties -->
<div ng-style="{ color: theme.color, 'font-size': theme.size + 'px' }">
  Styled text
</div>

ng-bind-template & ng-init

ng-bind-template binds several expressions to an element's text without raw {{ }}. ng-init evaluates an expression once when the element initializes — handy for demos but discouraged in real apps because it puts logic in the markup. Set initial state in the controller instead.

angularjs
<!-- bind multiple expressions to text -->
<p ng-bind-template="Hello {{ first }} {{ last }}"></p>

<!-- ng-init: set a local (use sparingly, prefer controller) -->
<div ng-init="qty = 1; cost = 5">
  Total: {{ qty * cost }}
</div>

ng-src / ng-href / ng-disabled

Use ng-src and ng-href instead of src='{{ }}' so the browser doesn't issue a request against the literal '{{ }}' URL before Angular interpolates it. ng-disabled toggles the disabled attribute based on an expression — perfect for disabling submit buttons while a form is invalid or a request is in flight.

angularjs
<!-- avoid broken requests before binding resolves -->
<img ng-src="{{ user.avatarUrl }}" alt="avatar">
<a ng-href="{{ profileUrl }}">Profile</a>

<button ng-disabled="form.$invalid || saving">
  {{ saving ? 'Saving...' : 'Save' }}
</button>
07

Custom Directives

Basic Directive

A directive extends HTML. restrict: 'E' makes it a custom element (<greeting>); 'A' an attribute. The simplest directives just return a template. Use Element/Attribute restricts by default; Class/Comment restricts are rare. Modern best practice favors components for template-based directives.

angularjs
app.directive('greeting', function() {
  return {
    restrict: 'E',              // E=element, A=attr, C=class, M=comment
    template: '<h1>Hello there!</h1>'
  };
});

<!-- usage -->
<greeting></greeting>
<!-- or as attribute: <div greeting></div> -->

Directive Definition Object (DDO)

The Directive Definition Object configures every aspect: restrict, scope (isolate or not), template/templateUrl, transclude, controller, and the link function. templateUrl loads the template via $http (cached); template is inlined. Keep DOM manipulation in link, view-model logic in the controller.

angularjs
app.directive('card', function() {
  return {
    restrict: 'E',
    scope: {},                  // isolate scope
    templateUrl: 'card.html',
    transclude: true,
    controller: function() { /* ... */ },
    controllerAs: 'vm',
    bindToController: {},
    link: function(scope, el, attrs) { /* DOM work */ }
  };
});

Isolate Scope Bindings (@ = &)

@ reads the attribute as a string (one-way, parent->child, interpolated). = creates a two-way binding to the parent's expression. & exposes a function that executes an expression in the parent scope — used for event callbacks. These three bindings are the heart of reusable directive components.

angularjs
app.directive('profile', function() {
  return {
    restrict: 'E',
    scope: {
      name: '@',          // read attribute as STRING (one-way)
      user: '=',          // two-way binding to expression
      onSave: '&'         // callback: evaluate expression in parent scope
    },
    template: '<p>{{ name }} / {{ user.email }}</p>'
  };
});

<profile name="{{ n }}" user="currentUser" on-save="save()"></profile>

Link Function

The link function runs after the directive's element is compiled. Use it for direct DOM manipulation, event listeners (via jQuery Lite's .on), and observing interpolated attributes with attrs.$observe. scope is the directive's scope, element is the jqLite-wrapped DOM node, attrs the normalized attributes.

angularjs
app.directive('highlight', function() {
  return {
    restrict: 'A',
    link: function(scope, element, attrs) {
      element.on('mouseenter', function() {
        element.css('background', 'yellow');
      });
      element.on('mouseleave', function() {
        element.css('background', '');
      });
      attrs.$observe('highlight', function(val) {
        // react to attribute interpolation changes
      });
    }
  };
});

Compile Function

The compile function runs once on the original template element before Angular clones it for ng-repeat etc. Use it for template-level transformations that don't need the scope. It returns a link function (or pre/post link object). Most directives don't need compile — prefer link for instance work.

angularjs
app.directive('repeatN', function() {
  return {
    restrict: 'E',
    compile: function(tElement, tAttrs) {
      // runs ONCE on the TEMPLATE element, before cloning
      var n = parseInt(tAttrs.count, 10);
      for (var i = 1; i < n; i++) {
        tElement.after(tElement.clone());
      }
      // optionally return a (post) link function
      return function link(scope, el, attrs) {
        el.addClass('repeated');
      };
    }
  };
});

Transclusion

transclude: true captures the original element's content and inserts it into the template where ng-transclude appears. The transcluded content keeps its original (parent) scope, not the directive's isolate scope. This lets a wrapper directive (panel, modal, card) wrap arbitrary caller-supplied content.

angularjs
app.directive('panel', function() {
  return {
    restrict: 'E',
    transclude: true,
    template: '<div class="panel"><div class="panel-body" ng-transclude></div></div>'
  };
});

<!-- the inner content lands inside ng-transclude -->
<panel>
  <h3>Title</h3>
  <p>Body content goes here.</p>
</panel>
08

Filters

Built-in Filters

Angular ships number, currency, date, lowercase, uppercase, json, limitTo, orderBy, and filter. The argument after the colon configures the filter. currency accepts an ISO symbol or custom character. Filters re-run on every digest, so cache heavy computations or compute in the controller.

angularjs
<p>{{ 1234.5 | number:2 }}</p>          <!-- 1,234.50 -->
<p>{{ 9.99 | currency:'USD' }}</p>     <!-- $9.99 -->
<p>{{ 128 | currency:'&euro;' }}</p>   <!-- €128.00 -->
<p>{{ 'hi' | uppercase }}</p>          <!-- HI -->
<p>{{ 'HI' | lowercase }}</p>          <!-- hi -->
<p>{{ 'a b c' | limitTo:2 }}</p>       <!-- a  -->

date Filter

The date filter formats a Date object, ISO 8601 string, or epoch milliseconds. Predefined formats include 'short','medium','long','full','shortDate','mediumDate','shortTime'. Custom formats use tokens: y (year), M (month), d (day), H (hour), m (minute), s (second), EEEE (full weekday).

angularjs
<p>{{ now | date }}</p>                       <!-- Jan 1, 2025 -->
<p>{{ now | date:'yyyy-MM-dd' }}</p>        <!-- 2025-01-01 -->
<p>{{ now | date:'shortTime' }}</p>         <!-- 3:45 PM -->
<p>{{ now | date:'EEEE, MMMM d, y' }}</p>   <!-- Wednesday, January 1, 2025 -->

<!-- accepts a Date, ISO string, or millis timestamp -->

filter Filter (Array Search)

The filter filter selects array items matching a string, object, or predicate function. An object matches items whose specified fields contain the given values. The optional true third argument forces exact match. For large arrays, filter in the controller to avoid re-running per digest.

angularjs
<!-- match any field containing the query string -->
<li ng-repeat="u in users | filter:query">{{ u.name }}</li>

<!-- match a specific field with an object -->
<li ng-repeat="u in users | filter:{ role: 'admin' }">{{ u.name }}</li>

<!-- strict equality on a field -->
<li ng-repeat="u in users | filter:{ id: 5 }:true">{{ u.name }}</li>

orderBy Filter

orderBy sorts an array by a field name (prefix '-' for descending), an array of such fields, or a comparator function. It creates a new sorted array each digest — for big lists, sort once in the controller and reuse. Combined with filter it powers search+sort UIs.

angularjs
<!-- ascending by 'name' -->
<li ng-repeat="u in users | orderBy:'name'">{{ u.name }}</li>

<!-- descending -->
<li ng-repeat="u in users | orderBy:'-name'">{{ u.name }}</li>

<!-- multiple fields -->
<li ng-repeat="u in users | orderBy:['role','-age']">{{ u.name }}</li>

<!-- by a function -->
<li ng-repeat="u in users | orderBy:scoreFn">{{ u.name }}</li>

Custom Filter

Register a filter factory with app.filter. The factory returns a function that transforms input (the value before the pipe) plus any extra arguments. Filters must be pure and side-effect-free. They are injectable, so you can depend on other services inside the factory.

angularjs
app.filter('capitalize', function() {
  return function(input) {
    if (!input) return '';
    input = String(input);
    return input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
  };
});

<!-- usage -->
<p>{{ 'hELLo' | capitalize }}</p>   <!-- Hello -->

$filter Service in Controllers

Inject $filter to apply a filter inside JS (controllers, services). Call $filter('name')(value, arg1, arg2). This avoids re-running the filter every digest and lets you reuse the result. Useful when you need the filtered value in logic, not just the view.

angularjs
app.controller('ListCtrl', ['$scope', '$filter', function($scope, $filter) {
  $scope.users = [/* ... */];

  // run the orderBy filter programmatically
  $scope.sorted = $filter('orderBy')($scope.users, 'name');

  // currency filter to format a number
  $scope.priceText = $filter('currency')(99.5, '$');
}]);
09

Services ($http, $location, ...)

$http Basics

$http performs XHR requests and returns a Promise. The resolved value is a response object with data, status, headers, and config. .then handles success, .catch errors. Since Angular 1.6, .success/.error callbacks are removed — use promise .then/.catch consistently.

angularjs
app.controller('ApiCtrl', ['$scope', '$http', function($scope, $http) {
  $http.get('/api/users')
    .then(function(response) {
      $scope.users = response.data;       // response: {data, status, headers, config}
    })
    .catch(function(error) {
      console.error('failed', error.status);
    });
}]);

$http POST/PUT with Config

Convenience methods exist for get/post/put/delete/patch/jsonp/head. For full control, pass a config object: params become query string, headers set request headers, responseType controls how the body is parsed. $http applies default transforms (JSON parse) automatically.

angularjs
$http.post('/api/users', { name: 'Ada' })
  .then(function(res) { console.log('created', res.data); });

// with headers / query params / response type
$http({
  method: 'GET',
  url: '/api/report',
  params: { format: 'pdf' },
  headers: { 'X-Auth': token },
  responseType: 'arraybuffer'
}).then(function(res) { /* res.data is binary */ });

$location Service

$location is a wrapper over window.location that stays in sync with the browser URL and the digest cycle. Use it to read or change path, search params, and hash. To enable HTML5 pretty URLs, configure $locationProvider.html5Mode(true) and add a <base> tag.

angularjs
// read the URL
$location.path();          // '/users/42'
$location.search();        // { tab: 'edit' }
$location.hash();          // 'top'
$location.absUrl();        // full URL

// change without full reload
$location.path('/dashboard');
$location.search('q', 'angular');   // ?q=angular
$location.hash('section-2');

$timeout & $interval

$timeout and $interval are digest-aware wrappers around setTimeout/setInterval — they trigger $apply so scope updates are picked up. Always cancel them on $destroy to prevent leaks and 'digest on a destroyed scope' errors. Pass false as the 3rd arg to skip the digest if you don't need it.

angularjs
// $timeout: like setTimeout but triggers a $digest
var promise = $timeout(function() {
  $scope.status = 'ready';
}, 1000);

// cancel
$timeout.cancel(promise);

// $interval: like setInterval, also digest-aware
var ticker = $interval(function() { $scope.now = Date.now(); }, 5000);
$scope.$on('$destroy', function() { $interval.cancel(ticker); });

$log & $window / $document

$log is a thin wrapper over console that supports log/info/warn/error/debug and can be decorated for production logging. $window and $document wrap the globals — always inject them rather than calling window/document directly, so tests can mock them and your code stays environment-agnostic.

angularjs
app.controller('LogCtrl', ['$log', '$window', function($log, $window) {
  $log.log('plain');
  $log.info('info');
  $log.warn('warning');
  $log.error('error');

  $window.alert('Hi');      // safer than bare alert()
  $window.innerWidth;
}]);

// $document wraps document; $window wraps window.
// Injecting them (instead of using globals) makes testing easy.

$rootScope.broadcast & Global Events

Use $rootScope.$broadcast for app-wide events (login/logout, theme change). Listeners on any scope receive it. For events that should only be heard by $rootScope listeners, $rootScope.$emit is cheaper (doesn't descend). Avoid overusing global events — prefer services with explicit APIs for most communication.

angularjs
// anywhere: broadcast a global event
$rootScope.$broadcast('auth:logout');

// in a controller: listen
$scope.$on('auth:logout', function() {
  $scope.currentUser = null;
});

// $emit on $rootScope = effectively global too
$rootScope.$emit('something', data); // only $rootScope.$on hears this
10

Factories, Services & Providers

factory()

factory() takes a function that returns the service instance — the factory runs once (singleton) and the return value is shared. This is the most common way to create a service: great for encapsulating private state behind a clean API. Anything you return (object, function, primitive) becomes the service.

angularjs
app.factory('UserService', ['$http', function($http) {
  var current = null;            // private state

  return {
    login: function(creds) {
      return $http.post('/login', creds).then(function(res) {
        current = res.data;
        return current;
      });
    },
    getCurrent: function() { return current; },
    isLoggedIn: function() { return !!current; }
  };
}]);

service()

service() instantiates the constructor with new, so you attach properties to this. Equivalent to a factory that returns a new instance — both are singletons. Use service() when you prefer a constructor/class style; use factory() when you need to return a specific object or wrap something.

angularjs
app.service('Counter', function() {
  // invoked with 'new' -> 'this' is the instance
  this.count = 0;
  this.inc = function() { this.count++; };
  this.reset = function() { this.count = 0; };
});

// usage: Counter.inc(); Counter.count;

provider()

provider() is the lowest-level recipe: it exposes a config-time object (with setters) and a $get factory that builds the instance. Only providers are injectable into config() blocks (suffix 'Provider'). Use provider() when a service needs configuration before instantiation; otherwise prefer factory().

angularjs
app.provider('Greeter', function() {
  // config-time: defaults settable from .config()
  this.greeting = 'Hello';        // default

  this.setGreeting = function(g) { this.greeting = g; };

  // factory that produces the service instance
  this.$get = function() {
    var self = this;
    return {
      greet: function(name) { return self.greeting + ', ' + name + '!'; }
    };
  };
});

// configure in a config block:
app.config(['GreeterProvider', function(GreeterProvider) {
  GreeterProvider.setGreeting('Bonjour');
}]);

constant() vs value()

Both register a simple injectable value. constant() is available during the config phase (so providers can use it) and is immutable. value() is available only at run phase. Rule of thumb: use constant for configuration that providers depend on; use value for shared runtime data or helpers.

angularjs
// constant: injectable in config AND run phases
app.constant('MAX_USERS', 1000);

// value: injectable in run phase only (not config)
app.value('toastConfig', { duration: 3000 });

// value can hold any type, even a function
app.value('logger', function(msg) { console.log(msg); });

factory vs service vs provider

Under the hood every recipe is a provider: factory's function becomes $get, service is a factory that news-up the constructor. Pick by readability: factory for most cases, service for constructor-style classes, provider when you need pre-instantiation config. Components only consume, so the choice is internal.

angularjs
// all three are singletons; the difference is HOW the instance is built

// factory: function returns the instance
app.factory('A', function() { return { x: 1 }; });

// service: constructor is 'new'-ed
app.service('B', function() { this.x = 1; });

// provider: $get returns the instance (configurable)
app.provider('C', function() {
  this.$get = function() { return { x: 1 }; };
});

$provide.decorator()

decorator() intercepts an existing service's instantiation — $delegate is the original instance, which you can wrap, extend, or replace. Use it for cross-cutting concerns (logging, caching, error reporting) without touching the original service. Decorators run lazily, only when the service is first injected.

angularjs
app.decorator('$log', ['$delegate', function($delegate) {
  // $delegate is the original $log instance
  var origError = $delegate.error;
  $delegate.error = function(msg) {
    // e.g., send to error tracking, then call original
    trackError(msg);
    origError.apply($delegate, arguments);
  };
  return $delegate;   // return the (modified) service
}]);
11

Routing (ngRoute & ui-router)

ngRoute Setup

ngRoute is the built-in router (in a separate angular-route.js file). Register routes with .when(path, config) and a fallback with .otherwise(). ng-view is the outlet where the matched template+controller render. ngRoute supports only one outlet and simple :param URLs — for nested views use ui-router.

angularjs
<!-- include angular-route.js (separate file in 1.x) -->
<script src="angular-route.js"></script>

var app = angular.module('myApp', ['ngRoute']);

app.config(['$routeProvider', function($routeProvider) {
  $routeProvider
    .when('/home', { templateUrl: 'home.html', controller: 'HomeCtrl' })
    .when('/users/:id', { templateUrl: 'user.html', controller: 'UserCtrl' })
    .otherwise({ redirectTo: '/home' });
}]);

<!-- where the matched view renders -->
<div ng-view></div>

ngRoute Route Params

$routeParams exposes URL segments (:id) as a string-keyed object — values are always strings, so convert numbers with parseInt. It updates after a route change. For dynamic links use ng-href. The default hashbang prefix is #! (since 1.6); configure with $locationProvider.hashPrefix('').

angularjs
app.controller('UserCtrl', ['$routeParams', 'UserSvc',
  function($routeParams, UserSvc) {
    // URL /users/42  ->  $routeParams.id === '42' (always a string)
    this.user = UserSvc.get($routeParams.id);
  }]);

<!-- links -->
<a href="#!/users/42">View user 42</a>
<!-- with HTML5 mode, use a plain href="/users/42" -->

ngRoute Lifecycle Events

ngRoute broadcasts $routeChangeStart (cancellable via event.preventDefault), $routeChangeSuccess, and $routeChangeError. The 'next'/'current' arguments carry the route config (with resolve outcomes). Use them for auth guards, loading indicators, and breadcrumb titles. resolve rejections fire $routeChangeError.

angularjs
app.run(['$rootScope', function($rootScope) {
  $rootScope.$on('$routeChangeStart', function(event, next, current) {
    if (next.requireAuth && !AuthService.isLoggedIn()) {
      event.preventDefault();             // cancel navigation
    }
  });

  $rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
    $rootScope.pageTitle = current.$$route.title;
  });

  $rootScope.$on('$routeChangeError', function(event, current, previous, rejection) {
    console.error('route failed', rejection);
  });
}]);

ui-router States

ui-router organizes the app into named states (not URL paths). ui-sref links by state name and parameters, and ui-view is the outlet. States can be nested and have their own URLs. This decouples navigation from URLs and supports multiple/named views — far more powerful than ngRoute for real apps.

angularjs
var app = angular.module('myApp', ['ui.router']);

app.config(['$stateProvider', function($stateProvider) {
  $stateProvider
    .state('home', { url: '/home', templateUrl: 'home.html' })
    .state('users', { url: '/users', templateUrl: 'users.html' })
    .state('users.detail', {
      url: '/:id',
      templateUrl: 'user-detail.html',
      controller: 'UserDetailCtrl'
    });
}]);

<!-- outlet -->
<div ui-view></div>
<!-- links: state name instead of URL -->
<a ui-sref="users.detail({ id: 42 })">User 42</a>

ui-router resolve & Nested States

resolve maps keys to promises; the controller isn't instantiated until all resolve. If a resolve rejects, $stateChangeError fires and the state isn't entered. Resolve values are injectable into the controller by their key. Child states inherit and can access parent resolves — useful for data preloading.

angularjs
$stateProvider.state('users.detail', {
  url: '/:id',
  templateUrl: 'user-detail.html',
  controller: 'UserDetailCtrl',
  controllerAs: 'vm',
  resolve: {
    // injected into the controller; waits for these before activating
    user: ['UserSvc', '$stateParams', function(UserSvc, $stateParams) {
      return UserSvc.get($stateParams.id);   // returns a promise
    }]
  }
});

app.controller('UserDetailCtrl', ['user', function(user) {
  this.user = user;   // already resolved
}]);

ui-router Multiple Named Views

ui-router can render multiple named ui-view outlets per state. The views map keys match the ui-view names in the template; the empty-string key targets the unnamed outlet. This lets a single state compose several regions (header, sidebar, main) — impossible with ngRoute's single ng-view.

angularjs
$stateProvider.state('dashboard', {
  url: '/dashboard',
  views: {
    '':        { templateUrl: 'dashboard/main.html' },
    'sidebar': { templateUrl: 'dashboard/sidebar.html', controller: 'SideCtrl' },
    'header':  { templateUrl: 'dashboard/header.html' }
  }
});

<!-- index.html -->
<div ui-view="header"></div>
<div ui-view="sidebar"></div>
<div ui-view></div>          <!-- the unnamed '' view -->
12

Forms & Validation

Form with ng-model

Give the form a name attribute to expose its validation state on the scope. novalidate turns off the browser's built-in bubbles so Angular controls validation. Each named input publishes its own state (myForm.email.$valid, $dirty, $error). ng-model is required for validation to track the input.

angularjs
<form name="myForm" novalidate>
  <input name="email" type="email" ng-model="user.email" required>
  <input name="age" type="number" ng-model="user.age" min="18" required>

  <button ng-disabled="myForm.$invalid" ng-click="save()">Save</button>
</form>

<!-- novalidate disables the browser's native validation so Angular handles it -->

Validation States

Each input exposes $pristine/$dirty (changed?), $touched/$untouched (blurred?), $valid/$invalid, and $error (a map of failing validators). The form aggregates these: myForm.$invalid is true if any input is invalid. Show messages only after $touched or $dirty so you don't yell at users before they type.

angularjs
<input name="email" ng-model="user.email" required>
<p ng-show="myForm.email.$touched && myForm.email.$error.required">
  Email is required.
</p>

<!-- states available on each input / the form -->
<!-- $pristine, $dirty, $touched, $untouched, $valid, $invalid, $error -->

Built-in Validators

Built-in validators: required, type=email/url/number, min/max, ng-minlength/ng-maxlength, ng-pattern. Each failing validator sets a key in $error (e.g., $error.required, $error.minlength, $error.pattern). Escape backslashes in ng-pattern regexes inside HTML attributes.

angularjs
<input name="email" type="email" ng-model="user.email" required>
<input name="age" type="number" ng-model="user.age" min="18" max="120">
<input name="code" ng-model="user.code"
       ng-minlength="4" ng-maxlength="8" required>
<input name="url" type="url" ng-model="user.url">
<input name="zip" ng-model="user.zip" ng-pattern="/^\d{5}$/">

Custom Validator (Directive)

Add custom validators via a directive that requires ngModel and registers a function on ctrl.$validators. Return true for valid, false for invalid — the failure appears in $error under your key (here 'even'). For async validation, use ctrl.$asyncValidators returning a promise.

angularjs
app.directive('evenNumber', function() {
  return {
    require: 'ngModel',
    link: function(scope, el, attrs, ctrl) {
      ctrl.$validators.even = function(modelValue, viewValue) {
        if (ctrl.$isEmpty(modelValue)) return true;   // let 'required' handle empty
        return parseInt(modelValue, 10) % 2 === 0;
      };
    }
  };
});

<input ng-model="n" even-number>
<p ng-show="form.n.$error.even">Must be even!</p>

ngMessages

ngMessages shows the first matching error (or all with ng-messages-multiple). It's far cleaner than stacking ng-if for each validator. Extract shared messages into a partial and include with ng-messages-include='messages.html'. It plays well with both built-in and custom validator keys.

angularjs
<!-- include angular-messages.js, add module 'ngMessages' -->
<script src="angular-messages.js"></script>

<div ng-messages="myForm.email.$error" ng-if="myForm.email.$touched">
  <div ng-message="required">Email is required.</div>
  <div ng-message="email">Enter a valid email.</div>
  <div ng-message="server">That email is taken.</div>
</div>

<!-- a template can be reused across fields with ng-messages-include -->

Form Submission

ng-submit fires when the form is submitted (Enter key or a type='submit' button) and only if ng-model validations pass unless you override. Pass myForm.$valid to the handler as a safety check. novalidate ensures Angular (not the browser) drives validation. Disable the button with ng-disabled to prevent double submits.

angularjs
<form name="myForm" ng-submit="save(myForm.$valid)" novalidate>
  <input name="email" ng-model="user.email" required>
  <button type="submit" ng-disabled="myForm.$invalid">Save</button>
</form>

app.controller('FormCtrl', ['$scope', function($scope) {
  $scope.save = function(isValid) {
    if (!isValid) return;
    // ...submit to server
  };
}]);
13

Events (ng-click, ng-change, ...)

ng-click

ng-click evaluates an expression when the element is clicked. $event is the native DOM event, so you can call stopPropagation/preventDefault on it — though prefer ng-submit for forms and built-in directives where possible. ng-click works on any element, not just buttons.

angularjs
<button ng-click="count = count + 1">Inc</button>
<span>{{ count }}</span>

<!-- call a scope method -->
<button ng-click="save()">Save</button>

<!-- pass the native event with $event -->
<button ng-click="save($event)">Save</button>
<button ng-click="save($event); $event.stopPropagation()">Save</button>

ng-change

ng-change fires after ng-model has updated the model — so $scope.country is already the new value. It requires ng-model on the same element. Use it to react to input/select/checkbox changes (cascading dropdowns, live filtering) without setting up your own change listener.

angularjs
<select ng-model="country" ng-change="onCountryChange()">
  <option value="us">US</option>
  <option value="uk">UK</option>
</select>

app.controller('LocCtrl', ['$scope', function($scope) {
  $scope.onCountryChange = function() {
    // fires AFTER ng-model updates $scope.country
    $scope.cities = CityService.forCountry($scope.country);
  };
}]);

ng-submit & ng-focus/blur family

ng-submit handles form submit. Angular also exposes event directives for many DOM events: ng-focus, ng-blur, ng-change, ng-copy, ng-cut, ng-paste, ng-keydown/up/press, ng-mousedown/up/enter/over/leave/move, and ng-dblclick. Each evaluates an expression, with $event available.

angularjs
<!-- ng-submit: on form submission -->
<form ng-submit="search()">
  <input ng-model="query">
</form>

<!-- the ng-focus / ng-blur / ng-copy / ng-cut / ng-paste directives -->
<input ng-model="q" ng-focus="onFocus()" ng-blur="onBlur()">
<input ng-copy="copied = true">

Keyboard & Mouse Events

Angular provides ng-keydown/ng-keypress/ng-keyup and ng-mousedown/up/enter/leave/over/move plus ng-dblclick. Each passes $event, so you can inspect $event.keyCode or $event.shiftKey. For app-wide shortcuts, bind on $document or $window inside a service rather than scattering handlers in templates.

angularjs
<!-- keyboard events -->
<input ng-keydown="onKey($event)">
<input ng-keypress="onKey($event)">

<!-- mouse events -->
<div ng-mouseenter="hover = true" ng-mouseleave="hover = false">
  {{ hover ? 'Hovering' : 'Not hovering' }}
</div>
<button ng-dblclick="open()">Double-click</button>

$event Object

$event is the native DOM event forwarded to your handler. You can read target, coordinates, modifier keys, and call preventDefault/stopPropagation. Resist heavy logic in click handlers — call a method on the controller/service instead. $event is available in every event directive.

angularjs
<button ng-click="onClick($event)">Click</button>

app.controller('ClickCtrl', ['$scope', function($scope) {
  $scope.onClick = function(event) {
    console.log(event.target);    // clicked element
    console.log(event.clientX, event.clientY);
    event.preventDefault();
    event.stopPropagation();
  };
}]);

Event Modifiers (Manual)

Unlike Vue/Angular 2+, AngularJS has no .stop/.prevent/.once modifiers — you call event methods yourself inside the handler. This is more verbose but explicit. For frequently needed behaviors (auto preventDefault on submit), ng-submit already does it for forms.

angularjs
<!-- AngularJS has NO built-in modifiers like .stop / .prevent.
     Call the method on $event instead. -->
<a href="/delete" ng-click="del($event)">Delete</a>

app.controller('DelCtrl', ['$scope', function($scope) {
  $scope.del = function(event) {
    event.preventDefault();   // stop the link navigation
    $scope.removeItem();
  };
}]);
14

$watch / $digest / $apply

$scope.$watch

$watch registers a listener that runs whenever the watched value changes during a digest. The first argument can be a string expression or a function; the callback receives (newVal, oldVal). $watch returns a de-registration function — call it to stop watching and avoid leaks, especially in directives.

angularjs
app.controller('WatchCtrl', ['$scope', function($scope) {
  $scope.user = { name: 'Ada' };

  // watch a path expression (string)
  var unbind = $scope.$watch('user.name', function(newVal, oldVal) {
    console.log('name changed', oldVal, '->', newVal);
  });

  // stop watching (frees resources)
  unbind();
}]);

$watchCollection

$watchCollection watches one level of an array or object: it fires when items are added/removed/replaced, but does NOT deeply compare item properties. It's far cheaper than deep $watch (third arg true). Use it for arrays of primitives or when you only care about list membership, not deep contents.

angularjs
$scope.items = [1, 2, 3];

// watch array/object shallowly: detects add/remove/replace but NOT deep mutation
$scope.$watchCollection('items', function(newItems, oldItems) {
  console.log('collection changed', newItems);
});

// fires on push/splice, or when an item is replaced,
// but NOT when an item's own properties change.

Deep $watch (third argument)

Passing true as the third argument makes $watch do a deep value comparison — it fires when any nested property changes. This is expensive (walks the whole object each digest). Prefer watching a specific path or using $watchCollection. Reserve deep watching for small objects where you genuinely need it.

angularjs
$scope.config = { ui: { theme: 'dark' } };

// third arg true -> deep comparison by value (expensive!)
$scope.$watch('config', function(n, o) {
  console.log('config (deep) changed');
}, true);

// prefer watching a specific path when possible:
$scope.$watch('config.ui.theme', function(n, o) { /* cheaper */ });

$scope.$apply

Code outside Angular's world (raw setTimeout, XHR without $http, jQuery events, Promise callbacks) updates the model but Angular doesn't know — call $apply() to trigger a $digest. The function form wraps and exception-handled the changes and runs a single digest. Inside Angular services ($http, $timeout) $apply is already done — don't double-apply.

angularjs
// NON-Angular async callback (e.g., a 3rd-party library) won't trigger a digest
externalLib.onReady(function() {
  $scope.message = 'Ready!';

  // tell Angular to run a digest so the view updates
  $scope.$apply();
});

// or wrap the change so errors are caught and digest runs once
$scope.$apply(function() {
  $scope.message = 'Ready!';
  $scope.ready = true;
});

$evalAsync & $timeout (safe apply)

Prefer $evalAsync or $timeout(fn, 0) over a manual $apply when you're not sure whether a digest is already in progress — they avoid 'digest already in progress' errors. $evalAsync batches into the current digest; $timeout defers to the next tick. Use $timeout when you also need the DOM to have rendered.

angularjs
// $evalAsync: schedule work later in the SAME digest if one is running,
//             otherwise start a new one. Safer than $apply.
$scope.$evalAsync(function() {
  $scope.computed = heavyCompute();
});

// $timeout(..., 0) always runs in a fresh digest — useful when you
// need the DOM updated before your code runs.
$timeout(function() {
  $scope.deferred = true;
}, 0);

The $digest Cycle Explained

$digest is the loop that propagates model changes to the view: it re-evaluates every watcher until values stop changing (dirty-checking), capped at 10 iterations. Because every watcher runs every digest, performance scales with watcher count. Reduce watchers via one-time binding (::), track by, and moving computation into the controller.

angularjs
// a $digest iterates over ALL watchers on $scope and its children:
//   1. read each watched value, compare to previous
//   2. if changed, call the listener
//   3. listeners may change other watched values -> re-run
//   4. repeat until stable (max 10 iterations -> error)

// so: keep watchers cheap and few.
// track by, one-time binding (::), and compute in controllers help.
15

Components (.component())

.component() Basics

.component() is a sugar over directive, optimized for template-based components. It defaults to restrict 'E', isolate scope, and controllerAs '$ctrl' — so use this (aliased $ctrl) instead of $scope. bindings replaces scope:{...}; '<' (one-way inbound) is preferred over '=' for inputs since 1.5.

angularjs
app.component('heroDetail', {
  template: '<h2>{{$ctrl.hero.name}}</h2><p>{{$ctrl.hero.power}}</p>',
  bindings: {
    hero: '<'        // one-way ('<'), two-way ('='), string ('@'), expr ('&')
  }
});

<!-- usage -->
<hero-detail hero="selectedHero"></hero-detail>

Component with Controller & Bindings

A component's controller uses this (aliased $ctrl in the template). bindings declare inputs ('<', '=') and outputs ('&'). One-way '<' is preferred for inputs. Outputs use '&' — call them with an argument object that becomes the locals of the parent's expression: onChange({ value: ... }).

angularjs
app.component('counter', {
  bindings: {
    start: '<',
    onChange: '&'
  },
  template: `
    <button ng-click="$ctrl.dec()">-</button>
    <span>{{ $ctrl.count }}</span>
    <button ng-click="$ctrl.inc()">+</button>
  `,
  controller: function() {
    this.$onInit = function() { this.count = this.start || 0; };
    this.inc = function() { this.count++; this.onChange({ value: this.count }); };
    this.dec = function() { this.count--; this.onChange({ value: this.count }); };
  }
});

Component Lifecycle Hooks

AngularJS 1.5+ component hooks mirror Angular 2+'s lifecycle: $onInit (init), $onChanges (binding changes, with isFirstChange), $doCheck (each digest), $postLink (after child DOM linked), $onDestroy (teardown). Use them instead of bolting logic into the constructor — they make component behavior explicit and testable.

angularjs
app.component('editor', {
  bindings: { doc: '<', onClose: '&' },
  controller: function() {
    this.$onInit = function() { /* after bindings, before first render */ };
    this.$onChanges = function(changes) {
      // changes.doc: { currentValue, previousValue, isFirstChange() }
    };
    this.$doCheck = function() { /* every digest: custom dirty checking */ };
    this.$onDestroy = function() { /* cleanup: timers, listeners */ };
    this.$postLink = function() { /* after child linking, DOM ready */ };
  }
});

$onChanges Detail

$onChanges fires whenever a one-way ('<') binding's reference changes, with a changes object: each key has currentValue, previousValue, and isFirstChange(). It does NOT fire for mutations of the same object (because the reference is unchanged) — use $doCheck for that. It always fires once on init with isFirstChange() true.

angularjs
app.component('userView', {
  bindings: { user: '<' },
  controller: function() {
    this.$onChanges = function(changesObj) {
      // changesObj is keyed by binding name
      if (changesObj.user) {
        var c = changesObj.user;
        console.log(c.previousValue, '->', c.currentValue);
        console.log('first time?', c.isFirstChange());
      }
    };
  }
});

Transclusion in Components

Set transclude: true and include ng-transclude in the template to let callers inject content. In components, multi-slot transclusion is also supported via transclude: { slotName: 'elementName' }. Transclusion lets you build reusable wrappers (panels, modals, cards) whose body is supplied by the parent.

angularjs
app.component('modal', {
  transclude: true,
  template: `
    <div class="modal">
      <div class="modal-body" ng-transclude></div>
    </div>
  `,
  bindings: { title: '@' }
});

<!-- caller's content lands in ng-transclude -->
<modal title="Confirm">
  <p>Are you sure?</p>
  <button>Yes</button>
</modal>

Component vs Directive

Use components for everything that renders a template — they're simpler and push you toward the Angular 2+ model (isolate scope, this/controllerAs, lifecycle hooks). Reserve directives for attribute-style behavior without a template (autofocus, tooltip trigger, input mask) and direct DOM manipulation in link.

angularjs
// COMPONENT: template-based, isolate scope, controllerAs by default
app.component('userCard', {
  template: '...',
  bindings: { user: '<' }
});

// DIRECTIVE: still needed for attribute behavior / DOM manipulation
app.directive('autoFocus', function() {
  return { restrict: 'A', link: function(scope, el) { el[0].focus(); } };
});
16

$q Promise Service

$q.defer() Basics

$q is Angular's Promise implementation (Promises/A+). Create with $q.defer(), then resolve(value) or reject(reason). The promise is digest-aware: resolution triggers a $digest so the view updates. Prefer $q over native Promise when you need Angular's auto-digest; otherwise native Promise works too.

angularjs
app.factory('AsyncWork', ['$q', function($q) {
  return function() {
    var deferred = $q.defer();

    setTimeout(function() {
      if (success) deferred.resolve('done');
      else         deferred.reject(new Error('failed'));
    }, 100);

    return deferred.promise;     // { then, catch, finally }
  };
}]);

// consume
AsyncWork().then(function(v) { console.log(v); })
          .catch(function(e) { console.error(e); });

$q.when / $q.all / $q.reject

$q.when wraps a plain value or a non-$q promise. $q.all waits for an array or object of promises and resolves with the same shape of results. $q.reject(reason) is a convenient way to return a rejected promise (e.g., for early validation in a chain). All are digest-aware.

angularjs
// $q.when: wrap a value (or foreign promise) in an Angular promise
$q.when(42).then(function(v) { /* v === 42 */ });

// $q.all: wait for many promises in parallel
$q.all([getUser(), getPosts(), getFriends()]).then(function(results) {
  var user = results[0], posts = results[1], friends = results[2];
});

// $q.all with an object (named results)
$q.all({ user: getUser(), posts: getPosts() }).then(function(r) {
  r.user; r.posts;
});

// $q.reject: shortcut to return a rejected promise
return $q.reject(new Error('bad input'));

Chaining Promises

Returning a value from .then passes it to the next handler; returning a promise makes the chain wait. A single .catch handles any rejection upstream. .finally runs on both success and failure — perfect for hiding loaders. Each handler runs inside Angular's digest, so scope updates just work.

angularjs
getUser(id)
  .then(function(user) {
    return getOrders(user);    // return a promise -> chain waits
  })
  .then(function(orders) {
    return orders.filter(paid); // return a value -> next step
  })
  .then(function(paidOrders) {
    $scope.paid = paidOrders;
  })
  .catch(function(err) {
    // any rejection above lands here
    console.error(err);
  })
  .finally(function() {
    $scope.loading = false;    // runs regardless
  });

$http Returns Promises

$http already returns a $q promise, so chain .then to transform the response (often extracting .data) and return a cleaner value. Wrapping $http in a service gives callers a tidy API and centralizes URL/base-url/error handling. Interceptors are another way to apply cross-cutting $http behavior.

angularjs
app.factory('UserApi', ['$http', function($http) {
  return {
    list: function() { return $http.get('/users').then(function(r) { return r.data; }); },
    get:  function(id) { return $http.get('/users/' + id).then(function(r) { return r.data; }); }
  };
}]);

// caller doesn't deal with response.data, just the user
UserApi.get(42).then(function(user) { $scope.user = user; });

$q Constructor (ES6-style)

Since 1.6, $q supports the ES6 Promise constructor form: $q(function(resolve, reject){...}). This is more concise than $q.defer() and matches native Promise syntax. Use it when wrapping a single async API. The resulting promise is still digest-aware, so view updates fire automatically on resolve.

angularjs
// Angular 1.6+ supports the resolver-style constructor, like native Promise
app.factory('loadImg', ['$q', function($q) {
  return function(src) {
    return $q(function(resolve, reject) {
      var img = new Image();
      img.onload  = function() { resolve(img); };
      img.onerror = function() { reject(new Error('load failed')); };
      img.src = src;
    });
  };
}]);

$q vs Native Promise

The key difference: $q triggers a $digest on resolution, native Promise does not. In modern AngularJS apps running on evergreen browsers, you can use native Promise but must wrap model updates in $apply (or $evalAsync). For consistency and convenience, prefer $q inside Angular code.

angularjs
// native Promise: no auto-digest; you must $apply manually
Promise.resolve().then(function() {
  $scope.x = 1;
  $scope.$apply();           // otherwise the view won't update
});

// $q promise: resolves inside a $digest automatically
$q.when().then(function() { $scope.x = 1; });   // view updates
17

Animations (ngAnimate)

ngAnimate Setup

ngAnimate is a separate module (its own JS file). Once added as a dependency, it automatically augments built-in directives so they add CSS classes during enter/leave/move transitions — you just write CSS. No JS needed for the common case. Without ngAnimate these directives change the DOM instantly.

angularjs
<!-- include angular-animate.js, then depend on 'ngAnimate' -->
<script src="angular-animate.js"></script>

var app = angular.module('myApp', ['ngAnimate']);

<!-- ngAnimate now adds classes automatically to:
     ng-repeat (enter/leave/move), ng-if, ng-show, ng-hide,
     ng-class, ng-view, ng-include, ui-view -->

CSS Transition Classes

ngAnimate adds two classes per event: a start class (e.g., .ng-enter) and an -active class (.ng-enter-active) one frame later. Define a CSS transition on the start class and the end state on the active class — Angular removes both when the transition ends. This is the simplest, most performant animation approach.

angularjs
/* .ng-enter / .ng-leave / .ng-move are added automatically */
.fade.ng-enter        { transition: opacity 0.3s; opacity: 0; }
.fade.ng-enter-active { opacity: 1; }

.fade.ng-leave        { transition: opacity 0.3s; opacity: 1; }
.fade.ng-leave-active { opacity: 0; }

<!-- apply the .fade class to your ng-if / ng-repeat element -->
<div class="fade" ng-if="show">Hello</div>

CSS Keyframe Animations

You can use CSS @keyframes animations instead of transitions — ngAnimate toggles the same .ng-enter/.ng-leave/.ng-move classes, and the keyframe runs. With keyframes you only need the single start class (no -active needed) because the animation drives the whole sequence. Good for bounce/spring effects.

angularjs
/* use @keyframes; ngAnimate still toggles the classes */
.pop.ng-enter { animation: pop-in 0.3s; }

@keyframes pop-in {
  0%   { transform: scale(0); }
  100% { transform: scale(1); }
}

<div class="pop" ng-if="visible">Pop!</div>

ng-repeat enter / leave / move

ng-repeat items get .ng-enter (new), .ng-leave (removed), and .ng-move (reordered) classes. Use track by so Angular can match items across changes and animate moves correctly. Stagger animations are possible with .ng-enter-stagger / .ng-leave-stagger to delay each item — great for list reveal effects.

angularjs
/* list items animate when added, removed, or reordered */
.list-item.ng-enter        { transition: all 0.3s; opacity: 0; transform: translateY(-10px); }
.list-item.ng-enter-active { opacity: 1; transform: translateY(0); }

.list-item.ng-leave-active { opacity: 0; transform: translateX(20px); }

/* .ng-move animates reordering (track by recommended) */
.list-item.ng-move        { transition: transform 0.3s; }
.list-item.ng-move-active { }

<ul><li class="list-item" ng-repeat="t in todos track by t.id">{{ t.text }}</li></ul>

$animate Service (JS Animations)

app.animation(cssSelector, factory) registers a JS animation for elements matching the selector. The enter/leave/move/class methods receive the element and a done callback. Useful when CSS isn't enough (e.g., jQuery.animate or a physics library). Return a cancel function for cleanup. JS animations are heavier than CSS — prefer CSS when possible.

angularjs
app.animation('.fade-js', ['$animate', function($animate) {
  return {
    enter: function(element, done) {
      element.css('opacity', 0);
      element.animate({ opacity: 1 }, 300, done);
      return function(cancelled) { /* cleanup if cancelled */ };
    },
    leave: function(element, done) {
      element.animate({ opacity: 0 }, 300, done);
    }
  };
}]);

<div class="fade-js" ng-if="show">Hello</div>

Disabling Animations

Turn animations off globally or per-subtree with $animate.enabled, or via the ng-animate-disabled class. This is useful for performance on large lists or to disable animations during testing. ng-animate-children lets a parent opt its children into animating together (by default a parent animation skips child animations).

angularjs
<!-- ng-animate-ref / ng-animate-sref control animation between routes -->

<!-- opt out per-element -->
<div ng-if="x" ng-animate-children>...</div>
<div ng-if="y" class="ng-animate-disabled">...</div>

// programmatically
$animate.enabled(false);                    // globally off
$animate.enabled(false, element);           // off for subtree
$animate.enabled(true, element);            // back on
18

Testing (Karma & Jasmine)

Karma Config Sketch

Karma is AngularJS's standard test runner: it launches real browsers, loads your scripts plus angular-mocks, and reports Jasmine results. angular-mocks provides module() and inject() helpers and mock services ($httpBackend). singleRun:true exits after one run (CI); omit for watch mode.

angularjs
// karma.conf.js (sketch)
module.exports = function(config) {
  config.set({
    frameworks: ['jasmine'],
    files: [
      'node_modules/angular/angular.js',
      'node_modules/angular-mocks/angular-mocks.js',
      'src/**/*.js',
      'test/**/*.spec.js'
    ],
    browsers: ['ChromeHeadless'],
    singleRun: true
  });
};

// run: karma start karma.conf.js

Jasmine Basics

Jasmine provides describe (suite), it (spec), beforeEach/afterEach (setup/teardown), and expect with matchers (toBe, toEqual, toContain, toBeTruthy, toThrow, etc.). xit/xdescribe skip specs. Tests are synchronous by default; for async, use done callback or Jasmine's async/await support.

angularjs
describe('UserService', function() {
  beforeEach(function() { /* setup */ });
  afterEach(function() { /* teardown */ });

  it('isLoggedIn is false by default', function() {
    var svc = makeService();
    expect(svc.isLoggedIn()).toBe(false);
  });

  it('greets by name', function() {
    expect(greet('Ada')).toEqual('Hi, Ada');
  });

  // pending / disabled
  xit('not yet implemented', function() {});
});

Testing a Controller

module('myApp') loads the Angular module; inject() resolves services (often aliased with leading/trailing underscores). Create a controller with $controller('Name', { $scope: scope }) passing fake dependencies. Then assert on the scope. This pattern also works for controllerAs: pass this-bound methods and inspect the returned instance.

angularjs
describe('TodoCtrl', function() {
  beforeEach(module('myApp'));

  var $controller, $scope;
  beforeEach(inject(function(_$controller_, _$rootScope_) {
    $scope = _$rootScope_.$new();
    $controller = _$controller_;
  }));

  it('starts with two todos', function() {
    $controller('TodoCtrl', { $scope: $scope });
    expect($scope.todos.length).toBe(2);
  });

  it('addTodo pushes and clears the input', function() {
    $controller('TodoCtrl', { $scope: $scope });
    $scope.todoText = 'New';
    $scope.addTodo();
    expect($scope.todos.length).toBe(3);
    expect($scope.todoText).toBe('');
  });
});

Testing a Service

Inject the service under test and $httpBackend from angular-mocks. expectGET(url).respond(data) sets up a fake response; flush() resolves pending requests synchronously. verifyNoOutstandingExpectation() in afterEach catches unmet expectations. This makes $http-based services fully testable without a real server.

angularjs
describe('UserService', function() {
  var UserService, $httpBackend;

  beforeEach(module('myApp'));
  beforeEach(inject(function(_UserService_, _$httpBackend_) {
    UserService = _UserService_;
    $httpBackend = _$httpBackend_;
  }));

  afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); });

  it('list() fetches users', function() {
    $httpBackend.expectGET('/api/users').respond([{ name: 'Ada' }]);
    var users;
    UserService.list().then(function(u) { users = u; });
    $httpBackend.flush();          // resolve the pending request
    expect(users.length).toBe(1);
  });
});

Testing a Directive

Compile a piece of HTML with $compile(html)(scope), then run scope.$digest() to evaluate bindings and link directives. Assert on the resulting jqLite element (el.find, el.text, el.attr). For templateUrl, use $httpBackend.expectGET to provide the template, then flush before digest. Test isolate scope via el.isolateScope().

angularjs
describe('greeting directive', function() {
  var $compile, $rootScope;
  beforeEach(module('myApp'));
  beforeEach(inject(function(_$compile_, _$rootScope_) {
    $compile = _$compile_; $rootScope = _$rootScope_;
  }));

  it('renders the template', function() {
    var scope = $rootScope.$new();
    scope.name = 'Ada';
    var el = $compile('<greeting name="name"></greeting>')(scope);
    scope.$digest();              // process watchers / bindings
    expect(el.find('h1').text()).toContain('Ada');
  });
});

$httpBackend Mocking

expectGET (and expectPOST/etc.) assert the request is made and fail if it isn't; whenGET silently returns data if called, ignoring if not. Use expect for 'this should happen' assertions, when for setup. flush() resolves pending requests in order. Always call verifyNoOutstanding* in afterEach to catch mismatches.

angularjs
// EXPECT: a specific request must happen (asserts it was made)
$httpBackend.expectGET('/api/users').respond(200, [{ id: 1 }]);
// WHEN:  just stub a request if it happens (no assertion)
$httpBackend.whenGET('/api/users').respond(200, [{ id: 1 }]);

// flush triggers all pending requests:
$httpBackend.flush();

// verify nothing was left unflushed:
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
19

Dependency Injection

DI Basics

Angular's DI resolves dependencies by inspecting function parameter names. This 'implicit' style is concise but breaks under minification (parameter names get shortened). It's fine for prototypes/demos. For production, use one of the annotation styles — inline array or $inject — or automate with ng-annotate.

angularjs
// services are injected by NAME into the constructor's parameters
app.controller('A', function($scope, $http, UserService) { /* ... */ });

// Angular inspects the parameter names to resolve dependencies.
// works in development but BREAKS after minification (see below).

Inline Array Annotation

The most common minification-safe style: pass an array whose elements are the service names (as strings), with the constructor last. The strings survive minification, so Angular resolves correctly. Always use this for any code that will be minified. Tedious to maintain by hand — ng-annotate or babel plugins can add it automatically.

angularjs
app.controller('A', ['$scope', '$http', 'UserService',
  function($scope, $http, UserService) {
    // parameter names no longer matter; the strings drive DI
    $scope.users = UserService.list();
  }
]);

$inject Property Annotation

Attach an $inject array of service-name strings to the constructor function. Angular reads it to resolve dependencies, ignoring the parameter names. This keeps the function declaration readable and lets the same constructor be reused. ng-annotate can generate $inject automatically from parameter names during build.

angularjs
function ACtrl($scope, UserService) {
  $scope.users = UserService.list();
}
ACtrl.$inject = ['$scope', 'UserService'];   // explicit list

app.controller('A', ACtrl);

// equivalent to the inline array form; useful when you want
// a separately-declared, reusable constructor function.

$injector Service

$injector is the DI container: .get(name) returns a singleton, .annotate(fn) returns the dependency list, .instantiate/.invoke build/call functions with their dependencies. Rarely needed in app code, but useful in dynamic scenarios (loading services by name) and inside custom directives or router resolve.

angularjs
// resolve and instantiate on demand
var svc = $injector.get('UserService');          // retrieve singleton
var fn  = $injector.annotate(MyCtrl);            // [' $scope', ...] via reflection
var inst = $injector.instantiate(MyCtrl);        // 'new' with DI
var res  = $injector.invoke(MyCtrl, context);    // call with DI

// list all registered service names (debug)
// $injector has modules / providers info

ng-annotate (Build-Time)

ng-annotate is a build-time tool that adds inline-array annotations automatically, so you write clean implicit DI and ship minification-safe code. Use /* @ngInject */ to disambiguate tricky cases. It integrates with gulp, grunt, webpack, babel. The successor ng-annotate-patched maintains support for modern toolchains.

angularjs
// Write clean code with a hint comment:
app.controller('A', /* @ngInject */ function($scope, UserService) {
  $scope.users = UserService.list();
});

// ng-annotate (run by your build tool) rewrites it to:
app.controller('A', ['$scope', 'UserService', function($scope, UserService) {
  $scope.users = UserService.list();
}]);

Strict DI Mode

ng-strict-di makes implicit (unannotated) dependency injection throw at runtime, so you catch minification-unsafe code during development instead of in production. Keep it on in dev and tests. It slightly impacts performance (extra checks), so it's usually removed in production builds.

angularjs
<html ng-app="myApp" ng-strict-di>
  ...
</html>

// or when bootstrapping manually:
angular.bootstrap(root, ['myApp'], { strictDi: true });

// in strict-DI mode, any service using IMPLICIT (unannotated) DI
// throws an error, catching minification bugs during development.
20

Best Practices & Performance

Use controllerAs

controllerAs makes it explicit which controller owns each property, avoids scope-inheritance primitive shadowing, and brings you closer to the Angular 2+ 'component' model. Bind to this (aliased) rather than $scope. For components, $ctrl is the default alias — use it consistently.

angularjs
<!-- prefer controllerAs over $scope -->
<div ng-controller="UsersCtrl as users">
  <li ng-repeat="u in users.list">{{ u.name }}</li>
</div>

app.controller('UsersCtrl', function(UserService) {
  // 'this' is exposed as 'users' in the template
  this.list = UserService.list();
  this.remove = function(id) { UserService.remove(id); };
});

One-time Binding (::)

Prefix an expression with :: to make it a one-time binding: Angular evaluates it once and, when defined, removes its watcher. For data that doesn't change (titles, timestamps, static lists), this cuts watcher count significantly — the single biggest performance win in AngularJS apps.

angularjs
<!-- static data: bind once, drop the watcher -->
<h1>{{ ::conference.name }}</h1>
<span>{{ ::user.createdAt | date }}</span>

<!-- ng-repeat over a static list -->
<li ng-repeat="item in ::staticItems">{{ item.label }}</li>

<!-- less watchers = faster digest for the whole app -->

track by in ng-repeat

Without track by, Angular uses an internal $$hashKey to match items — when the array changes it may re-render and recreate DOM for items that didn't actually change, losing child state. track by u.id gives a stable identity so Angular reuses nodes. This is essential for performance on large lists and fixes 'ng-repeat duplicates' errors.

angularjs
<!-- BAD: Angular uses $$hashKey, re-renders everything on change -->
<li ng-repeat="u in users">{{ u.name }}</li>

<!-- GOOD: stable identity -> Angular reuses DOM nodes -->
<li ng-repeat="u in users track by u.id">{{ u.name }}</li>

<!-- for primitives, use $index (only if list doesn't reorder) -->
<li ng-repeat="n in nums track by $index">{{ n }}</li>

Move Logic to Services

Controllers are hard to test and tied to $scope; services are singletons, injectable, and unit-testable. Push business logic, data access, and shared state into services, and keep controllers focused on wiring the view to those services. This separation also lets multiple controllers share the same logic.

angularjs
// BAD: logic crammed in the controller
app.controller('BadCtrl', function($scope) {
  $scope.calc = function() { /* 50 lines of business logic */ };
});

// GOOD: controller delegates to a testable service
app.controller('GoodCtrl', function(PriceService) {
  this.total = PriceService.calcTotal(this.items);   // thin
});

app.factory('PriceService', function() {
  return { calcTotal: function(items) { /* ... */ } };
});

Bind Once / Reduce Watchers

Every {{ }} and most bindings add a watcher that runs each digest. Avoid function calls inside bindings (they re-run every digest even if inputs are unchanged) — precompute and store the result. Combine one-time binding (::), track by, and controller-side computation to keep watcher counts low on big pages.

angularjs
<!-- compute once in the controller, render many times -->
app.controller('Ctrl', function() {
  this.sortedUsers = this.users.slice().sort(byName); // do it once
});

<!-- avoid function calls in bindings: they re-run each digest -->
<!-- BAD:  {{ heavyCompute() }} -->
<!-- GOOD: compute once, bind the result -->
<p>{{ cachedResult }}</p>

Module Organization

Organize by feature (not by file type) so each feature is a self-contained module with its controller, service, template, routes, and tests. The root app module just composes feature modules. This keeps related code together, makes features reusable across apps, and scales far better than a single giant module.

angularjs
// split features into sub-modules, each self-contained
angular.module('myApp.users', ['myApp.common']);
angular.module('myApp.posts', ['myApp.common']);
angular.module('myApp', ['myApp.users', 'myApp.posts', 'ngRoute']);

// file layout per feature:
//   users/users.module.js
//   users/users.controller.js
//   users/users.service.js
//   users/users.html
//   users/users.routes.js
//   users/users.spec.js

Was this helpful?