3. Apply/Digest Cycle
$digest:
$digest processes all the watch-Expressions for the current scope and its children
What happens when a watch-Expression is processed?
The value of the current watch-Expression is compared with the value of the previous
watch-Expression, and if they do not match then it is “dirty” and a listener is fired.
This is what we call as “Dirty Checking”.
Angular doesn’t directly call $digest(). Instead, it calls $scope.$apply(), which in turn calls
$rootScope.$digest(). As a result of this, a digest cycle starts at the $rootScope, and
subsequently visits all the child scopes calling the watchers along the way.
4. Now, let’s assume you attach an ng-click directive to a button and pass a function name to
it. When the button is clicked, AngularJS wraps the function call within $scope.$apply(). So,
your function executes as usual, change models (if any), and a $digest cycle starts to
ensure your changes are reflected in the view.
When & why should I call apply() manually ?
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.
It will account for only those model changes which are done inside AngularJS’ context (i.e.
the code that changes models is wrapped inside $apply()). Angular’s built-in directives
already do this so that any model changes you make are reflected in the view. However, if
you change any model outside of the Angular context, then you need to inform Angular of
the changes by calling $apply() manually. It’s like telling Angular that you are changing
some models and it should fire the watchers so that your changes propagate properly.
For example, if you use JavaScript’s setTimeout() function to update a scope model,
Angular has no way of knowing what you might change. In this case it’s your responsibility
to call $apply() manually, which triggers a $digest cycle. Similarly, if you have a directive
that sets up a DOM event listener and changes some models inside the handler function,
you need to call $apply() to ensure the changes take effect
5. Example
If you use JavaScript’s setTimeout() function to update a scope model, Angular has no way of
knowing what you might change. In this case it’s your responsibility to call $apply() manually,
which triggers a $digest cycle.
HTML
<body ng-app="myApp">
<div ng-controller="MessageController"> Delayed Message: {{message}} </div>
</body>
JS /*Without Apply*/
angular.module('myApp',[]).controller('MessageController', function($scope) {
$scope.getMessage = function() {
setTimeout(function() {
$scope.message = 'Fetched after 3 seconds';
}, 2000);
$scope.getMessage();
}
JS /*With Apply*/
angular.module('myApp',[]).controller('MessageController', function($scope) {
$scope.getMessage = function() {
setTimeout(function() {
$scope.$apply(function() {
$scope.message = 'Fetched after 3 seconds';
});
}, 2000);
}
$scope.getMessage();
});
6. Handling Memory
$destroy()
Removes the current scope (and all of its children) from the parent scope. Removal implies
that calls to $digest() will no longer propagate to the current scope and its children. Removal
also implies that the current scope is eligible for garbage collection.
The $destroy() is usually used by directives such as ngRepeat for managing the unrolling of
the loop.
Just before a scope is destroyed a $destroy event is broadcasted on this scope. Application
code can register a $destroy event handler that will give it chance to perform any necessary
clean-up.
Scope.$destroy();
Calling $destroy() on a scope causes an event “$destroy” to flow downstream.
7. Why & when should I manually handle memory ?
If you are using timers to update scope.
In cases where you have event listeners.
As an example, the following controller continuously updates a model value in one second intervals, and these updates
will continue forever, even after the controller’s view is gone and the scope is removed from its parent.
module.controller("TestController", function($scope, $timeout) {
var onTimeout = function() {
$scope.value += 1;
$timeout(onTimeout, 1000);
};
$timeout(onTimeout, 1000);
$scope.value = 0;
});
Listening for the $destroy event is an opportunity to halt the timer
module.controller("TestController", function($scope, $timeout) {
var onTimeout = function() {
$scope.value += 1;
timer = $timeout(onTimeout, 1000);
};
var timer = $timeout(onTimeout, 1000);
$scope.value = 0;
$scope.$on("$destroy", function() {
if (timer) {
$timeout.cancel(timer);
}
});
});
8. Promises
The AngularJS $q service is said to be inspired by Chris Kowal's Q library
(github.com/kriskowal/q). The library's goal is to allow users to monitor asynchronous
progress by providing a "promise" as a return from a call. In AngularJS, the semantics of
using a promise are:
var promise = callThatRunsInBackground();
promise.then(
function(answer) {
// do something
},
function(error) {
// report something
},
function(progress) {
// report progress
});
9. A number of Angular services return promises: $http, $interval, $timeout, for example. All
promise returns are single objects; you're expected to research the service itself to find out
what it returns. For example, for $http.get, the promise returns an object with four
keys:data, status, headers, and config. Those are the same as the four parameters fed to the
success callback if you use those semantics:
// this
$http.get('/api/v1/movies/avengers')
.success(function(data, status, headers, config) {
$scope.movieContent = data;
});
// is the same as
var promise = $http.get('/api/v1/movies/avengers');
promise.then(
function(payload) {
$scope.movieContent = payload.data;
});
10. Deferring Promises, Defining Custom Promises:
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
}, 1000);
return deferred.promise;
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});
11. Chaining Promises
defer.promise
.then(function () {
alert("I promised I would show up");
})
.then(function () {
alert("me too");
})
.then(function () {
alert("and I");
});
defer.resolve();
It is also possible to resolve with parameters. The chained promises will cascade their return values to the
subsequent promise:
defer.promise
.then(function (weapon) {
alert("You can have my " + weapon);
return "bow";
})
.then(function (weapon) {
alert("And my " + weapon);
return "axe";
})
.then(function () {
alert("And my " + weapon);
});
defer.resolve("sword");
12. Transclusion
Transclusion provides a way for a consumer of a directive to define a template that is
imported into the directive and displayed. For example you might have a directive that
outputs a table while allowing the consumer of the directive to control how the table rows
are rendered. Or, you might have a directive that outputs an error message while allowing
the consumer of the directive to supply HTML content that handles rendering the error
using different colors. By supporting this type of functionality the consumer of the directive
has more control over how specific parts of the HTML generated by the directive are
rendered.
Two key features are provided by AngularJS to support transclusion. The first is a property
that is used in directives named transclude. When a directive supports transclusion this
property is set to true. The second is a directive named ng-transclude that is used to define
where external content will be placed in a directive’s template
13. Markup
<div ng-app="phoneApp">
<div ng-controller="AppCtrl">
<panel>
<div class="button">Click me!</div>
</panel>
</div>
</div>
var app = angular.module('phoneApp', []);
app.controller("AppCtrl", function ($scope) {
});
app.directive("panel", function () {
return {
restrict: "E",
template: '<div class="panel">This is a panel component</div>'
}
});
return {
restruct: "E",
transclude: true,
template: '<div class="panel" ng-transclude>This is a panel component </div>'
}
14. Watch
The $scope.watch() function creates a watch of some variable. When you register a watch you pass two functions
as parameters to the $watch() function:
A value function
A listener function
Here is an example:
$scope.$watch(function() {},
function() {}
);
The first function is the value function and the second function is the listener function.
The value function should return the value which is being watched. AngularJS can then check the value returned
against the value the watch function returned the last time. That way AngularJS can determine if the value has
changed. Here is an example:
$scope.$watch(function(scope) { return scope.data.myVar },
function() {}
);
This example value function returns the $scope variable scope.data.myVar. If the value of this variable changes, a
different value will be returned, and AngularJS will call the listener function.
15. The watchExpression is called on every call to $digest() and should return the value that will
be watched. (Since $digest() reruns when it detects changes the watchExpression can execute
multiple times per $digest() and should be idempotent.)
The listener is called only when the value from the current watchExpression and the previous
call to watchExpression are not equal (with the exception of the initial run, see below).
Inequality is determined according to reference inequality, strict comparison via the !==
Javascript operator, unless objectEquality == true (see next point)
When objectEquality == true, inequality of the watchExpression is determined according to
the angular.equals function. To save the value of the object for later comparison, the
angular.copy function is used. This therefore means that watching complex objects will have
adverse memory and performance implications.
The watch listener may change the model, which may trigger other listeners to fire. This is
achieved by rerunning the watchers until no changes are detected. The rerun iteration limit is
10 to prevent an infinite loop deadlock.