# [WIP] @harmowatch/ngx-redux-core

Hey there, the package is still work in progress, please [check the open tasks](https://github.com/HarmoWatch/ngx-redux/projects/1).

## Decorator driven redux integration for Angular 2+

* What is ...
  * [Redux?](#what-is-redux)
  * [ngx-redux?](#what-is-ngx-redux)

* [Installation](#installation)
* [Usage](#usage)
  * [1. Import the root `ReduxModule`](#1-import-the-root-reduxmodule)
    * [1.1 Bootstrap your own Redux Store](#11-bootstrap-your-own-redux-store)
  * [2. Describe your state](#2-describe-your-state)
  * [3. Create a class representing your module state](#3-create-a-class-representing-your-module-state)
  * [4. Register the state](#4-register-the-state)
  * [5. Select data from the state](#5-select-data-from-the-state)
  * [6. Dispatch an Redux Action](#6-dispatch-an-redux-action)
  * [7. Reduce the State](#7-reduce-the-state)
  
* [Known violations / conflicts](#known-violations--conflicts)

## What is Redux?

Redux is a popular and common approach to manage a application state. The three principles of redux are:

- [Single source of truth](http://redux.js.org/docs/introduction/ThreePrinciples.html#single-source-of-truth)
- [State is read-only](http://redux.js.org/docs/introduction/ThreePrinciples.html#state-is-read-only)
- [Changes are made with pure functions](http://redux.js.org/docs/introduction/ThreePrinciples.html#changes-are-made-with-pure-functions)

[Read more about Redux](http://redux.js.org/)

## What is ngx-redux?

This package helps you to integrate Redux in your Angular 2+ application. By using *ngx-redux* you'll get the following
benefits:

- support for [lazy loaded NgModules](https://angular.io/guide/ngmodule#lazy-loading-modules-with-the-router)
- [Ahead-of-Time Compilation (AOT)](https://angular.io/guide/aot-compiler) support
- a [Angular Pipe](https://angular.io/guide/pipes) to select the values from the state
- better typescript and refactoring support
- a decorator and module driven approach
- easy to test

## Installation

First you need to install

- "redux" as peer dependency
- the "@harmowatch/ngx-redux-core" package itself

```sh
npm install redux @harmowatch/ngx-redux-core --save
```

## Usage

### 1. Import the root `ReduxModule`:

To use *ngx-redux* in your Angular project you have to import `ReduxModule.forRoot()` in the root NgModule of your
application.

The static [`forRoot`](https://angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root) method is a convention
that provides and configures services at the same time. Make sure you call this method in your root NgModule, only!

###### Example

```ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReduxModule } from '@harmowatch/ngx-redux-core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [ AppComponent ],
  imports: [
    BrowserModule,
    ReduxModule.forRoot(),
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}
```

#### 1.1 Bootstrap your own [Redux Store](http://redux.js.org/docs/basics/Store.html)

By default *ngx-redux* will bootstrap a [Redux Store](http://redux.js.org/docs/basics/Store.html) for you. 
Is the app running in [devMode](https://angular.io/api/core/isDevMode), the default store is prepared to work together 
with the [Redux DevTools](https://github.com/gaearon/redux-devtools).

If you want to add a [Middleware](http://redux.js.org/docs/advanced/Middleware.html) like logging, you've to provide a
custom *stateFactory*.

###### Example

```ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReduxModule, ReduxModuleRootReducer } from '@harmowatch/ngx-redux-core';
import { applyMiddleware, createStore, Store, StoreEnhancer } from 'redux';
import logger from 'redux-logger';

import { AppComponent } from './app.component';

export function enhancerFactory(): StoreEnhancer<{}> {
  return applyMiddleware(logger);
}

export function storeFactory(): Store<{}> {
  return createStore(
    ReduxModuleRootReducer.reduce,
    {},
    enhancerFactory()
  );
}

@NgModule({
  declarations: [ AppComponent ],
  imports: [
    BrowserModule,
    ReduxModule.forRoot({
      storeFactory,
    }),
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}
```

### 2. Describe your state

Ok, now you've to create a interface to describe the structure of your state.

###### Example 

```ts
export interface AppModuleStateInterface {
  todo: {
    items: string[];
  };
}
```

### 3. Create a class representing your module state

Before you can register your state to redux, you need to create a class that represents your state. This class is
responsible to resolve the initial state.

#### As you can see in the example below, the class ...

##### ... is decorated by `@ReduxState`

You need to decorate your class by `@ReduxState` and to provide a application wide unique state `name` to it. If you can
not be sure that your name is unique enough, then you can add a unique id to it (*as in the example shown below*). 

##### ... implements `ReduxStateInterface`

The `@ReduxState` decorator is only valid for classes which implement the `ReduxStateInterface`. This is an generic 
interface where you've to provide your previously created `AppModuleStateInterface`. The `ReduxStateInterface` compels 
you to implement a public method `getInitialState`. This method is responsible to know, how the initial state can be 
computed and will return it as an `Promise`, `Observable` or an implementation of the state interface directly.

> Note: The method `getInitialState` is called by *ngx-redux* automatically! Your state will be registered to the root
state **after** the initial state was resolved successfully.
 
###### Example 1) Interface implementation

```ts
import { ReduxState, ReduxStateInterface } from '@harmowatch/ngx-redux-core';
import { AppModuleStateInterface } from './app.module.state.interface';

@ReduxState({
  name: 'app-module-7c66b613-20bd-4d35-8611-5181ca4a0b72'
})
export class AppModuleState implements ReduxStateInterface<AppModuleStateInterface> {

  getInitialState(): AppModuleStateInterface {
    return {
      todo: {
        items: [ 'Item 1', 'Item 2' ],
      }
    };
  }

}
``` 
 
###### Example 2) Promise

```ts
import { ReduxState, ReduxStateInterface } from '@harmowatch/ngx-redux-core';
import { AppModuleStateInterface } from './app.module.state.interface';

@ReduxState({
  name: 'app-module-7c66b613-20bd-4d35-8611-5181ca4a0b72'
})
export class AppModuleState implements ReduxStateInterface<AppModuleStateInterface> {

  getInitialState(): Promise<AppModuleStateInterface> {
    return Promise.resolve({
      todo: {
        items: [ 'Item 1', 'Item 2' ],
      }
    });
  }

}
```

> Note: If you return a unresolved `Promise` your state is never registered!

###### Example 3) Observable

```ts
import { ReduxState, ReduxStateInterface } from '@harmowatch/ngx-redux-core';
import { AppModuleStateInterface } from './app.module.state.interface';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';

@ReduxState({
  name: 'app-module-7c66b613-20bd-4d35-8611-5181ca4a0b72'
})
export class AppModuleState implements ReduxStateInterface<AppModuleStateInterface> {

  getInitialState(): Observable<AppModuleStateInterface> {
    const subject = new BehaviorSubject<AppModuleStateInterface>({
      todo: {
        items: [ 'Item 1', 'Item 2' ],
      }
    });

    subject.complete();
    return subject.asObservable();
  }

}
```

> Note: If you return a uncompleted `Observable` your state is never registered!

### 4. Register the state:

The next thing you need to do, is to register your state. For that *ngx-redux* accepts a configuration property `state`. 

###### Example

```ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReduxModule } from '@harmowatch/ngx-redux-core';

import { AppComponent } from './app.component';
import { AppModuleState } from './app.module.state';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReduxModule.forRoot({
      state: {
        provider: AppModuleState,
      }
    }),
  ],
  providers: [],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}
```

> Note: For lazy loaded modules you've to use `forChild`.

Your redux module is ready to run now. Once your initial state was resolved, your redux module is registered to the 
global redux state like this:
                                       
```json
{
  "app-module-7c66b613-20bd-4d35-8611-5181ca4a0b72": {
    "todo": {
      "items": [
        "Item 1",
        "Item 2"
      ]
    }
  }
}
```

### 5. Select data from the state

To select values from the state you can choose between this three options:

- a [Angular Pipe](https://angular.io/guide/pipes)
- a Annotation
- a Class

Each selector will accept a relative *todo/items* or an absolute path 
*/app-module-7c66b613-20bd-4d35-8611-5181ca4a0b72/todo/items*. It's recommended to use relative paths only. The 
absolute path is only there to give you a maximum of flexibility.

#### 5.1 Using the `reduxSelect` pipe

The easiest way to get values from the state, is to use the `reduxSelect` pipe together with Angular's `async` pipe. The
right state is determined automatically, because you're in a Angular context.

###### Example 1) Relative path (recommended)

```angular2html
<pre>{{ 'todo/items' | reduxSelect | async | json }}</pre>
```

###### Example 2) Absolute path (avoid)

```angular2html
<pre>{{ '/app-module-7c66b613-20bd-4d35-8611-5181ca4a0b72/todo/items' | reduxSelect | async | json }}</pre> 
``` 

#### 5.2 Using the `@ReduxSelect` annotation

If you want to access the state values in your component you can use the `@ReduxSelect` decorator. *ngx-redux* can not
determine which state you mean automatically, because decorators run outside the Angular context. For that you've to
pass in a reference to your state class as 2nd argument. When you specify an absolute path, you don't need the 2nd 
argument anymore.

###### Example 1) Relative path (recommended)

```ts
import { Component } from '@angular/core';
import { ReduxSelect } from '@harmowatch/ngx-redux-core';
import { AppModuleState } from './app.module.state';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {

  @ReduxSelect('todo/items', AppModuleState)
  private todoItems: Observable<string[]>;

  constructor() {
    this.todoItems.subscribe((items) => console.log('ITEMS', items));
  }

}
```

###### Example 2) Absolute path (avoid)

```ts
import { Component } from '@angular/core';
import { ReduxSelect } from '@harmowatch/ngx-redux-core';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {


  @ReduxSelect('/app-module-7c66b613-20bd-4d35-8611-5181ca4a0b72/todo/items')
  private todoItems: Observable<string[]>;

  constructor() {
    this.todoItems.subscribe((items) => console.log('ITEMS', items));
  }

}
```

#### 5.3 Using the `ReduxStateSelector` class

###### Example 1) Relative path (recommended)

```ts
import { Component } from '@angular/core';
import { ReduxStateSelector } from '@harmowatch/ngx-redux-core';
import { AppModuleState } from './app.module.state';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {

  constructor() {
    const selector = new ReduxStateSelector('todo/items', AppModuleState);

    selector.getSubject().subscribe((items) => console.log('ITEMS', items));

    // or
    selector.getReplaySubject().subscribe((items) => console.log('ITEMS', items));

    // or
    selector.getObservable().subscribe((items) => console.log('ITEMS', items));

    // or
    selector.getBehaviorSubject([ 'Default Item' ]).subscribe((items) => console.log('ITEMS', items));
  }

}
```

###### Example 2) Absolute path (avoid)

```ts
import { Component } from '@angular/core';
import { ReduxStateSelector } from '@harmowatch/ngx-redux-core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {

  constructor() {
    const selector = new ReduxStateSelector('/app-module-7c66b613-20bd-4d35-8611-5181ca4a0b72/todo/items');

    selector.getSubject().subscribe((items) => console.log('ITEMS', items));

    // or
    selector.getReplaySubject().subscribe((items) => console.log('ITEMS', items));

    // or
    selector.getObservable().subscribe((items) => console.log('ITEMS', items));

    // or
    selector.getBehaviorSubject([ 'Default Item' ]).subscribe((items) => console.log('ITEMS', items));
  }

}
```

### 6. Dispatch an [Redux Action](http://redux.js.org/docs/basics/Actions.html)

To dispatch an action is very easy. Just annotate your class method by `@ReduxAction`. Everytime your method is called 
*ngx-redux* will dispatch a [Redux Action](http://redux.js.org/docs/basics/Actions.html) for you automatically! 
The `return` value of the decorated method will become the payload of the action and the name of the method is used as 
the action type.

> Note: It's very useful to write a provider, where the action method(s) are delivered by. See the example below. 

###### Example

```ts
import { Injectable } from '@angular/core';
import { ReduxAction } from '@harmowatch/ngx-redux-core';

@Injectable()
export class AppActions {

  @ReduxAction()
  public addTodo(todo: string): string {
    return todo;
  }

}
```

Then register the provider to your module:

```ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReduxModule } from '@harmowatch/ngx-redux-core';
import { AppActions } from './app.actions'; // (1) Add the import

import { AppComponent } from './app.component';
import { AppModuleState } from './app.module.state';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReduxModule.forRoot({
      state: {
        provider: AppModuleState,
      }
    }),
  ],
  providers: [ AppActions ], // (2) Add to the provider list
  bootstrap: [ AppComponent ]
})
export class AppModule {
}
```

Now you can inject the provider to your component:

###### Example

```ts
import { Component } from '@angular/core';
import { AppActions } from './app.actions';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {

  constructor(appActions: AppActions) {
    appActions.addTodo('SampleTodo');
  }

}
```

The example above will dispatch the following action:

```json
{
  "payload": "SampleTodo",
  "type": "addTodo"
}
```

Ok that's cool, but there's no information in the action type that this was an `AppActions` action, right?
But don't worry you can follow two different and very easy ways to fix that.

###### Example 1) Provide a action type

```ts
import { Injectable } from '@angular/core';
import { ReduxAction } from '@harmowatch/ngx-redux-core';

@Injectable()
export class AppActions {

  @ReduxAction({
    type: 'AppActions://addTodo'
  })
  public addTodo(todo: string): string {
    return todo;
  }

}
```

`addTodo` will dispatch the following action from now on:

```json
{
  "payload": "SampleTodo",
  "type": "AppActions://addTodo"
}
```

#### Example 2) Provide a action context (recommended)

```ts
import { Injectable } from '@angular/core';
import { ReduxAction, ReduxActionContext } from '@harmowatch/ngx-redux-core';

@ReduxActionContext({
  prefix: 'AppActions://'
})
@Injectable()
export class AppActions {

  @ReduxAction()
  public addTodo(todo: string): string {
    return todo;
  }

}
```

`addTodo` will dispatch the following action from now on:

```json
{
  "type": "todo/add",
  "payload": "SomeTodo" 
}
```

### Example 3) Combine the ReduxContext and action type

```ts
import { Injectable } from '@angular/core';
import { ReduxAction, ReduxActionContext } from '@harmowatch/ngx-redux-core';

@ReduxActionContext({
  prefix: 'AppActions://'
})
@Injectable()
export class AppActions {

  @ReduxAction({
    type: 'add-todo'
  })
  public addTodo(todo: string): string {
    return todo;
  }

}
```

`addTodo` will dispatch the following action from now on:

```json
{
  "payload": "SampleTodo",
  "type": "AppActions://add-todo"
}
```

### 7. Reduce the State

We have no way to manipulate the data that are stored in the [Redux Store](http://redux.js.org/docs/basics/Store.html)
yet. For that we need a reducer.

###### Example

```ts
import { ActionInterface, ReduxReducer } from '@harmowatch/ngx-redux-core';
import { AppActions } from './app.actions';
import { AppModuleStateInterface } from './app.module.state.interface';

export class AppModuleReducer {

  @ReduxReducer(AppActions.prototype.addTodo)
  static addTodo(state: AppModuleStateInterface, action: ActionInterface<string>) {
    return {
      ...state,
      todo : {
        ...state.todo,
        items : state.todo.items.concat(action.payload)
      }
    }
  }

}
```

The last thing you need to do, is to wire the reducer against the state:

###### Example 

```ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReduxModule } from '@harmowatch/ngx-redux-core';
import { AppActions } from './app.actions';

import { AppComponent } from './app.component';
import { AppModuleReducer } from './app.module.reducer'; // (1) Add the import
import { AppModuleState } from './app.module.state';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReduxModule.forRoot({
      state: {
        provider: AppModuleState,
        reducers: [ AppModuleReducer ] // (2) Register the reducer
      }
    }),
  ],
  providers: [ AppActions ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}
```

## Known violations / conflicts

### Redux principles

#### [Changes are made with pure functions](http://redux.js.org/docs/introduction/ThreePrinciples.html#changes-are-made-with-pure-functions)

One of the principles of Redux is to change the state using **pure functions**, only. Unfortunately there is 
**no typescript support** to decorate pure functions right now. That's the reason why *ngx-redux* uses classes where the
reducer functions are shipped by. To find a viable solution the reducer functions shall be written as static methods.
