Skip to content

Commit

Permalink
minor refatoring and readme updated to be AOT compliant
Browse files Browse the repository at this point in the history
  • Loading branch information
Gbuomprisco committed Nov 18, 2017
1 parent f7f1feb commit 04853df
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 43 deletions.
47 changes: 41 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ export class Reducer {
}
}

const initialState = [];
export const reducer = createReducer(Reducer)(initialState);
export const reducer = createReducer(Reducer);
```

### Full example
Expand Down Expand Up @@ -57,8 +56,44 @@ export class TodoReducer {
}
}

const initialState = [];
export const todos = createReducer(TodoReducer)(initialState);
export const reducer = createReducer(TodoReducer);
```

### Use with Ngrx and AOT

```javascript

import { reducer } from './todos.reducer.ts';

// initial state of the store's tree
const initialState = {
todos: []
};

// create your store tree
export const rootReducer = (
state: AppState = initialState,
action: Action
) => {
return {
todos: reducer(state.todos, action),
// here go your other reducers
};
};

// export a factory to be AOT compliant
export function reducerFactory() {
return rootReducer;
};

// let's pass the factory function to StoreModule
@NgModule({
// module's configuration ...
imports: [
StoreModule.forRoot(undefined, { reducerFactory })
]
})
export class AppModule { }
```

## Options
Expand All @@ -68,7 +103,7 @@ In order to prevent errors related to mutating the state, you can pass the optio

```javascript
const options = { freeze: true };
export const todos = createReducer(TodoReducer, options)(initialState);
export const todos = createReducer(TodoReducer, options);
```

### Log
Expand All @@ -79,6 +114,6 @@ It is possible to log every action by setting the property log to `true`. This h

```javascript
const options = { log: true };
export const todos = createReducer(TodoReducer, options)(initialState);
export const todos = createReducer(TodoReducer, options);
```

2 changes: 1 addition & 1 deletion example-app/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class AppComponent {
description: new FormControl('')
});
}


public toggleVisibleTodos(): void {
this.showDone = !this.showDone;
Expand Down
30 changes: 25 additions & 5 deletions example-app/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import { todos } from './todos/todo.selectors';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { StoreModule } from '@ngrx/store';

import { StoreModule, Action } from '@ngrx/store';
import { createReducer } from '../../../';
import { AppComponent } from './app.component';
import { todos } from './todos/todo.reducer';
import { reducer } from './todos/todo.reducer';
import { ActionReducerFactory, ActionReducerMap } from '@ngrx/store/src/models';
import { AppState } from './app.state';

const appState = {
todos: []
};

export const rootReducer = (
state: AppState = appState,
action: Action
) => {
return {
todos: reducer(state.todos, action)
};
};

export function reducerFactory() {
return rootReducer;
};

@NgModule({
declarations: [
Expand All @@ -14,8 +34,8 @@ import { todos } from './todos/todo.reducer';
BrowserModule,
FormsModule,
ReactiveFormsModule,
StoreModule.forRoot({
todos
StoreModule.forRoot(undefined, {
reducerFactory
})
],
providers: [],
Expand Down
6 changes: 3 additions & 3 deletions example-app/src/app/todos/todo.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CreateTodoAction, MarkTodoDoneAction, ArchiveTodoAction } from './todo.
import { createTodo } from './create-todo';

export class TodoReducer {
@Action
@Action
public createTodo(state: Todo[], action: CreateTodoAction): Todo[] {
return [ ...state, createTodo(action.payload)]
}
Expand All @@ -16,11 +16,11 @@ export class TodoReducer {
});
}

@Action
@Action
public archiveTodo(state: Todo[], action: ArchiveTodoAction): Todo[] {
return state.filter(todo => todo.id !== action.payload);
}
}

const options = {freeze: true, log: true};
export const todos = createReducer(TodoReducer, options)([]);
export const reducer = createReducer(TodoReducer, options);
4 changes: 3 additions & 1 deletion example-app/src/app/todos/todo.selectors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Todo } from '../app.state';
import { createSelector } from '@ngrx/store';

export const todos = (state: { todos: Todo[]}) => state.todos;
export const todos = (state: { todos: Todo[]}) => {
return state.todos;
}

export const todosSelector = (done) => createSelector(todos, (todos: Todo[]) => {
return todos.filter(todo => todo.done === done);
Expand Down
51 changes: 25 additions & 26 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,23 @@ export function createReducer<State>(Reducer: { new(): any }, options: ReducerOp
const instance = Object.create(Reducer.prototype);
const reducers = getReducerMethods(Reducer)(instance);

return (initialState: State): (state: State, action: ActionReducer) => State => {
if (options.freeze) {
Object.freeze(initialState);
return (prevState: State, action: ActionReducer): State => {
if (options.log) {
Object.freeze(prevState);
}

return (prevState: State = initialState, action: ActionReducer): State => {
const fn = reducers[action.type];
const exists = fn && typeof fn === 'function';
const reducer = exists ?
(...args: any[]): State => fn.apply(instance, args) : undefined;
const nextState = reducer ? reducer(prevState, action) : prevState;
const fn = reducers[action.type];
const exists = fn && typeof fn === 'function';
const reducer = exists ?
(...args: any[]): State => fn.apply(instance, args) : undefined;
const nextState = reducer ? reducer(prevState, action) : prevState;

if (options.log) {
log(action, prevState, nextState);
}
if (options.log) {
log(action, prevState, nextState);
}

return nextState;
};
}
return nextState;
};
}

/**
Expand All @@ -58,25 +56,26 @@ function log<State>(action: ActionReducer, prevState: State, nextState: State) {
* @param Reducer
*/
function getReducerMethods<State>(Reducer: { new(): any }) {
const methods = Object.getOwnPropertyNames(Reducer.prototype);
const getMetadata = (method: string) => Reflect.getMetadata(method, Reducer.prototype);
const prototype = Reducer.prototype;
const methods = Object.getOwnPropertyNames(prototype);

const getMetadata = (method: string) => {
const meta = Reflect.getMetadata('design:paramtypes', prototype, method);
const action = meta && meta[1];

return action ? new action().type : undefined;
}

return (instance: any): Methods<State> => {
return methods
.filter(getMetadata)
.map((method: string) => ({ [getMetadata(method).type]: instance[method] }))
.map((method: string) => ({ [getMetadata(method)]: instance[method] }))
.filter(item => item)
.reduce((acc: object, current: object) => ({...acc, ...current}));
.reduce((acc: object, current: object) => ({...acc, ...current}), {});
}
}

/**
* @name Action
*/
export function Action<C, P>(instance: C, method: string, descriptor: PropertyDescriptor) {
const types = Reflect.getMetadata('design:paramtypes', instance, method);
const action = types[1];
const type = new action().type;

Reflect.defineMetadata(method, { type, action }, instance);
}
export function Action<C, P>(instance: C, method: string, descriptor: PropertyDescriptor) {}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typed-reducer",
"version": "0.0.5",
"version": "0.1.0",
"description": "Framework-agnostic and class-based typed reducers",
"main": "index.js",
"scripts": {
Expand Down

0 comments on commit 04853df

Please sign in to comment.