Skip to content
This repository has been archived by the owner on Jan 10, 2018. It is now read-only.

Commit

Permalink
feat(Store): Enable fractal state management
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeRyanDev committed Dec 27, 2016
1 parent 30544e9 commit 835bdb5
Show file tree
Hide file tree
Showing 27 changed files with 1,013 additions and 596 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "./node_modules/typescript/lib"
}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const counterReducer: ActionReducer<number> = (state: number = 0, action:
}
```

In your app's main module, import those reducers and use the `StoreModule.provideStore(reducers)`
In your app's main module, import those reducers and use the `StoreModule.forRoot(reducers)`
function to provide them to Angular's injector:

```ts
Expand All @@ -85,7 +85,7 @@ import { counterReducer } from './counter';
@NgModule({
imports: [
BrowserModule,
StoreModule.provideStore({ counter: counterReducer })
StoreModule.forRoot({ counter: counterReducer })
]
})
export class AppModule {}
Expand Down
16 changes: 10 additions & 6 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export * from './src/dispatcher';
export * from './src/ng2';
export * from './src/reducer';
export * from './src/state';
export * from './src/store';
export * from './src/utils';
export { ActionsObservable } from './src/actions-observable';
export { StoreConfig } from './src/config';
export { Dispatcher } from './src/dispatcher';
export { Action, ActionReducer, ActionReducerMap, ActionReducerFactory } from './src/models';
export { StoreModule } from './src/module';
export { ReducerObservable } from './src/reducer-observable';
export { StateObservable, reduceState } from './src/state-observable';
export { Store } from './src/store';
export { combineReducers } from './src/utils';
export { compose } from '@ngrx/core';
25 changes: 13 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"scripts": {
"karma": "karma start --single-run",
"test:unit": "node tests.js",
"test:unit:coverage": "nyc npm run test:unit",
"test:ngc": "ngc -p ./spec/ngc/tsconfig.ngc.json",
"test": "npm run test:unit && npm run test:ngc",
"clean:pre": "rimraf release",
Expand Down Expand Up @@ -35,18 +36,18 @@
},
"homepage": "https://github.com/ngrx/store#readme",
"peerDependencies": {
"@angular/core": "^2.1.0",
"@angular/core": "4.0.0-beta.1",
"@ngrx/core": "^1.1.0",
"rxjs": "^5.0.0-beta.12"
"rxjs": "^5.0.0"
},
"devDependencies": {
"@angular/common": "^2.1.0",
"@angular/compiler": "^2.1.0",
"@angular/compiler-cli": "^2.1.0",
"@angular/core": "^2.1.0",
"@angular/platform-browser": "^2.1.0",
"@angular/platform-browser-dynamic": "^2.1.0",
"@angular/platform-server": "^2.1.0",
"@angular/common": "4.0.0-beta.1",
"@angular/compiler": "4.0.0-beta.1",
"@angular/compiler-cli": "4.0.0-beta.1",
"@angular/core": "4.0.0-beta.1",
"@angular/platform-browser": "4.0.0-beta.1",
"@angular/platform-browser-dynamic": "4.0.0-beta.1",
"@angular/platform-server": "4.0.0-beta.1",
"@ngrx/core": "^1.2.0",
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.38",
Expand All @@ -58,15 +59,15 @@
"nyc": "^8.3.2",
"rimraf": "^2.5.4",
"rollup": "^0.34.13",
"rxjs": "^5.0.0-beta.11",
"rxjs": "^5.0.0",
"ts-loader": "^0.8.2",
"ts-node": "^1.6.1",
"tslint": "^3.15.1",
"tslint-loader": "^2.1.5",
"typescript": "^2.0.2",
"typescript": "^2.1.4",
"uglifyjs": "^2.4.10",
"webpack": "^2.1.0-beta.21",
"zone.js": "^0.6.17"
"zone.js": "^0.7.2"
},
"nyc": {
"extension": [
Expand Down
29 changes: 9 additions & 20 deletions spec/edge.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {ReflectiveInjector} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {todos, todoCount} from './fixtures/edge_todos';

import {Store, StoreModule, Dispatcher, State, Action, combineReducers} from '../';
import { Observable } from 'rxjs/Observable';
import { todos, todoCount } from './fixtures/edge_todos';
import { createInjector } from './helpers/injector';
import { Store, StoreModule } from '../';


interface TestAppSchema {
Expand All @@ -14,41 +13,32 @@ interface TestAppSchema {
interface Todo { }

interface TodoAppSchema {
visibilityFilter: string;
todoCount: number;
todos: Todo[];
}



describe('ngRx Store', () => {

describe('basic store actions', function() {

let injector: ReflectiveInjector;
let store: Store<TestAppSchema>;
let dispatcher: Dispatcher;
describe('basic store actions', () => {
let store: Store<TodoAppSchema>;

beforeEach(() => {

injector = ReflectiveInjector.resolveAndCreate([
StoreModule.provideStore({ todos, todoCount }).providers
]);
const injector = createInjector(StoreModule.forRoot<TodoAppSchema>({ todos, todoCount }));

store = injector.get(Store);
dispatcher = injector.get(Dispatcher);
});

it('should provide an Observable Store', () => {
expect(store).toBeDefined();
});

it('should handle re-entrancy', (done) => {

let todosNextCount = 0;
let todosCountNextCount = 0;

store.select('todos').subscribe((todos: any[]) => {
todosNextCount++
todosNextCount++;
store.dispatch({ type: 'SET_COUNT', payload: todos.length })
});

Expand All @@ -65,7 +55,6 @@ describe('ngRx Store', () => {
expect(todosCountNextCount).toBe(2);
done();
}, 10);

});
});
});
16 changes: 11 additions & 5 deletions spec/fixtures/todos.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export interface TodoItem {
id: number;
completed: boolean;
text: string;
}

export const ADD_TODO = 'ADD_TODO';
export const COMPLETE_TODO = 'COMPLETE_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
Expand All @@ -20,7 +26,7 @@ export function visibilityFilter(state = VisibilityFilters.SHOW_ALL, {type, payl
}
};

export function todos(state = [], {type, payload}) {
export function todos(state: TodoItem[] = [], {type, payload}): TodoItem[] {
switch (type) {
case ADD_TODO:
return [
Expand All @@ -32,11 +38,11 @@ export function todos(state = [], {type, payload}) {
}
];
case COMPLETE_ALL_TODOS:
return state.map(todo => Object.assign({}, todo, {completed: true}));
return state.map(todo => ({ ...todo, completed: true }));
case COMPLETE_TODO:
return state.map(todo => {
return todo.id === payload.id ? Object.assign({}, todo, {completed: true}) : todo;
});
return state.map(todo =>
todo.id === payload.id ? { ...todo, completed: true } : todo
);
default:
return state;
}
Expand Down
18 changes: 18 additions & 0 deletions spec/helpers/injector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ReflectiveInjector, ModuleWithProviders } from '@angular/core';


export function createInjector({ ngModule, providers }: ModuleWithProviders): ReflectiveInjector {
const injector = ReflectiveInjector.resolveAndCreate([ ...(providers || []), ngModule ]);

injector.get(ngModule);

return injector;
}

export function createChildInjector(parent: ReflectiveInjector, { ngModule, providers }: ModuleWithProviders): ReflectiveInjector {
const injector = parent.resolveAndCreateChild([ ...(providers || []), ngModule ]);

injector.get(ngModule);

return injector;
}
51 changes: 28 additions & 23 deletions spec/integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {Observable} from 'rxjs/Observable';
import {ReflectiveInjector} from '@angular/core';
import 'rxjs/add/observable/combineLatest';

import {Store, StoreModule, Action, combineReducers, INITIAL_REDUCER, INITIAL_STATE} from '../';
import {counterReducer, INCREMENT, DECREMENT, RESET} from './fixtures/counter';
import {todos, visibilityFilter, VisibilityFilters, SET_VISIBILITY_FILTER, ADD_TODO, COMPLETE_TODO, COMPLETE_ALL_TODOS} from './fixtures/todos';
import 'rxjs/add/operator/first';
import { Observable } from 'rxjs/Observable';
import { ReflectiveInjector } from '@angular/core';
import { Store, StoreModule, Action, combineReducers, ReducerObservable } from '../';
import { createInjector } from './helpers/injector';
import { ResolvedConfig, STORE_CONFIG } from '../src/config';
import { counterReducer, INCREMENT, DECREMENT, RESET } from './fixtures/counter';
import { todos, visibilityFilter, VisibilityFilters, SET_VISIBILITY_FILTER, ADD_TODO, COMPLETE_TODO, COMPLETE_ALL_TODOS } from './fixtures/todos';

interface Todo {
id: number;
Expand All @@ -20,17 +22,13 @@ interface TodoAppSchema {
describe('ngRx Integration spec', () => {

describe('todo integration spec', function() {

let injector: ReflectiveInjector;
let store: Store<TodoAppSchema>;
let currentState: TodoAppSchema;

const rootReducer = combineReducers({ todos, visibilityFilter });
const initialValue = { todos: [], visibilityFilter: VisibilityFilters.SHOW_ALL };
const initialState = { todos: [], visibilityFilter: VisibilityFilters.SHOW_ALL };

injector = ReflectiveInjector.resolveAndCreate([
StoreModule.provideStore(rootReducer, initialValue).providers
]);
injector = createInjector(StoreModule.forRoot({ todos, visibilityFilter }, { initialState }));

store = injector.get(Store);

Expand All @@ -46,28 +44,35 @@ describe('ngRx Integration spec', () => {
const reducers = { test: function(){} };
spyOn(reducers, 'test');
const action = { type: 'Test Action' };
const reducer = ReflectiveInjector.resolveAndCreate([ StoreModule.provideStore(reducers).providers ]).get(INITIAL_REDUCER);
const specInjector = createInjector(StoreModule.forRoot(reducers));
const reducer$: ReducerObservable = specInjector.get(ReducerObservable);

expect(reducer).toBeDefined();
expect(typeof reducer === 'function').toBe(true);
reducer$.first().subscribe(reducer => {
expect(reducer).toBeDefined();
expect(typeof reducer === 'function').toBe(true);

reducer(undefined, action);
reducer(undefined, action);

expect(reducers.test).toHaveBeenCalledWith(undefined, action);
expect(reducers.test).toHaveBeenCalledWith(undefined, action);
});
});

it('should probe the reducer to resolve the initial state if no initial state is provided', () => {
const reducer = () => 2;
const initialState = ReflectiveInjector.resolveAndCreate([ StoreModule.provideStore(reducer).providers ]).get(INITIAL_STATE);
const reducers = { key: () => 2 };
const specInjector = createInjector(StoreModule.forRoot(reducers));
const config: ResolvedConfig = specInjector.get(ResolvedConfig);

expect(initialState).toBe(2);
expect(config.initialState).toEqual({ key: 2 });
});

it('should use a provided initial state', () => {
const reducer = () => 2;
const initialState = ReflectiveInjector.resolveAndCreate([ StoreModule.provideStore(reducer, 3).providers ]).get(INITIAL_STATE);
const reducers = { key: (n: number) => n || 2 };
const initialState = { key: 3 };
const specInjector = createInjector(StoreModule.forRoot(reducers, { initialState }));

const config: ResolvedConfig = specInjector.get(ResolvedConfig);

expect(initialState).toBe(3);
expect(config.initialState).toBe(initialState);
});

it('should start with no todos and showing all filter', () => {
Expand Down
32 changes: 32 additions & 0 deletions spec/modules.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'rxjs/add/operator/take';
import { zip } from 'rxjs/observable/zip';
import { ReflectiveInjector } from '@angular/core';
import { createInjector, createChildInjector } from './helpers/injector';
import { StoreModule, Store } from '../';


describe('Nested Store Modules', () => {
let store: Store<any>;

beforeEach(() => {
const parentReducers = { stateKey: () => 'root' };
const featureReducers = { stateKey: () => 'child' };

const rootInjector = createInjector(StoreModule.forRoot(parentReducers));
const featureInjector = createChildInjector(rootInjector, StoreModule.forFeature('inner', featureReducers))

store = rootInjector.get(Store);
});

it('should nest the child module in the root store object', () => {
store.take(1).subscribe(state => {
expect(state).toEqual({
stateKey: 'root',
inner: {
stateKey: 'child'
}
});
});
});
});

35 changes: 30 additions & 5 deletions spec/ngc/main.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
import { NgModule, Component } from '@angular/core';
import { platformDynamicServer } from '@angular/platform-server';
import { BrowserModule } from '@angular/platform-browser';
import { Store, StoreModule } from '../../';
import { Store, StoreModule, combineReducers } from '../../';
import { counterReducer, INCREMENT, DECREMENT } from '../fixtures/counter';
import { todos } from '../fixtures/todos';
import { Observable } from 'rxjs/Observable';

@Component({
selector: 'ngc-spec-child-component',
template: `
`
})
export class NgcSpecChildComponent { }

@NgModule({
imports: [
StoreModule.forFeature('feature', { todos: todos })
],
declarations: [
NgcSpecChildComponent,
],
exports: [
NgcSpecChildComponent,
]
})
export class FeatureModule { }

export interface AppState {
count: number;
}

export const storeConfig = {count: counterReducer};
export const initialState = { count : 0 };

@Component({
selector: 'ngc-spec-component',
template: `
<button (click)="increment()"> + </button>
<span> Count : {{ count | async }} </span>
<button (click)="decrement()"> + </button>
<ngc-spec-child-component></ngc-spec-child-component>
`
})
export class NgcSpecComponent {
Expand All @@ -36,7 +57,11 @@ export class NgcSpecComponent {
@NgModule({
imports: [
BrowserModule,
StoreModule.provideStore(storeConfig, initialState)
StoreModule.forRoot({ count: counterReducer }, {
initialState: { count : 0 },
reducerFactory: combineReducers
}),
FeatureModule
],
declarations: [NgcSpecComponent],
bootstrap: [NgcSpecComponent]
Expand Down
Loading

0 comments on commit 835bdb5

Please sign in to comment.