Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,16 @@ angular.module('controllers.primus', ['primus'])
### about $on and $filteredOn

`$filteredOn` takes as filter either :
* a function, taking the received data as arguments and returning true/false = match/don't match
* an object, whom keys will be deep-matched for correspondance with the 1st param of received data, using [lodash matches(...)](https://lodash.com/docs#matches). Example of a deep matching :

```javascript
primus.$on('node:update', {content: {id: 23, type: 'image'}}, …)
```
* a function, taking the received data as arguments and returning true/false = match/don't match

Both `$on` and `$filteredOn` will call the listener **in Angular context**, in an optimized way via [$evalAsync](http://www.bennadel.com/blog/2751-scope-applyasync-vs-scope-evalasync-in-angularjs-1-3.htm). So if you have several listeners on the same event, they will all get executed in the same $digest phase.

Both `$on` and `$filteredOn` will call the listener **in Angular context** (scope.apply). However, `$filteredOn` will not trigger any apply if the received data doesn't match the given filter. This is desirable if your Angular app is heavy.
`$filteredOn` will not trigger any apply if the received data doesn't match the given filter. This is desirable if your Angular app is heavy.

## License

Expand Down
26 changes: 14 additions & 12 deletions angular-primus.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,33 @@ function primusProvider() {

/**
* Listen on events of a given type.
* Calls the listener inside a $rootScope.$apply.
* Calls the listener inside in Angular context ($evalAsync)
*
* @param {String} event
* @param {Function} listener
* @returns {Function} Deregistration function for this listener.
*/

primus.$on = function $on(event, listener) {
// Wrap primus event with $rootScope.$apply.
primus.on(event, applyListener);
// run the listener in Angular context
primus.on(event, listenerInAngularContext);

function applyListener() {
function listenerInAngularContext() {
var args = arguments;
$rootScope.$apply(function () {
$rootScope.$evalAsync(function () {
listener.apply(null, args);
});
}

// Return the deregistration function
return function $off() {
primus.removeListener(event, applyListener);
primus.removeListener(event, listenerInAngularContext);
};
};

/**
* Listen on events of a given type, with a filtering pattern.
* If the pattern matches, calls the listener inside a $rootScope.$apply.
* If the pattern matches, calls the listener in Angular context ($evalAsync)
*
* @param {String} event
* @param {Object|Function} matchPattern
Expand All @@ -60,8 +60,6 @@ function primusProvider() {
*/

primus.$filteredOn = function $filteredOn(event, matchPattern, listener) {
// Wrap primus event with $rootScope.$apply.
primus.on(event, applyListener);

var checkMatch;
if (_.isFunction(matchPattern))
Expand All @@ -71,18 +69,22 @@ function primusProvider() {
else
throw new Error('angular-primus $filteredOn() : matchPattern must be a function or an object !');

function applyListener() {
// run the listener in Angular context
primus.on(event, filteredListenerInAngularContext);

function filteredListenerInAngularContext() {
var args = arguments;
var isMatching = checkMatch(args[0]);
if (! isMatching) return;
$rootScope.$apply(function () {

$rootScope.$evalAsync(function () {
listener.apply(null, args);
});
}

// Return the deregistration function
return function $off() {
primus.removeListener(event, applyListener);
primus.removeListener(event, filteredListenerInAngularContext);
};
};

Expand Down
68 changes: 59 additions & 9 deletions test/angular-primus.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,19 +101,22 @@ describe('Primus provider', function () {
primus = $injector.get('primus');
}));

it('should wrap method in $rootScope.$apply', function () {
it('should call the listener in Angular context', function () {
var watchSpy = sinon.spy();
$rootScope.$watch(watchSpy);

expect(watchSpy).to.not.be.called;
expect(watchSpy).to.not.have.been.called;

var listener = sinon.spy();

primus.$on('customEvent', listener);
primus.emit('customEvent');
// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();

expect(listener).to.be.called;
expect(watchSpy).to.be.called;
expect(listener, 'listener').to.have.been.called;
expect(watchSpy, 'watch').to.have.been.called;
});

it('should return a deregistration method', function () {
Expand All @@ -122,6 +125,7 @@ describe('Primus provider', function () {

off();
primus.emit('customEvent');
$rootScope.$digest();

expect(myListener).to.not.be.called;
});
Expand Down Expand Up @@ -157,6 +161,10 @@ describe('Primus provider', function () {
// base
listenerSpy.reset();
primus.emit('customEvent', {itemId: 1});
// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();

expect(listenerSpy, 'listenerSpy').to.have.been.calledOnce;

// variant
Expand All @@ -167,6 +175,8 @@ describe('Primus provider', function () {
foo: 42
}
});
$rootScope.$digest();

expect(listenerSpy, 'listenerSpy').to.have.been.calledOnce;
});

Expand All @@ -175,14 +185,19 @@ describe('Primus provider', function () {

// not the same value
primus.emit('customEvent', {itemId: 11});
// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;

// not the same key / missing key
primus.emit('customEvent', {id: 1});
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;

// nothing at all
primus.emit('customEvent', 42);
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;
});

Expand All @@ -192,6 +207,9 @@ describe('Primus provider', function () {
// base
listenerSpy.reset();
primus.emit('customEvent', {content: {id: 1}});
// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.have.been.calledOnce;

// variant
Expand All @@ -203,6 +221,7 @@ describe('Primus provider', function () {
foo: 42
}
});
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.have.been.calledOnce;
});

Expand All @@ -211,14 +230,19 @@ describe('Primus provider', function () {

// not the same value
primus.emit('customEvent', {content: {id: 11}});
// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;

// not the same key / missing key
primus.emit('customEvent', {content: {itemId: 1}});
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;

// nothing at all
primus.emit('customEvent', 42);
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;
});

Expand All @@ -228,6 +252,9 @@ describe('Primus provider', function () {
// base
listenerSpy.reset();
primus.emit('customEvent', {id: 1, content: {id: 1}});
// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.have.been.calledOnce;

// variant
Expand All @@ -240,6 +267,7 @@ describe('Primus provider', function () {
bar: 33
}
});
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.have.been.calledOnce;
});

Expand All @@ -248,22 +276,29 @@ describe('Primus provider', function () {

// not the same value - 1
primus.emit('customEvent', {id: 11, content: {id: 1}});
// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;

// not the same value - 2
primus.emit('customEvent', {id: 1, content: {id: 11}});
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;

// missing key - 1
primus.emit('customEvent', {id: 1});
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;

// missing key - 2
primus.emit('customEvent', {content: {id: 1}});
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;

// nothing at all
primus.emit('customEvent', 42);
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;
});

Expand All @@ -279,54 +314,69 @@ describe('Primus provider', function () {

listenerSpy.reset();
primus.emit('customEvent', {id: 1});
// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.have.been.calledOnce;

listenerSpy.reset();
primus.emit('customEvent', {id: 3});
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.have.been.calledOnce;
});

it('should filter correctly', function () {
primus.$filteredOn('customEvent', testFunction, listenerSpy);

primus.emit('customEvent', {id: 0});
// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;

primus.emit('customEvent', {id: 20});
primus.emit('customEvent', {id: 20});
$rootScope.$digest();
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;
});
});

});

it('when calling callback, should wrap it in $rootScope.$apply', function () {
it('when calling callback, should call it in Angular context', function () {
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;
expect(watchSpy, 'watchSpy').to.not.have.been.called;

primus.$filteredOn('customEvent', {id: 1}, listenerSpy);
primus.emit('customEvent', {id: 1});

// thanks to $evalAsync, listener exec is scheduled in an angular timeout,
// which won't happen in tests. Trigger it :
$rootScope.$digest();

expect(listenerSpy, 'listenerSpy').to.have.been.calledOnce;
expect(digestWasInProgress).to.be.true;
expect(watchSpy, 'watchSpy').to.have.been.calledTwice;
});

it('when NOT calling callback, should NOT call $rootScope.$apply', function () {
it('when NOT calling callback, should NOT trigger a $rootScope $digest', function () {
expect(listenerSpy, 'listenerSpy').to.not.have.been.called;
expect(watchSpy, 'watchSpy').to.not.have.been.called;

primus.$filteredOn('customEvent', {id: 1}, listenerSpy);
primus.emit('customEvent', {id: 2});

expect(listenerSpy, 'listenerSpy').to.not.have.been.called;
expect(watchSpy, 'watchSpy').to.not.have.been.called;
$rootScope.$digest();

expect(listenerSpy, 'listenerSpy').to.not.have.been.called;
expect(watchSpy, 'watchSpy').to.have.been.calledTwice; // still, due to our explicit $rootScope.$digest
});

it('should return a working deregistration method', function () {
var off = primus.$filteredOn('customEvent', {id: 1}, listenerSpy);

off();
primus.emit('customEvent', {id: 1});
$rootScope.$digest();

expect(listenerSpy, 'listenerSpy').to.not.have.been.called;
});
Expand Down