Skip to content

Commit 4513566

Browse files
e-schultzsmithad15
authored andcommitted
feat(dispatch): Create dispatch decorator (#385)
* feat(dispatch): Create dispatch decorator Initial implementation of a `@dispatch` decorator. ```ts class TestClass { instanceProperty = 'test' @dispatch() myMethod(value) { return { type: 'TEST', payload: { value, instanceProperty: this.instanceProperty } } } } ``` Need to do some integration testing, and ensure that the context of `this` is preserved as expected if action creators are using it. * chore(cleanup) Cleanup dispatch decorator * DRY up the unit tests a bit * Modify tsconfig setup so .spec files don't show errors in editor
1 parent 563a2f2 commit 4513566

File tree

8 files changed

+219
-9
lines changed

8 files changed

+219
-9
lines changed

packages/store/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"prebuild": "rimraf ./lib",
88
"build": "npm run build:src && npm run build:testing",
9-
"build:src": "ngc -p tsconfig.json",
9+
"build:src": "ngc -p tsconfig.build.json",
1010
"build:testing": "ngc -p tsconfig.testing.json",
1111
"lint": "tslint 'src/**/*.ts'",
1212
"prepublish": "npm run lint && npm test && npm run build",
@@ -74,7 +74,7 @@
7474
"ts-node": "^3.0.2",
7575
"tslint": "^5.1.0",
7676
"typedoc": "^0.6.0",
77-
"typescript": "^2.1.0",
77+
"typescript": "^2.3.0",
7878
"zone.js": "^0.8.4"
7979
},
8080
"peerDependencies": {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import 'reflect-metadata';
2+
import { NgZone } from '@angular/core';
3+
import { NgRedux } from '../components/ng-redux';
4+
import { dispatch } from './dispatch';
5+
6+
7+
class MockNgZone {
8+
run = fn => fn()
9+
}
10+
11+
describe('@dispatch', () => {
12+
let ngRedux;
13+
const mockNgZone = new MockNgZone() as NgZone;
14+
let defaultState;
15+
let rootReducer;
16+
17+
beforeEach(() => {
18+
defaultState = {
19+
value: 'init-value',
20+
instanceProperty: 'init-instanceProperty'
21+
};
22+
rootReducer = (state = defaultState, action) => {
23+
switch (action.type) {
24+
case 'TEST':
25+
const { value, instanceProperty } = action.payload;
26+
return Object.assign({}, state, { value, instanceProperty });
27+
default:
28+
return state;
29+
}
30+
31+
};
32+
ngRedux = new NgRedux(mockNgZone);
33+
ngRedux.configureStore(rootReducer, defaultState);
34+
});
35+
36+
it('should call dispatch with the result of the function', () => {
37+
38+
spyOn(NgRedux.instance, 'dispatch');
39+
const instance = new TestClass();
40+
const result = instance.classMethod('class method');
41+
const expectedArgs = {
42+
type: 'TEST',
43+
payload: {
44+
value: 'class method',
45+
instanceProperty: 'test'
46+
}
47+
}
48+
expect(result.type).toBe('TEST');
49+
expect(result.payload.value).toBe('class method');
50+
expect(result.payload.instanceProperty).toBe('test');
51+
expect(NgRedux.instance.dispatch).toHaveBeenCalledWith(expectedArgs)
52+
});
53+
54+
it('should work with property initalizers', () => {
55+
56+
spyOn(NgRedux.instance, 'dispatch');
57+
const instance = new TestClass();
58+
const result = instance.boundProperty('bound property');
59+
const expectedArgs = {
60+
type: 'TEST',
61+
payload: {
62+
value: 'bound property',
63+
instanceProperty: 'test'
64+
}
65+
}
66+
expect(result.type).toBe('TEST');
67+
expect(result.payload.value).toBe('bound property');
68+
expect(result.payload.instanceProperty).toBe('test');
69+
expect(NgRedux.instance.dispatch).toHaveBeenCalledWith(expectedArgs)
70+
})
71+
72+
it('work with properties bound to function defined outside of the class', () => {
73+
spyOn(NgRedux.instance, 'dispatch');
74+
const instance = new TestClass();
75+
const result = instance.externalFunction('external function');
76+
const expectedArgs = {
77+
type: 'TEST',
78+
payload: {
79+
value: 'external function',
80+
instanceProperty: 'test'
81+
}
82+
}
83+
expect(result.type).toBe('TEST');
84+
expect(result.payload.value).toBe('external function');
85+
expect(result.payload.instanceProperty).toBe('test');
86+
expect(NgRedux.instance.dispatch).toHaveBeenCalledWith(expectedArgs);
87+
})
88+
89+
90+
91+
function externalFunction(value) {
92+
return {
93+
type: 'TEST',
94+
payload: {
95+
value,
96+
instanceProperty: this.instanceProperty
97+
}
98+
}
99+
}
100+
class TestClass {
101+
instanceProperty = 'test'
102+
@dispatch()
103+
externalFunction = externalFunction
104+
@dispatch()
105+
classMethod(value) {
106+
return {
107+
type: 'TEST',
108+
payload: {
109+
value,
110+
instanceProperty: this.instanceProperty
111+
}
112+
}
113+
}
114+
115+
@dispatch()
116+
boundProperty = (value) => {
117+
return {
118+
type: 'TEST',
119+
payload: {
120+
value,
121+
instanceProperty: this.instanceProperty
122+
}
123+
}
124+
}
125+
}
126+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
NgRedux,
3+
} from '../components/ng-redux';
4+
5+
export function dispatch(): void | any {
6+
return function dispatchDecorator(target: object, key: string | symbol | number, descriptor?: PropertyDescriptor) {
7+
let originalMethod: Function;
8+
9+
descriptor = descriptor || Object.getOwnPropertyDescriptor(target, key);
10+
const wrapped = function (...args) {
11+
12+
const result = originalMethod.apply(this, args);
13+
NgRedux.instance.dispatch(result);
14+
return result;
15+
}
16+
17+
if (descriptor === undefined) {
18+
const dispatchDescriptor: PropertyDescriptor = {
19+
get: () => wrapped,
20+
set: (setMethod) => originalMethod = setMethod,
21+
}
22+
Object.defineProperty(target, key, dispatchDescriptor)
23+
return;
24+
} else {
25+
originalMethod = descriptor.value;
26+
descriptor.value = wrapped;
27+
return descriptor;
28+
}
29+
30+
}
31+
}

packages/store/src/decorators/select.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Observable } from 'rxjs/Observable';
44

55
import 'rxjs/add/operator/toArray';
66
import 'rxjs/add/operator/take';
7+
78
import { NgRedux } from '../components/ng-redux';
89
import { select, select$ } from './select';
910

packages/store/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from './components/ng-redux';
99
import { DevToolsExtension } from './components/dev-tools';
1010
import { select, select$ } from './decorators/select';
11+
import { dispatch } from './decorators/dispatch';
1112
import { NgReduxModule } from './ng-redux.module';
1213

1314
// Warning: don't do this:
@@ -25,4 +26,5 @@ export {
2526
DevToolsExtension,
2627
select,
2728
select$,
29+
dispatch,
2830
};

packages/store/tsconfig.build.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": ".",
4+
"target": "ES5",
5+
"module": "commonjs",
6+
"moduleResolution": "node",
7+
"sourceMap": true,
8+
"emitDecoratorMetadata": true,
9+
"experimentalDecorators": true,
10+
"removeComments": false,
11+
"outDir": "lib/src/",
12+
"noImplicitAny": false,
13+
"declaration": true,
14+
"lib": [
15+
"es2015",
16+
"es2015.iterable",
17+
"dom"
18+
],
19+
"paths": {
20+
"@angular-redux/store": [
21+
"./src"
22+
]
23+
}
24+
},
25+
"compileOnSave": false,
26+
"buildOnSave": false,
27+
"include": [
28+
"src/**/*.ts"
29+
],
30+
"exclude": [
31+
"node_modules",
32+
"lib",
33+
"**/*.spec.ts"
34+
],
35+
"angularCompilerOptions": {
36+
"strictMetadataEmit": true,
37+
"genDir": ".compiled"
38+
}
39+
}

packages/store/tsconfig.json

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,26 @@
1111
"outDir": "lib/src/",
1212
"noImplicitAny": false,
1313
"declaration": true,
14-
"lib" : ["es2015", "es2015.iterable", "dom"],
14+
"lib": [
15+
"es2015",
16+
"es2015.iterable",
17+
"dom"
18+
],
1519
"paths": {
16-
"@angular-redux/store": ["./src"]
20+
"@angular-redux/store": [
21+
"./src"
22+
]
1723
}
1824
},
1925
"compileOnSave": false,
2026
"buildOnSave": false,
21-
"include": ["src/**/*.ts"],
22-
"exclude": ["node_modules", "lib", "**/*.spec.ts"],
27+
"include": [
28+
"src/**/*.ts"
29+
],
30+
"exclude": [
31+
"node_modules",
32+
"lib"
33+
],
2334
"angularCompilerOptions": {
2435
"strictMetadataEmit": true,
2536
"genDir": ".compiled"

packages/store/yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,9 +1292,9 @@ [email protected]:
12921292
version "2.2.2"
12931293
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.2.tgz#606022508479b55ffa368b58fee963a03dfd7b0c"
12941294

1295-
typescript@^2.1.0:
1296-
version "2.2.1"
1297-
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.1.tgz#4862b662b988a4c8ff691cc7969622d24db76ae9"
1295+
typescript@^2.3.0:
1296+
version "2.3.2"
1297+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.2.tgz#f0f045e196f69a72f06b25fd3bd39d01c3ce9984"
12981298

12991299
uglify-js@^2.6:
13001300
version "2.8.22"

0 commit comments

Comments
 (0)