106 | ```
107 |
108 | ----
109 |
110 | ### Lazy Loaded Modules
111 |
112 | [Lazy Loaded Modules](./docs/how-to/use-lazy-loading.md) are also supported.
113 | So you can only initialize the reducer and the state when the respective NgModule is loaded.
114 |
115 | ----
116 |
117 | ### Redux DevTools Extension support
118 |
119 | The [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension) is fully supported and automatically
120 | enabled if your Angular app is running [in dev mode](https://angular.io/api/core/isDevMode).
121 |
122 | ----
123 |
124 | ### What is Redux?
125 |
126 | [Redux](http://redux.js.org/) is a popular and common approach to manage an application state.
127 | The three principles of redux are:
128 |
129 | - [Single source of truth](http://redux.js.org/docs/introduction/ThreePrinciples.html#single-source-of-truth)
130 | - [State is read-only](http://redux.js.org/docs/introduction/ThreePrinciples.html#state-is-read-only)
131 | - [Changes are made with pure functions](http://redux.js.org/docs/introduction/ThreePrinciples.html#changes-are-made-with-pure-functions)
132 |
133 | ----
134 |
135 | ## Installation
136 |
137 | The [redux](https://github.com/reactjs/redux) package itself is not shipped with @harmowatch/ngx-redux-core.
138 | Therefore you also have to install the redux package:
139 |
140 | ```sh
141 | $ npm install redux @harmowatch/ngx-redux-core --save
142 | ```
143 |
144 | ----
145 |
146 | ## Quickstart
147 |
148 | ### 1. Import the root `ReduxModule`:
149 |
150 | As the first step, you need to add `ReduxModule.forRoot()` to the root NgModule of your application.
151 |
152 | The static [`forRoot`](https://angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root) method is a convention
153 | that provides and configures services at the same time. Make sure you call this method only in your root NgModule!
154 |
155 | Please note that [Lazy loading](./docs/how-to/use-lazy-loading.md) is also supported.
156 |
157 | ```ts
158 | import { NgModule } from '@angular/core';
159 | import { BrowserModule } from '@angular/platform-browser';
160 | import { ReduxModule } from '@harmowatch/ngx-redux-core';
161 |
162 | import {YourModuleStateProvider} from '...';
163 | import {TodoListReducer} from '...';
164 |
165 | @NgModule({
166 | imports: [
167 | BrowserModule,
168 | ReduxModule.forRoot({
169 | state: {
170 | provider: YourModuleStateProvider, // You'll create it in step 2
171 | reducers: [ TodoListReducer ], // You'll create it in step 4
172 | }
173 | }),
174 | ],
175 | providers: [
176 | YourModuleStateProvider // You'll create it in step 2
177 | ],
178 | })
179 | export class AppModule {}
180 | ```
181 |
182 | ### 2. Create a state provider
183 |
184 | Now you have to create a provider for your module in order to describe and initialize the state.
185 |
186 | ```ts
187 | import { Injectable } from '@angular/core';
188 | import { ReduxState, ReduxStateProvider } from '@harmowatch/ngx-redux-core';
189 |
190 | export interface YourModuleState {
191 | items: string[];
192 | }
193 |
194 | @Injectable()
195 | @ReduxState({name: 'your-module'}) // Make sure you choose a application-wide unique name
196 | export class YourModuleStateProvider extends ReduxStateProvider {
197 |
198 | getInitialState(): Promise { // You can return Observable or YourModuleState as well
199 | return Promise.resolve({
200 | items: []
201 | });
202 | }}
203 |
204 | }
205 | ```
206 |
207 | > Don't forget to add the state as described in step 1
208 |
209 | You can have just one `ReduxStateProvider` per NgModule. But it's possible to have a state provider for each
210 | [lazy loaded](./docs/how-to/use-lazy-loading.md) module.
211 |
212 | ### 3. Create an action dispatcher
213 |
214 | To initiate a state change, a redux action must be dispatched. Let's assume that there is a component called
215 | `TodoListComponent` that displays a button. Each time the button is clicked, the view calls the function
216 | `addTodo` and passes the todo, which shall be added to the list.
217 |
218 | All you have to do is decorate the function with `@ReduxAction` and return the todo as a return value.
219 |
220 | ```ts
221 | import { Component } from '@angular/core';
222 | import { ReduxAction } from '@harmowatch/ngx-redux-core';
223 |
224 | @Component({templateUrl: './todo-list.component.html'})
225 | export class TodoListComponent {
226 |
227 | @ReduxAction()
228 | addTodo(label: string): string {
229 | return label; // your return value is the payload
230 | }
231 |
232 | }
233 | ```
234 |
235 | Now the following action is dispatched, every time the `addTodo` method was called:
236 |
237 | ```json
238 | {
239 | "type": "addTodo",
240 | "payload": "SampleTodo"
241 | }
242 | ```
243 |
244 | [You can also create a provider to dispatch actions.](./docs/how-to/create-an-actions-provider.md)
245 |
246 | ### 4. Create the reducer
247 |
248 | There's one more thing you need to do. You dispatch an action, but at the moment no reducer is listening to it.
249 | In order to change this, we need to create a reducer function that can make the state change as soon as the action
250 | is fired:
251 |
252 | ```ts
253 | import { ReduxReducer, ReduxActionWithPayload } from '@harmowatch/ngx-redux-core';
254 |
255 | import {TodoListComponent} from '...';
256 |
257 | export class TodoListReducer {
258 |
259 | @ReduxReducer(TodoListComponent.prototype.add)
260 | addTodo(state: TodoState, action: ReduxActionWithPayload): TodoState {
261 | return {
262 | ...state,
263 | items: state.items.concat(action.payload),
264 | };
265 | }
266 |
267 | }
268 | ```
269 |
270 | > Don't forget to add the state as described in step 1
271 |
272 | ### 5. Select values from the state
273 |
274 | To select a state value, you just can use the [reduxSelect](./docs/pipes/redux-select.md) pipe.
275 | But you've several options to select a state value. Please check out the
276 | [Select Pattern](./docs/articles/select-pattern.md) article for more information.
277 |
278 | ```angular2html
279 |
280 |
{{todo}}
281 |
282 | ```
283 |
284 | ----
285 |
286 | ## Documentation
287 |
288 | You'll find the latest docs [here](./docs/index.md).
289 |
290 | ----
291 |
292 | You like the project? Then please give me a Star and add you to the list of [Stargazers](https://github.com/HarmoWatch/ngx-redux-core/stargazers).
293 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "ngx-redux": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "architect": {
11 | "build": {
12 | "builder": "@angular-devkit/build-angular:browser",
13 | "options": {
14 | "outputPath": "dist",
15 | "index": "src/index.html",
16 | "main": "src/main.ts",
17 | "tsConfig": "src/tsconfig.app.json",
18 | "polyfills": "src/polyfills.ts",
19 | "assets": [
20 | "src/assets",
21 | "src/favicon.ico"
22 | ],
23 | "styles": [
24 | "src/styles.css"
25 | ],
26 | "scripts": []
27 | },
28 | "configurations": {
29 | "production": {
30 | "optimization": true,
31 | "outputHashing": "all",
32 | "sourceMap": false,
33 | "extractCss": true,
34 | "namedChunks": false,
35 | "aot": true,
36 | "extractLicenses": true,
37 | "vendorChunk": false,
38 | "buildOptimizer": true,
39 | "fileReplacements": [
40 | {
41 | "replace": "src/environments/environment.ts",
42 | "with": "src/environments/environment.prod.ts"
43 | }
44 | ]
45 | }
46 | }
47 | },
48 | "serve": {
49 | "builder": "@angular-devkit/build-angular:dev-server",
50 | "options": {
51 | "browserTarget": "ngx-redux:build"
52 | },
53 | "configurations": {
54 | "production": {
55 | "browserTarget": "ngx-redux:build:production"
56 | }
57 | }
58 | },
59 | "extract-i18n": {
60 | "builder": "@angular-devkit/build-angular:extract-i18n",
61 | "options": {
62 | "browserTarget": "ngx-redux:build"
63 | }
64 | },
65 | "test": {
66 | "builder": "@angular-devkit/build-angular:karma",
67 | "options": {
68 | "main": "src/test.ts",
69 | "karmaConfig": "./karma.conf.js",
70 | "polyfills": "src/polyfills.ts",
71 | "tsConfig": "src/tsconfig.spec.json",
72 | "scripts": [],
73 | "styles": [
74 | "src/styles.css"
75 | ],
76 | "assets": [
77 | "src/assets",
78 | "src/favicon.ico"
79 | ]
80 | }
81 | },
82 | "lint": {
83 | "builder": "@angular-devkit/build-angular:tslint",
84 | "options": {
85 | "tsConfig": [
86 | "src/tsconfig.app.json",
87 | "src/tsconfig.spec.json"
88 | ],
89 | "exclude": [
90 | "**/node_modules/**"
91 | ]
92 | }
93 | }
94 | }
95 | },
96 | "ngx-redux-e2e": {
97 | "root": "e2e",
98 | "sourceRoot": "e2e",
99 | "projectType": "application",
100 | "architect": {
101 | "e2e": {
102 | "builder": "@angular-devkit/build-angular:protractor",
103 | "options": {
104 | "protractorConfig": "./protractor.conf.js",
105 | "devServerTarget": "ngx-redux:serve"
106 | }
107 | },
108 | "lint": {
109 | "builder": "@angular-devkit/build-angular:tslint",
110 | "options": {
111 | "tsConfig": [
112 | "e2e/tsconfig.e2e.json"
113 | ],
114 | "exclude": [
115 | "**/node_modules/**"
116 | ]
117 | }
118 | }
119 | }
120 | }
121 | },
122 | "defaultProject": "ngx-redux",
123 | "schematics": {
124 | "@schematics/angular:component": {
125 | "prefix": "app",
126 | "styleext": "css"
127 | },
128 | "@schematics/angular:directive": {
129 | "prefix": "app"
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/docs/api/index.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md)
2 |
3 | # API
4 |
5 | - [ReduxStateProvider](./redux-state-provider.md)
6 |
--------------------------------------------------------------------------------
/docs/api/redux-state-provider.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md) / [api](./index.md)
2 |
3 | # ReduxStateProvider
4 |
5 | ### getInitialState
6 |
7 | To describe your module state, you need to create a state provider. This provider has to extend the `ReduxStateProvider`
8 | class and to provide the method `getInitialState`. As initial state you can return a `Promise`, `Observable` or directly
9 | a literal. Please note that the `ReduxStateProvider` is a generic class to which you must pass your state type. The
10 | `getInitialState` method is called by [@harmowatch/ngx-redux-core](../../README.md) during the initialization process.
11 |
12 | ##### `getInitialState` returns a literal
13 |
14 | ```ts
15 | import { Injectable } from '@angular/core';
16 | import { ReduxState, ReduxStateProvider } from '@harmowatch/ngx-redux-core';
17 |
18 | @Injectable()
19 | @ReduxState({name: 'your-module'})
20 | export class YourModuleStateProvider extends ReduxStateProvider {
21 |
22 | getInitialState(): YourModuleState {
23 | return {
24 | items: []
25 | };
26 | }}
27 |
28 | }
29 | ```
30 |
31 | ##### `getInitialState` returns a `Promise`
32 |
33 | The module state is not initialized until your Promise has been *resolved successfully*.
34 | If your `Promise` is rejected, the state will never be initialized!
35 |
36 | ```ts
37 | import { Injectable } from '@angular/core';
38 | import { ReduxState, ReduxStateProvider } from '@harmowatch/ngx-redux-core';
39 |
40 | @Injectable()
41 | @ReduxState({name: 'your-module'})
42 | export class YourModuleStateProvider extends ReduxStateProvider {
43 |
44 | getInitialState(): Promise {
45 | return Promise.resolve({
46 | items: []
47 | });
48 | }}
49 |
50 | }
51 | ```
52 |
53 | ##### `getInitialState` returns a `Observable`
54 |
55 | The module state is initialized *after* the `Observable` has been completed.
56 | If your `Observable` never completes, the state will never be initialized!
57 |
58 | ```ts
59 | import 'rxjs/add/observable/from';
60 |
61 | import { Injectable } from '@angular/core';
62 | import { ReduxState, ReduxStateProvider } from '@harmowatch/ngx-redux-core';
63 |
64 | @Injectable()
65 | @ReduxState({name: 'your-module'})
66 | export class YourModuleStateProvider extends ReduxStateProvider {
67 |
68 | getInitialState(): Observable {
69 | return Observable.from([{
70 | items: []
71 | }]);
72 | }
73 |
74 | }
75 | ```
76 |
77 | ### select
78 |
79 | This method is used by the [reduxSelect](../articles/select-pattern.md#the-reduxselect-pipe) pipe and the
80 | [@ReduxSelect](../articles/select-pattern.md#the-reduxselect-decorator) decorator to resolve the state.
81 | The `ReduxStateProvider`'s implementation caches the `select` calls, and returns the same instance of a
82 | [ReduxSelector](../articles/select-pattern.md) for the same selector string. You can overwrite this method to
83 | implement some custom `select` behavior.
84 |
85 | ### reduce
86 |
87 | This method is called by [@harmowatch/ngx-redux-core](../../README.md) to reduce your module's state. There's already a
88 | default implementation which will forward the reduce command to the appropriate
89 | [@ReduxReducer](../decorators/redux-reducer.md). You can overwrite this method to implement some custom reducer behavior.
90 |
91 | ### getState
92 |
93 | Returns a `Promise` with a snapshot of the state. A basic principle of [rxjs](https://github.com/ReactiveX/rxjs) is that
94 | you should not break out of the event chain, so it is better to use the [select](#select) method. In exceptional cases, the
95 | use of the `getState` method can also make sense. The `getState` method isn't used by [@harmowatch/ngx-redux-core](../../README.md)
96 | internally.
97 |
--------------------------------------------------------------------------------
/docs/articles/index.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md)
2 |
3 | # Articles
4 |
5 | - [Select Pattern](./select-pattern.md)
6 | - [Testing Guide](./testing-guide.md)
7 |
--------------------------------------------------------------------------------
/docs/articles/select-pattern.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [articles](./index.md)
2 |
3 | # The Select Pattern
4 |
5 | The select pattern allows you to get slices of your state as `ReduxSelector`. The `ReduxSelector`
6 | is a class which extends [RxJS](https://github.com/ReactiveX/rxjs)'s `ReplaySubject`. The
7 | `bufferSize` of the `ReplaySubject` is set to 1. The `ReplaySubject` has the advantage that you
8 | get the current state value immediately when subscribing to the `ReduxSelector`.
9 |
10 | Because that the ReduxSelector is based on the `ReplaySubject`, you have all the power of
11 | [RxJS](https://github.com/ReactiveX/rxjs) at your fingertips.
12 |
13 | To select a value, you must always specify a path. That path is separated by a "/". If your selector starts
14 | with a "/" it's called **absolute selector**, otherwise it's an **relative selector**. The difference is that
15 | in case of an **absolute selector** you have to know the name of the [ReduxStateProvider](../api/redux-state-provider.md).
16 |
17 | Therefore it's recommended to use always a **relative selector**, otherwise you will spread the name over the whole
18 | program code. I just wanted to mention the **absolute selector**, so you know it exists.
19 |
20 | Let's suppose, you have defined the following [ReduxStateProvider](../api/redux-state-provider.md):
21 |
22 | ```ts
23 | import { Injectable } from '@angular/core';
24 | import { ReduxState, ReduxStateProvider } from '@harmowatch/ngx-redux-core';
25 |
26 | interface YourModuleState {
27 | todo: {
28 | items: string[];
29 | isFetching: boolean;
30 | }
31 | }
32 |
33 | @Injectable()
34 | @ReduxState({name: 'your-module'})
35 | export class YourModuleStateProvider extends ReduxStateProvider {
36 |
37 | getInitialState(): YourModuleState {
38 | return {
39 | todo: {
40 | items: [],
41 | isFetching: false,
42 | }
43 | };
44 | }}
45 |
46 | }
47 | ```
48 |
49 | Then you can use the following selectors:
50 |
51 | | **relative selector** (recommended) | **absolute selector** (avoid) |
52 | | ----------------------------------- | ------------------------------ |
53 | | "" | "/your-module" |
54 | | "todo" | "/your-module/todo" |
55 | | "todo/items" | "/your-module/todo/items" |
56 | | "todo/isFetching" | "/your-module/todo/isFetching" |
57 |
58 | ## The reduxSelect pipe
59 |
60 | You can select values direct out of your view. This is very comfortable to use, because you don't have
61 | to create a class property in your component class. To select the value, the pipe will use the
62 | [ReduxStateProvider](../api/redux-state-provider.md#select)'s `select` method.
63 |
64 | ```angular2html
65 |
66 |
67 | {{todo}}
68 |
69 |
70 | ```
71 |
72 | ## The @ReduxSelect decorator
73 |
74 | The `@ReduxSelect` decorator can be added to any class property. It will turn the property into an `ReduxSelector`
75 | as described above. Unfortunately, the `@ReduxSelect` decorator cannot automatically determine the correct
76 | [ReduxStateProvider](../api/redux-state-provider.md). Therefore, you have to specify it as second argument.
77 |
78 | To select the value, the decorator will use the [ReduxStateProvider](../api/redux-state-provider.md#select)'s
79 | `select` method.
80 |
81 | If you use an **absolute selector**, you can omit the second argument, which is ignored anyway.
82 |
83 | ```ts
84 | class SomeClass {
85 |
86 | @ReduxSelect('todo/items', YourModuleStateProvider)
87 | private todoItems: ReduxSelector;
88 |
89 | constructor() {
90 | this.todoItems.subscribe(todoItems => {
91 | console.log('todo items', todoItems);
92 | });
93 | }
94 |
95 | }
96 | ```
97 |
98 | ## The ReduxStateProvider
99 |
100 | Another simple way to select state values is the [ReduxStateProvider](../api/redux-state-provider.md).
101 | You can simply inject it via Angular's dependency injector.
102 |
103 | ```ts
104 | @Component({
105 | templateUrl: './some.component.html',
106 | })
107 | class SomeComponent {
108 |
109 | constructor(state: YourModuleStateProvider) {
110 | state.select('todo/items').subscribe(todoItems => {
111 | console.log('todo items', todoItems);
112 | });
113 | }
114 |
115 | }
116 | ```
117 |
118 | ## The ReduxSelector
119 |
120 | You can instantiate the `ReduxSelector` by yourself. As with the decorator, the [ReduxStateProvider](../api/redux-state-provider.md#select)
121 | cannot be determined automatically even with manual instantiation. Therefore, you have to specify it as second argument.
122 | If you instantiate the `ReduxSelector` by yourself, the [ReduxStateProvider](../api/redux-state-provider.md#select)'s
123 | `select` method is **not** used.
124 |
125 | If you use an **absolute selector**, you can omit the second argument, which is ignored anyway.
126 |
127 | ```ts
128 | class SomeClass {
129 |
130 | constructor(zone: NgZone) {
131 | new ReduxSelector(zone, 'todo/items', YourModuleStateProvider).subscribe(todoItems => {
132 | console.log('todo items', todoItems);
133 | });
134 | }
135 |
136 | }
137 | ```
138 |
--------------------------------------------------------------------------------
/docs/articles/testing-guide.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [articles](./index.md)
2 |
3 | # Testing Guide
4 |
5 | ## Matcher
6 |
7 | There are some jasmine matchers provided by this package. This makes it easy to test whether a method triggers a redux action and
8 | if the reducer really listens to it. There is also a matcher available which will ensure that the reducer does not work on the state reference.
9 |
10 | ### Installation
11 |
12 | #### Add the framework and the plugin to your karma config
13 |
14 | ```js
15 | module.exports = function (config) {
16 | config.set({
17 | // ...
18 | frameworks: ['jasmine', '@angular/cli', '@harmowatch/ngx-redux-core'], // (1) Enable the framework
19 | plugins: [
20 | require('karma-jasmine'),
21 | require('karma-chrome-launcher'),
22 | require('karma-jasmine-html-reporter'),
23 | require('karma-coverage-istanbul-reporter'),
24 | require('@angular/cli/plugins/karma'),
25 | require('@harmowatch/ngx-redux-core/plugins/karma') // (2) Require the plugin
26 | ],
27 | // ...
28 | });
29 | };
30 | ```
31 |
32 | ### Usage
33 |
34 | ##### toDispatchAction
35 |
36 | ```ts
37 | it('will dispatch a redux action', () => {
38 | expect(TodoListComponent.prototype.toggleListMode).toDispatchAction();
39 | // or test for a specific action name
40 | expect(TodoListComponent.prototype.toggleListMode).toDispatchAction('toggleListMode');
41 | });
42 | ```
43 |
44 | ##### toReduceOn
45 |
46 | ```ts
47 | it('listens to the correct actions', () => {
48 | expect(TodoReducer.prototype.add).toReduceOn(TodoListComponent.prototype.add);
49 | });
50 |
51 | // or you can check for many actions
52 | it('listens to the correct actions', () => {
53 | expect(TodoReducer.prototype.add).toReduceOn(
54 | 'addTodo',
55 | TodoComponent.prototype.add,
56 | TodoListComponent.prototype.add,
57 | );
58 | });
59 | ```
60 |
61 | ##### notToMutateTheGivenState
62 |
63 | ```ts
64 | it('does not mutate the given state', () => {
65 | expect(TodoReducer.prototype.add).notToMutateTheGivenState(state);
66 | });
67 | ```
68 |
69 | ## ReduxTestingStore
70 |
71 | The ReduxTestingStore provides a [Store](https://redux.js.org/docs/api/Store.html) implementation that can be used in
72 | your unit tests. It adds an additional method `setState()` which allows easy state manipulation.
73 |
74 | ### Example
75 |
76 | ```ts
77 | describe('TodoListComponent', () => {
78 |
79 | let fixture: ComponentFixture;
80 | let testingStore: ReduxTestingStore;
81 |
82 | beforeEach(async(() => {
83 | TestBed.configureTestingModule({
84 | declarations: [
85 | TodoListComponent
86 | ],
87 | imports: [
88 | ReduxModule.forRoot({
89 | storeFactory: ReduxTestingStore.factory, // (1) Provide the ReduxTestingStore.factory
90 | state: {
91 | provider: TodoStateProvider,
92 | }
93 | })
94 | ],
95 | providers: [
96 | TodoStateProvider,
97 | ],
98 | }).compileComponents();
99 | }));
100 |
101 | beforeEach(async(() => {
102 | testingStore = TestBed.get(ReduxStore);
103 | fixture = TestBed.createComponent(TodoListComponent);
104 | fixture.detectChanges();
105 | }));
106 |
107 | describe('todo rendering', () => {
108 |
109 | beforeEach(async(() => {
110 |
111 | /*
112 | * set the state in a async beforeEach block, so your "it" is called after the state change was applied
113 | */
114 | testingStore.setState(TodoStateProvider, {
115 | ...TodoStateProvider.default,
116 | items: [{
117 | uuid: '14cf5fdb-afcf-4297-9507-ff645e47d3af',
118 | label: 'Some todo',
119 | creationDate: '2018-03-30T14:44:01.452Z',
120 | }, {
121 | uuid: '38150de6-7ad6-4007-af71-409a668a449e',
122 | label: 'Some other todo',
123 | creationDate: '2018-03-30T14:44:26.796Z',
124 | }],
125 | }).then(() => fixture.detectChanges());
126 |
127 | }));
128 |
129 | it('displays two todo items', () => {
130 |
131 | const todos = fixture.debugElement.queryAll(By.css('.todo-item > span'))
132 | .map(({nativeElement}) => nativeElement.innerText.trim());
133 |
134 | expect(todos).toEqual([
135 | 'Some todo',
136 | 'Some other todo',
137 | ]);
138 |
139 | });
140 |
141 | });
142 |
143 | });
144 | ```
145 |
--------------------------------------------------------------------------------
/docs/decorators/index.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md)
2 |
3 | # Decorators
4 |
5 | - [@ReduxAction](./redux-action.md)
6 | - [@ReduxActionContext](./redux-action-context.md)
7 | - [@ReduxReducer](./redux-reducer.md)
8 | - [@ReduxSelect](../articles/select-pattern.md#the-reduxselect-decorator)
9 | - [@ReduxState](./redux-state.md)
10 |
--------------------------------------------------------------------------------
/docs/decorators/redux-action-context.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md) / [decorators](./index.md)
2 |
3 | # @ReduxActionContext
4 |
5 | Marks a class as redux action context. This context is used by the [@ReduxAction](./redux-action.md) decorator to resolve the
6 | action type.
7 |
8 | ```ts
9 | @ReduxActionContext({
10 | prefix: string;
11 | })
12 | ```
13 |
14 | ## Configuration Options
15 |
16 | ### ```prefix```
17 |
18 | Sets the prefix that shall be used for all redux actions that will use this context. The prefix and the action type is
19 | concatenated to one string.
20 |
21 | | | |
22 | | ------------- | ------ |
23 | | type | string |
24 | | mandatory | yes |
25 |
26 | ```ts
27 | @ReduxActionContext({
28 | prefix: 'some-prefix/'
29 | })
30 | class SomeClass { // This will dispatch the following action:
31 | //
32 | @ReduxAction() // {
33 | public someMethod(): string { // "type": "some-prefix/someMethod",
34 | return 'some-return-value'; // "payload": "some-return-value"
35 | } // }
36 |
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/decorators/redux-action.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md) / [decorators](./index.md)
2 |
3 | # @ReduxAction
4 |
5 | Marks a class method as redux action dispatcher. Every time the decorated method is called, it will dispatch a new
6 | [redux action](https://redux.js.org/docs/basics/Actions.html). The return value of the decorated method is used as payload.
7 |
8 | ```ts
9 | @ReduxAction({
10 | type?: string;
11 | contextClass?: {},
12 | onDispatchSuccess?: Function;
13 | })
14 | ```
15 |
16 | ## Configuration Options
17 |
18 | ### ```type```
19 |
20 | Overwrites the type of the redux action.
21 |
22 | | | |
23 | | ------------- | -------------------------------- |
24 | | default value | The name of the decorated method |
25 | | type | string |
26 | | mandatory | no |
27 |
28 | ```ts
29 | class SomeClass { // This will dispatch the following action:
30 | //
31 | @ReduxAction({type: 'some-action-type'}) // {
32 | public someMethod(): string { // "type": "some-action-type",
33 | return 'some-return-value'; // "payload": "some-return-value"
34 | } // }
35 |
36 | }
37 | ```
38 |
39 | ### ```contextClass```
40 |
41 | Defines the context class which is used to resolve the [@ReduxActionContext](./redux-action-context.md).
42 |
43 | | | |
44 | | ------------- | ---------- |
45 | | default value | this class |
46 | | type | class |
47 | | mandatory | no |
48 |
49 | ```ts
50 | @ReduxActionContext({prefix: 'some-prefix/')
51 | class SomeContextClass {
52 |
53 | }
54 |
55 | class SomeClass { // This will dispatch the following action:
56 | //
57 | @ReduxAction({contextClass: SomeContextClass}) // {
58 | public someMethod(): string { // "type": "some-prefix/someMethod",
59 | return 'some-return-value'; // "payload": "some-return-value"
60 | } // }
61 |
62 | }
63 | ```
64 |
65 | The example above will dispatch the following [redux action](https://redux.js.org/docs/basics/Actions.html):
66 |
67 | ```json
68 | {
69 | "type": "some-prefix/someMethod",
70 | "payload": "some-return-value"
71 | }
72 | ```
73 |
74 | ### ```onDispatchSuccess```
75 |
76 | Defines a callback function which is called as soon as the action is dispatched. The `this` context of the decorated method
77 | is bound to the callback function. That means that you can use `this` within the callback function.
78 |
79 | | | |
80 | | ------------- | ---------- |
81 | | default value | this class |
82 | | type | class |
83 | | mandatory | no |
84 |
85 | ```ts
86 | class SomeClass {
87 |
88 | protected someProperty: string;
89 |
90 | @ReduxAction({
91 | onDispatchSuccess: SomeClass.prototype.someMethodDispatchSuccess
92 | })
93 | public someMethod(): string {
94 | this.someProperty = '123';
95 | return 'some-return-value';
96 | }
97 |
98 | public someMethodDispatchSuccess() {
99 | console.log(this.someProperty); // will log 123
100 | }
101 |
102 | }
103 | ```
104 |
105 | ## Special return values
106 |
107 | ### Promise
108 |
109 | If the method returns a `Promise`, the [redux action](https://redux.js.org/docs/basics/Actions.html) will not be fired
110 | until the `Promise` is resolved *successfully*.
111 |
112 | ```ts
113 | class SomeClass { // This will dispatch the following action:
114 | //
115 | @ReduxAction() // {
116 | public someMethod(): Promise { // "type": "someMethod",
117 | return Promise.resolve('some-return-value'); // "payload": "some-return-value"
118 | } // }
119 |
120 | }
121 | ```
122 |
123 | ### Observable
124 |
125 | If the method returns a `Observable`, the [redux action](https://redux.js.org/docs/basics/Actions.html) will not be fired
126 | until the `Observable` is *completed*. The last value in the event stream is used as the payload.
127 |
128 | ```ts
129 | class SomeClass { // This will dispatch the following action:
130 | //
131 | @ReduxAction() // {
132 | public someMethod(): Observable { // "type": "someMethod",
133 | return Observable.from([ // "payload": "some-return-value"
134 | 'not-used-as-payload', // }
135 | 'some-return-value'
136 | ]);
137 | }
138 |
139 | }
140 | ```
141 |
--------------------------------------------------------------------------------
/docs/decorators/redux-reducer.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md) / [decorators](./index.md)
2 |
3 | # @ReduxReducer
4 |
5 | Marks a class method as redux reducer and wires the reducer method against one or many redux actions.
6 |
7 | ```ts
8 | @ReduxAction(string|string[]|Function|Function[])
9 | ```
10 |
11 | ## Reference variants
12 |
13 | ### By prototype
14 |
15 | The recommended and simplest way is to wire the reducer directly via the prototype with the action method. This gives you the
16 | advantage that TypeScript can validate the return value of the ReduxAction against the expected payload of your Reducer
17 | function. This is also the only way to enable IDE refactoring.
18 |
19 | 
20 |
21 | ```ts
22 | class SomeReducerClass {
23 |
24 | @ReduxReducer(SomeActionClass.prototype.addItem) // addItem returns string
25 | public addItem(state: SomeState, action: ReduxActionWithPayload): SomeState {
26 | return {
27 | ...state,
28 | items: state.items.concat(action.payload),
29 | };
30 | }
31 |
32 | }
33 | ```
34 |
35 | The reducer can also listen to several actions at the same time:
36 |
37 | ```ts
38 | class SomeReducerClass {
39 |
40 | @ReduxReducer([
41 | SomeActionClass.prototype.addItem, // addItem returns string
42 | SomeActionClass.prototype.addDefaultItem, // addDefaultItem returns string
43 | ])
44 | public addItem(state: SomeState, action: ReduxActionWithPayload): SomeState {
45 | return {
46 | ...state,
47 | items: state.items.concat(action.payload),
48 | };
49 | }
50 |
51 | }
52 | ```
53 |
54 | Or you can decorate the reducer method multiple times:
55 |
56 | ```ts
57 | class SomeReducerClass {
58 |
59 | @ReduxReducer(SomeActionClass.prototype.addItem) // addItem returns string
60 | @ReduxReducer(SomeActionClass.prototype.addDefaultItem) // addDefaultItem returns string
61 | public addItem(state: SomeState, action: ReduxActionWithPayload): SomeState {
62 | return {
63 | ...state,
64 | items: state.items.concat(action.payload),
65 | };
66 | }
67 |
68 | }
69 | ```
70 |
71 | The array notation and the TypeScript type check currently only work well for actions which return the same type.
72 | So if you have something like this, TypeScript will throw a type error:
73 |
74 | ```ts
75 | class SomeReducerClass {
76 |
77 | @ReduxReducer([
78 | SomeActionClass.prototype.addItem, // addItem returns string
79 | SomeActionClass.prototype.addItems // addItems returns string[]
80 | ])
81 | public addItem(state: SomeState, action: ReduxActionWithPayload): SomeState {
82 | return {
83 | ...state,
84 | items: state.items.concat(action.payload),
85 | };
86 | }
87 |
88 | }
89 | ```
90 |
91 | But you can fall back to the following solution:
92 |
93 | ```ts
94 | class SomeReducerClass {
95 |
96 | @ReduxReducer(SomeActionClass.prototype.addItem) // addItem returns string
97 | @ReduxReducer(SomeActionClass.prototype.addItems) // addItems returns string[]
98 | public addItem(state: SomeState, action: ReduxActionWithPayload): SomeState {
99 | return {
100 | ...state,
101 | items: state.items.concat(action.payload),
102 | };
103 | }
104 |
105 | }
106 | ```
107 |
108 | ### By string
109 |
110 | It is also possible to refer directly to an action by string. However, this option should only be used if there
111 | is no other option. You can also specify this as an array or decorate the method multiple times.
112 |
113 | ```ts
114 | class SomeReducerClass {
115 |
116 | @ReduxReducer('some-action-type')
117 | public addItem(state: SomeState, action: ReduxActionWithPayload): SomeState {
118 | return {
119 | ...state,
120 | items: state.items.concat(action.payload),
121 | };
122 | }
123 |
124 | }
125 | ```
126 |
127 | ## Activation
128 |
129 | Don't forget to activate your reducer! You've to add the reducer class to the reducers array like this:
130 |
131 | ```ts
132 | @NgModule({
133 | imports: [
134 | ReduxModule.forRoot({
135 | state: {
136 | provider: SomeStateProvider,
137 | reducers: [ SomeReducerClass ],
138 | }
139 | }),
140 | ],
141 | providers: [SomeStateProvider]
142 | })
143 | ```
144 |
--------------------------------------------------------------------------------
/docs/decorators/redux-state.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md) / [decorators](./index.md)
2 |
3 | # @ReduxState
4 |
5 | Marks a [ReduxStateProvider](../api/redux-state-provider.md) class as redux state.
6 | This is required to provide a unique name within the global application state.
7 |
8 | ```ts
9 | @ReduxState({
10 | name: string;
11 | })
12 | ```
13 |
14 | ## Configuration Options
15 |
16 | ### ```name```
17 |
18 | Defines the unique state name, that will be used as key in the global application state.
19 |
20 | | | |
21 | | ------------- | -------------------------------- |
22 | | type | string |
23 | | mandatory | yes |
24 |
25 | ```ts
26 | class SomeClass {
27 |
28 | @ReduxAction({name: 'my-unique-name'})
29 | public someMethod(): string {
30 | return 'some-return-value';
31 | }
32 |
33 | }
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/how-to/create-an-actions-provider.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md) / [how to](./index.md)
2 |
3 | # Create an actions provider
4 |
5 | Of course you can also create a provider to fire your Redux actions. Here's an example using
6 | [@ReduxActionContext](../decorators/redux-action-context.md) and [@ReduxAction](../decorators/redux-action.md):
7 |
8 | ## Example
9 |
10 | ```ts
11 | import { Injectable } from '@angular/core';
12 | import { ReduxAction, ReduxActionContext } from '@harmowatch/ngx-redux-core';
13 |
14 | @Injectable()
15 | @ReduxActionContext({prefix: 'TodoActions://'})
16 | export class TodoActions {
17 |
18 | @ReduxAction()
19 | add(label: string): TodoListItem {
20 | return label; // your return value is the payload
21 | }
22 |
23 | }
24 | ```
25 |
26 | Now `@harmowatch/ngx-redux-core` will dispatch the following action, every time the `add` method was called:
27 |
28 | ```json
29 | {
30 | "type": "TodoActions://add",
31 | "payload": "SampleTodo"
32 | }
33 | ```
34 |
35 |
--------------------------------------------------------------------------------
/docs/how-to/index.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md)
2 |
3 | # How to
4 |
5 | - [Create an actions provider](./create-an-actions-provider.md)
6 | - [Provide your own store](./provide-your-own-store.md)
7 | - [Use lazy loading](./use-lazy-loading.md)
8 |
--------------------------------------------------------------------------------
/docs/how-to/provide-your-own-store.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md) / [how to](./index.md)
2 |
3 | # Provide your own store
4 |
5 | Sometimes you want to have your own redux store to register a custom middleware or enhancer. To make this possible,
6 | you can specify your own `storeFactory`, which will be used instead of the one that comes with @harmowatch/ngx-redux-core.
7 | The method `ReduxModule.forRoot` offers a configuration option `storeFactory` for this purpose.
8 |
9 | ## Example
10 |
11 | ```ts
12 | import { isDevMode, NgModule } from '@angular/core';
13 | import { BrowserModule } from '@angular/platform-browser';
14 |
15 | import { AppComponent } from './app.component';
16 | import { createStore, Store } from 'redux';
17 | import { ReduxModule, ReduxReducerProvider, ReduxRootState } from '@harmowatch/ngx-redux-core';
18 | import { RouterModule } from '@angular/router';
19 |
20 | // AOT compiler requires an exported function for factories
21 | export function storeFactory(reduxReducerProvider: ReduxReducerProvider): Store {
22 | return createStore(
23 | reduxReducerProvider.rootReducer, // the rootReducer to enable the @ReduxReducer decorated methods
24 | {}, // the initial state
25 | ReduxModule.defaultEnhancerFactory(isDevMode()), // in devMode this will enable the "Redux DevTools Extension"
26 | );
27 | }
28 |
29 | @NgModule({
30 | declarations: [
31 | AppComponent
32 | ],
33 | imports: [
34 | BrowserModule,
35 | ReduxModule.forRoot({
36 | storeFactory,
37 | }),
38 | ],
39 | exports: [
40 | AppComponent,
41 | ],
42 | bootstrap: [ AppComponent ]
43 | })
44 | export class AppModule {
45 | }
46 | ```
47 |
48 | For more information, how to create a store, please see the [redux docs](https://redux.js.org/api-reference/createstore).
49 |
50 | ----
51 |
52 | To use this *new feature* you've to install the **next** version (>= 0.2.3-0).
53 |
54 | ```
55 | $ npm install @harmowatch/ngx-redux-core@next
56 | ```
57 |
--------------------------------------------------------------------------------
/docs/how-to/use-lazy-loading.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md) / [how to](./index.md)
2 |
3 | # Use lazy loading
4 |
5 | As described in the [Angular - Router & Navigation](https://angular.io/guide/router#asynchronous-routing) docs,
6 | it's possible to load a [Angular Module](https://angular.io/guide/ngmodules) asynchronously. To use this together
7 | with `@harmowatch/ngx-redux-core` you just have to use the static `ReduxModule.forChild()` method in your lazy loaded module.
8 |
9 | Make sure you've initialized the `ReduxModule.forRoot()` module in your app module as well.
10 |
11 | ## Example
12 |
13 | *app.module.ts* (The root NgModule)
14 |
15 | ```ts
16 | import { NgModule } from '@angular/core';
17 | import { BrowserModule } from '@angular/platform-browser';
18 | import { RouterModule } from '@angular/router';
19 | import { ReduxModule } from '@harmowatch/ngx-redux-core';
20 |
21 | import { AppComponent } from './app.component';
22 |
23 | @NgModule({
24 | declarations: [
25 | AppComponent
26 | ],
27 | imports: [
28 | BrowserModule,
29 | ReduxModule.forRoot(), // import the ReduxModule.forRoot()
30 | RouterModule.forRoot([
31 | {path: 'todo', loadChildren: './todo/todo.module#TodoModule'}, // lazy load you child module
32 | {path: '', redirectTo: 'todo', pathMatch: 'prefix'},
33 | ], {useHash: true})
34 | ],
35 | exports: [
36 | AppComponent,
37 | ],
38 | bootstrap: [ AppComponent ]
39 | })
40 | export class AppModule {
41 | }
42 | ```
43 |
44 | *todo.module.ts* (The lazy loaded NgModule)
45 |
46 | ```ts
47 | import { CommonModule } from '@angular/common';
48 | import { NgModule } from '@angular/core';
49 | import { FormsModule } from '@angular/forms';
50 | import { RouterModule } from '@angular/router';
51 | import { ReduxModule } from '@harmowatch/ngx-redux-core';
52 |
53 | import { TodoListComponent } from './list/todo-list.component';
54 | import { TodoListReducer } from './list/todo-list.reducer';
55 | import { TodoModuleStateProvider } from './todo.module.state.provider';
56 |
57 | @NgModule({
58 | imports: [
59 | CommonModule,
60 | FormsModule,
61 | ReduxModule.forChild({ // use forChild instead of forRoot in lazy loaded modules
62 | state: {
63 | provider: TodoModuleStateProvider,
64 | reducers: [ TodoListReducer ],
65 | }
66 | }),
67 | RouterModule.forChild([
68 | {path: '', component: TodoListComponent},
69 | ]),
70 | ],
71 | declarations: [TodoListComponent],
72 | providers: [TodoModuleStateProvider]
73 | })
74 | export class TodoModule { }
75 | ```
76 |
77 | For more information, please see the [example app](https://github.com/HarmoWatch/ngx-redux-core/tree/master/src/example-app)
78 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../README.md)
2 |
3 | # Docs
4 |
5 | - [Articles](./articles/index.md)
6 | - [The Select Pattern](./articles/select-pattern.md)
7 | - [Testing Guide](./articles/testing-guide.md)
8 | - [Decorators](./decorators/index.md)
9 | - [@ReduxAction](./decorators/redux-action.md)
10 | - [@ReduxActionContext](./decorators/redux-action-context.md)
11 | - [@ReduxReducer](./decorators/redux-reducer.md)
12 | - [@ReduxSelect](./articles/select-pattern.md#the-reduxselect-decorator)
13 | - [@ReduxState](./decorators/redux-state.md)
14 | - [Pipes](./pipes/index.md)
15 | - [reduxSelect](./pipes/redux-select.md)
16 | - [API](./api/index.md)
17 | - [ReduxStateProvider](./api/redux-state-provider.md)
18 | - [How to](./how-to/index.md)
19 | - [Create an actions provider](./how-to/create-an-actions-provider.md)
20 | - [Provide your own store](./how-to/provide-your-own-store.md)
21 | - [Use lazy loading](./how-to/use-lazy-loading.md)
22 |
--------------------------------------------------------------------------------
/docs/pipes/index.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md)
2 |
3 | # Pipes
4 |
5 | - [reduxSelect](./redux-select.md)
6 |
--------------------------------------------------------------------------------
/docs/pipes/redux-select.md:
--------------------------------------------------------------------------------
1 | ###### [@harmowatch/ngx-redux-core](../../README.md) / [docs](../index.md) / [pipes](./index.md)
2 |
3 | # reduxSelect
4 |
5 | Selects a value from the state directly out of your view. For the selection it will use your
6 | [ReduxStateProvider](../api/redux-state-provider.md#select)'s select method.
7 |
8 | ## Using a relative selector, second argument needed (recommended)
9 |
10 | ```angular2html
11 |
12 |
13 | {{todo}}
14 |
15 |
16 | ```
17 |
18 | ## Select the same value using a absolute selector, no second argument needed (avoid)
19 |
20 | ```angular2html
21 |