From b7ae00f8e55a98b5d5f4b0b2f7b02d9a818e72b2 Mon Sep 17 00:00:00 2001 From: jtassin Date: Wed, 15 Nov 2017 18:49:27 +0100 Subject: [PATCH 01/14] creating a configurable debounce mode --- .idea/vcs.xml | 6 ++++++ src/components/digestMiddleware.js | 18 ++++++++++++++---- src/components/ngRedux.js | 8 +++++++- 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/components/digestMiddleware.js b/src/components/digestMiddleware.js index ae7752d..cfe851d 100644 --- a/src/components/digestMiddleware.js +++ b/src/components/digestMiddleware.js @@ -1,7 +1,17 @@ -export default function digestMiddleware($rootScope) { +let toRun; + +export default function digestMiddleware($rootScope, debounceConfig) { return store => next => action => { - const res = next(action); - $rootScope.$evalAsync(res); - return res; + const res = next(action); + if(debounceConfig && debounceConfig.wait && debounceConfig.wait > 0) { + toRun = res; + window.setTimeout(() => { + $rootScope.$evalAsync(toRun); + toRun = undefined; + }, debounceConfig.wait); + } else { + $rootScope.$evalAsync(toRun); + } + return res; }; } diff --git a/src/components/ngRedux.js b/src/components/ngRedux.js index 9c6f087..5511ff9 100644 --- a/src/components/ngRedux.js +++ b/src/components/ngRedux.js @@ -41,6 +41,12 @@ export default function ngReduxProvider() { _initialState = initialState || {}; }; + this.config = { + debounce: { + wait: undefined, + }, + }; + this.$get = ($injector) => { const resolveMiddleware = middleware => isString(middleware) ? $injector.get(middleware) @@ -71,7 +77,7 @@ 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); From 9d2508b7d9c421fee30d98168d8e160d47ca331c Mon Sep 17 00:00:00 2001 From: jtassin Date: Wed, 15 Nov 2017 18:49:27 +0100 Subject: [PATCH 02/14] creating a configurable debounce mode --- .idea/vcs.xml | 6 ++++++ src/components/digestMiddleware.js | 18 ++++++++++++++---- src/components/ngRedux.js | 8 +++++++- 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/components/digestMiddleware.js b/src/components/digestMiddleware.js index ae7752d..cfe851d 100644 --- a/src/components/digestMiddleware.js +++ b/src/components/digestMiddleware.js @@ -1,7 +1,17 @@ -export default function digestMiddleware($rootScope) { +let toRun; + +export default function digestMiddleware($rootScope, debounceConfig) { return store => next => action => { - const res = next(action); - $rootScope.$evalAsync(res); - return res; + const res = next(action); + if(debounceConfig && debounceConfig.wait && debounceConfig.wait > 0) { + toRun = res; + window.setTimeout(() => { + $rootScope.$evalAsync(toRun); + toRun = undefined; + }, debounceConfig.wait); + } else { + $rootScope.$evalAsync(toRun); + } + return res; }; } diff --git a/src/components/ngRedux.js b/src/components/ngRedux.js index 9c6f087..5511ff9 100644 --- a/src/components/ngRedux.js +++ b/src/components/ngRedux.js @@ -41,6 +41,12 @@ export default function ngReduxProvider() { _initialState = initialState || {}; }; + this.config = { + debounce: { + wait: undefined, + }, + }; + this.$get = ($injector) => { const resolveMiddleware = middleware => isString(middleware) ? $injector.get(middleware) @@ -71,7 +77,7 @@ 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); From 41548131797744cb9630ecebb713084eb3e7681b Mon Sep 17 00:00:00 2001 From: jtassin Date: Wed, 15 Nov 2017 18:54:21 +0100 Subject: [PATCH 03/14] useless .idea --- .idea/vcs.xml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From fbcaac132796a3c2feacfd5800ca969b5cdf48c7 Mon Sep 17 00:00:00 2001 From: jtassin Date: Wed, 15 Nov 2017 18:55:09 +0100 Subject: [PATCH 04/14] .idea in gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c3152f4..b1916f7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ es coverage *.tgz examples/**/dist +.idea From c281dad25e0385965670c53762c11eb923bc69aa Mon Sep 17 00:00:00 2001 From: jtassin Date: Wed, 15 Nov 2017 19:01:33 +0100 Subject: [PATCH 05/14] add docuementation for debouncing mode --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index b4a3b4a..d364876 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ For Angular 2 see [ng2-redux](https://github.com/wbuchwalter/ng2-redux). - [API](#api) - [Dependency Injectable Middleware](#dependency-injectable-middleware) - [Routers](#routers) +- [Debouncing the digest](#debouncing-the-digest) - [Using DevTools](#using-devtools) - [Additional Resources](#additional-resources) @@ -188,6 +189,24 @@ $ngReduxProvider.createStoreWith(reducers, [thunk, 'myInjectableMiddleware']); Middlewares passed as **string** will then be resolved throught angular's injector. +## Debouncing the digest +You can debounce the digest triggered by store modification (usefull in huge apps with a lot of store modification) 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, + }; +}); +``` + +This will debounce the digest 100ms. Every store modification within this time will be handled by this digest. + ## Routers See [redux-ui-router](https://github.com/neilff/redux-ui-router) to make ng-redux and UI-Router work together.
See [ng-redux-router](https://github.com/amitport/ng-redux-router) to make ng-redux and angular-route work together. From d00388e2ee6ef8cc0ac4e33497b9d061d3eddfc6 Mon Sep 17 00:00:00 2001 From: jtassin Date: Thu, 16 Nov 2017 11:16:20 +0100 Subject: [PATCH 06/14] invert middlewares and resolvedEnhancers to handle correclty thunk --- src/components/ngRedux.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ngRedux.js b/src/components/ngRedux.js index 5511ff9..e6f059d 100644 --- a/src/components/ngRedux.js +++ b/src/components/ngRedux.js @@ -44,6 +44,7 @@ export default function ngReduxProvider() { this.config = { debounce: { wait: undefined, + maxWait: undefined, }, }; @@ -83,7 +84,7 @@ export default function ngReduxProvider() { 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)); return assign({}, store, { connect: Connector(store) }); }; From 93e1cbbce376a8fef2555e4ed8e5005461039cd1 Mon Sep 17 00:00:00 2001 From: jtassin Date: Thu, 16 Nov 2017 11:17:32 +0100 Subject: [PATCH 07/14] add lodash.debounce for debounce config --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b0f974a..dc5b5f2 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,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", From 4372bb5b23e2e0f1129d7985fda74a6095b90816 Mon Sep 17 00:00:00 2001 From: jtassin Date: Thu, 16 Nov 2017 11:18:01 +0100 Subject: [PATCH 08/14] Doc for debounce --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d364876..a7fc379 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ For Angular 2 see [ng2-redux](https://github.com/wbuchwalter/ng2-redux). - [API](#api) - [Dependency Injectable Middleware](#dependency-injectable-middleware) - [Routers](#routers) -- [Debouncing the digest](#debouncing-the-digest) +- [Config](#config) - [Using DevTools](#using-devtools) - [Additional Resources](#additional-resources) @@ -189,7 +189,9 @@ $ngReduxProvider.createStoreWith(reducers, [thunk, 'myInjectableMiddleware']); Middlewares passed as **string** will then be resolved throught angular's injector. -## Debouncing the digest +## Config + +### Debouncing the digest You can debounce the digest triggered by store modification (usefull in huge apps with a lot of store modification) by passing a config parameter to the `ngReduxProvider`. ```javascript @@ -201,11 +203,14 @@ angular.module('ngapplication').config(($ngReduxProvider) => { // eslint-disable-next-line $ngReduxProvider.config.debounce = { wait: 100, + mawWait: 500, }; }); ``` -This will debounce the digest 100ms. Every store modification within this time will be handled by this digest. +This will debounce the digest for 100ms with a maximum defaly time of 500ms. Every store modification within this time will be handled by this 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.
From 91c8ff646ff573a048949db1f08c9c8d99c31e7b Mon Sep 17 00:00:00 2001 From: jtassin Date: Thu, 16 Nov 2017 11:18:24 +0100 Subject: [PATCH 09/14] use of logasth.debounce instead of home made timeout --- src/components/digestMiddleware.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/components/digestMiddleware.js b/src/components/digestMiddleware.js index cfe851d..a8ba2ff 100644 --- a/src/components/digestMiddleware.js +++ b/src/components/digestMiddleware.js @@ -1,17 +1,13 @@ -let toRun; +import debounce from 'lodash.debounce'; export default function digestMiddleware($rootScope, debounceConfig) { + let debouncedFunction = $rootScope.$evalAsync; + if(debounceConfig && debounceConfig.wait && debounceConfig.wait > 0) { + debouncedFunction = debounce($rootScope.$evalAsync, debounceConfig.wait, { maxWait: debounceConfig.maxWait }); + } return store => next => action => { const res = next(action); - if(debounceConfig && debounceConfig.wait && debounceConfig.wait > 0) { - toRun = res; - window.setTimeout(() => { - $rootScope.$evalAsync(toRun); - toRun = undefined; - }, debounceConfig.wait); - } else { - $rootScope.$evalAsync(toRun); - } + debouncedFunction(res); return res; }; } From b4d699cc0352af285005c31ed6c29051ab6946d3 Mon Sep 17 00:00:00 2001 From: jtassin Date: Thu, 16 Nov 2017 11:51:30 +0100 Subject: [PATCH 10/14] add unit tests for debounce mode --- test/components/digestMiddleware.spec.js | 64 ++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 test/components/digestMiddleware.spec.js diff --git a/test/components/digestMiddleware.spec.js b/test/components/digestMiddleware.spec.js new file mode 100644 index 0000000..5fa67e9 --- /dev/null +++ b/test/components/digestMiddleware.spec.js @@ -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); + }); + }); + +}); From 9cce8482dc9d538e70f73d64cc0c00146becd95f Mon Sep 17 00:00:00 2001 From: jtassin Date: Thu, 16 Nov 2017 11:52:45 +0100 Subject: [PATCH 11/14] wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a7fc379..8c094e1 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ Middlewares passed as **string** will then be resolved throught angular's inject ## Config ### Debouncing the digest -You can debounce the digest triggered by store modification (usefull in huge apps with a lot of store modification) by passing a config parameter to the `ngReduxProvider`. +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'; From ec4bfe2bb827651d298bb1ec42ede4310344c5b3 Mon Sep 17 00:00:00 2001 From: jtassin Date: Thu, 16 Nov 2017 11:53:34 +0100 Subject: [PATCH 12/14] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c094e1..dd97f47 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ angular.module('ngapplication').config(($ngReduxProvider) => { }); ``` -This will debounce the digest for 100ms with a maximum defaly time of 500ms. Every store modification within this time will be handled by this digest. +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. From f0453a7cb57a3fe38fc494087c7096b15a31bc1b Mon Sep 17 00:00:00 2001 From: jtassin Date: Thu, 16 Nov 2017 14:41:14 +0100 Subject: [PATCH 13/14] debounce - little trick to avoid a random "Scope.eval is not a function" error --- src/components/digestMiddleware.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/digestMiddleware.js b/src/components/digestMiddleware.js index a8ba2ff..98718c6 100644 --- a/src/components/digestMiddleware.js +++ b/src/components/digestMiddleware.js @@ -1,9 +1,11 @@ import debounce from 'lodash.debounce'; export default function digestMiddleware($rootScope, debounceConfig) { - let debouncedFunction = $rootScope.$evalAsync; + let debouncedFunction = (expr) => { + $rootScope.$evalAsync(expr); + }; if(debounceConfig && debounceConfig.wait && debounceConfig.wait > 0) { - debouncedFunction = debounce($rootScope.$evalAsync, debounceConfig.wait, { maxWait: debounceConfig.maxWait }); + debouncedFunction = debounce(debouncedFunction, debounceConfig.wait, { maxWait: debounceConfig.maxWait }); } return store => next => action => { const res = next(action); From 5324db83c04cee2f0251ce82e533b66ac0865d40 Mon Sep 17 00:00:00 2001 From: Julien TASSIN Date: Sun, 22 Apr 2018 11:38:32 +0200 Subject: [PATCH 14/14] Typo issue mawwait => maxwait --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd97f47..028957e 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ angular.module('ngapplication').config(($ngReduxProvider) => { // eslint-disable-next-line $ngReduxProvider.config.debounce = { wait: 100, - mawWait: 500, + maxWait: 500, }; }); ```