Web application platform
AngularJS was a JavaScript-based open-source front-end web framework for developing single-page applications. It was maintained mainly by Google and a community of individuals and corporations. It aimed to simplify both the development and the testing of such applications by providing a framework for client-side model–view–controller (MVC) and model–view–viewmodel (MVVM) architectures, along with components commonly used in web applications and progressive web applications.
AngularJS was used as the frontend of the MEAN stack, that consisted of MongoDB database, Express.js web application server framework, AngularJS itself (or Angular), and Node.js server runtime environment.
As of January 1, 2022, Google no longer updates AngularJS to fix security, browser compatibility, or jQuery issues. The Angular team recommends upgrading to Angular (v2+) as the best path forward, but they also provided some other options.
What Is AngularJS?
AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template language and lets you extend HTML's syntax to express your application's components clearly and succinctly. AngularJS's data binding and dependency injection eliminate much of the code you would otherwise have to write. And it all happens within the browser, making it an ideal partner with any server technology.
AngularJS is what HTML would have been, had it been designed for applications. HTML is a great declarative language for static documents. It does not contain much in the way of creating applications, and as a result building web applications is an exercise in what do I have to do to trick the browser into doing what I want?
The impedance mismatch between dynamic applications and static documents is often solved with:
a library - a collection of functions which are useful when writing web apps. Your code is in charge and it calls into the library when it sees fit. E.g., jQuery.
frameworks - a particular implementation of a web application, where your code fills in the details. The framework is in charge and it calls into your code when it needs something app specific. E.g., durandal, ember, etc.
AngularJS takes another approach. It attempts to minimize the impedance mismatch between document centric HTML and what an application needs by creating new HTML constructs. AngularJS teaches the browser new syntax through a construct we call directives. Examples include:
A complete client-side solution
AngularJS is not a single piece in the overall puzzle of building the client-side of a web application. It handles all of the DOM and AJAX glue code you once wrote by hand and puts it in a well-defined structure. This makes AngularJS opinionated about how a CRUD (Create, Read, Update, Delete) application should be built. But while it is opinionated, it also tries to make sure that its opinion is just a starting point you can easily change. AngularJS comes with the following out-of-the-box:
AngularJS's sweet spot
AngularJS simplifies application development by presenting a higher level of abstraction to the developer. Like any abstraction, it comes at a cost of flexibility. In other words, not every app is a good fit for AngularJS. AngularJS was built with the CRUD application in mind. Luckily CRUD applications represent the majority of web applications. To understand what AngularJS is good at, though, it helps to understand when an app is not a good fit for AngularJS.
Games and GUI editors are examples of applications with intensive and tricky DOM manipulation. These kinds of apps are different from CRUD apps, and as a result are probably not a good fit for AngularJS. In these cases it may be better to use a library with a lower level of abstraction, such as jQuery.
The Zen of AngularJS
AngularJS is built around the belief that declarative code is better than imperative when it comes to building UIs and wiring software components together, while imperative code is excellent for expressing business logic.
AngularJS frees you from the following pains:
Data Binding
Data-binding in AngularJS apps is the automatic synchronization of data between the model and view components. The way that AngularJS implements data-binding lets you treat the model as the single-source-of-truth in your application. The view is a projection of the model at all times. When the model changes, the view reflects the change, and vice versa.
Data Binding in Classical Template Systems
Most templating systems bind data in only one direction: they merge template and model components together into a view. After the merge occurs, changes to the model or related sections of the view are NOT automatically reflected in the view. Worse, any changes that the user makes to the view are not reflected in the model. This means that the developer has to write code that constantly syncs the view with the model and the model with the view.
Data Binding in AngularJS Templates
AngularJS templates work differently. First the template (which is the uncompiled HTML along with any additional markup or directives) is compiled on the browser. The compilation step produces a live view. Any changes to the view are immediately reflected in the model, and any changes in the model are propagated to the view. The model is the single-source-of-truth for the application state, greatly simplifying the programming model for the developer. You can think of the view as simply an instant projection of your model.
Because the view is just a projection of the model, the controller is completely separated from the view and unaware of it. This makes testing a snap because it is easy to test your controller in isolation without the view and the related DOM/browser dependency.
Understanding Controllers
n AngularJS, a Controller is defined by a JavaScript constructor function that is used to augment the AngularJS Scope.
Controllers can be attached to the DOM in different ways. For each of them, AngularJS will instantiate a new Controller object, using the specified Controller's constructor function:
If the controller has been attached using the controller as syntax then the controller instance will be assigned to a property on the scope.
Use controllers to:
Do not use controllers to:
In general, a Controller shouldn't try to do too much. It should contain only the business logic needed for a single view.
The most common way to keep Controllers slim is by encapsulating work that doesn't belong to controllers into services and then using these services in Controllers via dependency injection. This is discussed in the Dependency Injection and Services sections of this guide.
Setting up the initial state of a $scope object
Typically, when you create an application you need to set up the initial state for the AngularJS $scope. You set up the initial state of a scope by attaching properties to the $scope object. The properties contain the view model (the model that will be presented by the view). All the $scope properties will be available to the template at the point in the DOM where the Controller is registered.
The following example demonstrates creating a GreetingController, which attaches a greeting property containing the string 'Hola!' to the $scope:
var myApp = angular.module('myApp',[]);
myApp.controller('GreetingController', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
}]);
We create an AngularJS Module, myApp, for our application. Then we add the controller's constructor function to the module using the .controller() method. This keeps the controller's constructor function out of the global scope.
We attach our controller to the DOM using the ng-controller directive. The greeting property can now be data-bound to the template:
<div ng-controller="GreetingController">
{{ greeting }}
</div>
Adding Behavior to a Scope Object
In order to react to events or execute computation in the view we must provide behavior to the scope. We add behavior to the scope by attaching methods to the $scope object. These methods are then available to be called from the template/view.
The following example uses a Controller to add a method, which doubles a number, to the scope:
var myApp = angular.module('myApp',[]);
myApp.controller('DoubleController', ['$scope', function($scope) {
$scope.double = function(value) { return value * 2; };
}]);
Once the Controller has been attached to the DOM, the double method can be invoked in an AngularJS expression in the template:
<div ng-controller="DoubleController">
Two times <input ng-model="num"> equals {{ double(num) }}
</div>
As discussed in the Concepts section of this guide, any objects (or primitives) assigned to the scope become model properties. Any methods assigned to the scope are available in the template/view, and can be invoked via AngularJS expressions and ng event handler directives.
Simple Spicy Controller Example
To illustrate further how Controller components work in AngularJS, let's create a little app with the following components:
The message in our template contains a binding to the spice model which, by default, is set to the string "very". Depending on which button is clicked, the spice model is set to chili or jalapeño, and the message is automatically updated by data-binding.
<div ng-controller="SpicyController">
<button ng-click="chiliSpicy()">Chili</button>
<button ng-click="jalapenoSpicy()">Jalapeño</button>
<p>The food is {{spice}} spicy!</p>
</div>
Things to notice in the example above:
Spicy Arguments Example
Controller methods can also take arguments, as demonstrated in the following variation of the previous example.
<div ng-controller="SpicyController">
<input ng-model="customSpice">
<button ng-click="spicy('chili')">Chili</button>
<button ng-click="spicy(customSpice)">Custom spice</button>
<p>The food is {{spice}} spicy!</p>
</div>
Notice that the SpicyController Controller now defines just one method called spicy, which takes one argument called spice. The template then refers to this Controller method and passes in a string constant 'chili' in the binding for the first button and a model property customSpice (bound to an input box) in the second button.
Services
AngularJS services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app.
AngularJS services are:
Using a Service
To use an AngularJS service, you add it as a dependency for the component (controller, service, filter or directive) that depends on the service. AngularJS's dependency injection subsystem takes care of the rest.
<div id="simple" ng-controller="MyController">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng-init="message='test'" ng-model="message" >
<button ng-click="callNotify(message);">NOTIFY</button>
<p>(you have to click 3 times to see an alert)</p>
</div>
angular.
module('myServiceModule', []).
controller('MyController', ['$scope', 'notify', function($scope, notify) {
$scope.callNotify = function(msg) {
notify(msg);
};
}]).
factory('notify', ['$window', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length === 3) {
win.alert(msgs.join('\n'));
msgs = [];
}
};
}]);
it('should test service', function() {
expect(element(by.id('simple')).element(by.model('message')).getAttribute('value'))
.toEqual('test');
});
Creating Services
Application developers are free to define their own services by registering the service's name and service factory function, with an AngularJS module.
The service factory function generates the single object or function that represents the service to the rest of the application. The object or function returned by the service is injected into any component (controller, service, filter or directive) that specifies a dependency on the service.
Registering Services
Services are registered to modules via the Module API. Typically you use the Module factory API to register a service:
var myModule = angular.module('myModule', []);
myModule.factory('serviceId', function() {
var shinyNewServiceInstance;
// factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
Note that you are not registering a service instance, but rather a factory function that will create this instance when called.
Dependencies
Services can have their own dependencies. Just like declaring dependencies in a controller, you declare dependencies by specifying them in the service's factory function signature.
For more on dependencies, see the dependency injection docs.
The example module below has two services, each with various dependencies:
var batchModule = angular.module('batchModule', []);
/**
* The `batchLog` service allows for messages to be queued in memory and flushed
* to the console.log every 50 seconds.
*
* @param {*} message Message to be logged.
*/
batchModule.factory('batchLog', ['$interval', '$log', function($interval, $log) {
var messageQueue = [];
function log() {
if (messageQueue.length) {
$log.log('batchLog messages: ', messageQueue);
messageQueue = [];
}
}
// start periodic checking
$interval(log, 50000);
return function(message) {
messageQueue.push(message);
}
}]);
/**
* `routeTemplateMonitor` monitors each `$route` change and logs the current
* template via the `batchLog` service.
*/
batchModule.factory('routeTemplateMonitor', ['$route', 'batchLog', '$rootScope',
function($route, batchLog, $rootScope) {
return {
startMonitoring: function() {
$rootScope.$on('$routeChangeSuccess', function() {
batchLog($route.current ? $route.current.template : null);
});
}
};
}]);
In the example, note that:
Registering a Service with $provide
You can also register services via the $provide service inside of a module's config function:
angular.module('myModule', []).config(['$provide', function($provide) {
$provide.factory('serviceId', function() {
var shinyNewServiceInstance;
// factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
}]);
This technique is often used in unit tests to mock out a service's dependencies.
Unit Testing
The following is a unit test for the notify service from the Creating AngularJS Services example above. The unit test example uses a Jasmine spy (mock) instead of a real browser alert.
var mock, notify;
beforeEach(module('myServiceModule'));
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
module(function($provide) {
$provide.value('$window', mock);
});
inject(function($injector) {
notify = $injector.get('notify');
});
});
it('should not alert first two notifications', function() {
notify('one');
notify('two');
expect(mock.alert).not.toHaveBeenCalled();
});
it('should alert all after third notification', function() {
notify('one');
notify('two');
notify('three');
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
});
it('should clear messages after alert', function() {
notify('one');
notify('two');
notify('third');
notify('more');
notify('two');
notify('third');
expect(mock.alert.calls.count()).toEqual(2);
expect(mock.alert.calls.mostRecent().args).toEqual(["more\ntwo\nthird"]);
});
What are Scopes?
Scope is an object that refers to the application model. It is an execution context for expressions. Scopes are arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can watch expressions and propagate events.
Scope characteristics
Scopes provide APIs ($watch) to observe model mutations.
Scopes provide APIs ($apply) to propagate any model changes through the system into the view from outside of the "AngularJS realm" (controllers, services, AngularJS event handlers).
Scopes can be nested to limit access to the properties of application components while providing access to shared model properties. Nested scopes are either "child scopes" or "isolate scopes". A "child scope" (prototypically) inherits properties from its parent scope. An "isolate scope" does not. See isolated scopes for more information.
Scopes provide context against which expressions are evaluated. For example {{username}} expression is meaningless, unless it is evaluated against a specific scope which defines the username property.
Scope as Data-Model
Scope is the glue between application controller and the view. During the template linking phase the directives set up $watch expressions on the scope. The $watch allows the directives to be notified of property changes, which allows the directive to render the updated value to the DOM.
Both controllers and directives have reference to the scope, but not to each other. This arrangement isolates the controller from the directive as well as from the DOM. This is an important point since it makes the controllers view agnostic, which greatly improves the testing story of the applications.
angular.module('scopeExample', [])
.controller('MyController', ['$scope', function($scope) {
$scope.username = 'World';
$scope.sayHello = function() {
$scope.greeting = 'Hello ' + $scope.username + '!';
};
}]);
<div ng-controller="MyController">
Your name:
<input type="text" ng-model="username">
<button ng-click='sayHello()'>greet</button>
<hr>
{{greeting}}
</div>
In the above example notice that the MyController assigns World to the username property of the scope. The scope then notifies the input of the assignment, which then renders the input with username pre-filled. This demonstrates how a controller can write data into the scope.
Similarly the controller can assign behavior to scope as seen by the sayHello method, which is invoked when the user clicks on the 'greet' button. The sayHello method can read the username property and create a greeting property. This demonstrates that the properties on scope update automatically when they are bound to HTML input widgets.
Logically the rendering of {{greeting}} involves:
You can think of the scope and its properties as the data which is used to render the view. The scope is the single source-of-truth for all things view related.
From a testability point of view, the separation of the controller and the view is desirable, because it allows us to test the behavior without being distracted by the rendering details.
it('should say hello', function() {
var scopeMock = {};
var cntl = new MyController(scopeMock);
// Assert that username is pre-filled
expect(scopeMock.username).toEqual('World');
// Assert that we read new username and greet
scopeMock.username = 'angular';
scopeMock.sayHello();
expect(scopeMock.greeting).toEqual('Hello angular!');
});
Scope Hierarchies
Each AngularJS application has exactly one root scope, but may have any number of child scopes.
The application can have multiple scopes, because directives can create new child scopes. When new scopes are created, they are added as children of their parent scope. This creates a tree structure which parallels the DOM where they're attached.
The section Directives that Create Scopes has more info about which directives create scopes.
When AngularJS evaluates {{name}}, it first looks at the scope associated with the given element for the name property. If no such property is found, it searches the parent scope and so on until the root scope is reached. In JavaScript this behavior is known as prototypical inheritance, and child scopes prototypically inherit from their parents.
This example illustrates scopes in application, and prototypical inheritance of properties. The example is followed by a diagram depicting the scope boundaries.
<div class="show-scope-demo">
<div ng-controller="GreetController">
Hello {{name}}!
</div>
<div ng-controller="ListController">
<ol>
<li ng-repeat="name in names">{{name}} from {{department}}</li>
</ol>
</div>
</div>
angular.module('scopeExample', [])
.controller('GreetController', ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.name = 'World';
$rootScope.department = 'AngularJS';
}])
.controller('ListController', ['$scope', function($scope) {
$scope.names = ['Igor', 'Misko', 'Vojta'];
}]);
.show-scope-demo.ng-scope,
.show-scope-demo .ng-scope {
border: 1px solid red;
margin: 3px;
}
Notice that AngularJS automatically places ng-scope class on elements where scopes are attached. The <style> definition in this example highlights in red the new scope locations. The child scopes are necessary because the repeater evaluates {{name}} expression, but depending on which scope the expression is evaluated it produces different result. Similarly the evaluation of {{department}} prototypically inherits from root scope, as it is the only place where the department property is defined.
Retrieving Scopes from the DOM.
Scopes are attached to the DOM as $scope data property, and can be retrieved for debugging purposes. (It is unlikely that one would need to retrieve scopes in this way inside the application.) The location where the root scope is attached to the DOM is defined by the location of ng-app directive. Typically ng-app is placed on the <html> element, but it can be placed on other elements as well, if, for example, only a portion of the view needs to be controlled by AngularJS.
To examine the scope in the debugger:
Scope Events Propagation
Scopes can propagate events in similar fashion to DOM events. The event can be broadcasted to the scope children or emitted to scope parents.
angular.module('eventExample', [])
.controller('EventController', ['$scope', function($scope) {
$scope.count = 0;
$scope.$on('MyEvent', function() {
$scope.count++;
});
}]);
<div ng-controller="EventController">
Root scope <tt>MyEvent</tt> count: {{count}}
<ul>
<li ng-repeat="i in [1]" ng-controller="EventController">
<button ng-click="$emit('MyEvent')">$emit('MyEvent')</button>
<button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button>
<br>
Middle scope <tt>MyEvent</tt> count: {{count}}
<ul>
<li ng-repeat="item in [1, 2]" ng-controller="EventController">
Leaf scope <tt>MyEvent</tt> count: {{count}}
</li>
</ul>
</li>
</ul>
</div>
Scope Life Cycle
The normal flow of a browser receiving an event is that it executes a corresponding JavaScript callback. Once the callback completes the browser re-renders the DOM and returns to waiting for more events.
When the browser calls into JavaScript the code executes outside the AngularJS execution context, which means that AngularJS is unaware of model modifications. To properly process model modifications the execution has to enter the AngularJS execution context using the $apply method. Only model modifications which execute inside the $apply method will be properly accounted for by AngularJS. For example if a directive listens on DOM events, such as ng-click it must evaluate the expression inside the $apply method.
After evaluating the expression, the $apply method performs a $digest. In the $digest phase the scope examines all of the $watch expressions and compares them with the previous value. This dirty checking is done asynchronously. This means that assignment such as $scope.username="angular" will not immediately cause a $watch to be notified, instead the $watch notification is delayed until the $digest phase. This delay is desirable, since it coalesces multiple model updates into one $watch notification as well as guarantees that during the $watch notification no other $watches are running. If a $watch changes the value of the model, it will force additional $digest cycle.
The root scope is created during the application bootstrap by the $injector. During template linking, some directives create new child scopes.
During template linking, directives register watches on the scope. These watches will be used to propagate model values to the DOM.
For mutations to be properly observed, you should make them only within the scope.$apply(). AngularJS APIs do this implicitly, so no extra $apply call is needed when doing synchronous work in controllers, or asynchronous work with $http, $timeout or $interval services.
At the end of $apply, AngularJS performs a $digest cycle on the root scope, which then propagates throughout all child scopes. During the $digest cycle, all $watched expressions or functions are checked for model mutation and if a mutation is detected, the $watch listener is called.
When child scopes are no longer needed, it is the responsibility of the child scope creator to destroy them via scope.$destroy() API. This will stop propagation of $digest calls into the child scope and allow for memory used by the child scope models to be reclaimed by the garbage collector.
Scopes and Directives
During the compilation phase, the compiler matches directives against the DOM template. The directives usually fall into one of two categories:
When an external event (such as a user action, timer or XHR) is received, the associated expression must be applied to the scope through the $apply() method so that all listeners are updated correctly.
Directives that Create Scopes
In most cases, directives and scopes interact but do not create new instances of scope. However, some directives, such as ng-controller and ng-repeat, create new child scopes and attach the child scope to the corresponding DOM element.
A special type of scope is the isolate scope, which does not inherit prototypically from the parent scope. This type of scope is useful for component directives that should be isolated from their parent scope. See the directives guide for more information about isolate scopes in custom directives.
Note also that component directives, which are created with the .component() helper always create an isolate scope.
Controllers and Scopes
Scopes and controllers interact with each other in the following situations:
See the ng-controller for more information.
Scope $watch Performance Considerations
Dirty checking the scope for property changes is a common operation in AngularJS and for this reason the dirty checking function must be efficient. Care should be taken that the dirty checking function does not do any DOM access, as DOM access is orders of magnitude slower than property access on JavaScript object.
Scope $watch Depths
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
Integration with the browser event loop
The diagram and the example below describe how AngularJS interacts with the browser's event loop.
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc... You can also use $apply() to enter the AngularJS execution context from JavaScript. Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
Here is the explanation of how the Hello world example achieves the data-binding effect when the user enters text into the text field.
the ng-model and input directive set up a keydown listener on the <input> control.
the interpolation sets up a $watch to be notified of name changes.
Pressing an 'X' key causes the browser to emit a keydown event on the input control.
The input directive captures the change to the input's value and calls $apply("name = 'X';") to update the application model inside the AngularJS execution context.
AngularJS applies the name = 'X'; to the model.
The $digest loop begins
The $watch list detects a change on the name property and notifies the interpolation, which in turn updates the DOM.
AngularJS exits the execution context, which in turn exits the keydown event and with it the JavaScript execution context.
The browser re-renders the view with the updated text.
AngularJS applies the name = 'X'; to the model.
The $digest loop begins
The $watch list detects a change on the name property and notifies the interpolation, which in turn updates the DOM.
AngularJS exits the execution context, which in turn exits the keydown event and with it the JavaScript execution context.
The browser re-renders the view with the updated text.
Pressing an 'X' key causes the browser to emit a keydown event on the input control.
The input directive captures the change to the input's value and calls $apply("name = 'X';") to update the application model inside the AngularJS execution context.
AngularJS applies the name = 'X'; to the model.
the ng-model and input directive set up a keydown listener on the <input> control.
the interpolation sets up a $watch to be notified of name changes.
Here is the explanation of how the Hello world example achieves the data-binding effect when the user enters text into the text field.
Enter the AngularJS execution context by calling scope.$apply(stimulusFn), where stimulusFn is the work you wish to do in the AngularJS execution context.
AngularJS executes the stimulusFn(), which typically modifies application state.
AngularJS enters the $digest loop. The loop is made up of two smaller loops which process $evalAsync queue and the $watch list. The $digest loop keeps iterating until the model stabilizes, which means that the $evalAsync queue is empty and the $watch list does not detect any changes.
Enter the AngularJS execution context by calling scope.$apply(stimulusFn), where stimulusFn is the work you wish to do in the AngularJS execution context.
AngularJS executes the stimulusFn(), which typically modifies application state.
AngularJS enters the $digest loop. The loop is made up of two smaller loops which process $evalAsync queue and the $watch list. The $digest loop keeps iterating until the model stabilizes, which means that the $evalAsync queue is empty and the $watch list does not detect any changes.
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc... You can also use $apply() to enter the AngularJS execution context from JavaScript. Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
The browser's event-loop waits for an event to arrive. An event is a user interaction, timer event, or network event (response from a server).
The event's callback gets executed. This enters the JavaScript context. The callback can modify the DOM structure.
Once the callback executes, the browser leaves the JavaScript context and re-renders the view based on DOM changes.
The browser's event-loop waits for an event to arrive. An event is a user interaction, timer event, or network event (response from a server).
The event's callback gets executed. This enters the JavaScript context. The callback can modify the DOM structure.
Once the callback executes, the browser leaves the JavaScript context and re-renders the view based on DOM changes.
The diagram and the example below describe how AngularJS interacts with the browser's event loop.
Integration with the browser event loop