├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── demo ├── .editorconfig ├── .gitignore ├── README.md ├── angular-cli.json ├── e2e │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── screen │ │ │ ├── index.ts │ │ │ ├── todo-footer.component.ts │ │ │ ├── todo-header.component.ts │ │ │ └── todo-list.component.ts │ │ ├── service │ │ │ ├── index.ts │ │ │ └── todo.service.ts │ │ ├── state │ │ │ ├── action.ts │ │ │ ├── index.ts │ │ │ └── todo.ts │ │ └── store │ │ │ ├── index.ts │ │ │ └── todo.store.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ ├── test.ts │ └── tsconfig.json └── tslint.json ├── dist ├── action.d.ts ├── action.js ├── action.js.map ├── action.metadata.json ├── bind-action.d.ts ├── bind-action.js ├── bind-action.js.map ├── bind-action.metadata.json ├── bind-data.d.ts ├── bind-data.js ├── bind-data.js.map ├── bind-data.metadata.json ├── constance.d.ts ├── constance.js ├── constance.js.map ├── constance.metadata.json ├── data-observer.d.ts ├── data-observer.js ├── data-observer.js.map ├── data-observer.metadata.json ├── index.d.ts ├── index.js ├── index.js.map ├── index.metadata.json ├── init.d.ts ├── init.js ├── init.js.map ├── init.metadata.json ├── observers.d.ts ├── observers.js ├── observers.js.map ├── observers.metadata.json ├── replaceable-state.d.ts ├── replaceable-state.js ├── replaceable-state.js.map ├── replaceable-state.metadata.json ├── state-selector.d.ts ├── state-selector.js ├── state-selector.js.map ├── state-selector.metadata.json ├── state.d.ts ├── state.js ├── state.js.map ├── state.metadata.json ├── store.d.ts ├── store.js ├── store.js.map └── store.metadata.json ├── package.json ├── src ├── action.ts ├── bind-action.ts ├── bind-data.ts ├── constance.ts ├── data-observer.ts ├── index.ts ├── init.ts ├── observers.ts ├── replaceable-state.ts ├── state-selector.ts ├── state.ts └── store.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | compiled/ 3 | *.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | compiled/ 2 | demo/ 3 | node_modules/ 4 | src/ 5 | *.log 6 | tsconfig.json 7 | tslint.json 8 | .gitignore -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.1.4 2 | 3 | * deprecate - this project is migrated to [StateX](https://github.com/rintoj/statex) 4 | 5 | # v1.1.3 6 | 7 | * Revert `@BindData` and `DataObserver` 8 | * Update `README` - Explain hotLoad 9 | 10 | # v1.1.2 11 | * refactor internal modules and functions 12 | * add `initialize()` function 13 | * add hot reload 14 | 15 | # v1.1.1 16 | 17 | * API Change: (non-breaking) Reducer function now return either of Observable, Promise or Application State as an object itself 18 | * Optimization: Observable and observer uses only the required operator, reducing the overall size of the library 19 | 20 | # v1.1.0 21 | 22 | * Add `DataObserver` class to prevent angular compiler from removing `ngOnInit` and `ngOnDestroy` functions 23 | 24 | # v1.0.0 25 | 26 | ## Breaking Change: This module is now AOT compatible. 27 | 28 | The selectorFunction to `@BindData()` decorator must be an exported standalone function, to avoid the below AOT error: 29 | 30 | ```bash 31 | ERROR in Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function 32 | ``` 33 | 34 | Therefore refactor your code from: 35 | 36 | ```ts 37 | @Component({ 38 | .... 39 | }) 40 | export class TodoComponent { 41 | 42 | @BindData((state: State) => state.todos) 43 | todos: Todo[] 44 | } 45 | ``` 46 | 47 | to: 48 | ```ts 49 | export function selectTodos(state: State) { 50 | return state.todos; 51 | } 52 | 53 | @Component({ 54 | .... 55 | }) 56 | export class TodoComponent { 57 | 58 | @BindData(selectTodos) 59 | todos: Todo[] 60 | } 61 | ``` 62 | 63 | ## Other fixes 64 | 65 | * Bug fix: Context was missing when `ngOnInit` and `ngOnDestroy` functions were called by reflux 66 | 67 | # v0.2.0 68 | 69 | * Bug fix: Added `bindImmediate` flag to `BindData` decorator to enable it to be used with non-components. 70 | 71 | # v0.1.0 72 | 73 | * Bug fix: After compiling a production build using `ng build --prod`, actions subscribers received incorrect action. 74 | 75 | # v0.0.3 76 | 77 | * Updating documentation 78 | 79 | # v0.0.2 80 | 81 | * Initial version 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Rinto Jose (rintoj) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # angular-reflux 3 | This module will help you implement a unidirectional data flow (Flux architecture) for an Angular 2 (or above) application in an elegant way. This is inspired by [refluxjs](https://github.com/reflux/refluxjs) and [redux](http://redux.js.org/). 4 | 5 | ## This project is migrated to [StateX](https://github.com/rintoj/statex) 6 | **This project is no longer maintained. To know how to migrate to StateX [read this](https://github.com/rintoj/statex#migrating-from-angular-reflux)** 7 | 8 | ## Update (24 Mar 2017) 9 | 10 | Since version `1.0.0`, this module is compatible with Angular's Ahead-of-Time Compilation (AOT). I have updated all examples to reflect this change. If you are here for the first time, never mind, continue reading. But if you have had used this module before and you want to refactor your code to make it AOT compatible check [Making Your Code AOT Compatible](#making-your-code-aot-compatible) section. 11 | 12 | ## About 13 | 14 | Flux is an architecture for unidirectional data flow. By forcing the data to flow in a single direction, Flux makes it easy to reason *how data-changes will affect the application* depending on what actions have been issued. The components themselves may only update application-wide data by executing an action to avoid double maintenance nightmares. 15 | 16 | Inspired by redux and refluxjs, I wrote this library to help you implement a unidirectional data flow in 5 simple steps. 17 | 18 | ## Install 19 | 20 | ``` 21 | npm install angular-reflux seamless-immutable --save 22 | ``` 23 | 24 | ## 5 Simple Steps 25 | 26 | ### 1. Define State 27 | To get the best out of TypeScript, declare an interface that defines the structure of the application-state. 28 | 29 | ```ts 30 | export interface Todo { 31 | id?: string; 32 | title?: string; 33 | completed?: boolean; 34 | } 35 | 36 | export interface State { 37 | todos?: Todo[]; 38 | selectedTodo?: Todo; 39 | } 40 | ``` 41 | 42 | ### 2. Define Action 43 | Define actions as classes with the necessary arguments passed on to the constructor. This way we will benefit from the type checking; never again we will miss-spell an action, miss a required parameter or pass a wrong parameter. Remember to extend the action from `Action` class. This makes your action listenable and dispatch-able. 44 | 45 | ```ts 46 | import { Action } from 'angular-reflux'; 47 | 48 | export class AddTodoAction extends Action { 49 | constructor(public todo: Todo) { super(); } 50 | } 51 | ``` 52 | 53 | ### 3. Create Store & Bind Action 54 | Use `@BindAction` decorator to bind a reducer function with an Action. The second parameter to the reducer function (`addTodo`) is an action (of type `AddTodoAction`); `@BindAction` uses this information to bind the correct action. Also remember to extend this class from `Store`. 55 | 56 | ```ts 57 | import { Injectable } from '@angular/core'; 58 | import { BindAction, Store } from 'angular-reflux'; 59 | import { Observable } from 'rxjs/Observable'; 60 | import { Observer } from 'rxjs/Observer'; 61 | 62 | @Injectable() 63 | export class TodoStore extends Store { 64 | 65 | @BindAction() 66 | addTodo(state: State, action: AddTodoAction): Observable { 67 | return Observable.create((observer: Observer) => { 68 | observer.next({ todos: state.todos.concat([action.todo]) }); 69 | observer.complete(); 70 | }); 71 | } 72 | } 73 | ``` 74 | 75 | Did you notice `@Injectable()`? Well, stores are injectable modules and uses Angular's dependency injection to instantiate. So take care of adding store to `providers` and to inject into `app.component`. Read [Organizing Stores](#organizing-stores) to understand more. 76 | 77 | ### 4. Dispatch Action 78 | 79 | No singleton dispatcher! Instead this module lets every action act as dispatcher by itself. One less dependency to define, inject and maintain. 80 | 81 | ```ts 82 | new AddTodoAction({ id: 'sd2wde', title: 'Sample task' }).dispatch(); 83 | ``` 84 | 85 | ### 5. Consume Data 86 | 87 | Use `@BindData` decorator and a selector function (parameter to the decorator) to get updates from application state. The property gets updated only when the value, returned by the selector function, changes from previous state to the current state. Additionally, just like a map function, you could map the data to another value as you choose. 88 | 89 | We may, at times need to derive additional properties from the data, sometimes using complex calculations. Therefore `@BindData` can be used with functions as well. 90 | 91 | ```ts 92 | import { BindData, DataObserver } from 'angular-reflux'; 93 | 94 | export function selectTodos(state: State) { 95 | return state.todos; 96 | } 97 | 98 | export function computeHasTodos(state: State) { 99 | return state.todos && state.todos.length > 0; 100 | } 101 | 102 | @Component({ 103 | .... 104 | }) 105 | export class TodoListComponent extends DataObserver { 106 | 107 | // mapping a direct value from state 108 | @BindData(selectTodos) 109 | protected todos: Todo[]; 110 | 111 | // mapping a different value from state 112 | @BindData(computeHasTodos) 113 | protected hasTodos: boolean; 114 | 115 | // works with functions to allow complex calculations 116 | @BindData(selectTodos) 117 | protected todosDidChange(todos: Todo[]) { 118 | // your calculations 119 | } 120 | } 121 | ``` 122 | 123 | ## Making Your Code AOT Compatible 124 | 125 | The selector function to `@BindData()` decorator must be an exported standalone function, to avoid the below AOT error: 126 | 127 | ```bash 128 | ERROR in Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function 129 | ``` 130 | 131 | Therefore refactor your code from: 132 | 133 | ```ts 134 | @Component({ 135 | .... 136 | }) 137 | export class TodoComponent { 138 | 139 | @BindData((state: State) => state.todos) 140 | todos: Todo[] 141 | } 142 | ``` 143 | 144 | to: 145 | 146 | ```ts 147 | export function selectTodos(state: State) { 148 | return state.todos; 149 | } 150 | 151 | @Component({ 152 | .... 153 | }) 154 | export class TodoComponent extends DataObserver { 155 | 156 | @BindData(selectTodos) 157 | todos: Todo[] 158 | } 159 | ``` 160 | 161 | Remember to extend your class from `DataObserver`. It is essential to instruct Angular Compiler to keep `ngOnInit` and `ngOnDestroy` life cycle events, which can only be achieved by implementing `OnInit` and `OnDestroy` interfaces. Because of this constraint all components using `@BindData` must extend itself from `DataObserver` which sets `ngOnInit` and `ngOnDestroy` properly; `@BindData` inturn depends on these functions. However if you would like to extend your class from your-own base class you may do so after making sure `ngOnInit` and `ngOnDestroy` are implemented properly. 162 | 163 | ## Reducer Functions & Async Tasks 164 | 165 | Reducer functions can return either of the following 166 | 167 | * A portion of the application state as plain object 168 | ```ts 169 | @BindAction() 170 | add(state: State, action: AddTodoAction): State { 171 | return { 172 | todos: (state.todos || []).concat(action.todo) 173 | } 174 | } 175 | ``` 176 | 177 | * A portion of the application state wrapped in Promise, if it needs to perform an async task. 178 | ```ts 179 | @BindAction() 180 | add(state: State, action: AddTodoAction): Promise { 181 | return new Promise((resolve, reject) => { 182 | asyncTask().then(() => { 183 | resolve({ 184 | todos: (state.todos || []).concat(action.todo) 185 | }) 186 | }) 187 | }) 188 | } 189 | ``` 190 | 191 | * A portion of the application state wrapped in Observables, if the application state needs update multiple times over a period of time, all when handling an action. For example, you have to show loader before starting the process, and hide loader after you have done processing, you may use this. 192 | ```ts 193 | @BindAction() 194 | add(state: State, action: AddTodoAction): Observable { 195 | return Observable.create((observer: Observer) => { 196 | observer.next({ showLoader: true }) 197 | asyncTask().then(() => { 198 | observer.next({ 199 | todos: (state.todos || []).concat(action.todo), 200 | showLoader: false 201 | }) 202 | observer.complete() 203 | }) 204 | }) 205 | } 206 | ``` 207 | 208 | ## Initializing State & Enabling HotLoad 209 | 210 | You can initialize the app state using the following code. 211 | 212 | ```ts 213 | ... 214 | import { INITIAL_STATE } from './../state' 215 | import { environment } from '../environments/environment' 216 | import { initialize } from 'angular-reflux' 217 | 218 | initialize(INITIAL_STATE, { 219 | hotLoad: !environment.production, 220 | domain: 'my-app' 221 | }) 222 | 223 | @NgModule({ 224 | .... 225 | bootstrap: [AppComponent] 226 | }) 227 | export class AppModule { } 228 | ``` 229 | 230 | If you set `hotLoad` to true, every change to the state is preserved in localStorage and re-initialized upon refresh. If a state exists in localStorage `INITIAL_STATE` will be ignored. This is very useful for development builds because developers can return to the same screen after every refresh. Remember the screens must written to react to state (reactive UI) in-order to achieve this. `domain` is an optional string to uniquely identify your application. 231 | 232 | ## Immutable Application State 233 | To take advantage of Angular 2’s change detection strategy — OnPush — we need to ensure that the state is indeed immutable. This module uses [seamless-immutable](https://github.com/rtfeldman/seamless-immutable) for immutability. 234 | 235 | Since application state is immutable, the reducer functions will not be able to update state; any attempt to update the state will result in error. Therefore a reducer function should either return a portion of the state that needs change (recommended) or a new application state wrapped in `ReplaceableState`, instead. 236 | 237 | ```ts 238 | export class TodoStore extends Store { 239 | 240 | @BindAction() 241 | selectTodo(state: State, action: SelectTodoAction): Observable { 242 | return Observable.create((observer: Observer) => { 243 | 244 | // returns only the changes 245 | observer.next({ 246 | selectedTodo: action.todo 247 | }); 248 | 249 | observer.complete(); 250 | }); 251 | } 252 | 253 | @BindAction() 254 | resetTodos(state: State, action: ResetTodosAction): Observable { 255 | return Observable.create((observer: Observer) => { 256 | 257 | // returns the entire state (use with CAUTION) 258 | observer.next(new ReplaceableState({ 259 | todos: [], 260 | selectedTodo: undefined 261 | })); 262 | 263 | observer.complete(); 264 | }); 265 | } 266 | } 267 | ``` 268 | 269 | ## Organizing Stores 270 | 271 | Store must be injectable, so add `@Injectable`. Create `STORES` array and a class `Stores` (again injectable) to maintain stores. When you create a new store remember to, inject to the `Stores`'s constructor and add it to the `STORES` array. 272 | 273 | ```ts 274 | import { Injectable } from '@angular/core'; 275 | import { TodoStore } from './todo.store'; 276 | 277 | @Injectable() 278 | export class Stores { 279 | constructor( private todoStore: TodoStore) { } 280 | } 281 | 282 | export const STORES = [ 283 | Stores, 284 | TodoStore 285 | ]; 286 | ``` 287 | 288 | Add `STORES` to the `providers` in `app.module.ts`. 289 | 290 | ```ts 291 | import { STORES } from './store/todo.store'; 292 | .... 293 | 294 | @NgModule({ 295 | .... 296 | providers: [ 297 | ...STORES, 298 | ... 299 | ], 300 | bootstrap: [AppComponent] 301 | }) 302 | export class AppModule { } 303 | 304 | ``` 305 | 306 | And finally, inject `Stores` into your root component (`app.component.ts`) 307 | 308 | ```ts 309 | @Component({ 310 | .... 311 | }) 312 | export class AppComponent { 313 | 314 | constructor(private stores: STORES) { } 315 | 316 | .... 317 | } 318 | ``` 319 | 320 | ## Sample Code 321 | 322 | Sample code is right [here](https://github.com/rintoj/angular-reflux-starter). You can clone my repo to get started with angular2 project integrated with this module. 323 | 324 | ```sh 325 | git clone https://github.com/rintoj/angular-reflux-starter 326 | ``` 327 | 328 | ### Hope this module is helpful to you. Please make sure to checkout my other [projects](https://github.com/rintoj) and [articles](https://medium.com/@rintoj). Enjoy coding! 329 | 330 | ## Contributing 331 | Contributions are very welcome! Just send a pull request. Feel free to contact [me](mailto:rintoj@gmail.com) or checkout my [GitHub](https://github.com/rintoj) page. 332 | 333 | ## Author 334 | 335 | **Rinto Jose** (rintoj) 336 | 337 | Follow me: 338 | [GitHub](https://github.com/rintoj) 339 | | [Facebook](https://www.facebook.com/rinto.jose) 340 | | [Twitter](https://twitter.com/rintoj) 341 | | [Google+](https://plus.google.com/+RintoJoseMankudy) 342 | | [Youtube](https://youtube.com/+RintoJoseMankudy) 343 | 344 | ## Versions 345 | [Check CHANGELOG](https://github.com/rintoj/angular-reflux/blob/master/CHANGELOG.md) 346 | 347 | ## License 348 | ``` 349 | The MIT License (MIT) 350 | 351 | Copyright (c) 2016 Rinto Jose (rintoj) 352 | 353 | Permission is hereby granted, free of charge, to any person obtaining a copy 354 | of this software and associated documentation files (the "Software"), to deal 355 | in the Software without restriction, including without limitation the rights 356 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 357 | copies of the Software, and to permit persons to whom the Software is 358 | furnished to do so, subject to the following conditions: 359 | 360 | The above copyright notice and this permission notice shall be included in 361 | all copies or substantial portions of the Software. 362 | 363 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 364 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 365 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 366 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 367 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 368 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 369 | THE SOFTWARE. 370 | ``` 371 | -------------------------------------------------------------------------------- /demo/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # IDEs and editors 11 | /.idea 12 | .project 13 | .classpath 14 | .c9/ 15 | *.launch 16 | .settings/ 17 | 18 | # IDE - VSCode 19 | .vscode/* 20 | !.vscode/settings.json 21 | !.vscode/tasks.json 22 | !.vscode/launch.json 23 | !.vscode/extensions.json 24 | 25 | # misc 26 | /.sass-cache 27 | /connect.lock 28 | /coverage/* 29 | /libpeerconnection.log 30 | npm-debug.log 31 | testem.log 32 | /typings 33 | 34 | # e2e 35 | /e2e/*.js 36 | /e2e/*.map 37 | 38 | #System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.28.3. 4 | 5 | ## Before you start 6 | 7 | Do the following before you start. From the directory `angular-reflux` 8 | 9 | ```bash 10 | npm pack 11 | ``` 12 | 13 | And from `angular-reflux/demo` 14 | 15 | ```bash 16 | npm install ../angular-reflux-.tgz 17 | ``` 18 | 19 | ## Development server 20 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 21 | 22 | ## Code scaffolding 23 | 24 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`. 25 | 26 | ## Build 27 | 28 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 29 | 30 | ## Running unit tests 31 | 32 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 33 | 34 | ## Running end-to-end tests 35 | 36 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 37 | Before running the tests make sure you are serving the app via `ng serve`. 38 | 39 | ## Deploying to GitHub Pages 40 | 41 | Run `ng github-pages:deploy` to deploy to GitHub Pages. 42 | 43 | ## Further help 44 | 45 | To get more help on the `angular-cli` use `ng help` or go check out the [Angular-CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 46 | -------------------------------------------------------------------------------- /demo/angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.28.3", 4 | "name": "demo" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.json", 19 | "prefix": "app", 20 | "styles": [ 21 | "styles.css" 22 | ], 23 | "scripts": [], 24 | "environments": { 25 | "source": "environments/environment.ts", 26 | "dev": "environments/environment.ts", 27 | "prod": "environments/environment.prod.ts" 28 | } 29 | } 30 | ], 31 | "e2e": { 32 | "protractor": { 33 | "config": "./protractor.conf.js" 34 | } 35 | }, 36 | "lint": [ 37 | { 38 | "files": "src/**/*.ts", 39 | "project": "src/tsconfig.json" 40 | }, 41 | { 42 | "files": "e2e/**/*.ts", 43 | "project": "e2e/tsconfig.json" 44 | } 45 | ], 46 | "test": { 47 | "karma": { 48 | "config": "./karma.conf.js" 49 | } 50 | }, 51 | "defaults": { 52 | "styleExt": "css", 53 | "prefixInterfaces": false, 54 | "inline": { 55 | "style": false, 56 | "template": false 57 | }, 58 | "spec": { 59 | "class": false, 60 | "component": true, 61 | "directive": true, 62 | "module": false, 63 | "pipe": true, 64 | "service": true 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /demo/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { DemoPage } from './app.po'; 2 | 3 | describe('demo App', function() { 4 | let page: DemoPage; 5 | 6 | beforeEach(() => { 7 | page = new DemoPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /demo/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class DemoPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /demo/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', 'angular-cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-remap-istanbul'), 12 | require('angular-cli/plugins/karma') 13 | ], 14 | files: [ 15 | { pattern: './src/test.ts', watched: false } 16 | ], 17 | preprocessors: { 18 | './src/test.ts': ['angular-cli'] 19 | }, 20 | mime: { 21 | 'text/x-typescript': ['ts','tsx'] 22 | }, 23 | remapIstanbulReporter: { 24 | reports: { 25 | html: 'coverage', 26 | lcovonly: './coverage/coverage.lcov' 27 | } 28 | }, 29 | angularCli: { 30 | config: './angular-cli.json', 31 | environment: 'dev' 32 | }, 33 | reporters: config.angularCli && config.angularCli.codeCoverage 34 | ? ['progress', 'karma-remap-istanbul'] 35 | : ['progress'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "test": "ng test", 10 | "build": "ng build", 11 | "build:prod": "ng build --prod --aot", 12 | "pree2e": "webdriver-manager update --standalone false --gecko false", 13 | "e2e": "protractor" 14 | }, 15 | "private": true, 16 | "dependencies": { 17 | "@angular/common": "^2.3.1", 18 | "@angular/compiler": "^2.3.1", 19 | "@angular/core": "^2.3.1", 20 | "@angular/forms": "^2.3.1", 21 | "@angular/http": "^2.3.1", 22 | "@angular/platform-browser": "^2.3.1", 23 | "@angular/platform-browser-dynamic": "^2.3.1", 24 | "@angular/router": "^3.3.1", 25 | "core-js": "^2.4.1", 26 | "rxjs": "^5.0.1", 27 | "seamless-immutable": "^7.1.1", 28 | "ts-helpers": "^1.1.1", 29 | "zone.js": "^0.7.2" 30 | }, 31 | "devDependencies": { 32 | "@angular/compiler-cli": "^2.3.1", 33 | "@types/jasmine": "2.5.38", 34 | "@types/node": "^6.0.42", 35 | "angular-cli": "1.0.0-beta.28.3", 36 | "codelyzer": "~2.0.0-beta.1", 37 | "jasmine-core": "2.5.2", 38 | "jasmine-spec-reporter": "2.5.0", 39 | "karma": "1.2.0", 40 | "karma-chrome-launcher": "^2.0.0", 41 | "karma-cli": "^1.0.1", 42 | "karma-jasmine": "^1.0.2", 43 | "karma-remap-istanbul": "^0.2.1", 44 | "protractor": "~4.0.13", 45 | "ts-node": "1.2.1", 46 | "tslint": "^4.3.0", 47 | "typescript": "~2.0.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /demo/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /demo/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | #todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); 52 | } 53 | 54 | #todoapp input::-webkit-input-placeholder { 55 | font-style: italic; 56 | font-weight: 300; 57 | color: #e6e6e6; 58 | } 59 | 60 | #todoapp input::-moz-placeholder { 61 | font-style: italic; 62 | font-weight: 300; 63 | color: #e6e6e6; 64 | } 65 | 66 | #todoapp input::input-placeholder { 67 | font-style: italic; 68 | font-weight: 300; 69 | color: #e6e6e6; 70 | } 71 | 72 | #todoapp h1 { 73 | position: absolute; 74 | top: -155px; 75 | width: 100%; 76 | font-size: 100px; 77 | font-weight: 100; 78 | text-align: center; 79 | color: rgba(175, 47, 47, 0.15); 80 | -webkit-text-rendering: optimizeLegibility; 81 | -moz-text-rendering: optimizeLegibility; 82 | text-rendering: optimizeLegibility; 83 | } 84 | 85 | #new-todo, 86 | .edit { 87 | position: relative; 88 | margin: 0; 89 | width: 100%; 90 | font-size: 24px; 91 | font-family: inherit; 92 | font-weight: inherit; 93 | line-height: 1.4em; 94 | border: 0; 95 | outline: none; 96 | color: inherit; 97 | padding: 6px; 98 | border: 1px solid #999; 99 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 100 | box-sizing: border-box; 101 | -webkit-font-smoothing: antialiased; 102 | -moz-font-smoothing: antialiased; 103 | font-smoothing: antialiased; 104 | } 105 | 106 | #new-todo { 107 | padding: 16px 16px 16px 60px; 108 | border: none; 109 | background: rgba(0, 0, 0, 0.003); 110 | box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03); 111 | } 112 | 113 | #main { 114 | position: relative; 115 | z-index: 2; 116 | border-top: 1px solid #e6e6e6; 117 | } 118 | 119 | label[for='toggle-all'] { 120 | display: none; 121 | } 122 | 123 | #toggle-all { 124 | position: absolute; 125 | top: -55px; 126 | left: -12px; 127 | width: 60px; 128 | height: 34px; 129 | text-align: center; 130 | border: none; 131 | /* Mobile Safari */ 132 | } 133 | 134 | #toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | #toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | #todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | #todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | #todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | #todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | #todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | #todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | #todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; 187 | /* Mobile Safari */ 188 | -webkit-appearance: none; 189 | appearance: none; 190 | } 191 | 192 | #todo-list li .toggle:after { 193 | content: url('data:image/svg+xml;utf8,'); 194 | } 195 | 196 | #todo-list li .toggle:checked:after { 197 | content: url('data:image/svg+xml;utf8,'); 198 | } 199 | 200 | #todo-list li label { 201 | white-space: pre-line; 202 | word-break: break-all; 203 | padding: 15px 60px 15px 15px; 204 | margin-left: 45px; 205 | display: block; 206 | line-height: 1.2; 207 | transition: color 0.4s; 208 | } 209 | 210 | #todo-list li.completed label { 211 | color: #d9d9d9; 212 | text-decoration: line-through; 213 | } 214 | 215 | #todo-list li .destroy { 216 | display: none; 217 | position: absolute; 218 | top: 0; 219 | right: 10px; 220 | bottom: 0; 221 | width: 40px; 222 | height: 40px; 223 | margin: auto 0; 224 | font-size: 30px; 225 | color: #cc9a9a; 226 | margin-bottom: 11px; 227 | transition: color 0.2s ease-out; 228 | } 229 | 230 | #todo-list li .destroy:hover { 231 | color: #af5b5e; 232 | } 233 | 234 | #todo-list li .destroy:after { 235 | content: '×'; 236 | } 237 | 238 | #todo-list li:hover .destroy { 239 | display: block; 240 | } 241 | 242 | #todo-list li .edit { 243 | display: none; 244 | } 245 | 246 | #todo-list li.editing:last-child { 247 | margin-bottom: -1px; 248 | } 249 | 250 | #footer { 251 | color: #777; 252 | padding: 10px 15px; 253 | height: 20px; 254 | text-align: center; 255 | border-top: 1px solid #e6e6e6; 256 | } 257 | 258 | #footer:before { 259 | content: ''; 260 | position: absolute; 261 | right: 0; 262 | bottom: 0; 263 | left: 0; 264 | height: 50px; 265 | overflow: hidden; 266 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2); 267 | } 268 | 269 | #todo-count { 270 | float: left; 271 | text-align: left; 272 | } 273 | 274 | #todo-count strong { 275 | font-weight: 300; 276 | } 277 | 278 | #filters { 279 | margin: 0; 280 | padding: 0; 281 | list-style: none; 282 | position: absolute; 283 | right: 0; 284 | left: 0; 285 | } 286 | 287 | #filters li { 288 | display: inline; 289 | } 290 | 291 | #filters li a { 292 | color: inherit; 293 | margin: 3px; 294 | padding: 3px 7px; 295 | text-decoration: none; 296 | border: 1px solid transparent; 297 | border-radius: 3px; 298 | } 299 | 300 | #filters li a.selected, 301 | #filters li a:hover { 302 | border-color: rgba(175, 47, 47, 0.1); 303 | } 304 | 305 | #filters li a.selected { 306 | border-color: rgba(175, 47, 47, 0.2); 307 | } 308 | 309 | #clear-completed, 310 | html #clear-completed:active { 311 | float: right; 312 | position: relative; 313 | line-height: 20px; 314 | text-decoration: none; 315 | cursor: pointer; 316 | position: relative; 317 | } 318 | 319 | #clear-completed:hover { 320 | text-decoration: underline; 321 | } 322 | 323 | #info { 324 | margin: 65px auto 0; 325 | color: #bfbfbf; 326 | font-size: 10px; 327 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 328 | text-align: center; 329 | } 330 | 331 | #info p { 332 | line-height: 1; 333 | } 334 | 335 | #info a { 336 | color: inherit; 337 | text-decoration: none; 338 | font-weight: 400; 339 | } 340 | 341 | #info a:hover { 342 | text-decoration: underline; 343 | } 344 | 345 | 346 | /* 347 | Hack to remove background from Mobile Safari. 348 | Can't use it globally since it destroys checkboxes in Firefox 349 | */ 350 | 351 | @media screen and (-webkit-min-device-pixel-ratio:0) { 352 | #toggle-all, 353 | #todo-list li .toggle { 354 | background: none; 355 | } 356 | #todo-list li .toggle { 357 | height: 40px; 358 | } 359 | #toggle-all { 360 | -webkit-transform: rotate(90deg); 361 | transform: rotate(90deg); 362 | -webkit-appearance: none; 363 | appearance: none; 364 | } 365 | } 366 | 367 | @media (max-width: 430px) { 368 | #footer { 369 | height: 50px; 370 | } 371 | #filters { 372 | bottom: 10px; 373 | } 374 | } 375 | 376 | todo-list { 377 | position: relative; 378 | } 379 | -------------------------------------------------------------------------------- /demo/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | }); 13 | TestBed.compileComponents(); 14 | }); 15 | 16 | it('should create the app', async(() => { 17 | const fixture = TestBed.createComponent(AppComponent); 18 | const app = fixture.debugElement.componentInstance; 19 | expect(app).toBeTruthy(); 20 | })); 21 | 22 | it(`should have as title 'app works!'`, async(() => { 23 | const fixture = TestBed.createComponent(AppComponent); 24 | const app = fixture.debugElement.componentInstance; 25 | expect(app.title).toEqual('app works!'); 26 | })); 27 | 28 | it('should render title in a h1 tag', async(() => { 29 | const fixture = TestBed.createComponent(AppComponent); 30 | fixture.detectChanges(); 31 | const compiled = fixture.debugElement.nativeElement; 32 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 33 | })); 34 | }); 35 | -------------------------------------------------------------------------------- /demo/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { Todo, TodoFilter } from './state/todo'; 3 | 4 | import { BindData } from 'angular-reflux'; 5 | import { FetchTodosAction } from './state/action'; 6 | import { State } from './state'; 7 | import { Stores } from './store'; 8 | 9 | export function selectTodos(state: State) { 10 | return state.todos; 11 | } 12 | 13 | export function selectFilter(state: State) { 14 | return state.filter; 15 | } 16 | 17 | @Component({ 18 | selector: 'todo-app', 19 | template: ` 20 |
21 | 22 | 23 | 24 |
25 | `, 26 | styleUrls: ['./app.component.css'], 27 | encapsulation: ViewEncapsulation.None 28 | }) 29 | export class AppComponent implements OnInit { 30 | 31 | @BindData(selectTodos) 32 | todos: Todo[]; 33 | 34 | @BindData(selectFilter) 35 | filter: TodoFilter; 36 | 37 | constructor(public stores: Stores) { } 38 | 39 | ngOnInit() { 40 | new FetchTodosAction().dispatch(); 41 | } 42 | } -------------------------------------------------------------------------------- /demo/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { AppComponent } from './app.component'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpModule } from '@angular/http'; 5 | import { NgModule } from '@angular/core'; 6 | import { SCREENS } from './screen'; 7 | import { SERVICES } from './service'; 8 | import { STORES } from './store'; 9 | 10 | /** 11 | * `AppModule` is the main entry point into Angular2's bootstrapping process 12 | */ 13 | @NgModule({ 14 | declarations: [ 15 | AppComponent, 16 | ...SCREENS 17 | ], 18 | imports: [ 19 | BrowserModule, 20 | FormsModule, 21 | HttpModule 22 | ], 23 | providers: [ 24 | ...STORES, 25 | ...SERVICES 26 | ], 27 | bootstrap: [AppComponent] 28 | }) 29 | export class AppModule { } 30 | -------------------------------------------------------------------------------- /demo/src/app/screen/index.ts: -------------------------------------------------------------------------------- 1 | import { TodoFooterComponent } from './todo-footer.component'; 2 | import { TodoHeaderComponent } from './todo-header.component'; 3 | import { TodoListComponent } from './todo-list.component'; 4 | 5 | export const SCREENS = [ 6 | TodoHeaderComponent, 7 | TodoListComponent, 8 | TodoFooterComponent 9 | ]; 10 | -------------------------------------------------------------------------------- /demo/src/app/screen/todo-footer.component.ts: -------------------------------------------------------------------------------- 1 | import { ClearCompletedTodosAction, SetFilterAction } from '../state/action'; 2 | import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; 3 | import { Todo, TodoFilter } from '../state/todo'; 4 | 5 | @Component({ 6 | selector: 'todo-footer', 7 | template: ` 8 |
9 | {{leftCount}} 10 | {{leftCount > 1 ? 'items' : 'item' }} left 11 | 12 | 29 | 30 |
31 | ` 32 | }) 33 | export class TodoFooterComponent implements OnChanges { 34 | 35 | @Input() 36 | todos: Todo[]; 37 | 38 | @Input() 39 | filter: TodoFilter; 40 | 41 | filterText: string; 42 | leftCount: number; 43 | completedCount: number; 44 | 45 | ngOnChanges(changes: SimpleChanges) { 46 | if (this.todos === undefined) return; 47 | this.completedCount = this.todos.filter(item => item.completed).length; 48 | this.leftCount = this.todos.length - this.completedCount; 49 | this.filterText = TodoFilter[this.filter]; 50 | } 51 | 52 | clearCompletedTodos() { 53 | new ClearCompletedTodosAction().dispatch(); 54 | } 55 | 56 | setFilter(filter: string) { 57 | new SetFilterAction(TodoFilter[filter]).dispatch(); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /demo/src/app/screen/todo-header.component.ts: -------------------------------------------------------------------------------- 1 | import { AddTodoAction } from '../state/action'; 2 | import { Component } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'todo-header', 6 | template: ` 7 | 13 | ` 14 | }) 15 | export class TodoHeaderComponent { 16 | 17 | todoText: string; 18 | 19 | addTodo() { 20 | if (this.todoText === undefined || this.todoText.trim() === '') { 21 | return; 22 | } 23 | 24 | const addTodoAction = new AddTodoAction({ 25 | title: this.todoText, 26 | completed: false 27 | }); 28 | 29 | addTodoAction.dispatch().then(() => this.todoText = ''); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /demo/src/app/screen/todo-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; 2 | import { MarkAllTodosAction, RemoveTodoAction, ToggleTodoAction } from '../state/action'; 3 | import { Todo, TodoFilter } from '../state/todo'; 4 | 5 | @Component({ 6 | selector: 'todo-list', 7 | template: ` 8 | 9 |
10 |
  • 11 |
    12 | 13 | 14 | 15 |
    16 |
  • 17 |
    18 | ` 19 | }) 20 | export class TodoListComponent implements OnChanges { 21 | 22 | @Input() 23 | todos: Todo[]; 24 | 25 | @Input() 26 | filter: TodoFilter; 27 | 28 | filteredTodos: Todo[]; 29 | allChecked: boolean; 30 | 31 | ngOnChanges(changes: SimpleChanges) { 32 | this.filteredTodos = this.filterTodos(this.todos, this.filter); 33 | this.allChecked = this.filteredTodos.filter(item => item.completed).length === this.filteredTodos.length; 34 | } 35 | 36 | markAll(complete: boolean) { 37 | new MarkAllTodosAction(complete).dispatch(); 38 | } 39 | 40 | toggleTodo(todo: Todo) { 41 | new ToggleTodoAction(todo.id).dispatch(); 42 | } 43 | 44 | removeTodo(event, todo: Todo) { 45 | event.stopPropagation(); 46 | new RemoveTodoAction(todo.id).dispatch(); 47 | } 48 | 49 | private filterTodos(todos: Todo[], filter: TodoFilter) { 50 | return (todos || []).filter(item => { 51 | if (filter === undefined || filter === TodoFilter.ALL) { 52 | return true; 53 | } 54 | if (filter === TodoFilter.COMPLETED) { 55 | return item.completed; 56 | } 57 | if (filter === TodoFilter.ACTIVE) { 58 | return !item.completed; 59 | } 60 | }); 61 | } 62 | } -------------------------------------------------------------------------------- /demo/src/app/service/index.ts: -------------------------------------------------------------------------------- 1 | import { TodoService } from './todo.service'; 2 | 3 | export const SERVICES = [ 4 | TodoService 5 | ]; 6 | -------------------------------------------------------------------------------- /demo/src/app/service/todo.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { Observer } from 'rxjs/Observer'; 4 | import { Todo } from '../state/todo'; 5 | 6 | @Injectable() 7 | export class TodoService { 8 | 9 | private readonly url = '/todos'; 10 | private _todos: Todo[]; 11 | 12 | fetch(): Observable { 13 | return Observable.create((observer: Observer) => { 14 | this._todos = JSON.parse(localStorage.getItem(this.url) || '[]'); 15 | observer.next(this._todos); 16 | observer.complete(); 17 | }); 18 | } 19 | 20 | add(todo: Todo): Observable { 21 | return Observable.create((observer: Observer) => { 22 | if (todo == undefined) { 23 | observer.error('"todo" is undefined!'); 24 | return observer.complete(); 25 | } 26 | 27 | this.todos = this.todos.concat([Object.assign({ 28 | id: this.generateId() 29 | }, todo)]); 30 | observer.next(this.todos); 31 | observer.complete(); 32 | }); 33 | } 34 | 35 | remove(id: string): Observable { 36 | return Observable.create((observer: Observer) => { 37 | if (id == undefined) { 38 | observer.error('"id" is undefined!'); 39 | return observer.complete(); 40 | } 41 | 42 | this.todos = this.todos.filter(item => item.id !== id); 43 | observer.next(this.todos); 44 | observer.complete(); 45 | }); 46 | } 47 | 48 | update(todo: Todo): Observable { 49 | return Observable.create((observer: Observer) => { 50 | if (todo == undefined) { 51 | observer.error('"todo" is undefined!'); 52 | return observer.complete(); 53 | } 54 | 55 | this.todos = this.todos.map(item => item.id === todo.id ? todo : item); 56 | observer.next(this.todos); 57 | observer.complete(); 58 | }); 59 | } 60 | 61 | toggle(id: string): Observable { 62 | return Observable.create((observer: Observer) => { 63 | if (id == undefined) { 64 | observer.error('"id" is undefined!'); 65 | return observer.complete(); 66 | } 67 | 68 | this.todos = this.todos.map(item => item.id === id ? Object.assign({}, item, { 69 | completed: !item.completed 70 | }) : item); 71 | observer.next(this.todos); 72 | observer.complete(); 73 | }); 74 | } 75 | 76 | clearCompleted(): Observable { 77 | return Observable.create((observer: Observer) => { 78 | this.todos = this.todos.filter(item => !item.completed); 79 | observer.next(this.todos); 80 | observer.complete(); 81 | }); 82 | } 83 | 84 | markAll(complete: boolean): Observable { 85 | return Observable.create((observer: Observer) => { 86 | this.todos = this.todos.map(item => Object.assign({}, item, { 87 | completed: complete 88 | })); 89 | observer.next(this.todos); 90 | observer.complete(); 91 | }); 92 | } 93 | 94 | private generateId() { 95 | return btoa(Math.random() + '').substr(4, 6).toLowerCase(); 96 | } 97 | 98 | private get todos() { 99 | return this._todos || []; 100 | } 101 | 102 | private set todos(todos: Todo[]) { 103 | this._todos = todos; 104 | localStorage.setItem(this.url, JSON.stringify(this._todos)); 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /demo/src/app/state/action.ts: -------------------------------------------------------------------------------- 1 | import { Todo, TodoFilter } from './todo'; 2 | 3 | import { Action } from 'angular-reflux'; 4 | import { State } from './'; 5 | 6 | export class FetchTodosAction extends Action { constructor() { super(); } } 7 | export class AddTodoAction extends Action { constructor(public todo: Todo) { super(); } } 8 | export class UpdateTodoAction extends Action { constructor(public todo: Todo) { super(); } } 9 | export class ToggleTodoAction extends Action { constructor(public id: string) { super(); } } 10 | export class RemoveTodoAction extends Action { constructor(public id: string) { super(); } } 11 | export class MarkAllTodosAction extends Action { constructor(public complete: boolean) { super(); } } 12 | export class ClearCompletedTodosAction extends Action { constructor() { super(); } } 13 | export class SetFilterAction extends Action { constructor(public filter: TodoFilter) { super(); } } -------------------------------------------------------------------------------- /demo/src/app/state/index.ts: -------------------------------------------------------------------------------- 1 | import { Todo, TodoFilter } from './todo'; 2 | 3 | export interface State { 4 | todos?: Todo[]; 5 | filter?: TodoFilter; 6 | } -------------------------------------------------------------------------------- /demo/src/app/state/todo.ts: -------------------------------------------------------------------------------- 1 | export enum TodoFilter { 2 | ALL, ACTIVE, COMPLETED 3 | } 4 | 5 | export interface Todo { 6 | id?: string; 7 | title?: string; 8 | completed?: boolean; 9 | } -------------------------------------------------------------------------------- /demo/src/app/store/index.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { TodoStore } from './todo.store'; 3 | 4 | @Injectable() 5 | export class Stores { 6 | constructor( 7 | todoStore: TodoStore 8 | ) { 9 | // empty block 10 | } 11 | } 12 | 13 | export const STORES = [ 14 | Stores, 15 | TodoStore 16 | ]; -------------------------------------------------------------------------------- /demo/src/app/store/todo.store.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AddTodoAction, 3 | ClearCompletedTodosAction, 4 | FetchTodosAction, 5 | MarkAllTodosAction, 6 | RemoveTodoAction, 7 | SetFilterAction, 8 | ToggleTodoAction, 9 | UpdateTodoAction, 10 | } from '../state/action'; 11 | import { BindAction, Store } from 'angular-reflux'; 12 | 13 | import { Injectable } from '@angular/core'; 14 | import { Observable } from 'rxjs/Observable'; 15 | import { Observer } from 'rxjs/Observer'; 16 | import { State } from '../state'; 17 | import { TodoService } from '../service/todo.service'; 18 | 19 | @Injectable() 20 | export class TodoStore extends Store { 21 | 22 | constructor(private service: TodoService) { 23 | super(); 24 | } 25 | 26 | @BindAction() 27 | fetchTodos(state: State, action: FetchTodosAction): Observable { 28 | return Observable.create((observer: Observer) => { 29 | this.service.fetch().subscribe( 30 | todos => observer.next({ todos: todos }), 31 | error => observer.error(error), 32 | () => observer.complete() 33 | ); 34 | }).share(); 35 | } 36 | 37 | @BindAction() 38 | addTodo(state: State, action: AddTodoAction): Observable { 39 | return Observable.create((observer: Observer) => { 40 | this.service.add(action.todo).subscribe( 41 | todos => observer.next({ todos: todos }), 42 | error => observer.error(error), 43 | () => observer.complete() 44 | ); 45 | }).share(); 46 | } 47 | 48 | @BindAction() 49 | removeTodo(state: State, action: RemoveTodoAction): Observable { 50 | return Observable.create((observer: Observer) => { 51 | this.service.remove(action.id).subscribe( 52 | todos => observer.next({ todos: todos }), 53 | error => observer.error(error), 54 | () => observer.complete() 55 | ); 56 | }).share(); 57 | } 58 | 59 | @BindAction() 60 | updateTodo(state: State, action: UpdateTodoAction): Observable { 61 | return Observable.create((observer: Observer) => { 62 | this.service.update(action.todo).subscribe( 63 | todos => observer.next({ todos: todos }), 64 | error => observer.error(error), 65 | () => observer.complete() 66 | ); 67 | }).share(); 68 | } 69 | 70 | @BindAction() 71 | toggleTodo(state: State, action: ToggleTodoAction): Observable { 72 | return Observable.create((observer: Observer) => { 73 | this.service.toggle(action.id).subscribe( 74 | todos => observer.next({ todos: todos }), 75 | error => observer.error(error), 76 | () => observer.complete() 77 | ); 78 | }).share(); 79 | } 80 | 81 | @BindAction() 82 | clearCompletedTodos(state: State, action: ClearCompletedTodosAction): Observable { 83 | return Observable.create((observer: Observer) => { 84 | this.service.clearCompleted().subscribe( 85 | todos => observer.next({ todos: todos }), 86 | error => observer.error(error), 87 | () => observer.complete() 88 | ); 89 | }).share(); 90 | } 91 | 92 | @BindAction() 93 | markAllTodos(state: State, action: MarkAllTodosAction): Observable { 94 | return Observable.create((observer: Observer) => { 95 | this.service.markAll(action.complete).subscribe( 96 | todos => observer.next({ todos: todos }), 97 | error => observer.error(error), 98 | () => observer.complete() 99 | ); 100 | }).share(); 101 | } 102 | 103 | @BindAction() 104 | setFilter(state: State, action: SetFilterAction): Observable { 105 | return Observable.create((observer: Observer) => { 106 | observer.next({ 107 | filter: action.filter 108 | }); 109 | observer.complete(); 110 | }).share(); 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /demo/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rintoj/angular-reflux/3b04d01c29327e9786137bb57d1c88e1cbc13652/demo/src/assets/.gitkeep -------------------------------------------------------------------------------- /demo/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /demo/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /demo/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rintoj/angular-reflux/3b04d01c29327e9786137bb57d1c88e1cbc13652/demo/src/favicon.ico -------------------------------------------------------------------------------- /demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | Loading... 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { environment } from './environments/environment'; 4 | import { AppModule } from './app/app.module'; 5 | 6 | if (environment.production) { 7 | enableProdMode(); 8 | } 9 | 10 | platformBrowserDynamic().bootstrapModule(AppModule); 11 | -------------------------------------------------------------------------------- /demo/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular and is loaded before the app. 2 | // You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | 21 | // If you need to support the browsers/features below, uncomment the import 22 | // and run `npm install import-name-here'; 23 | // Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 24 | 25 | // Needed for: IE9 26 | // import 'classlist.js'; 27 | 28 | // Animations 29 | // Needed for: All but Chrome and Firefox, Not supported in IE9 30 | // import 'web-animations-js'; 31 | 32 | // Date, currency, decimal and percent pipes 33 | // Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 34 | // import 'intl'; 35 | 36 | // NgClass on SVG elements 37 | // Needed for: IE10, IE11 38 | // import 'classlist.js'; 39 | -------------------------------------------------------------------------------- /demo/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /demo/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare var __karma__: any; 17 | declare var require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /demo/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": ["es6", "dom"], 8 | "mapRoot": "./", 9 | "module": "es6", 10 | "moduleResolution": "node", 11 | "outDir": "../dist/out-tsc", 12 | "sourceMap": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "../node_modules/@types" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": false, 14 | "forin": true, 15 | "import-blacklist": [true, "rxjs"], 16 | "import-spacing": true, 17 | "indent": [ 18 | true, 19 | "spaces" 20 | ], 21 | "interface-over-type-literal": true, 22 | "label-position": true, 23 | "max-line-length": [ 24 | true, 25 | 140 26 | ], 27 | "member-access": false, 28 | "member-ordering": [ 29 | true, 30 | "static-before-instance", 31 | "variables-before-functions" 32 | ], 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-variable": true, 46 | "no-empty": false, 47 | "no-empty-interface": true, 48 | "no-eval": true, 49 | "no-inferrable-types": true, 50 | "no-shadowed-variable": true, 51 | "no-string-literal": false, 52 | "no-string-throw": true, 53 | "no-switch-case-fall-through": true, 54 | "no-trailing-whitespace": true, 55 | "no-unused-expression": true, 56 | "no-use-before-declare": true, 57 | "no-var-keyword": true, 58 | "object-literal-sort-keys": false, 59 | "one-line": [ 60 | true, 61 | "check-open-brace", 62 | "check-catch", 63 | "check-else", 64 | "check-whitespace" 65 | ], 66 | "prefer-const": true, 67 | "quotemark": [ 68 | true, 69 | "single" 70 | ], 71 | "radix": true, 72 | "semicolon": [ 73 | "always" 74 | ], 75 | "triple-equals": [ 76 | true, 77 | "allow-undefined-check" 78 | ], 79 | "typedef-whitespace": [ 80 | true, 81 | { 82 | "call-signature": "nospace", 83 | "index-signature": "nospace", 84 | "parameter": "nospace", 85 | "property-declaration": "nospace", 86 | "variable-declaration": "nospace" 87 | } 88 | ], 89 | "typeof-compare": true, 90 | "unified-signatures": true, 91 | "variable-name": false, 92 | "whitespace": [ 93 | true, 94 | "check-branch", 95 | "check-decl", 96 | "check-operator", 97 | "check-separator", 98 | "check-type" 99 | ], 100 | 101 | "directive-selector": [true, "attribute", "app", "camelCase"], 102 | "component-selector": [true, "element", "app", "kebab-case"], 103 | "use-input-property-decorator": true, 104 | "use-output-property-decorator": true, 105 | "use-host-property-decorator": true, 106 | "no-input-rename": true, 107 | "no-output-rename": true, 108 | "use-life-cycle-interface": true, 109 | "use-pipe-transform-interface": true, 110 | "component-class-suffix": true, 111 | "directive-class-suffix": true, 112 | "no-access-missing-member": true, 113 | "templates-use-public": true, 114 | "invoke-injectable": true 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /dist/action.d.ts: -------------------------------------------------------------------------------- 1 | import 'rxjs/add/observable/empty'; 2 | import 'rxjs/add/observable/from'; 3 | import 'rxjs/add/operator/catch'; 4 | import 'rxjs/add/operator/map'; 5 | import 'rxjs/add/operator/mergeMap'; 6 | import 'rxjs/add/operator/share'; 7 | import 'rxjs/add/operator/skipWhile'; 8 | import { ActionObserver } from './observers'; 9 | /** 10 | * Defines an action which an be extended to implement custom actions for a reflux application 11 | * 12 | * @example 13 | * 14 | * // Create your own action class 15 | * class PageSwitchAction extends Action { 16 | * constructor(public pageId: string) { 17 | * super() 18 | * } 19 | * } 20 | * 21 | * // Subscribe to your action 22 | * new PageSwitchAction(undefined).subscribe((state: State, action: PageSwitchAction): Observable => { 23 | * return Observable.create((observer: Observer) => { 24 | * observer.next(updatedState) 25 | * observer.complete() 26 | * }).share() 27 | * }, this) 28 | * 29 | * // Dispatch your action 30 | * new PageSwitchAction('page1').dispatch() 31 | * 32 | * @export 33 | * @class Action 34 | */ 35 | export declare class Action { 36 | private static _lastAction; 37 | private static identities; 38 | private static subscriptions; 39 | /** 40 | * The last action occurred 41 | * 42 | * @readonly 43 | * @static 44 | * 45 | * @memberOf Action 46 | */ 47 | static readonly lastAction: any; 48 | /** 49 | * Returns identity of this class 50 | * 51 | * @readonly 52 | * @type {string} 53 | */ 54 | readonly identity: string; 55 | /** 56 | * Subscribe to this action. actionObserver will be called when 'dispatch()' is invoked 57 | * 58 | * @param {ActionObserver} actionObserver The function that process the action 59 | * @param {*} context Context binding 60 | * @returns {Action} 61 | */ 62 | subscribe(actionObserver: ActionObserver, context: any): Action; 63 | /** 64 | * Dispatch this action. Returns an observable which will be completed when all action subscribers 65 | * complete it's processing 66 | * 67 | * @returns {Observable} 68 | */ 69 | dispatch(): Promise; 70 | } 71 | -------------------------------------------------------------------------------- /dist/action.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | require("rxjs/add/observable/empty"); 4 | require("rxjs/add/observable/from"); 5 | require("rxjs/add/operator/catch"); 6 | require("rxjs/add/operator/map"); 7 | require("rxjs/add/operator/mergeMap"); 8 | require("rxjs/add/operator/share"); 9 | require("rxjs/add/operator/skipWhile"); 10 | var Immutable = require("seamless-immutable"); 11 | var Observable_1 = require("rxjs/Observable"); 12 | var replaceable_state_1 = require("./replaceable-state"); 13 | var state_1 = require("./state"); 14 | /** 15 | * Defines an action which an be extended to implement custom actions for a reflux application 16 | * 17 | * @example 18 | * 19 | * // Create your own action class 20 | * class PageSwitchAction extends Action { 21 | * constructor(public pageId: string) { 22 | * super() 23 | * } 24 | * } 25 | * 26 | * // Subscribe to your action 27 | * new PageSwitchAction(undefined).subscribe((state: State, action: PageSwitchAction): Observable => { 28 | * return Observable.create((observer: Observer) => { 29 | * observer.next(updatedState) 30 | * observer.complete() 31 | * }).share() 32 | * }, this) 33 | * 34 | * // Dispatch your action 35 | * new PageSwitchAction('page1').dispatch() 36 | * 37 | * @export 38 | * @class Action 39 | */ 40 | var Action = (function () { 41 | function Action() { 42 | } 43 | Object.defineProperty(Action, "lastAction", { 44 | /** 45 | * The last action occurred 46 | * 47 | * @readonly 48 | * @static 49 | * 50 | * @memberOf Action 51 | */ 52 | get: function () { 53 | return Action.lastAction; 54 | }, 55 | enumerable: true, 56 | configurable: true 57 | }); 58 | Object.defineProperty(Action.prototype, "identity", { 59 | /** 60 | * Returns identity of this class 61 | * 62 | * @readonly 63 | * @type {string} 64 | */ 65 | get: function () { 66 | var id = Action.identities.indexOf(this.constructor); 67 | if (id < 0) { 68 | Action.identities.push(this.constructor); 69 | id = Action.identities.indexOf(this.constructor); 70 | } 71 | return "c" + id; 72 | }, 73 | enumerable: true, 74 | configurable: true 75 | }); 76 | /** 77 | * Subscribe to this action. actionObserver will be called when 'dispatch()' is invoked 78 | * 79 | * @param {ActionObserver} actionObserver The function that process the action 80 | * @param {*} context Context binding 81 | * @returns {Action} 82 | */ 83 | Action.prototype.subscribe = function (actionObserver, context) { 84 | if (!Action.subscriptions[this.identity]) { 85 | Action.subscriptions[this.identity] = []; 86 | } 87 | Action.subscriptions[this.identity].push(actionObserver.bind(context)); 88 | return this; 89 | }; 90 | /** 91 | * Dispatch this action. Returns an observable which will be completed when all action subscribers 92 | * complete it's processing 93 | * 94 | * @returns {Observable} 95 | */ 96 | Action.prototype.dispatch = function () { 97 | var _this = this; 98 | Action._lastAction = this; 99 | var subscriptions = Action.subscriptions[this.identity]; 100 | if (subscriptions == undefined || subscriptions.length === 0) { 101 | return new Promise(function (resolve) { return resolve(); }); 102 | } 103 | var observable = Observable_1.Observable.from(subscriptions) 104 | .flatMap(function (actionObserver) { 105 | var result = actionObserver(state_1.State.current, _this); 106 | if (!(result instanceof Observable_1.Observable || result instanceof Promise)) { 107 | return Observable_1.Observable.create(function (observer) { 108 | observer.next(result); 109 | observer.complete(); 110 | }); 111 | } 112 | return result; 113 | }) 114 | .map(function (state) { 115 | if (state instanceof replaceable_state_1.ReplaceableState) { 116 | // replace the state with the new one if not 'undefined' 117 | return Immutable.from(state.state || {}); 118 | } 119 | else if (state != undefined) { 120 | // merge the state with existing state 121 | return state_1.State.current.merge(state, { deep: true }); 122 | } 123 | }) 124 | .skipWhile(function (state, i) { return i + 1 < subscriptions.length; }) 125 | .map(function (state) { 126 | if (state != undefined) { 127 | state_1.State.next(state); 128 | } 129 | return state; 130 | }) 131 | .catch(function (error) { return Observable_1.Observable.empty(); }) 132 | .share(); 133 | return new Promise(function (resolve, reject) { 134 | // to trigger observable 135 | observable.subscribe(function () { 136 | // empty function 137 | }, reject, function () { return resolve(state_1.State.current); }); 138 | }); 139 | }; 140 | return Action; 141 | }()); 142 | Action.identities = []; 143 | Action.subscriptions = []; 144 | exports.Action = Action; 145 | //# sourceMappingURL=action.js.map -------------------------------------------------------------------------------- /dist/action.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/action.ts"],"names":[],"mappings":";;AAAA,qCAAkC;AAClC,oCAAiC;AACjC,mCAAgC;AAChC,iCAA8B;AAC9B,sCAAmC;AACnC,mCAAgC;AAChC,uCAAoC;AAEpC,8CAA+C;AAG/C,8CAA4C;AAE5C,yDAAsD;AACtD,iCAA+B;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH;IAAA;IA+GA,CAAC;IAjGC,sBAAkB,oBAAU;QAR5B;;;;;;;WAOG;aACH;YACE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAA;QAC1B,CAAC;;;OAAA;IAQD,sBAAI,4BAAQ;QANZ;;;;;WAKG;aACH;YACE,IAAI,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACpD,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;gBACX,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;gBACxC,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YAClD,CAAC;YACD,MAAM,CAAC,MAAI,EAAI,CAAA;QACjB,CAAC;;;OAAA;IAED;;;;;;OAMG;IACI,0BAAS,GAAhB,UAAiB,cAA8B,EAAE,OAAY;QAC3D,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YACzC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAA;QAC1C,CAAC;QACD,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA;QACtE,MAAM,CAAC,IAAI,CAAA;IACb,CAAC;IAED;;;;;OAKG;IACH,yBAAQ,GAAR;QAAA,iBAwDC;QAtDC,MAAM,CAAC,WAAW,GAAG,IAAI,CAAA;QACzB,IAAI,aAAa,GAAqB,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACzE,EAAE,CAAC,CAAC,aAAa,IAAI,SAAS,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,IAAI,OAAO,CAAC,UAAA,OAAO,IAAI,OAAA,OAAO,EAAE,EAAT,CAAS,CAAC,CAAA;QAC1C,CAAC;QAED,IAAI,UAAU,GAAoB,uBAAU,CAAC,IAAI,CAAC,aAAa,CAAC;aAG7D,OAAO,CAAC,UAAC,cAA8B;YACtC,IAAM,MAAM,GAAG,cAAc,CAAC,aAAK,CAAC,OAAO,EAAE,KAAI,CAAC,CAAA;YAClD,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,YAAY,uBAAU,IAAI,MAAM,YAAY,OAAO,CAAC,CAAC,CAAC,CAAC;gBACjE,MAAM,CAAC,uBAAU,CAAC,MAAM,CAAC,UAAC,QAAuB;oBAC/C,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;oBACrB,QAAQ,CAAC,QAAQ,EAAE,CAAA;gBACrB,CAAC,CAAC,CAAA;YACJ,CAAC;YACD,MAAM,CAAC,MAAM,CAAA;QACf,CAAC,CAAC;aAGD,GAAG,CAAC,UAAC,KAAU;YACd,EAAE,CAAC,CAAC,KAAK,YAAY,oCAAgB,CAAC,CAAC,CAAC;gBACtC,wDAAwD;gBACxD,MAAM,CAAC,SAAS,CAAC,IAAI,CAAE,KAA0B,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;YAChE,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC;gBAC9B,sCAAsC;gBACtC,MAAM,CAAC,aAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YACnD,CAAC;QACH,CAAC,CAAC;aAGD,SAAS,CAAC,UAAC,KAAU,EAAE,CAAS,IAAK,OAAA,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,MAAM,EAA5B,CAA4B,CAAC;aAGlE,GAAG,CAAC,UAAC,KAAU;YACd,EAAE,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC;gBACvB,aAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACnB,CAAC;YACD,MAAM,CAAC,KAAK,CAAA;QACd,CAAC,CAAC;aAGD,KAAK,CAAC,UAAC,KAAU,IAAU,OAAA,uBAAU,CAAC,KAAK,EAAE,EAAlB,CAAkB,CAAC;aAG9C,KAAK,EAAE,CAAA;QAEV,MAAM,CAAC,IAAI,OAAO,CAAC,UAAC,OAAO,EAAE,MAAM;YACjC,wBAAwB;YACxB,UAAU,CAAC,SAAS,CAAC;gBACnB,iBAAiB;YACnB,CAAC,EAAE,MAAM,EAAE,cAAM,OAAA,OAAO,CAAC,aAAK,CAAC,OAAO,CAAC,EAAtB,CAAsB,CAAC,CAAA;QAC1C,CAAC,CAAC,CAAA;IACJ,CAAC;IACH,aAAC;AAAD,CA/GA,AA+GC;AA5GgB,iBAAU,GAAU,EAAE,CAAA;AACtB,oBAAa,GAAU,EAAE,CAAA;AAJ7B,wBAAM","file":"action.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/action.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"Action":{"__symbolic":"class","members":{"subscribe":[{"__symbolic":"method"}],"dispatch":[{"__symbolic":"method"}]},"statics":{"_lastAction":{"__symbolic":"error","message":"Variable not initialized","line":44,"character":17},"identities":[],"subscriptions":[],"lastAction":{"__symbolic":"error","message":"Variable not initialized","line":56,"character":20}}}}},{"__symbolic":"module","version":1,"metadata":{"Action":{"__symbolic":"class","members":{"subscribe":[{"__symbolic":"method"}],"dispatch":[{"__symbolic":"method"}]},"statics":{"_lastAction":{"__symbolic":"error","message":"Variable not initialized","line":44,"character":17},"identities":[],"subscriptions":[],"lastAction":{"__symbolic":"error","message":"Variable not initialized","line":56,"character":20}}}}}] -------------------------------------------------------------------------------- /dist/bind-action.d.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './action'; 2 | import { Observable } from 'rxjs/Observable'; 3 | /** 4 | * Decorator for defining an action handler 5 | * 6 | * @example 7 | * @BindAction() 8 | * addTodo(state: State, action: AddTodoAction): Observable { 9 | * return Observable.create((observer: Observer) => { 10 | * observer.next({ 11 | * todos: state.todos.concat([action.todo]) 12 | * }) 13 | * observer.complete() 14 | * }).share() 15 | * } 16 | * 17 | * @export 18 | * @template S 19 | * @returns 20 | */ 21 | export declare function BindAction(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { 22 | value: (state: any, action: Action) => Observable; 23 | }; 24 | -------------------------------------------------------------------------------- /dist/bind-action.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var constance_1 = require("./constance"); 4 | /** 5 | * Decorator for defining an action handler 6 | * 7 | * @example 8 | * @BindAction() 9 | * addTodo(state: State, action: AddTodoAction): Observable { 10 | * return Observable.create((observer: Observer) => { 11 | * observer.next({ 12 | * todos: state.todos.concat([action.todo]) 13 | * }) 14 | * observer.complete() 15 | * }).share() 16 | * } 17 | * 18 | * @export 19 | * @template S 20 | * @returns 21 | */ 22 | function BindAction() { 23 | return function (target, propertyKey, descriptor) { 24 | var metadata = Reflect.getMetadata('design:paramtypes', target, propertyKey); 25 | if (metadata.length < 2) 26 | throw new Error('BindAction: function must have two arguments!'); 27 | var refluxActions = {}; 28 | if (Reflect.hasMetadata(constance_1.REFLUX_ACTION_KEY, target)) { 29 | refluxActions = Reflect.getMetadata(constance_1.REFLUX_ACTION_KEY, target); 30 | } 31 | refluxActions[propertyKey] = metadata[1]; 32 | Reflect.defineMetadata(constance_1.REFLUX_ACTION_KEY, refluxActions, target); 33 | return { 34 | value: function (state, action) { 35 | return descriptor.value.call(this, state, action); 36 | } 37 | }; 38 | }; 39 | } 40 | exports.BindAction = BindAction; 41 | //# sourceMappingURL=bind-action.js.map -------------------------------------------------------------------------------- /dist/bind-action.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/bind-action.ts"],"names":[],"mappings":";;AAEA,yCAA+C;AAI/C;;;;;;;;;;;;;;;;;GAiBG;AACH;IAEE,MAAM,CAAC,UAAC,MAAW,EAAE,WAAmB,EAAE,UAA8B;QAEtE,IAAI,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,mBAAmB,EAAE,MAAM,EAAE,WAAW,CAAC,CAAA;QAC5E,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAAC,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;QAEzF,IAAI,aAAa,GAAG,EAAE,CAAA;QACtB,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,6BAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;YACnD,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,6BAAiB,EAAE,MAAM,CAAC,CAAA;QAChE,CAAC;QACD,aAAa,CAAC,WAAW,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;QACxC,OAAO,CAAC,cAAc,CAAC,6BAAiB,EAAE,aAAa,EAAE,MAAM,CAAC,CAAA;QAEhE,MAAM,CAAC;YACL,KAAK,EAAE,UAAU,KAAU,EAAE,MAAc;gBACzC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;YACnD,CAAC;SACF,CAAA;IACH,CAAC,CAAA;AACH,CAAC;AApBD,gCAoBC","file":"bind-action.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/bind-action.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"BindAction":{"__symbolic":"function","parameters":[],"value":{"__symbolic":"error","message":"Function call not supported","line":26,"character":9}}}},{"__symbolic":"module","version":1,"metadata":{"BindAction":{"__symbolic":"function","parameters":[],"value":{"__symbolic":"error","message":"Function call not supported","line":26,"character":9}}}}] -------------------------------------------------------------------------------- /dist/bind-data.d.ts: -------------------------------------------------------------------------------- 1 | import { StateSelector } from './state-selector'; 2 | import { Subscription } from 'rxjs/Subscription'; 3 | /** 4 | * Bind data for give key and target using a selector function 5 | * 6 | * @param {any} target 7 | * @param {any} key 8 | * @param {any} selectorFunc 9 | */ 10 | export declare function bindData(target: any, key: string, selector: StateSelector): Subscription; 11 | /** 12 | * Bind data to a variable or to a function 13 | * 14 | * @example 15 | * @BindData(state => state.todos) 16 | * todos: Todo[] 17 | * 18 | * @BindDAta(state => state.todos) 19 | * todosDidChange(todos: Todo[]) { 20 | * // your logic 21 | * } 22 | * 23 | * 24 | * @export 25 | * @param {*} selector 26 | * @returns 27 | */ 28 | export declare function BindData(selector: StateSelector, bindImmediate?: boolean): (target: any, propertyKey: string) => void; 29 | -------------------------------------------------------------------------------- /dist/bind-data.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var constance_1 = require("./constance"); 4 | var state_1 = require("./state"); 5 | /** 6 | * Bind data for give key and target using a selector function 7 | * 8 | * @param {any} target 9 | * @param {any} key 10 | * @param {any} selectorFunc 11 | */ 12 | function bindData(target, key, selector) { 13 | return state_1.State 14 | .select(selector) 15 | .subscribe(function (data) { 16 | if (typeof target[key] === 'function') 17 | return target[key].call(target, data); 18 | target[key] = data; 19 | }); 20 | } 21 | exports.bindData = bindData; 22 | /** 23 | * Bind data to a variable or to a function 24 | * 25 | * @example 26 | * @BindData(state => state.todos) 27 | * todos: Todo[] 28 | * 29 | * @BindDAta(state => state.todos) 30 | * todosDidChange(todos: Todo[]) { 31 | * // your logic 32 | * } 33 | * 34 | * 35 | * @export 36 | * @param {*} selector 37 | * @returns 38 | */ 39 | function BindData(selector, bindImmediate) { 40 | return function (target, propertyKey) { 41 | var bindingsMeta = Reflect.getMetadata(constance_1.REFLUX_DATA_BINDINGS_KEY, target); 42 | if (!Reflect.hasMetadata(constance_1.REFLUX_DATA_BINDINGS_KEY, target)) { 43 | bindingsMeta = { selectors: {}, subscriptions: [], destroyed: !bindImmediate }; 44 | var originalInit_1 = target.ngOnInit; 45 | target.ngOnInit = function ngOnInit() { 46 | var _this = this; 47 | var dataBindings = Reflect.getMetadata(constance_1.REFLUX_DATA_BINDINGS_KEY, this); 48 | if (dataBindings != undefined && dataBindings.destroyed === true) { 49 | dataBindings.subscriptions = dataBindings.subscriptions.concat(Object.keys(dataBindings.selectors) 50 | .map(function (key) { return bindData(_this, key, dataBindings.selectors[key]); })); 51 | dataBindings.destroyed = false; 52 | Reflect.defineMetadata(constance_1.REFLUX_DATA_BINDINGS_KEY, dataBindings, this); 53 | } 54 | return originalInit_1 && originalInit_1.call(this); 55 | }; 56 | var originalDestroy_1 = target.ngOnDestroy; 57 | target.ngOnDestroy = function ngOnDestroy() { 58 | var dataBindings = Reflect.getMetadata(constance_1.REFLUX_DATA_BINDINGS_KEY, this); 59 | if (dataBindings != undefined) { 60 | dataBindings.subscriptions.forEach(function (subscription) { return subscription.unsubscribe(); }); 61 | dataBindings.subscriptions = []; 62 | dataBindings.destroyed = true; 63 | Reflect.defineMetadata(constance_1.REFLUX_DATA_BINDINGS_KEY, dataBindings, this); 64 | } 65 | return originalDestroy_1 && originalDestroy_1.call(this); 66 | }; 67 | } 68 | bindingsMeta.selectors[propertyKey] = selector; 69 | if (bindImmediate) { 70 | bindingsMeta.subscriptions.push(bindData(target, propertyKey, selector)); 71 | } 72 | Reflect.defineMetadata(constance_1.REFLUX_DATA_BINDINGS_KEY, bindingsMeta, target); 73 | }; 74 | } 75 | exports.BindData = BindData; 76 | //# sourceMappingURL=bind-data.js.map -------------------------------------------------------------------------------- /dist/bind-data.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/bind-data.ts"],"names":[],"mappings":";;AAAA,yCAAsD;AACtD,iCAA+B;AAM/B;;;;;;GAMG;AACH,kBAA4B,MAAW,EAAE,GAAW,EAAE,QAAuB;IAC3E,MAAM,CAAC,aAAK;SACT,MAAM,CAAC,QAAQ,CAAC;SAChB,SAAS,CAAC,UAAA,IAAI;QACb,EAAE,CAAC,CAAC,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,UAAU,CAAC;YAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QAC5E,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;IACpB,CAAC,CAAC,CAAA;AACN,CAAC;AAPD,4BAOC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,kBAA4B,QAAuB,EAAE,aAAuB;IAC1E,MAAM,CAAC,UAAC,MAAW,EAAE,WAAmB;QAEtC,IAAI,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,oCAAwB,EAAE,MAAM,CAAC,CAAA;QACxE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,oCAAwB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;YAC3D,YAAY,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,aAAa,EAAE,CAAA;YAE9E,IAAI,cAAY,GAAG,MAAM,CAAC,QAAQ,CAAA;YAClC,MAAM,CAAC,QAAQ,GAAG;gBAAA,iBAajB;gBAZC,IAAI,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,oCAAwB,EAAE,IAAI,CAAC,CAAA;gBACtE,EAAE,CAAC,CAAC,YAAY,IAAI,SAAS,IAAI,YAAY,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC;oBAEjE,YAAY,CAAC,aAAa,GAAG,YAAY,CAAC,aAAa,CAAC,MAAM,CAC5D,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC;yBAChC,GAAG,CAAC,UAAA,GAAG,IAAI,OAAA,QAAQ,CAAC,KAAI,EAAE,GAAG,EAAE,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAhD,CAAgD,CAAC,CAChE,CAAA;oBAED,YAAY,CAAC,SAAS,GAAG,KAAK,CAAA;oBAC9B,OAAO,CAAC,cAAc,CAAC,oCAAwB,EAAE,YAAY,EAAE,IAAI,CAAC,CAAA;gBACtE,CAAC;gBACD,MAAM,CAAC,cAAY,IAAI,cAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAChD,CAAC,CAAA;YAED,IAAI,iBAAe,GAAG,MAAM,CAAC,WAAW,CAAA;YACxC,MAAM,CAAC,WAAW,GAAG;gBACnB,IAAI,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,oCAAwB,EAAE,IAAI,CAAC,CAAA;gBACtE,EAAE,CAAC,CAAC,YAAY,IAAI,SAAS,CAAC,CAAC,CAAC;oBAC9B,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,UAAA,YAAY,IAAI,OAAA,YAAY,CAAC,WAAW,EAAE,EAA1B,CAA0B,CAAC,CAAA;oBAC9E,YAAY,CAAC,aAAa,GAAG,EAAE,CAAA;oBAC/B,YAAY,CAAC,SAAS,GAAG,IAAI,CAAA;oBAC7B,OAAO,CAAC,cAAc,CAAC,oCAAwB,EAAE,YAAY,EAAE,IAAI,CAAC,CAAA;gBACtE,CAAC;gBACD,MAAM,CAAC,iBAAe,IAAI,iBAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACtD,CAAC,CAAA;QACH,CAAC;QAED,YAAY,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,QAAQ,CAAA;QAC9C,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;YAClB,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAA;QAC1E,CAAC;QACD,OAAO,CAAC,cAAc,CAAC,oCAAwB,EAAE,YAAY,EAAE,MAAM,CAAC,CAAA;IACxE,CAAC,CAAA;AACH,CAAC;AA1CD,4BA0CC","file":"bind-data.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/bind-data.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"bindData":{"__symbolic":"function","parameters":["target","key","selector"],"value":{"__symbolic":"error","message":"Function call not supported","line":17,"character":15}},"BindData":{"__symbolic":"function","parameters":["selector","bindImmediate"],"value":{"__symbolic":"error","message":"Function call not supported","line":41,"character":9}}}},{"__symbolic":"module","version":1,"metadata":{"bindData":{"__symbolic":"function","parameters":["target","key","selector"],"value":{"__symbolic":"error","message":"Function call not supported","line":17,"character":15}},"BindData":{"__symbolic":"function","parameters":["selector","bindImmediate"],"value":{"__symbolic":"error","message":"Function call not supported","line":41,"character":9}}}}] -------------------------------------------------------------------------------- /dist/constance.d.ts: -------------------------------------------------------------------------------- 1 | export declare const REFLUX_ACTION_KEY: symbol; 2 | export declare const REFLUX_DATA_BINDINGS_KEY: symbol; 3 | -------------------------------------------------------------------------------- /dist/constance.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.REFLUX_ACTION_KEY = Symbol('reflux:actions'); 4 | exports.REFLUX_DATA_BINDINGS_KEY = Symbol('reflux:dataBindings'); 5 | //# sourceMappingURL=constance.js.map -------------------------------------------------------------------------------- /dist/constance.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/constance.ts"],"names":[],"mappings":";;AAAa,QAAA,iBAAiB,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAA;AAC5C,QAAA,wBAAwB,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAAA","file":"constance.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/constance.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"REFLUX_ACTION_KEY":{"__symbolic":"call","expression":{"__symbolic":"reference","name":"Symbol"},"arguments":["reflux:actions"]},"REFLUX_DATA_BINDINGS_KEY":{"__symbolic":"call","expression":{"__symbolic":"reference","name":"Symbol"},"arguments":["reflux:dataBindings"]}}},{"__symbolic":"module","version":1,"metadata":{"REFLUX_ACTION_KEY":{"__symbolic":"call","expression":{"__symbolic":"reference","name":"Symbol"},"arguments":["reflux:actions"]},"REFLUX_DATA_BINDINGS_KEY":{"__symbolic":"call","expression":{"__symbolic":"reference","name":"Symbol"},"arguments":["reflux:dataBindings"]}}}] -------------------------------------------------------------------------------- /dist/data-observer.d.ts: -------------------------------------------------------------------------------- 1 | import { OnDestroy, OnInit } from '@angular/core'; 2 | /** 3 | * Every component that uses `@BindAction` must extends from this 4 | * class in order to make sure that AOT won't delete OnInit and 5 | * OnDestroy life-cycle events used by the decorator, irrespective 6 | * of the fact that it may or may not be used by the component itself 7 | * 8 | * @export 9 | * @class DataObserver 10 | * @implements {OnInit} 11 | * @implements {OnDestroy} 12 | */ 13 | export declare class DataObserver implements OnInit, OnDestroy { 14 | ngOnInit(): void; 15 | ngOnDestroy(): void; 16 | } 17 | -------------------------------------------------------------------------------- /dist/data-observer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * Every component that uses `@BindAction` must extends from this 5 | * class in order to make sure that AOT won't delete OnInit and 6 | * OnDestroy life-cycle events used by the decorator, irrespective 7 | * of the fact that it may or may not be used by the component itself 8 | * 9 | * @export 10 | * @class DataObserver 11 | * @implements {OnInit} 12 | * @implements {OnDestroy} 13 | */ 14 | var DataObserver = (function () { 15 | function DataObserver() { 16 | } 17 | DataObserver.prototype.ngOnInit = function () { 18 | // implementation will be injected by @BindData decorator 19 | }; 20 | DataObserver.prototype.ngOnDestroy = function () { 21 | // implementation will be injected by @BindData decorator 22 | }; 23 | return DataObserver; 24 | }()); 25 | exports.DataObserver = DataObserver; 26 | //# sourceMappingURL=data-observer.js.map -------------------------------------------------------------------------------- /dist/data-observer.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/data-observer.ts"],"names":[],"mappings":";;AAEA;;;;;;;;;;GAUG;AACH;IAAA;IASA,CAAC;IAPC,+BAAQ,GAAR;QACE,yDAAyD;IAC3D,CAAC;IAED,kCAAW,GAAX;QACE,yDAAyD;IAC3D,CAAC;IACH,mBAAC;AAAD,CATA,AASC,IAAA;AATY,oCAAY","file":"data-observer.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/data-observer.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"DataObserver":{"__symbolic":"class","members":{"ngOnInit":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}]}}}},{"__symbolic":"module","version":1,"metadata":{"DataObserver":{"__symbolic":"class","members":{"ngOnInit":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}]}}}}] -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './action'; 2 | export * from './bind-action'; 3 | export * from './bind-data'; 4 | export * from './data-observer'; 5 | export * from './init'; 6 | export * from './replaceable-state'; 7 | export * from './state'; 8 | export * from './state-selector'; 9 | export * from './store'; 10 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | __export(require("./action")); 7 | __export(require("./bind-action")); 8 | __export(require("./bind-data")); 9 | __export(require("./data-observer")); 10 | __export(require("./init")); 11 | __export(require("./replaceable-state")); 12 | __export(require("./state")); 13 | __export(require("./store")); 14 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,8BAAwB;AACxB,mCAA6B;AAC7B,iCAA2B;AAC3B,qCAA+B;AAC/B,4BAAsB;AACtB,yCAAmC;AACnC,6BAAuB;AAEvB,6BAAuB","file":"index.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/index.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{},"exports":[{"from":"./action"},{"from":"./bind-action"},{"from":"./bind-data"},{"from":"./data-observer"},{"from":"./init"},{"from":"./replaceable-state"},{"from":"./state"},{"from":"./state-selector"},{"from":"./store"}]},{"__symbolic":"module","version":1,"metadata":{},"exports":[{"from":"./action"},{"from":"./bind-action"},{"from":"./bind-data"},{"from":"./data-observer"},{"from":"./init"},{"from":"./replaceable-state"},{"from":"./state"},{"from":"./state-selector"},{"from":"./store"}]}] -------------------------------------------------------------------------------- /dist/init.d.ts: -------------------------------------------------------------------------------- 1 | export interface InitOptions { 2 | hotLoad?: boolean; 3 | domain?: string; 4 | } 5 | export declare function initialize(initialState: any, options?: InitOptions): void; 6 | -------------------------------------------------------------------------------- /dist/init.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Immutable = require("seamless-immutable"); 4 | var state_1 = require("./state"); 5 | function initialize(initialState, options) { 6 | options = options || { domain: 'default' }; 7 | var cacheKey = "reflux-cache:" + options.domain; 8 | if (options.hotLoad) { 9 | // for dev builds 10 | state_1.State.next(Immutable.from(JSON.parse(localStorage.getItem(cacheKey) || 'null') || initialState)); 11 | state_1.State.subscribe(function (state) { return localStorage.setItem(cacheKey, JSON.stringify(state)); }, function (error) { return console.error(error); }, undefined); 12 | } 13 | else if (initialState != undefined) { 14 | // for production 15 | state_1.State.next(Immutable.from(initialState)); 16 | } 17 | } 18 | exports.initialize = initialize; 19 | //# sourceMappingURL=init.js.map -------------------------------------------------------------------------------- /dist/init.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/init.ts"],"names":[],"mappings":";;AAAA,8CAA+C;AAE/C,iCAA+B;AAO/B,oBAA2B,YAAiB,EAAE,OAAqB;IAEjE,OAAO,GAAG,OAAO,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IAE1C,IAAM,QAAQ,GAAG,kBAAgB,OAAO,CAAC,MAAQ,CAAA;IAEjD,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QACpB,iBAAiB;QACjB,aAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAClC,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,MAAM,CACzC,IAAI,YAAY,CAAC,CAAC,CAAA;QAEnB,aAAK,CAAC,SAAS,CACb,UAAA,KAAK,IAAI,OAAA,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAArD,CAAqD,EAC9D,UAAA,KAAK,IAAI,OAAA,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAApB,CAAoB,EAAE,SAAS,CACzC,CAAA;IAEH,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,CAAC,YAAY,IAAI,SAAS,CAAC,CAAC,CAAC;QACrC,iBAAiB;QACjB,aAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAA;IAC1C,CAAC;AAEH,CAAC;AAtBD,gCAsBC","file":"init.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/init.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"InitOptions":{"__symbolic":"interface"},"initialize":{"__symbolic":"function"}}},{"__symbolic":"module","version":1,"metadata":{"InitOptions":{"__symbolic":"interface"},"initialize":{"__symbolic":"function"}}}] -------------------------------------------------------------------------------- /dist/observers.d.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './action'; 2 | import { Observable } from 'rxjs/Observable'; 3 | /** 4 | * Observer for next value from observable (used by subscribe() function) 5 | * 6 | * @export 7 | * @interface ActionObserver 8 | */ 9 | export interface ActionObserver { 10 | (state: any, action: Action): Observable; 11 | } 12 | /** 13 | * Observer for an error from observable (used by subscribe() function) 14 | * 15 | * @export 16 | * @interface ErrorObserver 17 | */ 18 | export interface ErrorObserver { 19 | (error: any): void; 20 | } 21 | -------------------------------------------------------------------------------- /dist/observers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=observers.js.map -------------------------------------------------------------------------------- /dist/observers.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"observers.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/observers.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"ActionObserver":{"__symbolic":"interface"},"ErrorObserver":{"__symbolic":"interface"}}},{"__symbolic":"module","version":1,"metadata":{"ActionObserver":{"__symbolic":"interface"},"ErrorObserver":{"__symbolic":"interface"}}}] -------------------------------------------------------------------------------- /dist/replaceable-state.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents replaceable state 3 | * 4 | * @export 5 | * @class ReplaceableState 6 | */ 7 | export declare class ReplaceableState { 8 | state: any; 9 | constructor(state: any); 10 | } 11 | -------------------------------------------------------------------------------- /dist/replaceable-state.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * Represents replaceable state 5 | * 6 | * @export 7 | * @class ReplaceableState 8 | */ 9 | var ReplaceableState = (function () { 10 | function ReplaceableState(state) { 11 | this.state = state; 12 | } 13 | return ReplaceableState; 14 | }()); 15 | exports.ReplaceableState = ReplaceableState; 16 | //# sourceMappingURL=replaceable-state.js.map -------------------------------------------------------------------------------- /dist/replaceable-state.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/replaceable-state.ts"],"names":[],"mappings":";;AACA;;;;;GAKG;AACH;IACE,0BAAmB,KAAU;QAAV,UAAK,GAAL,KAAK,CAAK;IAAI,CAAC;IACpC,uBAAC;AAAD,CAFA,AAEC,IAAA;AAFY,4CAAgB","file":"replaceable-state.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/replaceable-state.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"ReplaceableState":{"__symbolic":"class","members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","name":"any"}]}]}}}},{"__symbolic":"module","version":1,"metadata":{"ReplaceableState":{"__symbolic":"class","members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","name":"any"}]}]}}}}] -------------------------------------------------------------------------------- /dist/state-selector.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * State selector function 3 | * 4 | * @export 5 | * @interface StateSelector 6 | * @template T 7 | */ 8 | export interface StateSelector { 9 | (state: any): any; 10 | } 11 | -------------------------------------------------------------------------------- /dist/state-selector.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=state-selector.js.map -------------------------------------------------------------------------------- /dist/state-selector.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"state-selector.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/state-selector.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"StateSelector":{"__symbolic":"interface"}}},{"__symbolic":"module","version":1,"metadata":{"StateSelector":{"__symbolic":"interface"}}}] -------------------------------------------------------------------------------- /dist/state.d.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { StateSelector } from './state-selector'; 3 | import { Subscription } from 'rxjs/Subscription'; 4 | /** 5 | * Defines a stream for changing state in a reflux application 6 | * 7 | * @example 8 | * 9 | * // replace state 10 | * State.next(state) 11 | * 12 | * // subscribe to state stream 13 | * State.subscribe((state: State) => { 14 | * // do your action here 15 | * }) 16 | * 17 | * // or listen to a portion of the state 18 | * State 19 | * .select((state: State) => state.application.pageContainer) 20 | * .subscribe((state: State) => { 21 | * // do your action here 22 | * }) 23 | * 24 | * @export 25 | * @class StateStream 26 | * @extends {BehaviorSubject} 27 | */ 28 | export declare class State { 29 | private static state; 30 | private currentState; 31 | private subject; 32 | static readonly current: any; 33 | /** 34 | * Publish next state 35 | * @param state 36 | */ 37 | static next(state: any): void; 38 | /** 39 | * Subscribe to the stream 40 | * @param onNext 41 | * @param onError 42 | * @param onComplete 43 | */ 44 | static subscribe(onNext: any, onError: any, onComplete: any): Subscription; 45 | /** 46 | * Fires 'next' only when the value returned by this function changed from the previous value. 47 | * 48 | * @template T 49 | * @param {StateSelector} selector 50 | * @returns {Observable} 51 | */ 52 | static select(selector: StateSelector): Observable; 53 | constructor(); 54 | } 55 | -------------------------------------------------------------------------------- /dist/state.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Immutable = require("seamless-immutable"); 4 | var BehaviorSubject_1 = require("rxjs/BehaviorSubject"); 5 | var core_1 = require("@angular/core"); 6 | var Observable_1 = require("rxjs/Observable"); 7 | /** 8 | * Defines a stream for changing state in a reflux application 9 | * 10 | * @example 11 | * 12 | * // replace state 13 | * State.next(state) 14 | * 15 | * // subscribe to state stream 16 | * State.subscribe((state: State) => { 17 | * // do your action here 18 | * }) 19 | * 20 | * // or listen to a portion of the state 21 | * State 22 | * .select((state: State) => state.application.pageContainer) 23 | * .subscribe((state: State) => { 24 | * // do your action here 25 | * }) 26 | * 27 | * @export 28 | * @class StateStream 29 | * @extends {BehaviorSubject} 30 | */ 31 | var State = (function () { 32 | function State() { 33 | this.currentState = Immutable.from({}); 34 | this.subject = new BehaviorSubject_1.BehaviorSubject(this.currentState); 35 | } 36 | Object.defineProperty(State, "current", { 37 | get: function () { 38 | return State.state.currentState; 39 | }, 40 | enumerable: true, 41 | configurable: true 42 | }); 43 | /** 44 | * Publish next state 45 | * @param state 46 | */ 47 | State.next = function (state) { 48 | State.state.subject.next(state); 49 | }; 50 | /** 51 | * Subscribe to the stream 52 | * @param onNext 53 | * @param onError 54 | * @param onComplete 55 | */ 56 | State.subscribe = function (onNext, onError, onComplete) { 57 | return State.state.subject.subscribe(onNext, onError, onComplete); 58 | }; 59 | /** 60 | * Fires 'next' only when the value returned by this function changed from the previous value. 61 | * 62 | * @template T 63 | * @param {StateSelector} selector 64 | * @returns {Observable} 65 | */ 66 | State.select = function (selector) { 67 | var _this = this; 68 | return Observable_1.Observable.create(function (subscriber) { 69 | var previousState; 70 | var subscription = _this.subscribe(function (state) { 71 | var selection = select(state, selector); 72 | if (selection !== select(previousState, selector)) { 73 | previousState = state; 74 | subscriber.next(selection); 75 | } 76 | }, function (error) { return subscriber.error(error); }, function () { return subscriber.complete(); }); 77 | return subscription; 78 | }).share(); 79 | }; 80 | return State; 81 | }()); 82 | State.state = new State(); 83 | State.decorators = [ 84 | { type: core_1.Injectable }, 85 | ]; 86 | /** @nocollapse */ 87 | State.ctorParameters = function () { return []; }; 88 | exports.State = State; 89 | /** 90 | * Run selector function on the given state and return it's result. Return undefined if an error occurred 91 | * 92 | * @param {*} state 93 | * @param {StateSelector} selector 94 | * @returns The value return by the selector, undefined if an error occurred. 95 | */ 96 | function select(state, selector) { 97 | if (state == undefined) 98 | return; 99 | if (selector == undefined) 100 | return state; 101 | try { 102 | return selector(state); 103 | } 104 | catch (error) { 105 | return undefined; 106 | } 107 | } 108 | //# sourceMappingURL=state.js.map -------------------------------------------------------------------------------- /dist/state.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/state.ts"],"names":[],"mappings":";;AAAA,8CAA2B;AAE3B,wDAAgC;AAChC,sCAA2B;AAC3B,8CAA2B;AAI3B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;IAwDE;QACE,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC,IAAI,CAAM,EAAE,CAAC,CAAA;QAC3C,IAAI,CAAC,OAAO,GAAG,IAAI,iCAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IACvD,CAAC;IApDD,sBAAW,gBAAO;aAAlB;YACE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAA;QACjC,CAAC;;;OAAA;IAED;;;OAGG;IACI,UAAI,GAAX,UAAY,KAAK;QACf,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACjC,CAAC;IAED;;;;;OAKG;IACI,eAAS,GAAhB,UAAiB,MAAM,EAAE,OAAO,EAAE,UAAU;QAC1C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAA;IACnE,CAAC;IAED;;;;;;OAMG;IACI,YAAM,GAAb,UAAc,QAAuB;QAArC,iBAkBC;QAhBC,MAAM,CAAC,uBAAU,CAAC,MAAM,CAAC,UAAA,UAAU;YACjC,IAAI,aAAkB,CAAA;YACtB,IAAI,YAAY,GAAG,KAAI,CAAC,SAAS,CAC/B,UAAA,KAAK;gBACH,IAAI,SAAS,GAAG,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;gBACvC,EAAE,CAAC,CAAC,SAAS,KAAK,MAAM,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;oBAClD,aAAa,GAAG,KAAK,CAAA;oBACrB,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;gBAC5B,CAAC;YACH,CAAC,EACD,UAAA,KAAK,IAAI,OAAA,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,EAAvB,CAAuB,EAChC,cAAM,OAAA,UAAU,CAAC,QAAQ,EAAE,EAArB,CAAqB,CAC5B,CAAA;YAED,MAAM,CAAC,YAAY,CAAA;QACrB,CAAC,CAAC,CAAC,KAAK,EAAE,CAAA;IACZ,CAAC;IAaH,YAAC;AAAD,CAnEA,AAmEC;AAjEgB,WAAK,GAAU,IAAI,KAAK,EAAE,CAAA;AA2DpC,gBAAU,GAA0B;IAC3C,EAAE,IAAI,EAAE,iBAAU,EAAE;CACnB,CAAC;AACF,kBAAkB;AACX,oBAAc,GAAmE,cAAM,OAAA,EAC7F,EAD6F,CAC7F,CAAC;AAlEW,sBAAK;AAqElB;;;;;;GAMG;AACH,gBANgB,KAAO,EAAK,QAAU;IAOpC,EAAE,CAAC,CAAC,KANC,IAAQ,SAAA,CAAU;QAAC,MAAA,CAAA;IAOxB,EAAE,CAAC,CAAC,QANC,IAAW,SAAA,CAAU;QAAC,MAAA,CAAO,KAAA,CAAA;IAOlC,IANI,CAAA;QAOF,MAAM,CANC,QAAA,CAAS,KAAC,CAAK,CAAA;IAOxB,CAAC;IANC,KAAA,CAAA,CAAA,KAAQ,CAAK,CAAC,CAAA;QAOd,MAAM,CANC,SAAA,CAAA;IAOT,CAAC;AACH,CAAC","file":"state.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/state.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"State":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable"}}],"members":{"__ctor__":[{"__symbolic":"constructor"}]},"statics":{"state":{"__symbolic":"new","expression":{"__symbolic":"reference","name":"State"}},"current":{"__symbolic":"error","message":"Variable not initialized","line":40,"character":13},"subscribe":{"__symbolic":"function","parameters":["onNext","onError","onComplete"],"value":{"__symbolic":"call","expression":{"__symbolic":"select","expression":{"__symbolic":"select","expression":{"__symbolic":"select","expression":{"__symbolic":"reference","name":"State"},"member":"state"},"member":"subject"},"member":"subscribe"},"arguments":[{"__symbolic":"reference","name":"onNext"},{"__symbolic":"reference","name":"onError"},{"__symbolic":"reference","name":"onComplete"}]}},"select":{"__symbolic":"function","parameters":["selector"],"value":{"__symbolic":"error","message":"Function call not supported","line":71,"character":29}}}}}},{"__symbolic":"module","version":1,"metadata":{"State":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable"}}],"members":{"__ctor__":[{"__symbolic":"constructor"}]},"statics":{"state":{"__symbolic":"new","expression":{"__symbolic":"reference","name":"State"}},"current":{"__symbolic":"error","message":"Variable not initialized","line":40,"character":13},"subscribe":{"__symbolic":"function","parameters":["onNext","onError","onComplete"],"value":{"__symbolic":"call","expression":{"__symbolic":"select","expression":{"__symbolic":"select","expression":{"__symbolic":"select","expression":{"__symbolic":"reference","name":"State"},"member":"state"},"member":"subject"},"member":"subscribe"},"arguments":[{"__symbolic":"reference","name":"onNext"},{"__symbolic":"reference","name":"onError"},{"__symbolic":"reference","name":"onComplete"}]}},"select":{"__symbolic":"function","parameters":["selector"],"value":{"__symbolic":"error","message":"Function call not supported","line":71,"character":29}}}}}}] -------------------------------------------------------------------------------- /dist/store.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extend this class to create a store 3 | * 4 | * @export 5 | * @class Store 6 | */ 7 | export declare class Store { 8 | constructor(); 9 | } 10 | -------------------------------------------------------------------------------- /dist/store.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var constance_1 = require("./constance"); 4 | /** 5 | * Extend this class to create a store 6 | * 7 | * @export 8 | * @class Store 9 | */ 10 | var Store = (function () { 11 | function Store() { 12 | var _this = this; 13 | if (!Reflect.hasMetadata(constance_1.REFLUX_ACTION_KEY, this)) 14 | return; 15 | var refluxActions = Reflect.getMetadata(constance_1.REFLUX_ACTION_KEY, this); 16 | Object.keys(refluxActions).forEach(function (name) { return new refluxActions[name]().subscribe(_this[name], _this); }); 17 | } 18 | return Store; 19 | }()); 20 | exports.Store = Store; 21 | //# sourceMappingURL=store.js.map -------------------------------------------------------------------------------- /dist/store.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/store.ts"],"names":[],"mappings":";;AAAA,yCAA+C;AAO/C;;;;;GAKG;AACH;IACE;QAAA,iBAIC;QAHC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,6BAAiB,EAAE,IAAI,CAAC,CAAC;YAAC,MAAM,CAAA;QACzD,IAAI,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,6BAAiB,EAAE,IAAI,CAAC,CAAA;QAChE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,UAAA,IAAI,IAAI,OAAA,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,KAAI,CAAC,IAAI,CAAC,EAAE,KAAI,CAAC,EAArD,CAAqD,CAAC,CAAA;IACnG,CAAC;IACH,YAAC;AAAD,CANA,AAMC,IAAA;AANY,sBAAK","file":"store.js","sourceRoot":""} -------------------------------------------------------------------------------- /dist/store.metadata.json: -------------------------------------------------------------------------------- 1 | [{"__symbolic":"module","version":3,"metadata":{"Store":{"__symbolic":"class","members":{"__ctor__":[{"__symbolic":"constructor"}]}}}},{"__symbolic":"module","version":1,"metadata":{"Store":{"__symbolic":"class","members":{"__ctor__":[{"__symbolic":"constructor"}]}}}}] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-reflux", 3 | "version": "1.1.4", 4 | "description": "A uni-directional (flux-like) data flow architecture using immutability and observables.", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "npm run clean && npm run ngc --", 9 | "lint": "tslint src/**/*.ts", 10 | "prepublish": "npm run build", 11 | "start": "npm run watch", 12 | "test": "npm test", 13 | "clean": "rimraf dist && rimraf compiled", 14 | "tsc": "node_modules/typescript/bin/tsc", 15 | "tslint": "tslint", 16 | "ngc": "node_modules/.bin/ngc", 17 | "watch": "npm run tsc -- -w" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/rintoj/angular-reflux.git" 22 | }, 23 | "keywords": [ 24 | "angular 2", 25 | "angular", 26 | "flux", 27 | "reflux", 28 | "immutable", 29 | "observable", 30 | "redux" 31 | ], 32 | "author": "Rinto Jose", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/rintoj/angular-reflux/issues" 36 | }, 37 | "homepage": "https://github.com/rintoj/angular-reflux#readme", 38 | "devDependencies": { 39 | "@angular/cli": "^1.0.4", 40 | "@angular/common": "2.4.10", 41 | "@angular/compiler": "2.4.10", 42 | "@angular/compiler-cli": "^4.1.3", 43 | "@angular/core": "2.4.10", 44 | "@angular/platform-browser": "2.4.10", 45 | "@angular/platform-browser-dynamic": "2.4.10", 46 | "@types/node": "6.0.45", 47 | "@types/seamless-immutable": "^6.1.2", 48 | "rimraf": "^2.6.1", 49 | "rxjs": "5.2.0", 50 | "tslint": "4.5.1", 51 | "typescript": "2.2.1", 52 | "zone.js": "0.8.5" 53 | }, 54 | "dependencies": { 55 | "seamless-immutable": "7.1.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/action.ts: -------------------------------------------------------------------------------- 1 | import 'rxjs/add/observable/empty' 2 | import 'rxjs/add/observable/from' 3 | import 'rxjs/add/operator/catch' 4 | import 'rxjs/add/operator/map' 5 | import 'rxjs/add/operator/mergeMap' 6 | import 'rxjs/add/operator/share' 7 | import 'rxjs/add/operator/skipWhile' 8 | 9 | import * as Immutable from 'seamless-immutable' 10 | 11 | import { ActionObserver } from './observers' 12 | import { Observable } from 'rxjs/Observable' 13 | import { Observer } from 'rxjs/Observer' 14 | import { ReplaceableState } from './replaceable-state' 15 | import { State } from './state' 16 | 17 | /** 18 | * Defines an action which an be extended to implement custom actions for a reflux application 19 | * 20 | * @example 21 | * 22 | * // Create your own action class 23 | * class PageSwitchAction extends Action { 24 | * constructor(public pageId: string) { 25 | * super() 26 | * } 27 | * } 28 | * 29 | * // Subscribe to your action 30 | * new PageSwitchAction(undefined).subscribe((state: State, action: PageSwitchAction): Observable => { 31 | * return Observable.create((observer: Observer) => { 32 | * observer.next(updatedState) 33 | * observer.complete() 34 | * }).share() 35 | * }, this) 36 | * 37 | * // Dispatch your action 38 | * new PageSwitchAction('page1').dispatch() 39 | * 40 | * @export 41 | * @class Action 42 | */ 43 | export class Action { 44 | 45 | private static _lastAction: Action 46 | private static identities: any[] = [] 47 | private static subscriptions: any[] = [] 48 | 49 | /** 50 | * The last action occurred 51 | * 52 | * @readonly 53 | * @static 54 | * 55 | * @memberOf Action 56 | */ 57 | public static get lastAction() { 58 | return Action.lastAction 59 | } 60 | 61 | /** 62 | * Returns identity of this class 63 | * 64 | * @readonly 65 | * @type {string} 66 | */ 67 | get identity(): string { 68 | let id = Action.identities.indexOf(this.constructor) 69 | if (id < 0) { 70 | Action.identities.push(this.constructor) 71 | id = Action.identities.indexOf(this.constructor) 72 | } 73 | return `c${id}` 74 | } 75 | 76 | /** 77 | * Subscribe to this action. actionObserver will be called when 'dispatch()' is invoked 78 | * 79 | * @param {ActionObserver} actionObserver The function that process the action 80 | * @param {*} context Context binding 81 | * @returns {Action} 82 | */ 83 | public subscribe(actionObserver: ActionObserver, context: any): Action { 84 | if (!Action.subscriptions[this.identity]) { 85 | Action.subscriptions[this.identity] = [] 86 | } 87 | Action.subscriptions[this.identity].push(actionObserver.bind(context)) 88 | return this 89 | } 90 | 91 | /** 92 | * Dispatch this action. Returns an observable which will be completed when all action subscribers 93 | * complete it's processing 94 | * 95 | * @returns {Observable} 96 | */ 97 | dispatch(): Promise { 98 | 99 | Action._lastAction = this 100 | let subscriptions: ActionObserver[] = Action.subscriptions[this.identity] 101 | if (subscriptions == undefined || subscriptions.length === 0) { 102 | return new Promise(resolve => resolve()) 103 | } 104 | 105 | let observable: Observable = Observable.from(subscriptions) 106 | 107 | // convert 'Observable' returned by action subscribers to state 108 | .flatMap((actionObserver: ActionObserver): Observable => { 109 | const result = actionObserver(State.current, this) 110 | if (!(result instanceof Observable || result instanceof Promise)) { 111 | return Observable.create((observer: Observer) => { 112 | observer.next(result) 113 | observer.complete() 114 | }) 115 | } 116 | return result 117 | }) 118 | 119 | // merge or replace state 120 | .map((state: any) => { 121 | if (state instanceof ReplaceableState) { 122 | // replace the state with the new one if not 'undefined' 123 | return Immutable.from((state as ReplaceableState).state || {}) 124 | } else if (state != undefined) { 125 | // merge the state with existing state 126 | return State.current.merge(state, { deep: true }) 127 | } 128 | }) 129 | 130 | // wait until all the subscripts have completed processing 131 | .skipWhile((state: any, i: number) => i + 1 < subscriptions.length) 132 | 133 | // push 'next' state to 'stateStream' if there has been a change to the state 134 | .map((state: any) => { 135 | if (state != undefined) { 136 | State.next(state) 137 | } 138 | return state 139 | }) 140 | 141 | // catch any error occurred 142 | .catch((error: any): any => Observable.empty()) 143 | 144 | // make this sharable (to avoid multiple copies of this observable being created) 145 | .share() 146 | 147 | return new Promise((resolve, reject) => { 148 | // to trigger observable 149 | observable.subscribe(() => { 150 | // empty function 151 | }, reject, () => resolve(State.current)) 152 | }) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/bind-action.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './action' 2 | import { Observable } from 'rxjs/Observable' 3 | import { REFLUX_ACTION_KEY } from './constance' 4 | 5 | declare var Reflect: any 6 | 7 | /** 8 | * Decorator for defining an action handler 9 | * 10 | * @example 11 | * @BindAction() 12 | * addTodo(state: State, action: AddTodoAction): Observable { 13 | * return Observable.create((observer: Observer) => { 14 | * observer.next({ 15 | * todos: state.todos.concat([action.todo]) 16 | * }) 17 | * observer.complete() 18 | * }).share() 19 | * } 20 | * 21 | * @export 22 | * @template S 23 | * @returns 24 | */ 25 | export function BindAction() { 26 | 27 | return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { 28 | 29 | let metadata = Reflect.getMetadata('design:paramtypes', target, propertyKey) 30 | if (metadata.length < 2) throw new Error('BindAction: function must have two arguments!') 31 | 32 | let refluxActions = {} 33 | if (Reflect.hasMetadata(REFLUX_ACTION_KEY, target)) { 34 | refluxActions = Reflect.getMetadata(REFLUX_ACTION_KEY, target) 35 | } 36 | refluxActions[propertyKey] = metadata[1] 37 | Reflect.defineMetadata(REFLUX_ACTION_KEY, refluxActions, target) 38 | 39 | return { 40 | value: function (state: any, action: Action): Observable { 41 | return descriptor.value.call(this, state, action) 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/bind-data.ts: -------------------------------------------------------------------------------- 1 | import { REFLUX_DATA_BINDINGS_KEY } from './constance' 2 | import { State } from './state' 3 | import { StateSelector } from './state-selector' 4 | import { Subscription } from 'rxjs/Subscription' 5 | 6 | declare var Reflect: any 7 | 8 | /** 9 | * Bind data for give key and target using a selector function 10 | * 11 | * @param {any} target 12 | * @param {any} key 13 | * @param {any} selectorFunc 14 | */ 15 | export function bindData(target: any, key: string, selector: StateSelector): Subscription { 16 | return State 17 | .select(selector) 18 | .subscribe(data => { 19 | if (typeof target[key] === 'function') return target[key].call(target, data) 20 | target[key] = data 21 | }) 22 | } 23 | 24 | /** 25 | * Bind data to a variable or to a function 26 | * 27 | * @example 28 | * @BindData(state => state.todos) 29 | * todos: Todo[] 30 | * 31 | * @BindDAta(state => state.todos) 32 | * todosDidChange(todos: Todo[]) { 33 | * // your logic 34 | * } 35 | * 36 | * 37 | * @export 38 | * @param {*} selector 39 | * @returns 40 | */ 41 | export function BindData(selector: StateSelector, bindImmediate?: boolean) { 42 | return (target: any, propertyKey: string) => { 43 | 44 | let bindingsMeta = Reflect.getMetadata(REFLUX_DATA_BINDINGS_KEY, target) 45 | if (!Reflect.hasMetadata(REFLUX_DATA_BINDINGS_KEY, target)) { 46 | bindingsMeta = { selectors: {}, subscriptions: [], destroyed: !bindImmediate } 47 | 48 | let originalInit = target.ngOnInit 49 | target.ngOnInit = function ngOnInit() { 50 | let dataBindings = Reflect.getMetadata(REFLUX_DATA_BINDINGS_KEY, this) 51 | if (dataBindings != undefined && dataBindings.destroyed === true) { 52 | 53 | dataBindings.subscriptions = dataBindings.subscriptions.concat( 54 | Object.keys(dataBindings.selectors) 55 | .map(key => bindData(this, key, dataBindings.selectors[key])) 56 | ) 57 | 58 | dataBindings.destroyed = false 59 | Reflect.defineMetadata(REFLUX_DATA_BINDINGS_KEY, dataBindings, this) 60 | } 61 | return originalInit && originalInit.call(this) 62 | } 63 | 64 | let originalDestroy = target.ngOnDestroy 65 | target.ngOnDestroy = function ngOnDestroy() { 66 | let dataBindings = Reflect.getMetadata(REFLUX_DATA_BINDINGS_KEY, this) 67 | if (dataBindings != undefined) { 68 | dataBindings.subscriptions.forEach(subscription => subscription.unsubscribe()) 69 | dataBindings.subscriptions = [] 70 | dataBindings.destroyed = true 71 | Reflect.defineMetadata(REFLUX_DATA_BINDINGS_KEY, dataBindings, this) 72 | } 73 | return originalDestroy && originalDestroy.call(this) 74 | } 75 | } 76 | 77 | bindingsMeta.selectors[propertyKey] = selector 78 | if (bindImmediate) { 79 | bindingsMeta.subscriptions.push(bindData(target, propertyKey, selector)) 80 | } 81 | Reflect.defineMetadata(REFLUX_DATA_BINDINGS_KEY, bindingsMeta, target) 82 | } 83 | } -------------------------------------------------------------------------------- /src/constance.ts: -------------------------------------------------------------------------------- 1 | export const REFLUX_ACTION_KEY = Symbol('reflux:actions') 2 | export const REFLUX_DATA_BINDINGS_KEY = Symbol('reflux:dataBindings') -------------------------------------------------------------------------------- /src/data-observer.ts: -------------------------------------------------------------------------------- 1 | import { OnDestroy, OnInit } from '@angular/core' 2 | 3 | /** 4 | * Every component that uses `@BindAction` must extends from this 5 | * class in order to make sure that AOT won't delete OnInit and 6 | * OnDestroy life-cycle events used by the decorator, irrespective 7 | * of the fact that it may or may not be used by the component itself 8 | * 9 | * @export 10 | * @class DataObserver 11 | * @implements {OnInit} 12 | * @implements {OnDestroy} 13 | */ 14 | export class DataObserver implements OnInit, OnDestroy { 15 | 16 | ngOnInit() { 17 | // implementation will be injected by @BindData decorator 18 | } 19 | 20 | ngOnDestroy() { 21 | // implementation will be injected by @BindData decorator 22 | } 23 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './action' 2 | export * from './bind-action' 3 | export * from './bind-data' 4 | export * from './data-observer' 5 | export * from './init' 6 | export * from './replaceable-state' 7 | export * from './state' 8 | export * from './state-selector' 9 | export * from './store' -------------------------------------------------------------------------------- /src/init.ts: -------------------------------------------------------------------------------- 1 | import * as Immutable from 'seamless-immutable' 2 | 3 | import { State } from './state' 4 | 5 | export interface InitOptions { 6 | hotLoad?: boolean 7 | domain?: string 8 | } 9 | 10 | export function initialize(initialState: any, options?: InitOptions) { 11 | 12 | options = options || { domain: 'default' } 13 | 14 | const cacheKey = `reflux-cache:${options.domain}` 15 | 16 | if (options.hotLoad) { 17 | // for dev builds 18 | State.next(Immutable.from(JSON.parse( 19 | localStorage.getItem(cacheKey) || 'null' 20 | ) || initialState)) 21 | 22 | State.subscribe( 23 | state => localStorage.setItem(cacheKey, JSON.stringify(state)), 24 | error => console.error(error), undefined 25 | ) 26 | 27 | } else if (initialState != undefined) { 28 | // for production 29 | State.next(Immutable.from(initialState)) 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/observers.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './action' 2 | import { Observable } from 'rxjs/Observable' 3 | 4 | /** 5 | * Observer for next value from observable (used by subscribe() function) 6 | * 7 | * @export 8 | * @interface ActionObserver 9 | */ 10 | export interface ActionObserver { 11 | (state: any, action: Action): Observable 12 | } 13 | 14 | /** 15 | * Observer for an error from observable (used by subscribe() function) 16 | * 17 | * @export 18 | * @interface ErrorObserver 19 | */ 20 | export interface ErrorObserver { 21 | (error: any): void 22 | } 23 | -------------------------------------------------------------------------------- /src/replaceable-state.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Represents replaceable state 4 | * 5 | * @export 6 | * @class ReplaceableState 7 | */ 8 | export class ReplaceableState { 9 | constructor(public state: any) { } 10 | } -------------------------------------------------------------------------------- /src/state-selector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * State selector function 3 | * 4 | * @export 5 | * @interface StateSelector 6 | * @template T 7 | */ 8 | export interface StateSelector { 9 | (state: any): any 10 | } 11 | -------------------------------------------------------------------------------- /src/state.ts: -------------------------------------------------------------------------------- 1 | import * as Immutable from 'seamless-immutable' 2 | 3 | import { BehaviorSubject } from 'rxjs/BehaviorSubject' 4 | import { Injectable } from '@angular/core' 5 | import { Observable } from 'rxjs/Observable' 6 | import { StateSelector } from './state-selector' 7 | import { Subscription } from 'rxjs/Subscription' 8 | 9 | /** 10 | * Defines a stream for changing state in a reflux application 11 | * 12 | * @example 13 | * 14 | * // replace state 15 | * State.next(state) 16 | * 17 | * // subscribe to state stream 18 | * State.subscribe((state: State) => { 19 | * // do your action here 20 | * }) 21 | * 22 | * // or listen to a portion of the state 23 | * State 24 | * .select((state: State) => state.application.pageContainer) 25 | * .subscribe((state: State) => { 26 | * // do your action here 27 | * }) 28 | * 29 | * @export 30 | * @class StateStream 31 | * @extends {BehaviorSubject} 32 | */ 33 | @Injectable() 34 | export class State { 35 | 36 | private static state: State = new State() 37 | 38 | private currentState: any 39 | private subject: BehaviorSubject 40 | 41 | static get current() { 42 | return State.state.currentState 43 | } 44 | 45 | /** 46 | * Publish next state 47 | * @param state 48 | */ 49 | static next(state) { 50 | State.state.subject.next(state) 51 | } 52 | 53 | /** 54 | * Subscribe to the stream 55 | * @param onNext 56 | * @param onError 57 | * @param onComplete 58 | */ 59 | static subscribe(onNext, onError, onComplete): Subscription { 60 | return State.state.subject.subscribe(onNext, onError, onComplete) 61 | } 62 | 63 | /** 64 | * Fires 'next' only when the value returned by this function changed from the previous value. 65 | * 66 | * @template T 67 | * @param {StateSelector} selector 68 | * @returns {Observable} 69 | */ 70 | static select(selector: StateSelector): Observable { 71 | 72 | return Observable.create(subscriber => { 73 | let previousState: any 74 | let subscription = this.subscribe( 75 | state => { 76 | let selection = select(state, selector) 77 | if (selection !== select(previousState, selector)) { 78 | previousState = state 79 | subscriber.next(selection) 80 | } 81 | }, 82 | error => subscriber.error(error), 83 | () => subscriber.complete() 84 | ) 85 | 86 | return subscription 87 | }).share() 88 | } 89 | 90 | constructor() { 91 | this.currentState = Immutable.from({}) 92 | this.subject = new BehaviorSubject(this.currentState) 93 | } 94 | 95 | } 96 | 97 | /** 98 | * Run selector function on the given state and return it's result. Return undefined if an error occurred 99 | * 100 | * @param {*} state 101 | * @param {StateSelector} selector 102 | * @returns The value return by the selector, undefined if an error occurred. 103 | */ 104 | function select(state: any, selector: StateSelector) { 105 | if (state == undefined) return 106 | if (selector == undefined) return state 107 | try { 108 | return selector(state) 109 | } catch (error) { 110 | return undefined 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import { REFLUX_ACTION_KEY } from './constance' 2 | 3 | /** 4 | * Use reflection library 5 | */ 6 | declare var Reflect: any 7 | 8 | /** 9 | * Extend this class to create a store 10 | * 11 | * @export 12 | * @class Store 13 | */ 14 | export class Store { 15 | constructor() { 16 | if (!Reflect.hasMetadata(REFLUX_ACTION_KEY, this)) return 17 | let refluxActions = Reflect.getMetadata(REFLUX_ACTION_KEY, this) 18 | Object.keys(refluxActions).forEach(name => new refluxActions[name]().subscribe(this[name], this)) 19 | } 20 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "declaration": true, 11 | "strictNullChecks": false, 12 | "baseUrl": "./src", 13 | "outDir": "./dist", 14 | "lib": [ 15 | "dom", 16 | "es6" 17 | ], 18 | "types": [ 19 | "node" 20 | ] 21 | }, 22 | "exclude": [ 23 | "compiled", 24 | "demo", 25 | "dist", 26 | "node_modules" 27 | ], 28 | "compileOnSave": false, 29 | "buildOnSave": false, 30 | "angularCompilerOptions": { 31 | "genDir": "compiled" 32 | } 33 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "member-access": false, 4 | "member-ordering": [ 5 | true, 6 | "public-before-private", 7 | "static-before-instance", 8 | "variables-before-functions" 9 | ], 10 | "no-any": false, 11 | "no-inferrable-types": [false], 12 | "no-internal-module": true, 13 | "no-var-requires": true, 14 | "typedef": [false], 15 | "typedef-whitespace": [ 16 | true, { 17 | "call-signature": "nospace", 18 | "index-signature": "nospace", 19 | "parameter": "nospace", 20 | "property-declaration": "nospace", 21 | "variable-declaration": "nospace" 22 | }, { 23 | "call-signature": "space", 24 | "index-signature": "space", 25 | "parameter": "space", 26 | "property-declaration": "space", 27 | "variable-declaration": "space" 28 | } 29 | ], 30 | "ban": false, 31 | "curly": false, 32 | "forin": true, 33 | "label-position": true, 34 | "no-arg": true, 35 | "no-bitwise": true, 36 | "no-conditional-assignment": true, 37 | "no-console": [ 38 | true, 39 | "debug", 40 | "info", 41 | "time", 42 | "timeEnd", 43 | "trace" 44 | ], 45 | "no-construct": true, 46 | "no-debugger": true, 47 | "no-duplicate-variable": true, 48 | "no-empty": true, 49 | "no-eval": true, 50 | "no-null-keyword": true, 51 | "no-shadowed-variable": true, 52 | "no-string-literal": true, 53 | "no-switch-case-fall-through": true, 54 | "no-unused-expression": true, 55 | "no-use-before-declare": true, 56 | "no-var-keyword": true, 57 | "radix": true, 58 | "switch-default": true, 59 | "triple-equals": [ 60 | true, 61 | "allow-undefined-check" 62 | ], 63 | "eofline": false, 64 | "indent": [ 65 | true, 66 | "spaces" 67 | ], 68 | "max-line-length": [ 69 | true, 70 | 150 71 | ], 72 | "cyclomatic-complexity": [ 73 | true 74 | ], 75 | "no-require-imports": false, 76 | "no-trailing-whitespace": true, 77 | "object-literal-sort-keys": false, 78 | "trailing-comma": [ 79 | true, { 80 | "multiline": "never", 81 | "singleline": "never" 82 | } 83 | ], 84 | "no-angle-bracket-type-assertion": true, 85 | "only-arrow-functions": [ 86 | false, 87 | "allow-named-functions" 88 | ], 89 | "align": [true], 90 | "class-name": true, 91 | "comment-format": [ 92 | true, 93 | "check-space" 94 | ], 95 | "interface-name": [false], 96 | "jsdoc-format": true, 97 | "no-consecutive-blank-lines": [true], 98 | "no-parameter-properties": false, 99 | "one-line": [ 100 | true, 101 | "check-open-brace", 102 | "check-catch", 103 | "check-else", 104 | "check-finally", 105 | "check-whitespace" 106 | ], 107 | "quotemark": [ 108 | true, 109 | "single", 110 | "avoid-escape" 111 | ], 112 | "semicolon": [ 113 | true, 114 | "never" 115 | ], 116 | "variable-name": [ 117 | true, 118 | "check-format", 119 | "allow-leading-underscore", 120 | "ban-keywords" 121 | ], 122 | "whitespace": [ 123 | true, 124 | "check-branch", 125 | "check-decl", 126 | "check-operator", 127 | "check-separator", 128 | "check-type" 129 | ] 130 | } 131 | } --------------------------------------------------------------------------------