diff --git a/README.md b/README.md
index 213880d..0e83ff3 100644
--- a/README.md
+++ b/README.md
@@ -36,10 +36,10 @@ require('ng-redux');
angular.module('app', ['ngRedux'])
.config(($ngReduxProvider) => {
let reducer = redux.combineReducers(reducers);
- let store = redux.createStore(reducer);
+ let store = redux.createStore(reducer);
$ngReduxProvider.setReduxStore(store);
});
-```
+```
### Usage
```JS
@@ -48,18 +48,18 @@ angular.module('app', ['ngRedux'])
controllerAs: 'vm',
controller: TodoLoaderController,
template: "
{{todo.text}}
",
-
+
[...]
};
}
-
-class TodoLoaderController {
+
+class TodoLoaderController {
constructor(reduxConnector) {
this.todos = [];
reduxConnector.connect(state => state.todos, todos => this.todos = todos);
}
-
+
[...]
}
```
@@ -92,7 +92,7 @@ constructor(reduxConnector) {
this.disconnectTodos = reduxConnector.connect(state => state.todos, todos => this.todos = todos);
reduxConnector.connect(state => state.users, users => this.users = users);
}
-
+
disconnectSome() {
this.disconnectTodos();
}
@@ -104,7 +104,7 @@ disconnectSome() {
You don't need to create another service to get hold of Redux's store (although you can).
You can access the store via ```$ngRedux.getStore()```:
-```JS
+```JS
redux.bindActionCreators(actionCreator, $ngRedux.getStore().dispatch);
```
diff --git a/package.json b/package.json
index e159098..491889c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ng-redux",
- "version": "0.2.1",
+ "version": "0.3.0",
"description": "Redux bindings for Angular.js",
"main": "./lib/index.js",
"scripts": {
diff --git a/src/components/connector.js b/src/components/connector.js
index 001ab8b..cbc635f 100644
--- a/src/components/connector.js
+++ b/src/components/connector.js
@@ -1,64 +1,35 @@
-import shallowEqual from '../utils/shallowEqual';
import isFunction from '../utils/isFunction';
-import isPlainObject from '../utils/isPlainObject';
import invariant from 'invariant';
-export default function connectorFactory($ngRedux) {
- return {
- connect: (select, target) => {
- let connector = new Connector($ngRedux, select, target);
- return connector.unsubscribe;
+export default function Connector(store){
+ return {
+ connect: (selectors, callback) => {
+ if (!Array.isArray(selectors)) {
+ selectors = [selectors];
+ }
+
+ invariant(
+ isFunction(callback),
+ 'The callback parameter passed to connect must be a Function. Instead received %s.',
+ typeof selector
+ );
+
+ //Initial update
+ let params = selectors.map(selector => selector(store.getState()));
+ callback(...params);
+
+ let unsubscribe = store.subscribe(() => {
+ let nextParams = selectors.map(selector => selector(store.getState()));
+ if(params === null || params.some((param, index) => param !== nextParams[index])) {
+ callback(...nextParams);
+ params = nextParams.slice(0);
+ }
+ });
+
+ return unsubscribe;
+ },
+ getStore() {
+ return store;
}
}
-}
-
-export class Connector {
- constructor($ngRedux, selector, callback){
-
- invariant(
- isFunction(selector),
- 'The selector passed to connect must be a function. Instead received %s.',
- typeof selector
- );
-
- invariant(
- isFunction(callback),
- 'The callback passed to connect must be a function. Instead received %s.',
- typeof callback
- );
-
- this.select = selector;
- this.callback = callback;
- this.reduxStore = $ngRedux.getStore();
- this._sliceState = this.selectState(this.reduxStore.getState(), this.select);
- //force a first update to initialize subscribing component
- this.updateTarget(this.callback, this._sliceState);
- this.unsubscribe = this.reduxStore.subscribe(this.onStoreChanged.bind(this));
- }
-
- onStoreChanged() {
- let nextState = this.selectState(this.reduxStore.getState(), this.select);
- if (!this.isSliceEqual(this._sliceState, nextState)) {
- this.updateTarget(this.callback, nextState)
- this._sliceState = {...nextState};
- }
- }
-
- updateTarget(target, state){
- target(state)
- }
-
- selectState(state, selector) {
- let slice = selector(state);
-
- return slice;
- }
-
- isSliceEqual(slice, nextSlice) {
- const isRefEqual = slice === nextSlice;
- if (isRefEqual || typeof slice !== 'object' || typeof nextSlice !== 'object') {
- return isRefEqual;
- }
- return shallowEqual(slice, nextSlice);
- }
}
\ No newline at end of file
diff --git a/src/components/ngRedux.js b/src/components/ngRedux.js
new file mode 100644
index 0000000..1071da5
--- /dev/null
+++ b/src/components/ngRedux.js
@@ -0,0 +1,11 @@
+import Connector from './connector';
+
+export default function ngReduxProvider() {
+ let reduxStore = undefined;
+ this.setReduxStore = store => reduxStore = store;
+
+ this.$get = () => {
+ return Connector(reduxStore);
+ }
+}
+
diff --git a/src/components/provider.js b/src/components/provider.js
deleted file mode 100644
index c742803..0000000
--- a/src/components/provider.js
+++ /dev/null
@@ -1,15 +0,0 @@
-export default function ngReduxProvider() {
- let reduxStoreInstance = undefined;
- this.setReduxStore = store => reduxStoreInstance = store;
- this.$get = () => new NgRedux(reduxStoreInstance);
-}
-
-class NgRedux {
- constructor(store){
- this.reduxStore = store;
- }
-
- getStore() {
- return this.reduxStore;
- }
-}
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 2abc786..1aebff5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,7 +1,5 @@
-import connectorFactory from './components/connector';
-import ngReduxProvider from './components/provider';
+import ngReduxProvider from './components/ngRedux';
export default angular.module('ngRedux', [])
.provider('$ngRedux', ngReduxProvider)
- .factory('reduxConnector', ['$ngRedux', '$rootScope', connectorFactory])
.name;
\ No newline at end of file
diff --git a/src/utils/hashCode.js b/src/utils/hashCode.js
new file mode 100644
index 0000000..806c975
--- /dev/null
+++ b/src/utils/hashCode.js
@@ -0,0 +1,11 @@
+export default function hashCode(str){
+ var hash = 0;
+ if (str.length == 0) return hash;
+ for (i = 0; i < str.length; i++) {
+ var chr = str.charCodeAt(i);
+ hash = ((hash << 5) - hash) + chr;
+ hash = hash & hash; // Convert to 32bit integer
+ }
+
+ return hash;
+}
\ No newline at end of file
diff --git a/src/utils/shallowEqual.js b/src/utils/shallowEqual.js
index 9a87c45..e99773a 100644
--- a/src/utils/shallowEqual.js
+++ b/src/utils/shallowEqual.js
@@ -20,5 +20,4 @@
}
return true;
- }
-
\ No newline at end of file
+ }
\ No newline at end of file
diff --git a/test/components/connector.spec.js b/test/components/connector.spec.js
index 9c6f8a3..988ab7e 100644
--- a/test/components/connector.spec.js
+++ b/test/components/connector.spec.js
@@ -1,100 +1,79 @@
import expect from 'expect';
import {createStore} from 'redux';
-import {Connector, default as connectorFactory} from '../../src/components/connector';
+import {default as Connector, copyArray} from '../../src/components/connector';
describe('Connector', () => {
let store;
- let ngRedux;
+ let connector;
beforeEach(() => {
store = createStore((state, action) => {
- return {foo: 'bar', baz: action.payload};
+ return {foo: 'bar', baz: action.payload, anotherState: 12};
});
- ngRedux = {
- getStore: () => store
- };
+ connector = Connector(store);
});
it('Should throw when not passed a function as callback', () => {
- expect(() => new Connector(ngRedux, state => state, {})).toThrow();
- });
-
- it('Should throw when not passed a function as selector', () => {
- expect(() => new Connector(ngRedux, {}, () => {})).toThrow();
+ expect(connector.connect.bind(connector, () => {}, undefined)).toThrow();
+ expect(connector.connect.bind(connector, () => {}, {})).toThrow();
+ expect(connector.connect.bind(connector, () => {}, 15)).toThrow();
});
it('Callback should be called once directly after creation to allow initialization', () => {
let counter = 0;
let callback = () => counter++;
- let connector = new Connector(ngRedux, state => state, callback);
+ connector.connect(state => state, callback);
expect(counter).toBe(1);
});
it('Should call the function passed to connect when the store updates', () => {
let counter = 0;
let callback = () => counter++;
- let connector = new Connector(ngRedux, state => state, callback);
+ connector.connect(state => state, callback);
store.dispatch({type: 'ACTION', payload: 0});
store.dispatch({type: 'ACTION', payload: 1});
expect(counter).toBe(3);
});
- it('Should prevent unnecessary updates when state does not change', () => {
+ it('Should accept a function or an array of function as selector', () => {
+ let receivedState1, receivedState2;
+ connector.connect(state => state.foo, newState => receivedState1 = newState);
+ connector.connect([state => state.foo], newState => receivedState2 = newState);
+ expect(receivedState1).toBe('bar');
+ expect(receivedState1).toBe(receivedState2);
+ })
+
+ it('Should prevent unnecessary updates when state does not change (shallowly)', () => {
let counter = 0;
let callback = () => counter++;
- let connector = new Connector(ngRedux, state => state, callback);
+ connector.connect(state => state.baz, callback);
store.dispatch({type: 'ACTION', payload: 0});
store.dispatch({type: 'ACTION', payload: 0});
- store.dispatch({type: 'ACTION', payload: 0});
- expect(counter).toBe(2);
+ store.dispatch({type: 'ACTION', payload: 1});
+ expect(counter).toBe(3);
});
it('Should pass the selected state as argument to the callback', () => {
let receivedState;
- let connector = new Connector(ngRedux, state => state.foo, newState => receivedState = newState);
+ connector.connect(state => state.foo, newState => receivedState = newState);
store.dispatch({type: 'ACTION', payload: 1});
expect(receivedState).toBe('bar');
});
- it('Should unsubscribe when disconnect is called', () => {
- let counter = 0;
- let callback = () => counter++;
- let connector = new Connector(ngRedux, state => state, callback);
- store.dispatch({type: 'ACTION', payload: 0});
- connector.unsubscribe();
- store.dispatch({type: 'ACTION', payload: 2});
- expect(counter).toBe(2);
- });
-
- it('Factory: connect should create a new Connector', () => {
- let api = connectorFactory(ngRedux);
- let counter = 0;
- let callback = () => counter++;
- api.connect(state => state, callback);
- store.dispatch({type: 'ACTION', payload: 0});
+ it('Should pass all the selected state as argument to the callback when provided an array of selectors', () => {
+ connector.connect([state => state.foo, state => state.anotherState],
+ (foo, anotherState) => {
+ expect(foo).toBe('bar');
+ expect(anotherState).toBe(12);
+ });
store.dispatch({type: 'ACTION', payload: 1});
- store.dispatch({type: 'ACTION', payload: 2});
- expect(counter).toBe(4);
});
- it('Factory: should allow multiple Connector creation', () => {
- let api = connectorFactory(ngRedux);
+ it('Should return an unsubscribing function', () => {
let counter = 0;
let callback = () => counter++;
- api.connect(state => state, callback);
- api.connect(state => state, callback);
- store.dispatch({type: 'ACTION', payload: 0});
- // 2 initialization + each connection responding once to the action = 4
- expect(counter).toBe(4);
- })
-
- it('Factory: connect should return an unsubscribing function', () => {
- let api = connectorFactory(ngRedux);
- let counter = 0;
- let callback = () => counter++;
- let unsubscribe = api.connect(state => state, callback);
+ let unsubscribe = connector.connect(state => state, callback);
store.dispatch({type: 'ACTION', payload: 0});
unsubscribe();
- store.dispatch({type: 'ACTION', payload: 1});
store.dispatch({type: 'ACTION', payload: 2});
expect(counter).toBe(2);
});