Skip to content

Commit e2f0730

Browse files
committed
Merge pull request #3 from wbuchwalter/breaking-changes-0.3.0
Breaking changes 0.3.0
2 parents ed0b94a + e4e3b95 commit e2f0730

File tree

9 files changed

+92
-138
lines changed

9 files changed

+92
-138
lines changed

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ require('ng-redux');
4040
angular.module('app', ['ngRedux'])
4141
.config(($ngReduxProvider) => {
4242
let reducer = redux.combineReducers(reducers);
43-
let store = redux.createStore(reducer);
43+
let store = redux.createStore(reducer);
4444
$ngReduxProvider.setReduxStore(store);
4545
});
46-
```
46+
```
4747

4848
### Usage
4949
```JS
@@ -52,18 +52,18 @@ angular.module('app', ['ngRedux'])
5252
controllerAs: 'vm',
5353
controller: TodoLoaderController,
5454
template: "<div ng-repeat='todo in vm.todos'>{{todo.text}}</div>",
55-
55+
5656
[...]
5757
};
5858
}
59-
60-
class TodoLoaderController {
59+
60+
class TodoLoaderController {
6161

6262
constructor(reduxConnector) {
6363
this.todos = [];
6464
reduxConnector.connect(state => state.todos, todos => this.todos = todos);
6565
}
66-
66+
6767
[...]
6868
}
6969
```
@@ -96,7 +96,7 @@ constructor(reduxConnector) {
9696
this.disconnectTodos = reduxConnector.connect(state => state.todos, todos => this.todos = todos);
9797
reduxConnector.connect(state => state.users, users => this.users = users);
9898
}
99-
99+
100100
disconnectSome() {
101101
this.disconnectTodos();
102102
}
@@ -108,7 +108,7 @@ disconnectSome() {
108108
You don't need to create another service to get hold of Redux's store (although you can).
109109
You can access the store via ```$ngRedux.getStore()```:
110110

111-
```JS
111+
```JS
112112
redux.bindActionCreators(actionCreator, $ngRedux.getStore().dispatch);
113113
```
114114

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ng-redux",
3-
"version": "0.2.1",
3+
"version": "0.3.0",
44
"description": "Redux bindings for Angular.js",
55
"main": "./lib/index.js",
66
"scripts": {

src/components/connector.js

Lines changed: 29 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,35 @@
1-
import shallowEqual from '../utils/shallowEqual';
21
import isFunction from '../utils/isFunction';
3-
import isPlainObject from '../utils/isPlainObject';
42
import invariant from 'invariant';
53

6-
export default function connectorFactory($ngRedux) {
7-
return {
8-
connect: (select, target) => {
9-
let connector = new Connector($ngRedux, select, target);
10-
return connector.unsubscribe;
4+
export default function Connector(store){
5+
return {
6+
connect: (selectors, callback) => {
7+
if (!Array.isArray(selectors)) {
8+
selectors = [selectors];
9+
}
10+
11+
invariant(
12+
isFunction(callback),
13+
'The callback parameter passed to connect must be a Function. Instead received %s.',
14+
typeof selector
15+
);
16+
17+
//Initial update
18+
let params = selectors.map(selector => selector(store.getState()));
19+
callback(...params);
20+
21+
let unsubscribe = store.subscribe(() => {
22+
let nextParams = selectors.map(selector => selector(store.getState()));
23+
if(params === null || params.some((param, index) => param !== nextParams[index])) {
24+
callback(...nextParams);
25+
params = nextParams.slice(0);
26+
}
27+
});
28+
29+
return unsubscribe;
30+
},
31+
getStore() {
32+
return store;
1133
}
1234
}
13-
}
14-
15-
export class Connector {
16-
constructor($ngRedux, selector, callback){
17-
18-
invariant(
19-
isFunction(selector),
20-
'The selector passed to connect must be a function. Instead received %s.',
21-
typeof selector
22-
);
23-
24-
invariant(
25-
isFunction(callback),
26-
'The callback passed to connect must be a function. Instead received %s.',
27-
typeof callback
28-
);
29-
30-
this.select = selector;
31-
this.callback = callback;
32-
this.reduxStore = $ngRedux.getStore();
33-
this._sliceState = this.selectState(this.reduxStore.getState(), this.select);
34-
//force a first update to initialize subscribing component
35-
this.updateTarget(this.callback, this._sliceState);
36-
this.unsubscribe = this.reduxStore.subscribe(this.onStoreChanged.bind(this));
37-
}
38-
39-
onStoreChanged() {
40-
let nextState = this.selectState(this.reduxStore.getState(), this.select);
41-
if (!this.isSliceEqual(this._sliceState, nextState)) {
42-
this.updateTarget(this.callback, nextState)
43-
this._sliceState = {...nextState};
44-
}
45-
}
46-
47-
updateTarget(target, state){
48-
target(state)
49-
}
50-
51-
selectState(state, selector) {
52-
let slice = selector(state);
53-
54-
return slice;
55-
}
56-
57-
isSliceEqual(slice, nextSlice) {
58-
const isRefEqual = slice === nextSlice;
59-
if (isRefEqual || typeof slice !== 'object' || typeof nextSlice !== 'object') {
60-
return isRefEqual;
61-
}
62-
return shallowEqual(slice, nextSlice);
63-
}
6435
}

src/components/ngRedux.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Connector from './connector';
2+
3+
export default function ngReduxProvider() {
4+
let reduxStore = undefined;
5+
this.setReduxStore = store => reduxStore = store;
6+
7+
this.$get = () => {
8+
return Connector(reduxStore);
9+
}
10+
}
11+

src/components/provider.js

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/index.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import connectorFactory from './components/connector';
2-
import ngReduxProvider from './components/provider';
1+
import ngReduxProvider from './components/ngRedux';
32

43
export default angular.module('ngRedux', [])
54
.provider('$ngRedux', ngReduxProvider)
6-
.factory('reduxConnector', ['$ngRedux', '$rootScope', connectorFactory])
75
.name;

src/utils/hashCode.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function hashCode(str){
2+
var hash = 0;
3+
if (str.length == 0) return hash;
4+
for (i = 0; i < str.length; i++) {
5+
var chr = str.charCodeAt(i);
6+
hash = ((hash << 5) - hash) + chr;
7+
hash = hash & hash; // Convert to 32bit integer
8+
}
9+
10+
return hash;
11+
}

src/utils/shallowEqual.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,4 @@
2020
}
2121

2222
return true;
23-
}
24-
23+
}

test/components/connector.spec.js

Lines changed: 30 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,79 @@
11
import expect from 'expect';
22
import {createStore} from 'redux';
3-
import {Connector, default as connectorFactory} from '../../src/components/connector';
3+
import {default as Connector, copyArray} from '../../src/components/connector';
44

55
describe('Connector', () => {
66
let store;
7-
let ngRedux;
7+
let connector;
88
beforeEach(() => {
99
store = createStore((state, action) => {
10-
return {foo: 'bar', baz: action.payload};
10+
return {foo: 'bar', baz: action.payload, anotherState: 12};
1111
});
12-
ngRedux = {
13-
getStore: () => store
14-
};
12+
connector = Connector(store);
1513
});
1614

1715
it('Should throw when not passed a function as callback', () => {
18-
expect(() => new Connector(ngRedux, state => state, {})).toThrow();
19-
});
20-
21-
it('Should throw when not passed a function as selector', () => {
22-
expect(() => new Connector(ngRedux, {}, () => {})).toThrow();
16+
expect(connector.connect.bind(connector, () => {}, undefined)).toThrow();
17+
expect(connector.connect.bind(connector, () => {}, {})).toThrow();
18+
expect(connector.connect.bind(connector, () => {}, 15)).toThrow();
2319
});
2420

2521
it('Callback should be called once directly after creation to allow initialization', () => {
2622
let counter = 0;
2723
let callback = () => counter++;
28-
let connector = new Connector(ngRedux, state => state, callback);
24+
connector.connect(state => state, callback);
2925
expect(counter).toBe(1);
3026
});
3127

3228
it('Should call the function passed to connect when the store updates', () => {
3329
let counter = 0;
3430
let callback = () => counter++;
35-
let connector = new Connector(ngRedux, state => state, callback);
31+
connector.connect(state => state, callback);
3632
store.dispatch({type: 'ACTION', payload: 0});
3733
store.dispatch({type: 'ACTION', payload: 1});
3834
expect(counter).toBe(3);
3935
});
4036

41-
it('Should prevent unnecessary updates when state does not change', () => {
37+
it('Should accept a function or an array of function as selector', () => {
38+
let receivedState1, receivedState2;
39+
connector.connect(state => state.foo, newState => receivedState1 = newState);
40+
connector.connect([state => state.foo], newState => receivedState2 = newState);
41+
expect(receivedState1).toBe('bar');
42+
expect(receivedState1).toBe(receivedState2);
43+
})
44+
45+
it('Should prevent unnecessary updates when state does not change (shallowly)', () => {
4246
let counter = 0;
4347
let callback = () => counter++;
44-
let connector = new Connector(ngRedux, state => state, callback);
48+
connector.connect(state => state.baz, callback);
4549
store.dispatch({type: 'ACTION', payload: 0});
4650
store.dispatch({type: 'ACTION', payload: 0});
47-
store.dispatch({type: 'ACTION', payload: 0});
48-
expect(counter).toBe(2);
51+
store.dispatch({type: 'ACTION', payload: 1});
52+
expect(counter).toBe(3);
4953
});
5054

5155
it('Should pass the selected state as argument to the callback', () => {
5256
let receivedState;
53-
let connector = new Connector(ngRedux, state => state.foo, newState => receivedState = newState);
57+
connector.connect(state => state.foo, newState => receivedState = newState);
5458
store.dispatch({type: 'ACTION', payload: 1});
5559
expect(receivedState).toBe('bar');
5660
});
5761

58-
it('Should unsubscribe when disconnect is called', () => {
59-
let counter = 0;
60-
let callback = () => counter++;
61-
let connector = new Connector(ngRedux, state => state, callback);
62-
store.dispatch({type: 'ACTION', payload: 0});
63-
connector.unsubscribe();
64-
store.dispatch({type: 'ACTION', payload: 2});
65-
expect(counter).toBe(2);
66-
});
67-
68-
it('Factory: connect should create a new Connector', () => {
69-
let api = connectorFactory(ngRedux);
70-
let counter = 0;
71-
let callback = () => counter++;
72-
api.connect(state => state, callback);
73-
store.dispatch({type: 'ACTION', payload: 0});
62+
it('Should pass all the selected state as argument to the callback when provided an array of selectors', () => {
63+
connector.connect([state => state.foo, state => state.anotherState],
64+
(foo, anotherState) => {
65+
expect(foo).toBe('bar');
66+
expect(anotherState).toBe(12);
67+
});
7468
store.dispatch({type: 'ACTION', payload: 1});
75-
store.dispatch({type: 'ACTION', payload: 2});
76-
expect(counter).toBe(4);
7769
});
7870

79-
it('Factory: should allow multiple Connector creation', () => {
80-
let api = connectorFactory(ngRedux);
71+
it('Should return an unsubscribing function', () => {
8172
let counter = 0;
8273
let callback = () => counter++;
83-
api.connect(state => state, callback);
84-
api.connect(state => state, callback);
85-
store.dispatch({type: 'ACTION', payload: 0});
86-
// 2 initialization + each connection responding once to the action = 4
87-
expect(counter).toBe(4);
88-
})
89-
90-
it('Factory: connect should return an unsubscribing function', () => {
91-
let api = connectorFactory(ngRedux);
92-
let counter = 0;
93-
let callback = () => counter++;
94-
let unsubscribe = api.connect(state => state, callback);
74+
let unsubscribe = connector.connect(state => state, callback);
9575
store.dispatch({type: 'ACTION', payload: 0});
9676
unsubscribe();
97-
store.dispatch({type: 'ACTION', payload: 1});
9877
store.dispatch({type: 'ACTION', payload: 2});
9978
expect(counter).toBe(2);
10079
});

0 commit comments

Comments
 (0)