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.
<!-- 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.
<!-- 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.
<!-- 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.
<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.
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;
};
}]);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.
// 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 moduleModule 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.
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.
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.
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.
// 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 */ } };
}]);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).
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.
<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.
// 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).
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.
<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.
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
});
}]);$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).
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.
<!-- 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.
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.
// 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.
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 hierarchyIsolate 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.
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>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.
<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.
<!-- 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 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.
// 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.nameFilters 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.
<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.
<!-- 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>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.
<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.
<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.
<!-- 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.
<!-- 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.
<!-- 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.
<!-- 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>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.
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.
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.
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.
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.
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.
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>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.
<p>{{ 1234.5 | number:2 }}</p> <!-- 1,234.50 -->
<p>{{ 9.99 | currency:'USD' }}</p> <!-- $9.99 -->
<p>{{ 128 | currency:'€' }}</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).
<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.
<!-- 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.
<!-- 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.
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.
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, '$');
}]);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.
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.
$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.
// 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.
// $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.
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.
// 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 thisFactories, 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.
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.
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().
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.
// 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.
// 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.
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
}]);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.
<!-- 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('').
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.