Skip to content

Commit

Permalink
feat(dispatch): Create dispatch decorator (#385)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
e-schultz authored and smithad15 committed Jul 9, 2018
1 parent 563a2f2 commit 4513566
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 9 deletions.
4 changes: 2 additions & 2 deletions packages/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"prebuild": "rimraf ./lib",
"build": "npm run build:src && npm run build:testing",
"build:src": "ngc -p tsconfig.json",
"build:src": "ngc -p tsconfig.build.json",
"build:testing": "ngc -p tsconfig.testing.json",
"lint": "tslint 'src/**/*.ts'",
"prepublish": "npm run lint && npm test && npm run build",
Expand Down Expand Up @@ -74,7 +74,7 @@
"ts-node": "^3.0.2",
"tslint": "^5.1.0",
"typedoc": "^0.6.0",
"typescript": "^2.1.0",
"typescript": "^2.3.0",
"zone.js": "^0.8.4"
},
"peerDependencies": {
Expand Down
126 changes: 126 additions & 0 deletions packages/store/src/decorators/dispatch.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import 'reflect-metadata';
import { NgZone } from '@angular/core';
import { NgRedux } from '../components/ng-redux';
import { dispatch } from './dispatch';


class MockNgZone {
run = fn => fn()
}

describe('@dispatch', () => {
let ngRedux;
const mockNgZone = new MockNgZone() as NgZone;
let defaultState;
let rootReducer;

beforeEach(() => {
defaultState = {
value: 'init-value',
instanceProperty: 'init-instanceProperty'
};
rootReducer = (state = defaultState, action) => {
switch (action.type) {
case 'TEST':
const { value, instanceProperty } = action.payload;
return Object.assign({}, state, { value, instanceProperty });
default:
return state;
}

};
ngRedux = new NgRedux(mockNgZone);
ngRedux.configureStore(rootReducer, defaultState);
});

it('should call dispatch with the result of the function', () => {

spyOn(NgRedux.instance, 'dispatch');
const instance = new TestClass();
const result = instance.classMethod('class method');
const expectedArgs = {
type: 'TEST',
payload: {
value: 'class method',
instanceProperty: 'test'
}
}
expect(result.type).toBe('TEST');
expect(result.payload.value).toBe('class method');
expect(result.payload.instanceProperty).toBe('test');
expect(NgRedux.instance.dispatch).toHaveBeenCalledWith(expectedArgs)
});

it('should work with property initalizers', () => {

spyOn(NgRedux.instance, 'dispatch');
const instance = new TestClass();
const result = instance.boundProperty('bound property');
const expectedArgs = {
type: 'TEST',
payload: {
value: 'bound property',
instanceProperty: 'test'
}
}
expect(result.type).toBe('TEST');
expect(result.payload.value).toBe('bound property');
expect(result.payload.instanceProperty).toBe('test');
expect(NgRedux.instance.dispatch).toHaveBeenCalledWith(expectedArgs)
})

it('work with properties bound to function defined outside of the class', () => {
spyOn(NgRedux.instance, 'dispatch');
const instance = new TestClass();
const result = instance.externalFunction('external function');
const expectedArgs = {
type: 'TEST',
payload: {
value: 'external function',
instanceProperty: 'test'
}
}
expect(result.type).toBe('TEST');
expect(result.payload.value).toBe('external function');
expect(result.payload.instanceProperty).toBe('test');
expect(NgRedux.instance.dispatch).toHaveBeenCalledWith(expectedArgs);
})



function externalFunction(value) {
return {
type: 'TEST',
payload: {
value,
instanceProperty: this.instanceProperty
}
}
}
class TestClass {
instanceProperty = 'test'
@dispatch()
externalFunction = externalFunction
@dispatch()
classMethod(value) {
return {
type: 'TEST',
payload: {
value,
instanceProperty: this.instanceProperty
}
}
}

@dispatch()
boundProperty = (value) => {
return {
type: 'TEST',
payload: {
value,
instanceProperty: this.instanceProperty
}
}
}
}
});
31 changes: 31 additions & 0 deletions packages/store/src/decorators/dispatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
NgRedux,
} from '../components/ng-redux';

export function dispatch(): void | any {
return function dispatchDecorator(target: object, key: string | symbol | number, descriptor?: PropertyDescriptor) {
let originalMethod: Function;

descriptor = descriptor || Object.getOwnPropertyDescriptor(target, key);
const wrapped = function (...args) {

const result = originalMethod.apply(this, args);
NgRedux.instance.dispatch(result);
return result;
}

if (descriptor === undefined) {
const dispatchDescriptor: PropertyDescriptor = {
get: () => wrapped,
set: (setMethod) => originalMethod = setMethod,
}
Object.defineProperty(target, key, dispatchDescriptor)
return;
} else {
originalMethod = descriptor.value;
descriptor.value = wrapped;
return descriptor;
}

}
}
1 change: 1 addition & 0 deletions packages/store/src/decorators/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Observable } from 'rxjs/Observable';

import 'rxjs/add/operator/toArray';
import 'rxjs/add/operator/take';

import { NgRedux } from '../components/ng-redux';
import { select, select$ } from './select';

Expand Down
2 changes: 2 additions & 0 deletions packages/store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from './components/ng-redux';
import { DevToolsExtension } from './components/dev-tools';
import { select, select$ } from './decorators/select';
import { dispatch } from './decorators/dispatch';
import { NgReduxModule } from './ng-redux.module';

// Warning: don't do this:
Expand All @@ -25,4 +26,5 @@ export {
DevToolsExtension,
select,
select$,
dispatch,
};
39 changes: 39 additions & 0 deletions packages/store/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "ES5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"outDir": "lib/src/",
"noImplicitAny": false,
"declaration": true,
"lib": [
"es2015",
"es2015.iterable",
"dom"
],
"paths": {
"@angular-redux/store": [
"./src"
]
}
},
"compileOnSave": false,
"buildOnSave": false,
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib",
"**/*.spec.ts"
],
"angularCompilerOptions": {
"strictMetadataEmit": true,
"genDir": ".compiled"
}
}
19 changes: 15 additions & 4 deletions packages/store/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,26 @@
"outDir": "lib/src/",
"noImplicitAny": false,
"declaration": true,
"lib" : ["es2015", "es2015.iterable", "dom"],
"lib": [
"es2015",
"es2015.iterable",
"dom"
],
"paths": {
"@angular-redux/store": ["./src"]
"@angular-redux/store": [
"./src"
]
}
},
"compileOnSave": false,
"buildOnSave": false,
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "lib", "**/*.spec.ts"],
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
],
"angularCompilerOptions": {
"strictMetadataEmit": true,
"genDir": ".compiled"
Expand Down
6 changes: 3 additions & 3 deletions packages/store/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1292,9 +1292,9 @@ typescript@2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.2.tgz#606022508479b55ffa368b58fee963a03dfd7b0c"

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

uglify-js@^2.6:
version "2.8.22"
Expand Down

0 comments on commit 4513566

Please sign in to comment.