From 21f93163384f36fc4ae0934387339380e3dc3e9c Mon Sep 17 00:00:00 2001 From: rodyhaddad Date: Tue, 25 Feb 2014 12:27:20 -0500 Subject: [PATCH] feat(Scope): add `$watchGroup` method for observing a set of expressions Given an array of expressions, if any one expression changes then the listener function fires with an arrays of old and new values. $scope.watchGroup([expression1, expression2, expression3], function(newVals, oldVals) { // newVals and oldVals are arrays of values corresponding to expression1..3 ... }); Port of angular/angular.dart@a3c31ce1dddb4423faa316cb144568f3fc28b1a9 --- src/ng/rootScope.js | 54 ++++++++++++++++++++++++++++++- test/ng/rootScopeSpec.js | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index e7bdc4142923..274b36e83b59 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -354,6 +354,58 @@ function $RootScopeProvider(){ }; }, + /** + * @ngdoc method + * @name $rootScope.Scope#$watchGroup + * @function + * + * @description + * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. + * If any one expression in the collection changes the `listener` is executed. + * + * - The items in the `watchCollection` array are observed via standard $watch operation and are examined on every + * call to $digest() to see if any items changes. + * - The `listener` is called whenever any expression in the `watchExpressions` array changes. + * + * @param {Array.} watchExpressions Array of expressions that will be individually + * watched using {@link ng.$rootScope.Scope#$watch $watch()} + * + * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any + * expression in `watchExpressions` changes + * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching + * those of `watchExpression` + * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching + * those of `watchExpression` + * The `scope` refers to the current scope. + * + * @returns {function()} Returns a de-registration function for all listeners. + */ + $watchGroup: function(watchExpressions, listener) { + var oldValues = new Array(watchExpressions.length); + var newValues = new Array(watchExpressions.length); + var deregisterFns = []; + var changeCount = 0; + var self = this; + + forEach(watchExpressions, function (expr, i) { + deregisterFns.push(self.$watch(expr, function (value, oldValue) { + newValues[i] = value; + oldValues[i] = oldValue; + changeCount++; + })); + }, this); + + deregisterFns.push(self.$watch(function () {return changeCount;}, function () { + listener(newValues, oldValues, self); + })); + + return function deregisterWatchGroup() { + forEach(deregisterFns, function (fn) { + fn(); + }); + }; + }, + /** * @ngdoc method @@ -756,7 +808,7 @@ function $RootScopeProvider(){ // prevent NPEs since these methods have references to properties we nulled out this.$destroy = this.$digest = this.$apply = noop; - this.$on = this.$watch = function() { return noop; }; + this.$on = this.$watch = this.$watchGroup = function() { return noop; }; }, /** diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 01b5d1bdfdd4..13efd5d9110e 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -777,6 +777,75 @@ describe('Scope', function() { }); }); + describe('$watchGroup', function() { + var scope; + var log; + + beforeEach(inject(function($rootScope, _log_) { + scope = $rootScope.$new(); + log = _log_; + })); + + + it('should work for a group with just a single expression', function() { + scope.$watchGroup(['a'], function(values, oldValues, s) { + expect(s).toBe(scope); + log(oldValues + ' >>> ' + values); + }); + + scope.a = 'foo'; + scope.$digest(); + expect(log).toEqual('foo >>> foo'); + + log.reset(); + scope.$digest(); + expect(log).toEqual(''); + + scope.a = 'bar'; + scope.$digest(); + expect(log).toEqual('foo >>> bar'); + }); + + + it('should detect a change to any one expression in the group', function() { + scope.$watchGroup(['a', 'b'], function(values, oldValues, s) { + expect(s).toBe(scope); + log(oldValues + ' >>> ' + values); + }); + + scope.a = 'foo'; + scope.b = 'bar'; + scope.$digest(); + expect(log).toEqual('foo,bar >>> foo,bar'); + + log.reset(); + scope.$digest(); + expect(log).toEqual(''); + + scope.a = 'a'; + scope.$digest(); + expect(log).toEqual('foo,bar >>> a,bar'); + + log.reset(); + scope.a = 'A'; + scope.b = 'B'; + scope.$digest(); + expect(log).toEqual('a,bar >>> A,B'); + }); + + + it('should not call watch action fn when watchGroup was deregistered', function() { + var deregister = scope.$watchGroup(['a', 'b'], function(values, oldValues) { + log(oldValues + ' >>> ' + values); + }); + + deregister(); + scope.a = 'xxx'; + scope.b = 'yyy'; + scope.$digest(); + expect(log).toEqual(''); + }); + }); describe('$destroy', function() { var first = null, middle = null, last = null, log = null;