├── dist ├── main.d.ts ├── index.html ├── appName.d.ts ├── counter.d.ts ├── appName.js.map ├── counter.js.map ├── appName.js ├── rxjs-redux.js.map ├── counter.js ├── rxjs-redux.d.ts ├── main.js.map ├── rxjs-redux.js └── main.js ├── .gitignore ├── tsconfig.json ├── webpack.config.js ├── package.json ├── src ├── appName.ts ├── counter.ts ├── rxjs-redux.ts └── main.ts ├── LICENSE └── README.md /dist/main.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.tmp 2 | src/*.js 3 | src/*.d.ts 4 | src/*.js.map 5 | webpack.config.js 6 | .DS_Store 7 | node_modules/ 8 | typings/ 9 | node_modules/ 10 | npm-debug.log 11 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "declaration": true, 5 | "outDir": "dist/", 6 | "declarationDir": "dist/", 7 | "sourceMap": true, 8 | "lib": [ "es5", "es2015.promise", "dom" ] 9 | }, 10 | "include": [ 11 | "src/**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /dist/appName.d.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from "rxjs/Subject"; 2 | import { Observable } from "rxjs/Observable"; 3 | import "rxjs/add/operator/map"; 4 | export interface NameState { 5 | appName: string; 6 | } 7 | export declare const nameActions: { 8 | appName: Subject; 9 | }; 10 | export declare const nameMutator: Observable<(state: NameState) => NameState>; 11 | -------------------------------------------------------------------------------- /dist/counter.d.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from "rxjs/Subject"; 2 | import { Observable } from "rxjs/Observable"; 3 | import "rxjs/add/operator/map"; 4 | export interface CounterState { 5 | counter: number; 6 | } 7 | export declare const counterActions: { 8 | increment: Subject; 9 | decrement: Subject; 10 | }; 11 | export declare const counterMutator: Observable<(state: CounterState) => CounterState>; 12 | -------------------------------------------------------------------------------- /dist/appName.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"appName.js","sourceRoot":"","sources":["../src/appName.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,wCAAuC;AAEvC,iCAA+B;AAE/B,2CAAmD;AAMnD,yEAAyE;AAC5D,QAAA,WAAW,GAAG;IACvB,OAAO,EAAE,IAAI,iBAAO,EAAU;CACjC,CAAC;AAEF,wEAAwE;AACxE,oBAAoB;AACP,QAAA,WAAW,GAAG,gCAAmB,CAC1C,mBAAW,CAAC,OAAO,CAAC,GAAG,CAAC,UAAA,OAAO;IAC3B,MAAM,CAAC,UAAC,KAAgB,IAAK,OAAA,cAAM,KAAK,IAAE,OAAO,EAAE,OAAO,IAAG,EAAhC,CAAgC,CAAC;AAClE,CAAC,CAAC,CACL,CAAC"} -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | target: 'web', 5 | entry: "main.js", 6 | resolve: { 7 | root: path.resolve('./dist/'), 8 | extensions: ['', '.json', '.js'] 9 | }, 10 | output: { 11 | path: __dirname + '/dist/bundle/', 12 | filename: 'rxjs-redux.js', 13 | 14 | // will be the global variable that the autobahn.js file exports to 15 | library: 'RxJSRedux', 16 | libraryTarget: 'umd' 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-pattern-with-rx", 3 | "version": "1.0.0", 4 | "description": "", 5 | "files": [ 6 | "dist/" 7 | ], 8 | "main": "dist/rxjs-redux.js", 9 | "typings": "dist/rxjs-redux.d.ts", 10 | "scripts": { 11 | "build": "tsc && webpack", 12 | "serve": "http-server -o -p 31728 dist/" 13 | }, 14 | "author": "Timo Dörr ", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "http-server": "^0.9.0", 18 | "rxjs": "^5.0.3", 19 | "typescript": "^2.1.5", 20 | "webpack": "^1.13.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /dist/counter.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"counter.js","sourceRoot":"","sources":["../src/counter.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,wCAAuC;AAEvC,iCAA+B;AAE/B,2CAAmD;AAQnD,yEAAyE;AAC5D,QAAA,cAAc,GAAG;IAC1B,SAAS,EAAE,IAAI,iBAAO,EAAU;IAChC,SAAS,EAAE,IAAI,iBAAO,EAAU;CACnC,CAAC;AAEF,+EAA+E;AAC/E,oBAAoB;AACP,QAAA,cAAc,GAAG,gCAAmB,CAC7C,sBAAc,CAAC,SAAS,CAAC,GAAG,CAAC,UAAC,CAAK;IAAL,kBAAA,EAAA,KAAK;IAC/B,MAAM,CAAC,UAAC,KAAmB,IAAK,OAAA,cAAM,KAAK,IAAE,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,CAAC,IAAG,EAA1C,CAA0C,CAAA;AAC9E,CAAC,CAAC,EACF,sBAAc,CAAC,SAAS,CAAC,GAAG,CAAC,UAAC,CAAK;IAAL,kBAAA,EAAA,KAAK;IAC/B,MAAM,CAAC,UAAC,KAAmB,IAAK,OAAA,cAAM,KAAK,IAAE,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,CAAC,IAAG,EAA1C,CAA0C,CAAC;AAC/E,CAAC,CAAC,CACL,CAAC"} -------------------------------------------------------------------------------- /src/appName.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from "rxjs/Subject"; 2 | import { Observable } from "rxjs/Observable"; 3 | import "rxjs/add/operator/map"; 4 | 5 | import { createStateMutators } from "./rxjs-redux"; 6 | 7 | export interface NameState { 8 | appName: string; 9 | } 10 | 11 | // actions are actually just RxJS Subjects with explicit type information 12 | export const nameActions = { 13 | appName: new Subject(), 14 | }; 15 | 16 | // the reducer is an observable of reducer functions invoked whenever an 17 | // action is emitted 18 | export const nameMutator = createStateMutators( 19 | nameActions.appName.map(newName => { 20 | return (state: NameState) => ({ ...state, appName: newName }); 21 | }) 22 | ); 23 | -------------------------------------------------------------------------------- /dist/appName.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __assign = (this && this.__assign) || Object.assign || function(t) { 3 | for (var s, i = 1, n = arguments.length; i < n; i++) { 4 | s = arguments[i]; 5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 6 | t[p] = s[p]; 7 | } 8 | return t; 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | var Subject_1 = require("rxjs/Subject"); 12 | require("rxjs/add/operator/map"); 13 | var rxjs_redux_1 = require("./rxjs-redux"); 14 | // actions are actually just RxJS Subjects with explicit type information 15 | exports.nameActions = { 16 | appName: new Subject_1.Subject(), 17 | }; 18 | // the reducer is an observable of reducer functions invoked whenever an 19 | // action is emitted 20 | exports.nameMutator = rxjs_redux_1.createStateMutators(exports.nameActions.appName.map(function (newName) { 21 | return function (state) { return (__assign({}, state, { appName: newName })); }; 22 | })); 23 | //# sourceMappingURL=appName.js.map -------------------------------------------------------------------------------- /src/counter.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from "rxjs/Subject"; 2 | import { Observable } from "rxjs/Observable"; 3 | import "rxjs/add/operator/map"; 4 | 5 | import { createStateMutators } from "./rxjs-redux"; 6 | 7 | // note how we define the parts of the State used in this file in a separate interface 8 | // (all state interfaces will be merge later to form the global state). 9 | export interface CounterState { 10 | counter: number; 11 | } 12 | 13 | // actions are actually just RxJS Subjects with explicit type information 14 | export const counterActions = { 15 | increment: new Subject(), 16 | decrement: new Subject() 17 | }; 18 | 19 | // the state mutators is an observable of mutator functions invoked whenever an 20 | // action is emitted 21 | export const counterMutator = createStateMutators( 22 | counterActions.increment.map((n = 1) => { 23 | return (state: CounterState) => ({ ...state, counter: state.counter + n }) 24 | }), 25 | counterActions.decrement.map((n = 1) => { 26 | return (state: CounterState) => ({ ...state, counter: state.counter - n }); 27 | }) 28 | ); 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Timo Dörr 2 | Copyright (c) 2015-2016 Michael Zalecki 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /dist/rxjs-redux.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"rxjs-redux.js","sourceRoot":"","sources":["../src/rxjs-redux.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8BAA4D;AAC5D,qCAAmC;AACnC,kCAAgC;AAChC,2CAAyC;AAOzC;;;;;;GAMG;AACH;IAA+B,0BAAU;IAAzC;;IAA2C,CAAC;IAAD,aAAC;AAAD,CAAC,AAA5C,CAA+B,YAAO,GAAM;AAA/B,wBAAM;AAAyB,CAAC;AAG7C;;;;GAIG;AACH,qBAA+B,aAA2C,EAAE,YAAe;IACvF,MAAM,CAAC,aAAa;SACf,IAAI,CAAC,UAAC,KAAQ,EAAE,OAAyB,IAAK,OAAA,OAAO,CAAC,KAAK,CAAC,EAAd,CAAc,EAAE,YAAY,CAAC;SAI3E,aAAa,CAAC,CAAC,CAAC;SAChB,QAAQ,EAAE,CAAC;AACpB,CAAC;AARD,kCAQC;AAED;IAAuC,qBAA8C;SAA9C,UAA8C,EAA9C,qBAA8C,EAA9C,IAA8C;QAA9C,gCAA8C;;IACjF,MAAM,CAAC,eAAU,CAAC,KAAK,CAAC,KAAK,CAAC,eAAU,EAAE,WAAW,CAAC,CAAC;AAC3D,CAAC;AAFD,kDAEC;AAOD;IAII,eAAY,YAAe;QAF3B,kBAAa,GAAG,IAAI,YAAO,EAAoB,CAAC;QAG5C,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAC/D,CAAC;IAED,0BAAU,GAAV,UAAc,MAAqB,EAAE,OAAsB;QAA3D,iBAGC;QAFG,IAAM,eAAe,GAAyB,UAAC,OAAO,IAAK,OAAA,UAAC,KAAK,IAAK,OAAA,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,EAAvB,CAAuB,EAAlC,CAAkC,CAAC;QAC9F,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,UAAA,EAAE,IAAI,OAAA,KAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,EAA3B,CAA2B,CAAC,CAAC;IACpF,CAAC;IAED,sBAAM,GAAN,UAAU,EAAmB;QACzB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IACL,YAAC;AAAD,CAAC,AAhBD,IAgBC;AAhBY,sBAAK"} -------------------------------------------------------------------------------- /dist/counter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __assign = (this && this.__assign) || Object.assign || function(t) { 3 | for (var s, i = 1, n = arguments.length; i < n; i++) { 4 | s = arguments[i]; 5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 6 | t[p] = s[p]; 7 | } 8 | return t; 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | var Subject_1 = require("rxjs/Subject"); 12 | require("rxjs/add/operator/map"); 13 | var rxjs_redux_1 = require("./rxjs-redux"); 14 | // actions are actually just RxJS Subjects with explicit type information 15 | exports.counterActions = { 16 | increment: new Subject_1.Subject(), 17 | decrement: new Subject_1.Subject() 18 | }; 19 | // the state mutators is an observable of mutator functions invoked whenever an 20 | // action is emitted 21 | exports.counterMutator = rxjs_redux_1.createStateMutators(exports.counterActions.increment.map(function (n) { 22 | if (n === void 0) { n = 1; } 23 | return function (state) { return (__assign({}, state, { counter: state.counter + n })); }; 24 | }), exports.counterActions.decrement.map(function (n) { 25 | if (n === void 0) { n = 1; } 26 | return function (state) { return (__assign({}, state, { counter: state.counter - n })); }; 27 | })); 28 | //# sourceMappingURL=counter.js.map -------------------------------------------------------------------------------- /dist/rxjs-redux.d.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Subject, Subscription } from "rxjs/Rx"; 2 | import "rxjs/add/observable/merge"; 3 | import "rxjs/add/operator/scan"; 4 | import "rxjs/add/operator/publishReplay"; 5 | export declare type StateMutation = (state: S) => S; 6 | /** 7 | * Helper type so that you can create more expressive action types (which are just subjects) 8 | * @example 9 | * var incrementAction = new Action(); 10 | * var addAction = new Action(); 11 | * var genericAction = new Action(); 12 | */ 13 | export declare class Action

extends Subject

{ 14 | } 15 | /** 16 | * Creates an Observable of state which will emit everytime the state is mutated. 17 | * @param stateMutators 18 | * @param initialState 19 | */ 20 | export declare function createState(stateMutators: Observable>, initialState: S): Observable; 21 | export declare function createStateMutators(...observables: Observable>[]): Observable>; 22 | export declare type Reducer = (state: S, actionPayload: P) => S; 23 | export declare type GenericReducer = (payload: P) => StateMutation; 24 | export declare class Store { 25 | state: Observable; 26 | stateMutators: Subject>; 27 | constructor(initialState: S); 28 | addReducer

(action: Observable

, reducer: Reducer): Subscription; 29 | select(fn: (state: S) => T): Observable; 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UPDATE: I have created a standalone and more advanced project on GitHub with ready-to-use code on GitHub: . All further development happens there. Please use this repo in the future. This existing repo is kept for educational purposes in the meantime. 2 | 3 | Redux pattern implemented in RxJS 4 | ===== 5 | 6 | This is a sample project / tutorial that demonstrates how to implement the Redux pattern (i.e. for usage with React) in TypeScript using only [RxJS v5](https://github.com/ReactiveX/rxjs) inspired by the blog post by [Michael Zalecki](http://michalzalecki.com/use-rxjs-with-react/). The code is very simple and can be used in production (you only need to include the `rxjs-redux.ts` file which has less than 20 lines of code). 7 | 8 | Advantages 9 | ----- 10 | 11 | * __Typed actions by default__: No need for [FSA](https://github.com/acdlite/flux-standard-action) or magic string constants; all actions are implemented using the `.next()` and `.error()` methods on typed `Rx.Subject`s. 12 | * No need for endless `switch` statements 13 | * Almost __[no boilerplate code](https://github.com/Dynalon/redux-pattern-with-rx/blob/master/src/rxjs-redux.ts)__, no dependencies other than RxJS using only the `map`, `merge`, `scan` and `publishReplay` operators. 14 | 15 | How to build 16 | ---- 17 | 18 | 1. Clone this repo 19 | 1. `npm install` 20 | 1. `npm run build` 21 | 1. `npm run serve` to start a http-server in `dist/` for testing 22 | 23 | Production use 24 | ---- 25 | 26 | You can use this code in production, all you need is to copy the file [rxjs-redux.ts](https://github.com/Dynalon/redux-pattern-with-rx/blob/master/src/rxjs-redux.ts) into your project (or use the compiled .js alternative if you use plain JavaScript). 27 | 28 | Notes 29 | ---- 30 | 31 | * Webpack is used as a module bundler, but the pattern will also work using other module bundlers or typescript module outputs. 32 | -------------------------------------------------------------------------------- /dist/main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/rxjs-redux.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Subject, Subscription } from "rxjs/Rx"; 2 | import "rxjs/add/observable/merge"; 3 | import "rxjs/add/operator/scan"; 4 | import "rxjs/add/operator/publishReplay"; 5 | 6 | // THIS FILE CONTAINS THE BOILERPLATE CODE YOU NEED TO COPY/REFERENCE 7 | 8 | /// STATIC INTERFACES - WIRE UP ACTIONS/STATEMUTATORS (~REDUCERS) STATICALLY (=REDUX STYLE) 9 | export type StateMutation = (state: S) => S; 10 | 11 | /** 12 | * Helper type so that you can create more expressive action types (which are just subjects) 13 | * @example 14 | * var incrementAction = new Action(); 15 | * var addAction = new Action(); 16 | * var genericAction = new Action(); 17 | */ 18 | export class Action

extends Subject

{ }; 19 | 20 | 21 | /** 22 | * Creates an Observable of state which will emit everytime the state is mutated. 23 | * @param stateMutators 24 | * @param initialState 25 | */ 26 | export function createState(stateMutators: Observable>, initialState: S): Observable { 27 | return stateMutators 28 | .scan((state: S, reducer: StateMutation) => reducer(state), initialState) 29 | 30 | // these two lines make our observable hot and have it emit the last state 31 | // upon subscription 32 | .publishReplay(1) 33 | .refCount(); 34 | } 35 | 36 | export function createStateMutators(...observables: Observable>[]): Observable> { 37 | return Observable.merge.apply(Observable, observables); 38 | } 39 | 40 | /// DYNAMIC INTERFACE - ABLE TO WIRE UP ACTIONS/REDUCERS AT DYNAMICALLY AT RUNTIME 41 | 42 | export type Reducer = (state: S, actionPayload: P) => S; 43 | export type GenericReducer = (payload: P) => StateMutation; 44 | 45 | export class Store { 46 | state: Observable; 47 | stateMutators = new Subject>(); 48 | 49 | constructor(initialState: S) { 50 | this.state = createState(this.stateMutators, initialState); 51 | } 52 | 53 | addReducer

(action: Observable

, reducer: Reducer): Subscription { 54 | const combinedReducer: GenericReducer = (payload) => (state) => reducer(state, payload); 55 | return action.map(combinedReducer).subscribe(cr => this.stateMutators.next(cr)); 56 | } 57 | 58 | select(fn: (state: S) => T) { 59 | return this.state.map(fn); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /dist/rxjs-redux.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __extends = (this && this.__extends) || (function () { 3 | var extendStatics = Object.setPrototypeOf || 4 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 5 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 6 | return function (d, b) { 7 | extendStatics(d, b); 8 | function __() { this.constructor = d; } 9 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 10 | }; 11 | })(); 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | var Rx_1 = require("rxjs/Rx"); 14 | require("rxjs/add/observable/merge"); 15 | require("rxjs/add/operator/scan"); 16 | require("rxjs/add/operator/publishReplay"); 17 | /** 18 | * Helper type so that you can create more expressive action types (which are just subjects) 19 | * @example 20 | * var incrementAction = new Action(); 21 | * var addAction = new Action(); 22 | * var genericAction = new Action(); 23 | */ 24 | var Action = (function (_super) { 25 | __extends(Action, _super); 26 | function Action() { 27 | return _super !== null && _super.apply(this, arguments) || this; 28 | } 29 | return Action; 30 | }(Rx_1.Subject)); 31 | exports.Action = Action; 32 | ; 33 | /** 34 | * Creates an Observable of state which will emit everytime the state is mutated. 35 | * @param stateMutators 36 | * @param initialState 37 | */ 38 | function createState(stateMutators, initialState) { 39 | return stateMutators 40 | .scan(function (state, reducer) { return reducer(state); }, initialState) 41 | .publishReplay(1) 42 | .refCount(); 43 | } 44 | exports.createState = createState; 45 | function createStateMutators() { 46 | var observables = []; 47 | for (var _i = 0; _i < arguments.length; _i++) { 48 | observables[_i] = arguments[_i]; 49 | } 50 | return Rx_1.Observable.merge.apply(Rx_1.Observable, observables); 51 | } 52 | exports.createStateMutators = createStateMutators; 53 | var Store = (function () { 54 | function Store(initialState) { 55 | this.stateMutators = new Rx_1.Subject(); 56 | this.state = createState(this.stateMutators, initialState); 57 | } 58 | Store.prototype.addReducer = function (action, reducer) { 59 | var _this = this; 60 | var combinedReducer = function (payload) { return function (state) { return reducer(state, payload); }; }; 61 | return action.map(combinedReducer).subscribe(function (cr) { return _this.stateMutators.next(cr); }); 62 | }; 63 | Store.prototype.select = function (fn) { 64 | return this.state.map(fn); 65 | }; 66 | return Store; 67 | }()); 68 | exports.Store = Store; 69 | //# sourceMappingURL=rxjs-redux.js.map -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import {  Observable } from "rxjs/Observable"; 2 | 3 | // imports for static example (Redux like) 4 | import { createState, createStateMutators, StateMutation } from "./rxjs-redux"; 5 | import { counterActions, counterMutator, CounterState } from "./counter"; 6 | import { nameActions, nameMutator, NameState } from "./appName"; 7 | 8 | // imports for dynamic example 9 | import { Action, Reducer, Store } from "./rxjs-redux"; 10 | 11 | // our application state as a strongly typed interface 12 | interface IAppState extends CounterState, NameState /* otherState1, otherState2,...*/ { } 13 | 14 | /// --- STATIC EXAMPLE 15 | 16 | // best practice is to use a plain object as State instance to allow serialization etc. 17 | const initialState: IAppState = { 18 | counter: 0, 19 | appName: "Initial Name" 20 | }; 21 | 22 | // put together all reducers just as with createReducer in Redux 23 | const stateMutators = createStateMutators( 24 | counterMutator, 25 | nameMutator 26 | /* ... 27 | myOtherReducer1, 28 | myOtherReducer2 29 | */ 30 | ); 31 | 32 | // the state observable replaces the store known from Redux 33 | const state: Observable = createState(stateMutators, initialState); 34 | 35 | // helper function to print state changes 36 | function printStateChange(newState) { 37 | const stateJson = document.createTextNode(JSON.stringify(newState)); 38 | document.querySelector("body").appendChild(stateJson); 39 | // add a line break after each state update 40 | const breakLine = document.createElement("br"); 41 | document.querySelector("body").appendChild(breakLine); 42 | } 43 | // output every state change 44 | state.subscribe(printStateChange); 45 | 46 | // dispatch some actions 47 | counterActions.increment.next(); 48 | counterActions.increment.next(); 49 | nameActions.appName.next("Foo"); 50 | counterActions.decrement.next(5); 51 | counterActions.increment.next(8); 52 | nameActions.appName.next("Bar"); 53 | 54 | 55 | /// --- DYNAMIC EXAMPLE USING AN EXPLICIT STORE 56 | const store = new Store(initialState); 57 | 58 | const incrementAction = new Action(); 59 | const decrementAction = new Action(); 60 | 61 | const incrementReducer: Reducer = (state, n = 1) => ({ ...state, counter: state.counter + n }); 62 | const decrementReducer = (state: IAppState, n: number = 1) => ({ ...state, counter: state.counter - n }); 63 | 64 | store.select(s => s.counter).subscribe(printStateChange); 65 | 66 | const incSubscription = store.addReducer(incrementAction, incrementReducer); 67 | 68 | incrementAction.next(1); 69 | incrementAction.next(1); 70 | 71 | // add a new reducer during runtime 72 | const decSubscription = store.addReducer(decrementAction, decrementReducer); 73 | decrementAction.next(1); 74 | decrementAction.next(1); 75 | 76 | // remove reducer during runtime to deactivate it 77 | decSubscription.unsubscribe(); 78 | 79 | // these have no effect now 80 | decrementAction.next(1); 81 | decrementAction.next(1); 82 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __assign = (this && this.__assign) || Object.assign || function(t) { 3 | for (var s, i = 1, n = arguments.length; i < n; i++) { 4 | s = arguments[i]; 5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 6 | t[p] = s[p]; 7 | } 8 | return t; 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | // imports for static example (Redux like) 12 | var rxjs_redux_1 = require("./rxjs-redux"); 13 | var counter_1 = require("./counter"); 14 | var appName_1 = require("./appName"); 15 | // imports for dynamic example 16 | var rxjs_redux_2 = require("./rxjs-redux"); 17 | /// --- STATIC EXAMPLE 18 | // best practice is to use a plain object as State instance to allow serialization etc. 19 | var initialState = { 20 | counter: 0, 21 | appName: "Initial Name" 22 | }; 23 | // put together all reducers just as with createReducer in Redux 24 | var stateMutators = rxjs_redux_1.createStateMutators(counter_1.counterMutator, appName_1.nameMutator 25 | /* ... 26 | myOtherReducer1, 27 | myOtherReducer2 28 | */ 29 | ); 30 | // the state observable replaces the store known from Redux 31 | var state = rxjs_redux_1.createState(stateMutators, initialState); 32 | // helper function to print state changes 33 | function printStateChange(newState) { 34 | var stateJson = document.createTextNode(JSON.stringify(newState)); 35 | document.querySelector("body").appendChild(stateJson); 36 | // add a line break after each state update 37 | var breakLine = document.createElement("br"); 38 | document.querySelector("body").appendChild(breakLine); 39 | } 40 | // output every state change 41 | state.subscribe(printStateChange); 42 | // dispatch some actions 43 | counter_1.counterActions.increment.next(); 44 | counter_1.counterActions.increment.next(); 45 | appName_1.nameActions.appName.next("Foo"); 46 | counter_1.counterActions.decrement.next(5); 47 | counter_1.counterActions.increment.next(8); 48 | appName_1.nameActions.appName.next("Bar"); 49 | /// --- DYNAMIC EXAMPLE USING AN EXPLICIT STORE 50 | var store = new rxjs_redux_2.Store(initialState); 51 | var incrementAction = new rxjs_redux_2.Action(); 52 | var decrementAction = new rxjs_redux_2.Action(); 53 | var incrementReducer = function (state, n) { 54 | if (n === void 0) { n = 1; } 55 | return (__assign({}, state, { counter: state.counter + n })); 56 | }; 57 | var decrementReducer = function (state, n) { 58 | if (n === void 0) { n = 1; } 59 | return (__assign({}, state, { counter: state.counter - n })); 60 | }; 61 | store.select(function (s) { return s.counter; }).subscribe(printStateChange); 62 | var incSubscription = store.addReducer(incrementAction, incrementReducer); 63 | incrementAction.next(1); 64 | incrementAction.next(1); 65 | // add a new reducer during runtime 66 | var decSubscription = store.addReducer(decrementAction, decrementReducer); 67 | decrementAction.next(1); 68 | decrementAction.next(1); 69 | // remove reducer during runtime to deactivate it 70 | decSubscription.unsubscribe(); 71 | // these have no effect now 72 | decrementAction.next(1); 73 | decrementAction.next(1); 74 | //# sourceMappingURL=main.js.map --------------------------------------------------------------------------------