From c3b1dbc1eab9b35067e68668f9bb2e6a9d91c3d8 Mon Sep 17 00:00:00 2001 From: Oded Niv Date: Sun, 19 Jun 2016 17:46:33 +0300 Subject: [PATCH] feat($rootScope): allow $watch to be triggered by an event Allow $watch to support a new syntax of "eventName: watchExp", in addition to the old syntax of just "watchExp". This means it will not be added as a regular watcher but rather use $on to decide on when to evaluate the watchExp and execute the listener. Since watchers are wasteful and run every digest cycle, its a good idea to allow the developer to be more mindful and decide when the watcher is evaluated (including ng-if, ng-class, etc). --- src/ng/rootScope.js | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index eddcf61da4d6..146eceb6d3a8 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -94,6 +94,14 @@ function $RootScopeProvider() { return ChildScope; } + function watchEquals(value, last, objectEquality) { + return value !== last && + !(objectEquality + ? equals(value, last) + : (typeof value === 'number' && typeof last === 'number' + && isNaN(value) && isNaN(last))) + } + this.$get = ['$exceptionHandler', '$parse', '$browser', function($exceptionHandler, $parse, $browser) { @@ -384,6 +392,29 @@ function $RootScopeProvider() { * @returns {function()} Returns a deregistration function for this listener. */ $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { + if (!angular.isFunction(listener)) { + listener = angular.noop; + } + + if (typeof watchExp === 'string' && watchExp.match(/^ *[a-zA-Z]+ *:/)) { + var statement = watchExp.split(':'), + eventName = statement[0].trim(), + get = $parse(statement.slice(1).join(':')), + last = initWatchVal, + scope = this; + function watchOnEvaluate() { + var reallyLast, + value = get(scope); + if (watchEquals(value, last, objectEquality)) { + reallyLast = last; // before setting new last + last = objectEquality ? copy(value, null) : value; + return listener(value, (reallyLast === initWatchVal ? value : reallyLast), scope); + } + } + watchOnEvaluate(); + return this.$on(eventName, watchOnEvaluate); + } + var get = $parse(watchExp); if (get.$$watchDelegate) { @@ -401,10 +432,6 @@ function $RootScopeProvider() { lastDirtyWatch = null; - if (!isFunction(listener)) { - watcher.fn = noop; - } - if (!array) { array = scope.$$watchers = []; } @@ -795,11 +822,7 @@ function $RootScopeProvider() { // circuit it with === operator, only when === fails do we use .equals if (watch) { get = watch.get; - if ((value = get(current)) !== (last = watch.last) && - !(watch.eq - ? equals(value, last) - : (typeof value === 'number' && typeof last === 'number' - && isNaN(value) && isNaN(last)))) { + if (watchEquals((value = get(current)), (last = watch.last), watch.eq)) { dirty = true; lastDirtyWatch = watch; watch.last = watch.eq ? copy(value, null) : value;