diff --git a/src/ng/compile.js b/src/ng/compile.js
index 9940cd4bde2f..68e49f2bcaf1 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -52,8 +52,9 @@
* There are many different options for a directive.
*
* The difference resides in the return value of the factory function.
- * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
- * or just the `postLink` function (all other properties will have the default values).
+ * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)}
+ * that defines the directive properties, or just the `postLink` function (all other properties will have
+ * the default values).
*
*
* **Best Practice:** It's recommended to use the "directive definition object" form.
@@ -117,6 +118,125 @@
* });
* ```
*
+ * ### Life-cycle hooks
+ * Directive controllers can provide the following methods that are called by Angular at points in the life-cycle of the
+ * directive:
+ * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
+ * had their bindings initialized (and before the pre & post linking functions for the directives on
+ * this element). This is a good place to put initialization code for your controller.
+ * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
+ * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
+ * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
+ * component such as cloning the bound value to prevent accidental mutation of the outer value.
+ * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
+ * changes. Any actions that you wish to take in response to the changes that you detect must be
+ * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
+ * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not
+ * be detected by Angular's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments;
+ * if detecting changes, you must store the previous value(s) for comparison to the current values.
+ * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
+ * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
+ * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
+ * components will have their `$onDestroy()` hook called before child components.
+ * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
+ * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
+ * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
+ * they are waiting for their template to load asynchronously and their own compilation and linking has been
+ * suspended until that occurs.
+ *
+ * #### Comparison with Angular 2 life-cycle hooks
+ * Angular 2 also uses life-cycle hooks for its components. While the Angular 1 life-cycle hooks are similar there are
+ * some differences that you should be aware of, especially when it comes to moving your code from Angular 1 to Angular 2:
+ *
+ * * Angular 1 hooks are prefixed with `$`, such as `$onInit`. Angular 2 hooks are prefixed with `ng`, such as `ngOnInit`.
+ * * Angular 1 hooks can be defined on the controller prototype or added to the controller inside its constructor.
+ * In Angular 2 you can only define hooks on the prototype of the Component class.
+ * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in Angular 1 than you would to
+ * `ngDoCheck` in Angular 2
+ * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be
+ * propagated throughout the application.
+ * Angular 2 does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an
+ * error or do nothing depending upon the state of `enableProdMode()`.
+ *
+ * #### Life-cycle hook examples
+ *
+ * This example shows how you can check for mutations to a Date object even though the identity of the object
+ * has not changed.
+ *
+ *
+ *
+ * angular.module('do-check-module', [])
+ * .component('app', {
+ * template:
+ * 'Month: ' +
+ * 'Date: {{ $ctrl.date }}' +
+ * '',
+ * controller: function() {
+ * this.date = new Date();
+ * this.month = this.date.getMonth();
+ * this.updateDate = function() {
+ * this.date.setMonth(this.month);
+ * };
+ * }
+ * })
+ * .component('test', {
+ * bindings: { date: '<' },
+ * template:
+ * '
{{ $ctrl.log | json }}
',
+ * controller: function() {
+ * var previousValue;
+ * this.log = [];
+ * this.$doCheck = function() {
+ * var currentValue = this.date && this.date.valueOf();
+ * if (previousValue !== currentValue) {
+ * this.log.push('doCheck: date mutated: ' + this.date);
+ * previousValue = currentValue;
+ * }
+ * };
+ * }
+ * });
+ *
+ *
+ *
+ *
+ *
+ *
+ * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the
+ * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large
+ * arrays or objects can have a negative impact on your application performance)
+ *
+ *
+ *
+ *
',
+ * controller: function() {
+ * this.log = [];
+ *
+ * this.$doCheck = function() {
+ * if (this.items_ref !== this.items) {
+ * this.log.push('doCheck: items changed');
+ * this.items_ref = this.items;
+ * }
+ * if (!angular.equals(this.items_clone, this.items)) {
+ * this.log.push('doCheck: items mutated');
+ * this.items_clone = angular.copy(this.items);
+ * }
+ * };
+ * }
+ * });
+ *
+ *
*
*
* ### Directive Definition Object
@@ -292,25 +412,6 @@
* The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
* `true` if the specified slot contains content (i.e. one or more DOM nodes).
*
- * The controller can provide the following methods that act as life-cycle hooks:
- * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
- * had their bindings initialized (and before the pre & post linking functions for the directives on
- * this element). This is a good place to put initialization code for your controller.
- * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
- * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
- * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
- * component such as cloning the bound value to prevent accidental mutation of the outer value.
- * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
- * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
- * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
- * components will have their `$onDestroy()` hook called before child components.
- * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
- * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
- * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
- * they are waiting for their template to load asynchronously and their own compilation and linking has been
- * suspended until that occurs.
- *
- *
* #### `require`
* Require another directive and inject its controller as the fourth argument to the linking function. The
* `require` property can be a string, an array or an object:
@@ -2499,6 +2600,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
$exceptionHandler(e);
}
}
+ if (isFunction(controllerInstance.$doCheck)) {
+ controllerScope.$watch(function() { controllerInstance.$doCheck(); });
+ controllerInstance.$doCheck();
+ }
if (isFunction(controllerInstance.$onDestroy)) {
controllerScope.$on('$destroy', function callOnDestroyHook() {
controllerInstance.$onDestroy();
@@ -3151,7 +3256,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
forEach(bindings, function initializeBinding(definition, scopeName) {
var attrName = definition.attrName,
optional = definition.optional,
- mode = definition.mode, // @, =, or &
+ mode = definition.mode, // @, =, <, or &
lastValue,
parentGet, parentSet, compare, removeWatch;
diff --git a/src/ng/http.js b/src/ng/http.js
index 7da80bccc92c..b90e16e90dfb 100644
--- a/src/ng/http.js
+++ b/src/ng/http.js
@@ -1123,7 +1123,7 @@ function $HttpProvider() {
* @description
* Shortcut method to perform `JSONP` request.
* If you would like to customise where and how the callbacks are stored then try overriding
- * or decorating the {@link jsonpCallbacks} service.
+ * or decorating the {@link $jsonpCallbacks} service.
*
* @param {string} url Relative or absolute URL specifying the destination of the request.
* The name of the callback should be the string `JSON_CALLBACK`.
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index e8d9d277ea99..6acc2ec6f7a6 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -3828,6 +3828,96 @@ describe('$compile', function() {
});
});
+ describe('$doCheck', function() {
+ it('should call `$doCheck`, if provided, for each digest cycle, after $onChanges and $onInit', function() {
+ var log = [];
+
+ function TestController() { }
+ TestController.prototype.$doCheck = function() { log.push('$doCheck'); };
+ TestController.prototype.$onChanges = function() { log.push('$onChanges'); };
+ TestController.prototype.$onInit = function() { log.push('$onInit'); };
+
+ angular.module('my', [])
+ .component('dcc', {
+ controller: TestController,
+ bindings: { 'prop1': '<' }
+ });
+
+ module('my');
+ inject(function($compile, $rootScope) {
+ element = $compile('')($rootScope);
+ expect(log).toEqual([
+ '$onChanges',
+ '$onInit',
+ '$doCheck'
+ ]);
+
+ // Clear log
+ log = [];
+
+ $rootScope.$apply();
+ expect(log).toEqual([
+ '$doCheck',
+ '$doCheck'
+ ]);
+
+ // Clear log
+ log = [];
+
+ $rootScope.$apply('val = 2');
+ expect(log).toEqual([
+ '$doCheck',
+ '$onChanges',
+ '$doCheck'
+ ]);
+ });
+ });
+
+ it('should work if $doCheck is provided in the constructor', function() {
+ var log = [];
+
+ function TestController() {
+ this.$doCheck = function() { log.push('$doCheck'); };
+ this.$onChanges = function() { log.push('$onChanges'); };
+ this.$onInit = function() { log.push('$onInit'); };
+ }
+
+ angular.module('my', [])
+ .component('dcc', {
+ controller: TestController,
+ bindings: { 'prop1': '<' }
+ });
+
+ module('my');
+ inject(function($compile, $rootScope) {
+ element = $compile('')($rootScope);
+ expect(log).toEqual([
+ '$onChanges',
+ '$onInit',
+ '$doCheck'
+ ]);
+
+ // Clear log
+ log = [];
+
+ $rootScope.$apply();
+ expect(log).toEqual([
+ '$doCheck',
+ '$doCheck'
+ ]);
+
+ // Clear log
+ log = [];
+
+ $rootScope.$apply('val = 2');
+ expect(log).toEqual([
+ '$doCheck',
+ '$onChanges',
+ '$doCheck'
+ ]);
+ });
+ });
+ });
describe('$onChanges', function() {