Skip to content

Commit a0b0b52

Browse files
committed
New API tentative #2x
1 parent 84d06e3 commit a0b0b52

File tree

6 files changed

+91
-97
lines changed

6 files changed

+91
-97
lines changed

examples/counter/components/counter.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export default function counter() {
1313
class CounterController {
1414

1515
constructor($ngRedux, $scope) {
16-
$ngRedux.connect($scope, this.mapStateToScope, CounterActions, 'vm');
16+
const unsubscribe = $ngRedux.connect(this.mapStateToScope, CounterActions)(this);
17+
$scope.$on('$destroy', unsubscribe);
1718
}
1819

1920
// Which part of the Redux global state does our component want to receive on $scope?

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"babel-loader": "^5.3.2",
2424
"expect": "^1.8.0",
2525
"mocha": "^2.2.5",
26+
"sinon": "^1.16.1",
2627
"webpack": "^1.10.5"
2728
},
2829
"peerDependencies": {

src/components/connector.js

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,58 @@
11
import shallowEqual from '../utils/shallowEqual';
22
import wrapActionCreators from '../utils/wrapActionCreators';
3-
import findControllerAsKey from '../utils/findControllerAsKey';
43
import invariant from 'invariant';
54
import _ from 'lodash';
65

76
export default function Connector(store) {
8-
return (scope, mapStateToScope, mapDispatchToScope = {}) => {
9-
10-
invariant(
11-
scope && _.isFunction(scope.$on) && _.isFunction(scope.$destroy),
12-
'The scope parameter passed to connect must be an instance of $scope.'
13-
);
7+
return (mapStateToTarget, mapDispatchToTarget = dispatch => ({dispatch})) => {
148
invariant(
15-
_.isFunction(mapStateToScope),
16-
'mapStateToScope must be a Function. Instead received $s.', mapStateToScope
9+
_.isFunction(mapStateToTarget),
10+
'mapStateToTarget must be a Function. Instead received $s.', mapStateToTarget
1711
);
12+
1813
invariant(
19-
_.isPlainObject(mapDispatchToScope) || _.isFunction(mapDispatchToScope),
20-
'mapDispatchToScope must be a plain Object or a Function. Instead received $s.', mapDispatchToScope
14+
_.isPlainObject(mapDispatchToTarget) || _.isFunction(mapDispatchToTarget),
15+
'mapDispatchToTarget must be a plain Object or a Function. Instead received $s.', mapDispatchToTarget
2116
);
2217

23-
const propertyKey = findControllerAsKey(scope);
18+
let slice = getStateSlice(store.getState(), mapStateToTarget);
19+
20+
const finalMapDispatchToTarget = _.isPlainObject(mapDispatchToTarget) ?
21+
wrapActionCreators(mapDispatchToTarget) :
22+
mapDispatchToTarget;
2423

25-
let slice = getStateSlice(store.getState(), mapStateToScope);
26-
let target = propertyKey ? scope[propertyKey] : scope;
24+
//find better name
25+
const actions = finalMapDispatchToTarget(store.dispatch);
2726

28-
const finalMapDispatchToScope = _.isPlainObject(mapDispatchToScope) ?
29-
wrapActionCreators(mapDispatchToScope) :
30-
mapDispatchToScope;
27+
return (target) => {
3128

32-
//Initial update
33-
_.assign(target, slice, finalMapDispatchToScope(store.dispatch));
29+
invariant(
30+
_.isFunction(target) || _.isObject(target),
31+
'The target parameter passed to connect must be a Function or a plain object.'
32+
);
33+
34+
//Initial update
35+
updateTarget(target, slice, actions);
36+
37+
const unsubscribe = store.subscribe(() => {
38+
const nextSlice = getStateSlice(store.getState(), mapStateToTarget);
39+
if (!shallowEqual(slice, nextSlice)) {
40+
slice = nextSlice;
41+
updateTarget(target, slice, actions);
42+
}
43+
});
44+
return unsubscribe;
45+
}
3446

35-
subscribe(scope, store, () => {
36-
const nextSlice = getStateSlice(store.getState(), mapStateToScope);
37-
if (!shallowEqual(slice, nextSlice)) {
38-
slice = nextSlice;
39-
_.assign(target, slice);
40-
}
41-
});
4247
}
4348
}
4449

45-
function subscribe(scope, store, callback) {
46-
const unsubscribe = store.subscribe(callback);
47-
48-
scope.$on('$destroy', () => {
49-
unsubscribe();
50-
});
50+
function updateTarget(target, StateSlice, dispatch) {
51+
if(_.isFunction(target)) {
52+
target(StateSlice, dispatch);
53+
} else {
54+
_.assign(target, StateSlice, dispatch);
55+
}
5156
}
5257

5358
function getStateSlice(state, mapStateToScope) {

src/utils/findControllerAsKey.js

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

test/components/connector.spec.js

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,100 @@
11
import expect from 'expect';
2+
let sinon = require('sinon');
23
import { createStore } from 'redux';
34
import Connector from '../../src/components/connector';
45
import _ from 'lodash';
56

67
describe('Connector', () => {
78
let store;
89
let connect;
9-
let scopeStub;
10+
let targetObj;
1011

1112
beforeEach(() => {
1213
store = createStore((state, action) => ({
1314
foo: 'bar',
1415
baz: action.payload
1516
}));
16-
scopeStub = {
17-
$on: () => {},
18-
$destroy: () => {}
19-
};
17+
targetObj = {};
2018
connect = Connector(store);
2119
});
2220

23-
it('Should throw when not passed a $scope object', () => {
24-
expect(connect.bind(connect, () => { }, () => ({}))).toThrow();
25-
expect(connect.bind(connect, 15, () => ({}))).toThrow();
26-
expect(connect.bind(connect, undefined, () => ({}))).toThrow();
27-
expect(connect.bind(connect, {}, () => ({}))).toThrow();
21+
it('Should throw when target is not a Function or a plain object', () => {
22+
expect(connect(() => ({})).bind(connect, 15)).toThrow();
23+
expect(connect(() => ({})).bind(connect, undefined)).toThrow();
24+
expect(connect(() => ({})).bind(connect, 'test')).toThrow();
25+
26+
expect(connect(() => ({})).bind(connect, {})).toNotThrow();
27+
expect(connect(() => ({})).bind(connect, () => {})).toNotThrow();
2828

29-
expect(connect.bind(connect, scopeStub, () => ({}))).toNotThrow();
3029
});
3130

32-
it('Should throw when selector does not return a plain object as target', () => {
33-
expect(connect.bind(connect, scopeStub, state => state.foo)).toThrow();
31+
it('Should throw when selector does not return a plain object', () => {
32+
expect(connect.bind(connect, state => state.foo)).toThrow();
3433
});
3534

36-
it('Should extend scope with selected state once directly after creation', () => {
37-
connect(
38-
scopeStub,
35+
it('Should extend target (Object) with selected state once directly after creation', () => {
36+
connect(
3937
() => ({
4038
vm: { test: 1 }
41-
}));
39+
}))(targetObj);
4240

43-
expect(scopeStub.vm).toEqual({ test: 1 });
41+
expect(targetObj.vm).toEqual({ test: 1 });
4442
});
4543

46-
it('Should update the scope passed to connect when the store updates', () => {
47-
connect(scopeStub, state => state);
44+
it('Should update the target (Object) passed to connect when the store updates', () => {
45+
connect(state => state)(targetObj);
4846
store.dispatch({ type: 'ACTION', payload: 0 });
49-
expect(scopeStub.baz).toBe(0);
47+
expect(targetObj.baz).toBe(0);
5048
store.dispatch({ type: 'ACTION', payload: 1 });
51-
expect(scopeStub.baz).toBe(1);
49+
expect(targetObj.baz).toBe(1);
5250
});
5351

5452
it('Should prevent unnecessary updates when state does not change (shallowly)', () => {
55-
connect(scopeStub, state => state);
53+
connect(state => state)(targetObj);
5654
store.dispatch({ type: 'ACTION', payload: 5 });
5755

58-
expect(scopeStub.baz).toBe(5);
56+
expect(targetObj.baz).toBe(5);
5957

60-
scopeStub.baz = 0;
58+
targetObj.baz = 0;
6159

6260
//this should not replace our mutation, since the state didn't change
6361
store.dispatch({ type: 'ACTION', payload: 5 });
6462

65-
expect(scopeStub.baz).toBe(0);
63+
expect(targetObj.baz).toBe(0);
64+
65+
});
6666

67+
it('Should extend target (object) with actionCreators', () => {
68+
connect(() => ({}), { ac1: () => { }, ac2: () => { } })(targetObj);
69+
expect(_.isFunction(targetObj.ac1)).toBe(true);
70+
expect(_.isFunction(targetObj.ac2)).toBe(true);
6771
});
6872

69-
it('Should extend scope with actionCreators', () => {
70-
connect(scopeStub, () => ({}), { ac1: () => { }, ac2: () => { } });
71-
expect(_.isFunction(scopeStub.ac1)).toBe(true);
72-
expect(_.isFunction(scopeStub.ac2)).toBe(true);
73+
it('Should return an unsubscribing function', () => {
74+
const unsubscribe = connect(state => state)(targetObj);
75+
store.dispatch({ type: 'ACTION', payload: 5 });
76+
77+
expect(targetObj.baz).toBe(5);
78+
79+
unsubscribe();
80+
81+
store.dispatch({ type: 'ACTION', payload: 7 });
82+
83+
expect(targetObj.baz).toBe(5);
84+
7385
});
7486

75-
it('Should provide dispatch to mapDispatchToScope when receiving a Function', () => {
87+
it('Should provide dispatch to mapDispatchToTarget when receiving a Function', () => {
7688
let receivedDispatch;
77-
connect(scopeStub, () => ({}), dispatch => { receivedDispatch = dispatch });
89+
connect(() => ({}), dispatch => { receivedDispatch = dispatch })(targetObj);
7890
expect(receivedDispatch).toBe(store.dispatch);
7991
});
8092

93+
it('Should call target (Function) with mapStateToTarget and mapDispatchToTarget results ', () => {
94+
95+
//let targetFunc = sinon.spy();
96+
//connect(targetFunc, state => state.pojo);
97+
expect(false).toBe(true);
98+
});
99+
81100
});

test/utils/findControllerAs.spec.js

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

0 commit comments

Comments
 (0)