Skip to content
This repository has been archived by the owner on Jul 8, 2021. It is now read-only.

Commit

Permalink
feat(actions): less boilerplate!
Browse files Browse the repository at this point in the history
  • Loading branch information
amcdnl committed Feb 13, 2018
1 parent f3446a8 commit 75f6f40
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 47 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 2.4.0 - 2/13/18
- Feature: Make less boilerplate required
- Refactor: Get rid of `SelectMap` and consolidate to `Select`
- Perf: Improve `Select` perf by caching GET

# 2.3.0 - 2/11/18
- Feature: `SelectMap` decorator

Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ Actions/reducer utility for NGRX. It provides a handful of functions to make NGR
- `ofAction(MyActionClass)`: Lettable operator for NGRX Effects
- `createReducer(MyStoreClass)`: Reducer bootstrap function
- `@Select('my.prop')`: Select decorator
- `@SelectMap(state => 'foo')`: Select map decorator

Inspired by [redux-act](https://github.com/pauldijou/redux-act) and [redux-actions](https://github.com/reduxactions/redux-actions) for Redux.

Expand All @@ -34,9 +33,11 @@ npm i ngrx-actions --S
Next, create an action just like you do with NGRX today:

```javascript
export class MyAction implements Action {
readonly type = 'My Action';
constructor(public payload: MyObj) {}
export class MyAction {
// Type is optional. If not provided it will use the class name.
readonly type = 'My Action';

constructor(public payload: MyObj) {}
}
```

Expand Down Expand Up @@ -161,7 +162,7 @@ export class MyComponent {
@Select() color: Observable<string>;

// Remap the slice to a new object
@SelectMap(state => state.map(f => 'blue')) color$: Observable<string>;
@Select(state => state.map(f => 'blue')) color$: Observable<string>;
}
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngrx-actions",
"version": "2.3.0",
"version": "2.4.0",
"description": "Actions and Reducer Utility Library for NGRX",
"main": "dist/index.js",
"scripts": {
Expand Down
13 changes: 7 additions & 6 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ export function Action(...actionsKlasses: ActionType[]) {

for (const klass of actionsKlasses) {
const inst = new klass();
if (meta.actions[inst.type]) {
const type = inst.type || klass.name;

if (meta.actions[type]) {
throw new Error(
`@Action for '${inst.type}' is defined multiple times in functions '${
meta.actions[inst.type].fn
}' and '${name}'`
`@Action for '${type}' is defined multiple times in functions '${meta.actions[type].fn}' and '${name}'`
);
}
meta.actions[inst.type] = {

meta.actions[type] = {
action: klass,
fn: name,
type: inst.type
type
};
}
};
Expand Down
4 changes: 2 additions & 2 deletions src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { NGRX_ACTIONS_META, StoreMetadata } from './internals';

export function createReducer<TState = any>(store: {
new (...args: any[]): any;
}): (state: TState, action: Action) => TState {
}): (state: TState, action: Action | any) => TState {
if (!store.hasOwnProperty(NGRX_ACTIONS_META)) {
throw new Error('A reducer can be created from a @Store decorated class only.');
}
Expand All @@ -12,7 +12,7 @@ export function createReducer<TState = any>(store: {
const instance = new store();

return function(state: any = initialState, action: Action) {
const meta = actions[action.type];
const meta = actions[action.type || action.constructor.name];
if (meta) {
const result = instance[meta.fn](state, action);
if (result === undefined) {
Expand Down
2 changes: 1 addition & 1 deletion src/of-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function ofAction<T extends Action>(allowedType: ActionType<T>): Operator
export function ofAction<T extends Action>(...allowedTypes: ActionType[]): OperatorFunction<Action, T>;
export function ofAction(...allowedTypes: ActionType[]): OperatorFunction<Action, Action> {
const allowedMap = {};
allowedTypes.forEach(klass => (allowedMap[new klass().type] = true));
allowedTypes.forEach(klass => (allowedMap[new klass().type || klass.name] = true));
return filter((action: Action) => {
return allowedMap[action.type];
});
Expand Down
2 changes: 1 addition & 1 deletion src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export { NgrxActionsModule } from './module';
export { Action } from './action';
export { ofAction } from './of-action';
export { Store } from './store';
export { Select, NgrxSelect, SelectMap } from './select';
export { Select, NgrxSelect } from './select';
export { createReducer } from './factory';
41 changes: 14 additions & 27 deletions src/select.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Injectable } from '@angular/core';
import { Store, Selector } from '@ngrx/store';
import { map } from 'rxjs/operators/map';

@Injectable()
export class NgrxSelect {
Expand Down Expand Up @@ -39,16 +38,24 @@ export function Select<TState = any, TValue = any>(
} else {
fn = selectorOrFeature;
}

const createSelect = () => {
const store = NgrxSelect.store;
if (!store) {
throw new Error('NgrxSelect not connected to store!');
}
return store.select(fn);
};

// Redefine property
let select;
if (delete target[name]) {
Object.defineProperty(target, name, {
get: () => {
// get connected store
const store = NgrxSelect.store;
if (!store) {
throw new Error('NgrxSelect not connected to store!');
get: function() {
if (!select) {
select = createSelect();
}
return store.select(fn);
return select;
},
enumerable: true,
configurable: true
Expand All @@ -57,26 +64,6 @@ export function Select<TState = any, TValue = any>(
};
}

/**
* Slice a state portion of the state and then map it to a new object.
*/
export function SelectMap<TState = any, TValue = any>(fn: Selector<TState, TValue>) {
return function(target: any, name: string) {
const store = NgrxSelect.store;
if (!store) {
throw new Error('NgrxSelect not connected to store!');
}
const select = store.select(state => state).pipe(map(fn));
if (delete target[name]) {
Object.defineProperty(target, name, {
get: () => select,
enumerable: true,
configurable: true
});
}
};
}

/**
* The generated function is faster than:
* - pluck (Observable operator)
Expand Down
22 changes: 19 additions & 3 deletions src/spec/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ describe('actions', () => {
readonly type = 'myaction2';
}

class MyAction3 {}

@Store({ foo: true })
class Bar {
@Action(MyAction2)
Expand All @@ -80,11 +82,19 @@ describe('actions', () => {
foo(state) {
state.foo = false;
}

@Action(MyAction3)
foo2(state) {
state.foo = true;
}
}

const reducer = createReducer(Bar);
const res = reducer(undefined, new MyAction());
expect(res.foo).toBe(false);

const res2 = reducer(undefined, new MyAction3());
expect(res2.foo).toBe(true);
});

it('supports multiple actions', () => {
Expand Down Expand Up @@ -165,9 +175,9 @@ describe('actions', () => {
constructor(public foo: any, public bar: any) {}
}

const action = new MyAction('foo'),
action2 = new MyAction2(),
action3 = new MyAction3('a', 0);
const action = new MyAction('foo');
const action2 = new MyAction2();
const action3 = new MyAction3('a', 0);
const actions = of<NgRxAction>(action, action2, action3);
const tappedActions: NgRxAction[] = [];
actions.pipe(ofAction<MyAction | MyAction2>(MyAction, MyAction2)).subscribe(a => {
Expand Down Expand Up @@ -204,6 +214,8 @@ describe('actions', () => {
@Select('myFeature.bar.a.b.c.d') hello$: Observable<string>; // deeply nested props
@Select() myFeature: Observable<FooState>; // implied by name
@Select(msBar) bar$: Observable<any>; // using MemoizedSelector
@Select(state => [])
bar2$: Observable<any[]>; // Remapping to different obj
}

const store = new NgRxStore(of(globalState), undefined, undefined);
Expand All @@ -221,6 +233,10 @@ describe('actions', () => {
expect(n).toBe(globalState.myFeature);
});

mss.bar2$.subscribe(n => {
expect(n.length).toBe(0);
});

mss.bar$.subscribe(n => {
expect(n).toBe(globalState.myFeature.bar);
});
Expand Down
2 changes: 1 addition & 1 deletion src/symbols.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { Action } from '@ngrx/store';

export type ActionType<T extends Action = Action> = { new (...args: any[]): T };
export type ActionType<T extends Action = Action | any> = { new (...args: any[]): T };

0 comments on commit 75f6f40

Please sign in to comment.