Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redux section rewrite #864

Merged
merged 16 commits into from
Jan 23, 2017
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,21 @@

---

* [Redux and Ngrx](handout/redux/README.md)
* [Review of Reducers and Pure Functions](handout/redux/review_of_reducers_and_pure_functions.md)
* [Redux and @ngrx](handout/redux/README.md)
* [Adding @ngrx to your Project](handout/redux/adding_ngrx_to_your_project.md)
* [Defining your Main Application State](handout/redux/defining_your_main_application_state.md)
* [Example Application](handout/redux/example_application.md)
* [Reading your Application State using Selectors](handout/redux/reading_your_application_state_using_selectors.md)
* [Actions](handout/redux/actions.md)
* [Modifying your Application State by Dispatching Actions](handout/redux/modifying_your_application_state_by_dispatching_actions.md)
* [Reducers and Pure Functions](handout/redux/reducers_and_pure_functions.md)
* [Reducers as State Management](handout/redux/reducers_as_state_management.md)
* [Redux Actions](handout/redux/redux_actions.md)
* [Configuring your Application to use Redux](handout/redux/configuring_your_application_to_use_redux.md)
* [Using Redux with Components](handout/redux/using_redux_with_components.md)
* [Redux and Component Architecture](handout/redux/redux_and_component_architecture.md)
* [Getting More From Redux and Ngrx](handout/redux/getting_more_from_redux_and_ngrx.md)
* [Creating your Application's Root Reducer](handout/redux/creating_your_applications_root_reducer.md)
* [Configuring your Application](handout/redux/configuring_your_application.md)
* [Implementing Components](handout/redux/implementing_components.md)
* [Component Architecture](handout/redux/component_architecture.md)
* [Side Effects](handout/redux/side_effects.md)
* [Getting More From Redux and @ngrx](handout/redux/getting_more_from_redux_and_ngrx.md)

---

Expand Down
19 changes: 13 additions & 6 deletions handout/redux/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Redux and Ngrx
# Redux and @ngrx

## What is Redux?

Expand All @@ -10,13 +10,20 @@ Where Flux applications traditionally have multiple stores, Redux applications
have only one global, read-only application state. This state is calculated by
"reducing" over a collection or stream of actions that update it in controlled ways.

One popular Angular 2 specific implementation of the Redux pattern is [Ng2-Redux](https://github.com/wbuchwalter/ng2-redux), which we'll be using to describe how to use this approach in an application.
One popular Angular 2 specific implementation of the Redux pattern is
[Ng2-Redux](https://github.com/wbuchwalter/ng2-redux).


## What is Ngrx?
## What is @ngrx?

Redux implementation has been very well received and has inspired the creation of [ngrx](https://github.com/ngrx "ngrx collection"), a set of modules that implement the same way of managing state as well as some of the middleware and tools in the Redux ecosystem. Ngrx was created to be used specifically with Angular 2 and [RxJS](https://github.com/Reactive-Extensions/RxJS), as it leans heavily on the observable paradigm.
Redux implementation has been very well received and has inspired the creation
of [@ngrx](https://github.com/ngrx "ngrx collection"), a set of modules that
implement the same way of managing state as well as some of the middleware and
tools in the Redux ecosystem. @ngrx was created to be used specifically with
Angular 2 and [RxJS](https://github.com/Reactive-Extensions/RxJS), as it leans
heavily on the observable paradigm.

Although we'll be using Ng2-Redux, a lot of the same lessons apply with regards to ngrx though the syntax may be different and have some slight differences in what abstractions are involved.
We'll describe how to use this approach in an application.

*For further on Redux and ngrx see the [Further reading](../further-reading.html#redux-and-ngrx) section*
*For further on Redux and @ngrx see the
[Further reading](../further-reading.html#redux-and-ngrx) section*
58 changes: 58 additions & 0 deletions handout/redux/actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Actions

Redux uses a concept called Actions, which describe state changes to your
application. Redux actions are simple JSON objects that implement the `Action`
interface provided by [@ngrx](https://github.com/ngrx):

```typescript
export interface Action {
type: string;
payload?: any;
}
```

The `type` property is a string used to uniquely identify your action to your
application. It's a common convention to use *lisp-case* (such as `MY_ACTION`),
however you are free to use whatever casing style that makes to your team, as
long as it's consistent across the project.

The `payload` property provides a way to pass additional data to other parts of
Redux, and it's entirely optional.

Here is an example:

```typescript
const loginSendAction: Action = {
type: 'LOGIN_SEND',
payload: {
username: 'katie',
password: '35c0cd1ecbbb68c75498b83c4e79fe2b'
}
};
```

> Plain objects are used so that the actions are serializable and can be
replayable into the application state. Even if your actions involve asynchronous
logic, the final dispatched action will remain a plain JSON object.

To simplify action creation, you can create a factory function to take care of
the repeating parts within your application:

_app/store/createAction.ts_
```typescript
import {Action} from '@ngrx/store';

export function createAction(type, payload?): Action {
return { type, payload };
}
```

The resulting creation of the `LOGIN_SEND` action becomes much more succinct and
cleaner:

```typescript
const loginSendAction: Action = createAction('LOGIN_SEND', {
username: 'katie',
password: '35c0cd1ecbbb68c75498b83c4e79fe2b'
});
```
15 changes: 15 additions & 0 deletions handout/redux/adding_ngrx_to_your_project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Adding @ngrx to your Project

In your console, run the following command to add
[@ngrx](https://github.com/ngrx) to your list of dependencies in `package.json`:

```shell
npm install @ngrx/core @ngrx/store --save
```

If you plan on using the [@ngrx/effects](https://github.com/ngrx/effects)
extensions to add side-effect capabilities, then also run the following command:

```shell
npm install @ngrx/effects --save
```
159 changes: 159 additions & 0 deletions handout/redux/component_architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Component Architecture

Our previous `CounterComponent` example is called a **smart
component** - it knew about Redux, the structure of the state and the actions it
needed to call. In theory you can drop this component into any area of your
application and just let it work, but it will be tightly bound to that specific
slice of state and those specific actions.

For example, what if we wanted to have multiple counters tracking different
things on the page? Or how about counting the number of red clicks vs blue
clicks?

To help make components more generic and reusable, it's worth trying to separate
them into _container_ components and _presentational_ components.

<table>
<thead>
<tr>
<th></th>
<th scope="col" style="text-align:left">Container Components</th>
<th scope="col" style="text-align:left">Presentational Components</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row" style="text-align:right">Location</th>
<td>Top level, route handlers</td>
<td>Middle and leaf components</td>
</tr>
<tr>
<th scope="row" style="text-align:right">Aware of Redux</th>
<td>Yes</th>
<td>No</th>
</tr>
<tr>
<th scope="row" style="text-align:right">To read data</th>
<td>Subscribe to Redux state</td>
<td>Read state from @Input properties</td>
</tr>
<tr>
<th scope="row" style="text-align:right">To change data</th>
<td>Dispatch Redux actions</td>
<td>Invoke callbacks from @Output properties</td>
</tr>
</tbody>
</table>

[redux docs](http://redux.js.org/docs/basics/UsageWithReact.html)

Keeping this in mind, let's refactor our `CounterComponent` to be a
_presentational_ component.

## Modifying `AppComponent` to become a smart component

First, let's modify our top-level application component to use the
`CounterService` and `CounterActions`, just as `CounterComponent` did:

_app/app.component.ts_

```typescript
import {Component} from '@angular/core';
import {Observable} from 'rxjs';

import {Counter} from '../../models/counter';
import {CounterService} from '../../services/counter.service';
import {CounterActions} from '../../store/counter/counter.actions';

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

counter$: Observable<Counter>;

constructor(
counterService: CounterService,
public actions: CounterActions
) {
this.counter$ = counterService.getCounter();
}

}
```

Now our `AppComponent` is a smart-component, because it's aware of Redux, it's
presence in the application state and the underlying services. As with previous
examples, we can use the `async` pipe to obtain the most recent `counter` value
and pass it along to other components within the template.

And while we haven't looked at the `@Output()`'s on `CounterComponent` just yet,
we'll want to delegate those events to our action creators in `CounterActions`.

_app/app.component.html_

```html
<counter [counter]="counter$ | async"
(onIncrement)="actions.increment()"
(onDecrement)="actions.decrement()"
(onReset)="actions.reset()">
</counter>
```

## Modifying `CounterComponent` to become a presentation component

In turn, we need to make the `CounterComponent` from a *smart component* into a
*dumb component*. For this, we will pass the data into the component using
`@Input` properties and click events using `@Output()` properties, removing the
use of `CounterService` and `CounterActions` entirely.

_app/counter/counter.component.ts_
```typescript
import {Component, Input, EventEmitter, Output} from '@angular/core';

import {Counter} from '../../models/counter';

@Component({
selector: 'counter',
templateUrl: './counter.component.html'
})
export class CounterComponent {

@Input()
counter: Counter;

@Output()
onIncrement: EventEmitter<void> = new EventEmitter<void>();

@Output()
onDecrement: EventEmitter<void> = new EventEmitter<void>();

@Output()
onReset: EventEmitter<void> = new EventEmitter<void>();

}
```

Our child components become much simpler and testable, because we don't have to
use the `async` pipe to work with our state, which removes a lot of pain when
dealing with lots of `@Input`'s or the need to use complex expressions with
`Observable`'s.

We can also now simply use core Angular features to emit values whenever a click
event happens:

_app/counter/counter.component.html_

```html
<p>
Clicked: {{counter.currentValue}} times
<button (click)="onIncrement.emit()">+</button>
<button (click)="onDecrement.emit()">-</button>
<button (click)="onReset.emit()">Reset</button>
</p>
```

We now have a nicely-reusable presentational component with no knowledge of
Redux or our application state.
44 changes: 44 additions & 0 deletions handout/redux/configuring_your_application.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Configuring your application #

Once you have your reducers created, it’s time to configure your Angular
application. In your main application module, simple add the
`StoreModule.provideStore()` call to your `@NgModule`'s imports:

_app/app.module.ts_
```typescript
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {StoreModule} from '@ngrx/store';
import {EffectsModule} from '@ngrx/effects';

import 'rxjs/Rx';

import {rootReducer} from './store/rootReducer';
import {CounterActions} from './store/actions';
import {CounterEffects} from './store/effects';
import {AppComponent, CounterComponent} from './components';
import {CounterService} from './services';

@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
StoreModule.provideStore(rootReducer)
],
declarations: [
AppComponent,
CounterComponent
],
providers: [
CounterActions,
CounterService
],
bootstrap: [AppComponent]
})
export class AppModule {

}
```
Loading