feat(scope): add listener deregistration fn for $watch and $on

- both $watch and $on now return a function which when called
  deregisters the listener
- $removeListener was removed and replaced with the above
- added more tests for $watch and $on

Closes #542
This commit is contained in:
Igor Minar 2011-09-01 14:19:22 -07:00
parent a5607e3061
commit 31b8624121
2 changed files with 94 additions and 45 deletions

View file

@ -257,22 +257,29 @@ Scope.prototype = {
* - `string`: Evaluated as {@link guide/dev_guide.expressions expression}
* - `function(scope, newValue, oldValue)`: called with current `scope` an previous and
* current values as parameters.
* @returns {function()} Returns a deregistration function for this listener.
*/
$watch: function(watchExp, listener) {
var scope = this;
var get = compileToFn(watchExp, 'watch');
var listenFn = compileToFn(listener || noop, 'listener');
var array = scope.$$watchers;
var scope = this,
get = compileToFn(watchExp, 'watch'),
listenFn = compileToFn(listener || noop, 'listener'),
array = scope.$$watchers,
watcher = {
fn: listenFn,
last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run.
get: get
};
if (!array) {
array = scope.$$watchers = [];
}
// we use unshift since we use a while loop in $digest for speed.
// the while loop reads in reverse order.
array.unshift({
fn: listenFn,
last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run.
get: get
});
array.unshift(watcher);
return function() {
angularArray.remove(array, watcher);
};
},
/**
@ -538,6 +545,7 @@ Scope.prototype = {
*
* @param {string} name Event name to listen on.
* @param {function(event)} listener Function to call when the event is emitted.
* @returns {function()} Returns a deregistration function for this listener.
*
* The event listener function format is: `function(event)`. The `event` object passed into the
* listener has the following attributes
@ -553,29 +561,12 @@ Scope.prototype = {
this.$$listeners[name] = namedListeners = [];
}
namedListeners.push(listener);
return function() {
angularArray.remove(namedListeners, listener);
};
},
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$removeListener
* @function
*
* @description
* Remove the on listener registered by {@link angular.scope.$on $on}.
*
* @param {string} name Event name to remove on.
* @param {function} listener Function to remove.
*/
$removeListener: function(name, listener) {
var namedListeners = this.$$listeners[name],
i;
if (namedListeners) {
i = indexOf(namedListeners, listener);
namedListeners.splice(i, 1);
}
},
/**
* @workInProgress

View file

@ -207,6 +207,7 @@ describe('Scope', function() {
expect(log).toEqual('abc');
});
it('should repeat watch cycle from the root elemnt', function() {
var log = '';
var child = root.$new();
@ -269,6 +270,29 @@ describe('Scope', function() {
root.$digest();
expect(callCount).toEqual(1);
});
it('should return a function that allows listeners to be unregistered', function() {
var root = angular.scope(),
listener = jasmine.createSpy('watch listener'),
listenerRemove;
listenerRemove = root.$watch('foo', listener);
root.$digest(); //init
expect(listener).toHaveBeenCalled();
expect(listenerRemove).toBeDefined();
listener.reset();
root.foo = 'bar';
root.$digest(); //triger
expect(listener).toHaveBeenCalledOnce();
listener.reset();
root.foo = 'baz';
listenerRemove();
root.$digest(); //trigger
expect(listener).not.toHaveBeenCalled();
});
});
@ -434,6 +458,55 @@ describe('Scope', function() {
describe('events', function() {
describe('$on', function() {
it('should add listener for both $emit and $broadcast events', function() {
var log = '',
root = angular.scope(),
child = root.$new();
function eventFn(){
log += 'X';
}
child.$on('abc', eventFn);
expect(log).toEqual('');
child.$emit('abc');
expect(log).toEqual('X');
child.$broadcast('abc');
expect(log).toEqual('XX');
});
it('should return a function that deregisters the listener', function() {
var log = '',
root = angular.scope(),
child = root.$new(),
listenerRemove;
function eventFn(){
log += 'X';
}
listenerRemove = child.$on('abc', eventFn);
expect(log).toEqual('');
expect(listenerRemove).toBeDefined();
child.$emit('abc');
child.$broadcast('abc');
expect(log).toEqual('XX');
log = '';
listenerRemove();
child.$emit('abc');
child.$broadcast('abc');
expect(log).toEqual('');
});
});
describe('$emit', function() {
var log, child, grandChild, greatGrandChild;
@ -480,21 +553,6 @@ describe('Scope', function() {
});
it('should remove event listener', function() {
function eventFn(){
log += 'abc;';
}
child.$on('abc', eventFn);
child.$emit('abc');
expect(log).toEqual('abc;');
log = '';
child.$removeListener('abc', eventFn);
child.$emit('abc');
expect(log).toEqual('');
});
it('should forward method arguments', function() {
child.$on('abc', function(event, arg1, arg2){
expect(event.name).toBe('abc');