Skip to content

Commit

Permalink
Merge pull request #175 from jtassin/master
Browse files Browse the repository at this point in the history
Debounce of the digests triggered by store modification
  • Loading branch information
AntJanus committed Apr 24, 2018
2 parents f9494cc + 5324db8 commit 87d1808
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ umd
coverage
*.tgz
examples/**/dist
.idea
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ For Angular 2 see [ng2-redux](https://github.com/wbuchwalter/ng2-redux).
- [API](#api)
- [Dependency Injectable Middleware](#dependency-injectable-middleware)
- [Routers](#routers)
- [Config](#config)
- [Using DevTools](#using-devtools)
- [Additional Resources](#additional-resources)

Expand Down Expand Up @@ -199,6 +200,29 @@ $ngReduxProvider.createStoreWith(reducers, [thunk, 'myInjectableMiddleware']);

Middlewares passed as **string** will then be resolved throught angular's injector.

## Config

### Debouncing the digest
You can debounce the digest triggered by store modification (usefull in huge apps with a lot of store modifications) by passing a config parameter to the `ngReduxProvider`.

```javascript
import angular from 'angular';

angular.module('ngapplication').config(($ngReduxProvider) => {
'ngInject';

// eslint-disable-next-line
$ngReduxProvider.config.debounce = {
wait: 100,
maxWait: 500,
};
});
```

This will debounce the digest for 100ms with a maximum delay time of 500ms. Every store modification within this time will be handled by this delayed digest.

[lodash.debounce](https://lodash.com/docs/4.17.4#debounce) is used for the debouncing.

## Routers
See [redux-ui-router](https://github.com/neilff/redux-ui-router) to make ng-redux and UI-Router work together. <br>
See [ng-redux-router](https://github.com/amitport/ng-redux-router) to make ng-redux and angular-route work together.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"babel-runtime": "^6.26.0",
"invariant": "^2.2.2",
"lodash.curry": "^4.1.1",
"lodash.debounce": "^4.0.8",
"lodash.isfunction": "^3.0.8",
"lodash.isobject": "^3.0.2",
"lodash.isplainobject": "^4.0.6",
Expand Down
16 changes: 12 additions & 4 deletions src/components/digestMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
export default function digestMiddleware($rootScope) {
import debounce from 'lodash.debounce';

export default function digestMiddleware($rootScope, debounceConfig) {
let debouncedFunction = (expr) => {
$rootScope.$evalAsync(expr);
};
if(debounceConfig && debounceConfig.wait && debounceConfig.wait > 0) {
debouncedFunction = debounce(debouncedFunction, debounceConfig.wait, { maxWait: debounceConfig.maxWait });
}
return store => next => action => {
const res = next(action);
$rootScope.$evalAsync(res);
return res;
const res = next(action);
debouncedFunction(res);
return res;
};
}
11 changes: 9 additions & 2 deletions src/components/ngRedux.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export default function ngReduxProvider() {
_initialState = initialState || {};
};

this.config = {
debounce: {
wait: undefined,
maxWait: undefined,
},
};

this.$get = ($injector) => {
const resolveMiddleware = middleware => isString(middleware)
? $injector.get(middleware)
Expand Down Expand Up @@ -81,13 +88,13 @@ export default function ngReduxProvider() {
}

// digestMiddleware needs to be the last one.
resolvedMiddleware.push(digestMiddleware($injector.get('$rootScope')));
resolvedMiddleware.push(digestMiddleware($injector.get('$rootScope'), this.config.debounce));

// combine middleware into a store enhancer.
const middlewares = applyMiddleware(...resolvedMiddleware);

// compose enhancers with middleware and create store.
const store = createStore(_reducer, _initialState, compose(...resolvedStoreEnhancer, middlewares));
const store = createStore(_reducer, _initialState, compose(middlewares, ...resolvedStoreEnhancer));

const mergedStore = assign({}, store, { connect: Connector(store) });

Expand Down
64 changes: 64 additions & 0 deletions test/components/digestMiddleware.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import expect from 'expect';
import sinon from 'sinon';
import digestMiddleware from '../../src/components/digestMiddleware';


describe('digestMiddleware', () => {

it('Should debounce the $evalAsync function if debounce is enabled', (done) => {
const $evalAsync = sinon.spy();
const $rootScope = {
$evalAsync,
};
const firstAction = 1;
const secondAction = 2;
const debounceConfig = {
wait: 10,
};
const next = sinon.spy((action) => (action));
const middleware = digestMiddleware($rootScope, debounceConfig);
middleware()(next)(firstAction);
setTimeout(() => {
middleware()(next)(secondAction);
}, 1);
setTimeout(() => {
expect($evalAsync.calledOnce).toBe(true);
expect(next.calledTwice).toBe(true);
expect(next.firstCall.calledWithExactly(firstAction)).toBe(true);
expect(next.secondCall.calledWithExactly(secondAction)).toBe(true);
expect($evalAsync.firstCall.calledWithExactly(secondAction)).toBe(true);
done();
}, debounceConfig.wait + 10);

});

it('Should not debounce the $evalAsync function if debounce is disabled', () => {
const disabledDebounceConfigs = [
null,
undefined,
{},
{ wait: 0 },
];
disabledDebounceConfigs.forEach(() => {
const $evalAsync = sinon.spy();
const $rootScope = {
$evalAsync,
};
const firstAction = 1;
const secondAction = 2;
const debounceConfig = {};

const next = sinon.spy((action) => (action));
const middleware = digestMiddleware($rootScope, debounceConfig);
middleware()(next)(firstAction);
middleware()(next)(secondAction);
expect($evalAsync.calledTwice).toBe(true);
expect(next.calledTwice).toBe(true);
expect(next.firstCall.calledWithExactly(firstAction)).toBe(true);
expect(next.secondCall.calledWithExactly(secondAction)).toBe(true);
expect($evalAsync.firstCall.calledWithExactly(firstAction)).toBe(true);
expect($evalAsync.secondCall.calledWithExactly(secondAction)).toBe(true);
});
});

});

0 comments on commit 87d1808

Please sign in to comment.