diff --git a/SUMMARY.md b/SUMMARY.md index 67902fbc..848dd152 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -211,14 +211,22 @@ --- -* [Redux and Ngrx](handout/redux/README.md) - * [Review of Reducers and Pure Functions](handout/redux/review_of_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) +* [State Management](handout/state-management/README.md) + * [Redux and @ngrx](handout/state-management/ngrx/README.md) + * [Adding @ngrx to your Project](handout/state-management/ngrx/adding_ngrx_to_your_project.md) + * [Defining your Main Application State](handout/state-management/ngrx/defining_your_main_application_state.md) + * [Example Application](handout/state-management/ngrx/example_application.md) + * [Reading your Application State using Selectors](handout/state-management/ngrx/reading_your_application_state_using_selectors.md) + * [Actions](handout/state-management/ngrx/actions.md) + * [Modifying your Application State by Dispatching Actions](handout/state-management/ngrx/modifying_your_application_state_by_dispatching_actions.md) + * [Reducers and Pure Functions](handout/state-management/ngrx/reducers_and_pure_functions.md) + * [Reducers as State Management](handout/state-management/ngrx/reducers_as_state_management.md) + * [Creating your Application's Root Reducer](handout/state-management/ngrx/creating_your_applications_root_reducer.md) + * [Configuring your Application](handout/state-management/ngrx/configuring_your_application.md) + * [Implementing Components](handout/state-management/ngrx/implementing_components.md) + * [Component Architecture](handout/state-management/ngrx/component_architecture.md) + * [Side Effects](handout/state-management/ngrx/side_effects.md) + * [Getting More From Redux and @ngrx](handout/state-management/ngrx/getting_more_from_redux_and_ngrx.md) --- diff --git a/handout/redux/README.md b/handout/redux/README.md deleted file mode 100644 index bfb10e41..00000000 --- a/handout/redux/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Redux and Ngrx - -## What is Redux? - -Redux is an application state manager for JavaScript applications, and keeps -with the core principles of the Flux-architecture by having a unidirectional -data flow in your application. - -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 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. - - -## 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 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. - -*For further on Redux and ngrx see the [Further reading](../further-reading.html#redux-and-ngrx) section* diff --git a/handout/redux/configuring_your_application_to_use_redux.md b/handout/redux/configuring_your_application_to_use_redux.md deleted file mode 100644 index 8316bd93..00000000 --- a/handout/redux/configuring_your_application_to_use_redux.md +++ /dev/null @@ -1,80 +0,0 @@ -# Configuring your Application to use Redux # - -Once you have the reducers and actions created, it is time to configure your -Angular application to make use of Ng2-Redux. For this, we will need to: - -* Register Ng2-Redux with Angular -* Create our application reducer -* Create and configure a store - -## Registering Ng2-Redux with Angular - -_app/index.ts_ -```typescript -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { NgReduxModule, NgRedux } from 'ng2-redux'; -import { SimpleRedux } from './containers/app-container'; - -@NgModule({ - imports: [ - BrowserModule, - NgReduxModule - ], - declarations: [ - SimpleRedux - ], - bootstrap: [ SimpleRedux ] -}) -class AppModule { -} -platformBrowserDynamic().bootstrapModule(AppModule); -``` - -Here, we're simply adding the `NgReduxModule` class as an import in our NgModule declaration. - -## Create our Application Reducer - -_app/reducers/index.ts_ -```javascript -import { combineReducers } from 'redux'; -import counter from './counter-reducer'; - -export default combineReducers({ - counter -}); -``` - -`combineReducers` allows us to break out our application into smaller reducers -with a single area of concern. Each reducer that you pass into it will control a -property on the state. So when we are subscribing to our state changes with -Ng2-Redux's `@select` decorator, we are able to select a counter property, -or any other reducers you have provided. - -## Create and Configure a Store - -Next we want Ng2-Redux to configure our store based on settings we provide. -This should be done once, in the top-level component of your application. - -_app/containers/app-container.ts_ -```javascript -import { Component } from '@angular/core'; -import { NgRedux } from 'ng2-redux'; -import logger from '../store/configure-logger'; -import reducer from '../reducers'; - -@Component({ - // ... -}) -class SimpleRedux { - constructor(ngRedux: NgRedux) { - const initialState = {}; - const middleware = [ logger ]; - ngRedux.configureStore(reducer, initialState, middleware); - } -} -``` - -In this example we are creating a store that uses the `redux-logger` -middleware, which will add some logging functionality to the application. diff --git a/handout/redux/reducers_as_state_management.md b/handout/redux/reducers_as_state_management.md deleted file mode 100644 index c555121d..00000000 --- a/handout/redux/reducers_as_state_management.md +++ /dev/null @@ -1,66 +0,0 @@ -# Reducers as State Management - -This simple idea turns out to be very powerful. With Redux, you replay a series -of events into the reducer and get your new application state as a result. - -Reducers in a Redux application should not mutate the state, but return a copy -of it, and be side-effect free. This encourages you to think of your application -as UI that gets "computed" from a series of events in time. - -Let's take a look at a simple counter reducer. - -## Simple Reducer - -_app/reducer/counter-reducer.ts_ -```javascript -import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter-actions'; - -export default function counter(state = 0, action) { - switch (action.type) { - case INCREMENT_COUNTER: - return state + 1; - case DECREMENT_COUNTER: - return state - 1; - default: - return state; - } -} -``` - -We can see here that we are passing in an initial state and an action. To -handle each action we have set up a switch statement. Instead of each reducer -needing to explicitly subscribe to the dispatcher, every action gets passed into -each reducer, which handles the actions it's interested in and then returns the -new state along to the next reducer. - -Reducers should be side-effect free. This means that they should not -modify things outside of their own scope. They should simply compute the next -application state as a pure function of the reducer's arguments. - -For this reason, side-effect causing operations, such as -updating a record in a database, generating an id, etc. should be handled -elsewhere in the application such as in the action creators, using middleware such as ['Epics' from redux-observable](https://github.com/redux-observable/redux-observable) or [ngrx/effects](https://github.com/ngrx/effects). - -Another consideration when creating your reducers is to ensure that they are immutable and not modifying the state of your application. If you mutate your application state, it can cause unexpected behavior. There are a few ways to help maintain immutability in your reducers. One way is by using new ES6 features such as `Object.assign` or the spread operator for arrays. - -```js -function immutableObjectReducer(state = { someValue: 'value'} , action) { - switch(action.payload) { - case SOME_ACTION: - return Object.assign({}, state, { someValue: action.payload.value }); - default: - return state; - } -} - -function immutableArrayReducer(state = [1,2,3], action) { - switch(action.payload) { - case ADD_ITEM: - return [...state,action.payload.value] - default: - return state; - } -} -``` - -However, when dealing with complex or deeply nested objects, it can be difficult to maintain immutability in your application using this syntax. This is where a library like Immutable.js can help. diff --git a/handout/redux/redux_actions.md b/handout/redux/redux_actions.md deleted file mode 100644 index 04d9d56e..00000000 --- a/handout/redux/redux_actions.md +++ /dev/null @@ -1,155 +0,0 @@ -# Redux Actions - -Redux actions should generally be simple JSON objects. This is because -they should be serializable and replayable into the application state. Even if -your actions involve asynchronous logic, the final dispatched action should -remain a plain JSON object. - -Redux action creators are generally where side-effects should happen, such as -making API calls or generating IDs. This is because when the final action gets -dispatched to the reducers, we want to update the application state to reflect -what has already happened. - -Let's take a look at the actions that are used in this example. For now, let's -just focus on some simple synchronous actions. - -## Synchronous Actions - -Most Redux apps have a set of functions, called "action creators", that are -used to set up and dispatch actions. - -In Angular, it's convenient to define "action creator services" for your -action creators to live in; these services can be injected into the components -that need to dispatch the actions. - -_app/actions/counter-actions.ts_ -```javascript -import { Injectable } from '@angular/core'; -import { NgRedux } from 'ng2-redux'; - -export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; -export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'; - -@Injectable() -export class CounterActions { - constructor(private redux: NgRedux) {} - - increment() { - this.redux.dispatch({ type: INCREMENT_COUNTER }); - } - - decrement() { - this.redux.dispatch({ type: DECREMENT_COUNTER }); - } -} -``` - -As you can see, the action creators are simple functions that (optionally) -take parameters, and then dispatch a JSON object containing more information. - -The `dispatch` function expects to be called with something that conforms to -the "Action" interface from the Redux library: - -```typescript -import { Action } from 'redux'; -``` - -This interface has the following properties: - -* _type_ - a string/enum representing the action -* _payload?_ - optional, the data that you want to pass into the reducer if applicable -* _error?_ - optional, indicates if this message is due to an error -* _metaData?_ - optional - any extra information - -## Asynchronous Actions - -This "ActionCreatorService" pattern comes in handy if you must handle -asynchronous or conditional actions (users of react-redux may recognize this -pattern as analogous to redux-thunk in a dependency-injected world). - -_app/actions/counter-actions.ts_ -```typescript -import { Injectable } from '@angular/core'; -import { NgRedux } from 'ng2-redux'; - -export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; -export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'; - -@Injectable() -export class CounterActions { - constructor(private redux: NgRedux) {} - - // ... - - incrementIfOdd() { - const { counter } = this.redux.getState(); - - if (counter % 2 === 0) return; - this.redux.dispatch({ type: INCREMENT_COUNTER }); - } - - incrementAsync(timeInMs = 1000) { - this.delay(timeInMs).then(() => this.redux.dispatch({ type: INCREMENT_COUNTER })); - } - - private delay(timeInMs) { - return new Promise((resolve, reject) => { - setTimeout(() => resolve() , timeInMs); - }); - } -} -``` - -In the `incrementIfOdd` action, we are using the `getState` function to -get the current state of the application. - -In the `incrementAsync` action, we are delaying the actual call to `dispatch`. -For example, we have created a Promise that will resolve after the delay. Once -the Promise resolves, we can then do a dispatch with the increase action. - -[View Ng2-Redux Example](https://plnkr.co/edit/wDVKsW7ux3dvxNdD7PO0?p=preview) -[View Ngrx Example](https://plnkr.co/edit/7XmqzIwuynA5UGANPft0?p=preview) - -## Actions that Depend on Other Services - -The ActionCreatorService pattern becomes necessary in cases where your action -creators must use other Angular services. Consider the following -ActionCreatorService that handles a remote API call: - -```typescript -import { Injectable } from '@angular/core'; -import { NgRedux } from 'ng2-redux'; -import { AuthService } from '../services/auth/'; - -@Injectable() -export class SessionActions { - static LOGIN_USER_PENDING = 'LOGIN_USER_PENDING'; - static LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS'; - static LOGIN_USER_ERROR = 'LOGIN_USER_ERROR'; - static LOGOUT_USER = 'LOGOUT_USER'; - - constructor( - private ngRedux: NgRedux, - private authService: AuthService) {} - - loginUser(credentials) { - const username = credentials.username; - const password = credentials.password; - - this.ngRedux.dispatch({ type: SessionActions.LOGIN_USER_PENDING }); - - this.authService.login(username, password) - .then(result => this.ngRedux.dispatch({ - type: SessionActions.LOGIN_USER_SUCCESS, - payload: result - })) - .catch(() => this.ngRedux.dispatch({ - type: SessionActions.LOGIN_USER_ERROR - })); - }; - - logoutUser = () => { - this.ngRedux.dispatch({ type: SessionActions.LOGOUT_USER }); - }; -} -``` diff --git a/handout/redux/redux_and_component_architecture.md b/handout/redux/redux_and_component_architecture.md deleted file mode 100644 index 6c13f851..00000000 --- a/handout/redux/redux_and_component_architecture.md +++ /dev/null @@ -1,178 +0,0 @@ -# Redux and Component Architecture - -In the above example, our `counter` component is a smart component. -It knows about Redux, the structure of the state and the actions it needs 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? -For example, 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Container ComponentsPresentational Components
LocationTop level, route handlersMiddle and leaf components
Aware of ReduxYes - No -
To read dataSubscribe to Redux stateRead state from @Input properties
To change dataDispatch Redux actionsInvoke callbacks from @Output properties
- -[redux docs](http://redux.js.org/docs/basics/UsageWithReact.html) - - Keeping this in mind, let's refactor our `counter` to be a _presentational_ - component. First, let's modify our `app-container` to have two counter - components on it as we currently have it. - -```javascript -import { Component } from '@angular/core'; - -@Component({ - selector: 'simple-redux' - template: ` -
-

Redux: Two components, one state.

-
-

Click Counter

- -
-
-

Curse Counter

- -
-
- ` -}) -export class SimpleRedux {} -``` -[View Example](https://plnkr.co/edit/w9qg7UklSryt4ujmCpTy?p=preview) - -As you can see in the example, when clicking on the buttons the numbers in both components will update in sync. -This is because the counter component is coupled to a specific piece of state and action. - -Looking at the example, you can see that there is already an _app/reducers/curse-reducer.ts_ and _app/actions-curse-actions.ts_. -They are pretty much the same as the counter actions and counter reducer, -we just wanted to create a new reducer to hold the state of it. - -To turn the counter component from a smart component into a dumb component, -we need to change it to have data and callbacks passed down into it. -For this, we will pass the data into the component using `@Input` properties, -and the action callbacks as `@Output` properties. - -We now have a nicely-reusable presentational component with no knowledge of -Redux or our application state. - -_app/components/counter-component.ts_ -```javascript -import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { Observable } from 'rxjs'; - -@Component({ - selector: 'counter', - template: ` -

- Clicked: {{ counter | async }} times - - - - -

- ` -}) -export class Counter { - @Input() counter: Observable; - @Output() increment = new EventEmitter(); - @Output() decrement = new EventEmitter(); - @Output() incrementIfOdd = new EventEmitter(); - @Output() incrementAsync = new EventEmitter(); -} -``` - -Next, let's modify the main app container to hook up these inputs and outputs -to the template. - -`@Component` -_app/src/containers/app-containter.ts_ -```typescript -@Component({ - selector: 'simple-redux', - providers: [ CounterActions, CurseActions ], - template: ` -
-

Redux: Presentational Counters

-
-

Click Counter

- - -
-
-

Curse Counter

- - -
-
- ` -}) -``` - -At this point, the template is attempting to call actions on our two -ActionCreatorServices, `CounterActions` and `CurseActions`; we just need to hook -those up using Dependency Injection: - -_app/src/containers/app-container.ts_ -```typescript -import { Component, View, Inject, OnDestroy, OnInit } from '@angular/core'; -import { select } from 'ng2-redux'; -import { Observable } from 'rxjs'; -import { CounterActions } from '../actions/counter-actions'; -import { CurseActions } from '../actions/curse-actions'; - -@Component({ /* see above .... */}) -export class SimpleRedux { - @select() counter$: Observable; - @select() curse$: Observable; - - constructor( - public counterActions: CounterActions, - public curseActions: CurseActions) { - } -} -``` -[View Ng2-Redux Example](https://plnkr.co/edit/Ci7RDJPIcu43AD3zSZ1O?p=preview) -[View Ngrx Example](https://plnkr.co/edit/2FraZ1rGDVoamX1Qtwv6?p=preview) - -Our two `Observable`s, `counter$` and `curse$`, will now get updated with a new -value every time the relevant store properties are updated by the rest of the -system. diff --git a/handout/redux/review_of_reducers_and_pure_functions.md b/handout/redux/review_of_reducers_and_pure_functions.md deleted file mode 100644 index a604db10..00000000 --- a/handout/redux/review_of_reducers_and_pure_functions.md +++ /dev/null @@ -1,10 +0,0 @@ -# Review of Reducers and Pure Functions - -One of the core concepts of Redux is the *reducer*. A reducer is a function with the signature `(accumulator: T, item: U) => T`. Reducers are often used in JavaScript through the `Array.reduce` method, which iterates over each of the array's items and accumulates a single value as a result. Reducers should be *pure functions*, meaning they don't generate any side-effects. - -A simple example of a reducer is the sum function: - -```javascript -let x = [1,2,3].reduce((value, state) => value + state, 0) -// x == 6 -``` diff --git a/handout/redux/using_redux_with_components.md b/handout/redux/using_redux_with_components.md deleted file mode 100644 index 29f7c836..00000000 --- a/handout/redux/using_redux_with_components.md +++ /dev/null @@ -1,76 +0,0 @@ -# Using Redux with Components - -We will use the -[select pattern](https://github.com/angular-redux/ng2-redux#the-select-pattern) -from Ng2-Redux to bind our components to the store. To demonstrate how this -works, let's take a look at a small example with a counter component. - -## Counter Example - -Let's start by building out a counter component. The component will be -responsible for keeping track of how many times it was clicked and displaying -that amount. - -_app/components/counter-component.ts_ - -```javascript -import { Component } from '@angular/core'; -import { select } from 'ng2-redux'; -import { Observable } from 'rxjs'; -import { CounterActions } from '../actions/counter-actions'; - -@Component({ - selector: 'counter', - providers: [ CounterActions ], - template: ` -

- Clicked: {{ counter$ | async }} times - - - - -

- ` -}) -export class Counter { - @select() counter$: Observable; - - constructor(public actions: CounterActions) {} -} -``` - -[View Example](https://plnkr.co/edit/pujePgvmkyKHurXtOS3k?p=preview) - -The template syntax should be familiar by now, displaying a `Observable` counter -with the async pipe and handling some click events. - -In this case, the click events are bound to expressions that call our action -creators from the `CounterActions` ActionCreatorService. - -Let's take a look at the use of `@select`. - -`@select` is a feature of Ng2-Redux which is designed to help you attach your -store's state to your components in a declarative way. You can attach it to a -property of your component class and Ng2-Redux will create an -`Observable` and bind it to that property for you. - -In this case, `@select` has no parameters, so Ng2-Redux will look for a store -property with the same name as the class variable. It omits the trailing `$` -since that's simply a naming convention for `Observables`. - -So now, any time `store.counter` is updated by a reducer, `counter$` will -receive the new value and `| async` will update it in the template. - -Note that `@select` supports a wide range of arguments to allow you to select -portions of your Redux store with a great deal of flexibility. See the -[Ng2-Redux](https://github.com/angular-redux/ng2-redux#the-select-pattern) docs -for more details. - -The Ng2-Redux "select pattern" style differs a bit from the "connect" -style used by `react-redux`; however by using Angular's DI and TypeScript's -decorators, we can have a nicely declarative binding where most of the work is -done in the template. We also get the power of `Observables` and -`OnPush` change detection for better performance. - -Either way, we still benefit from the Redux fundamentals of reducers and one-way -data-flow. diff --git a/handout/state-management/README.md b/handout/state-management/README.md new file mode 100644 index 00000000..27777218 --- /dev/null +++ b/handout/state-management/README.md @@ -0,0 +1,25 @@ +# State Management + +For larger Angular applications with a lot of asynchronous activity and where +there's a lot of state that is being shared and manipulated across multiple +components and modules, managing state can be quite challenging. In a typical +application, we're managing things like: + +* Data that comes from the server and whether it's pending or resulted in an + error +* UI state like toggles, alerts and errors messages +* User input, such as form submissions, filters and search queries +* Custom themes, credentials and localization +* Many other types of state + +As the application grows, how do we know that a state change in one module will +consistently and accurately reflected in other modules? And what if these +modifications result in even more state changes? Eventually, it becomes +extremely difficult to reason about what's actually happening in your +application, and be a large source of bugs. + +In Angular, there are 3 main ways to solve this problem. + +1. [Redux using @ngrx](ngrx/README.md); +2. Redux using ng2-redux; and +3. Angular Services and RxJS. \ No newline at end of file diff --git a/handout/state-management/ng2-redux/README.md b/handout/state-management/ng2-redux/README.md new file mode 100644 index 00000000..be4a29a8 --- /dev/null +++ b/handout/state-management/ng2-redux/README.md @@ -0,0 +1,4 @@ +# Redux and ng2-redux + +One popular Angular specific implementation of the Redux pattern is +[Ng2-Redux](https://github.com/wbuchwalter/ng2-redux). \ No newline at end of file diff --git a/handout/state-management/ngrx/README.md b/handout/state-management/ngrx/README.md new file mode 100644 index 00000000..e3aecd00 --- /dev/null +++ b/handout/state-management/ngrx/README.md @@ -0,0 +1,26 @@ +# Redux and @ngrx + +## What is Redux? + +Redux is an application state manager for JavaScript applications, and keeps +with the core principles of the Flux-architecture by having a unidirectional +data flow in your application. + +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. + +## What is @ngrx? + +Redux state managers have been very well received and have 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 and [RxJS](https://github.com/Reactive-Extensions/RxJS), as it leans +heavily on the observable paradigm. + +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* diff --git a/handout/state-management/ngrx/actions.md b/handout/state-management/ngrx/actions.md new file mode 100644 index 00000000..faf8f416 --- /dev/null +++ b/handout/state-management/ngrx/actions.md @@ -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' +}); +``` \ No newline at end of file diff --git a/handout/state-management/ngrx/adding_ngrx_to_your_project.md b/handout/state-management/ngrx/adding_ngrx_to_your_project.md new file mode 100644 index 00000000..e7e04761 --- /dev/null +++ b/handout/state-management/ngrx/adding_ngrx_to_your_project.md @@ -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 +``` \ No newline at end of file diff --git a/handout/state-management/ngrx/component_architecture.md b/handout/state-management/ngrx/component_architecture.md new file mode 100644 index 00000000..3a2daabd --- /dev/null +++ b/handout/state-management/ngrx/component_architecture.md @@ -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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Container ComponentsPresentational Components
LocationTop level, route handlersMiddle and leaf components
Aware of ReduxYes + No +
To read dataSubscribe to Redux stateRead state from @Input properties
To change dataDispatch Redux actionsInvoke callbacks from @Output properties
+ +[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; + + 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 + + +``` + +## 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 = new EventEmitter(); + + @Output() + onDecrement: EventEmitter = new EventEmitter(); + + @Output() + onReset: EventEmitter = new EventEmitter(); + +} +``` + +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 +

+ Clicked: {{counter.currentValue}} times + + + +

+``` + +We now have a nicely-reusable presentational component with no knowledge of +Redux or our application state. \ No newline at end of file diff --git a/handout/state-management/ngrx/configuring_your_application.md b/handout/state-management/ngrx/configuring_your_application.md new file mode 100644 index 00000000..d414ee41 --- /dev/null +++ b/handout/state-management/ngrx/configuring_your_application.md @@ -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 { + +} +``` \ No newline at end of file diff --git a/handout/state-management/ngrx/creating_your_applications_root_reducer.md b/handout/state-management/ngrx/creating_your_applications_root_reducer.md new file mode 100644 index 00000000..13d318dd --- /dev/null +++ b/handout/state-management/ngrx/creating_your_applications_root_reducer.md @@ -0,0 +1,15 @@ +# Creating your Application's Root Reducer + +[@ngrx](https://github.com/ngrx) allows us to break our application into smaller +reducers with a single area of concern. We can combine these reducers by +creating an object that mirrors the application's `AppState`, where each +property will point to one of those smaller reducers. + +_app/store/rootReducer.ts_ +```typescript +import {counterReducer} from './counter/counter.reducer'; + +export const rootReducer = { + counter: counterReducer +}; +``` \ No newline at end of file diff --git a/handout/state-management/ngrx/defining_your_main_application_state.md b/handout/state-management/ngrx/defining_your_main_application_state.md new file mode 100644 index 00000000..3fb7ca5b --- /dev/null +++ b/handout/state-management/ngrx/defining_your_main_application_state.md @@ -0,0 +1,37 @@ +# Defining your Main Application State + +When building an application using Redux, the first thing to think about is, +"What state do I want to store?" It is generally a good idea to capture all +of the application's state so that it can be accessible from anywhere and all +in one place for easy inspection. + +In the application state, we store things like: + +* Data received through API calls +* User input +* Presentation state, such as menu and button toggles +* Application preferences +* Internationalization messages +* Themes and other customizable areas of your application + +To define your application state, use an interface called `AppState` or +`IAppState`, depending on the naming conventions used on your project. + +Here's an example: + +_app/models/appState.ts_ +```typescript +export interface AppState { + readonly colors: Colors; + readonly localization: Localization; + readonly login: Login; + readonly projectList: ProjectList; + readonly registration: Registration; + readonly showMainNavigation: boolean; +} +``` + +> **Note:** We're using `readonly` to ensure compile-time immutability, and it +provides the simplest immutable implementation without adding more dependencies +to clutter the examples. However, feel free to use another approach on your +project that makes sense for your team. \ No newline at end of file diff --git a/handout/state-management/ngrx/example_application.md b/handout/state-management/ngrx/example_application.md new file mode 100644 index 00000000..31123d43 --- /dev/null +++ b/handout/state-management/ngrx/example_application.md @@ -0,0 +1,26 @@ +# Example Application + +In this chapter, you'll be creating a simple counter application using +[@ngrx](https://github.com/ngrx). Your app will allow users to increment and +decrement a number by one, as well as reset that value back to zero. Here's the +`AppState` that we'll be using throughout the example: + +_app/models/appState.ts_ +```typescript +import {Counter} from './counter'; + +export interface AppState { + readonly counter: Counter; +} +``` + +_app/models/counter.ts_ +```typescript +export interface Counter { + readonly currentValue: number; +} +``` + +> It's good practice to declare each interface in its own file, and create a +logical directory structure if you have seven or more interfaces used by your +application. \ No newline at end of file diff --git a/handout/redux/getting_more_from_redux_and_ngrx.md b/handout/state-management/ngrx/getting_more_from_redux_and_ngrx.md similarity index 83% rename from handout/redux/getting_more_from_redux_and_ngrx.md rename to handout/state-management/ngrx/getting_more_from_redux_and_ngrx.md index c4618b8a..90805f24 100644 --- a/handout/redux/getting_more_from_redux_and_ngrx.md +++ b/handout/state-management/ngrx/getting_more_from_redux_and_ngrx.md @@ -1,4 +1,4 @@ -# Getting More From Redux and Ngrx +# Getting more from Redux and @ngrx ## Redux @@ -9,9 +9,9 @@ Redux has a number of tools and middleware available in its ecosystem to facilit - *[redux-observable](https://github.com/redux-observable/redux-observable)* - an RxJS-based model for handling side-effects on action streams. - *[ng2-redux-router](https://github.com/dagstuan/ng2-redux-router) - reactive glue between the Angular router and your redux store. -## Ngrx +## @ngrx -Ngrx provides most of its Redux implementation through the [ngrx/store](https://github.com/ngrx/store) module. Other modules are available for better integration and development. +@ngrx provides most of its Redux implementation through the [ngrx/store](https://github.com/ngrx/store) module. Other modules are available for better integration and development. - *[ngrx/store-devtools](https://github.com/ngrx/store-devtools)* - an ngrx implementation of the Redux DevTools - *[ngrx/effects](https://github.com/ngrx/effects)* - a model for performing side-effects similar to `redux-saga` diff --git a/handout/state-management/ngrx/implementing_components.md b/handout/state-management/ngrx/implementing_components.md new file mode 100644 index 00000000..f0d8534f --- /dev/null +++ b/handout/state-management/ngrx/implementing_components.md @@ -0,0 +1,52 @@ +# Implementing Components + +To demonstrate how to use the `CounterService` in your components, let's start +by building out a small `CounterComponent`. The component will be +responsible for incrementing and decrementing the counter by one, as well as +allowing the user to reset the counter to zero. + +_app/components/counter.component.ts_ + +```typescript +import {Component, Input} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; + +import {CounterService} from '../services'; +import {CounterActions} from '../store/counter/counter.actions'; + +@Component({ + selector: 'counter', + templateUrl: './counter.component.html' +}) +export class CounterComponent { + + private currentValue$: Observable; + + constructor( + counterService: CounterService, + public actions: CounterActions + ) { + this.currentValue$ = counterService.getCurrentValue(); + } + +} +``` + +_app/components/counter.component.html_ +```html +

+ Clicked: {{currentValue$ | async}} times + + + +

+``` + +The template syntax should be familiar by now, displaying an `Observable` +counter value with the `async` pipe. Any time `appState.counter.currentValue` is +updated by a reducer, `currentValue$` will receive the new value and `| async` +will update it in the template. + +The component also handles some click events. Each click event is bound to +expressions that call our action creators from the `CounterActions` +ActionCreatorService. \ No newline at end of file diff --git a/handout/state-management/ngrx/modifying_your_application_state_by_dispatching_actions.md b/handout/state-management/ngrx/modifying_your_application_state_by_dispatching_actions.md new file mode 100644 index 00000000..231275c3 --- /dev/null +++ b/handout/state-management/ngrx/modifying_your_application_state_by_dispatching_actions.md @@ -0,0 +1,145 @@ +# Modifying your Application State by Dispatching Actions + +Most Redux apps have a set of functions, called "action creators", that are +used to set up and dispatch actions. + +In Angular, it's convenient to define your action creators as `@Injectable()` +services, decoupling the dispatch, creation and side-effect logic from the +`@Component` classes in your application. + +## Synchronous Actions + +Here is a simple example: + +_app/store/counter/counter.actions.ts_ +```typescript +import {Injectable} from '@angular/core'; +import {Store} from '@ngrx/store'; + +import {createAction} from '../createAction'; +import {AppState} from '../../models/appState'; + +@Injectable() +export class CounterActions { + + static INCREMENT = 'INCREMENT'; + static DECREMENT = 'DECREMENT'; + static RESET = 'RESET'; + + constructor(private store: Store) { + + } + + increment() { + this.store.dispatch(createAction(CounterActions.INCREMENT)); + } + + decrement() { + this.store.dispatch(createAction(CounterActions.DECREMENT)); + } + + reset() { + this.store.dispatch(createAction(CounterActions.RESET)); + } + +} +``` + +As you can see, the action creators are simple functions that dispatch `Action` +objects containing more information that describes the state modification. + +## Asynchronous Actions + +This "ActionCreatorService" pattern comes in handy if you must handle +asynchronous or conditional actions (users of react-redux may recognize this +pattern as analogous to redux-thunk in a dependency-injected world). + +_app/store/counter/counter.actions.ts_ +```typescript +import {Injectable} from '@angular/core'; +import {Store} from '@ngrx/store'; + +import {createAction} from '../createAction'; +import {AppState} from '../../models/appState'; + + +@Injectable() +export class CounterActions { + + constructor(private store: Store) { + + } + + incrementIfOdd() { + this.store.select(appState => appState.counter.currentValue) + .take(1) + .subscribe(currentValue => { + if (currentValue % 2 !== 0) { + this.store.dispatch(createAction(CounterActions.INCREMENT); + } + }); + } + + incrementAsync(timeInMs: number = 1000) { + this.delay(timeInMs).then(() => this.store.dispatch(createAction(CounterActions.INCREMENT))); + } + + private delay(timeInMs: number) { + return new Promise((resolve) => { + setTimeout(() => resolve() , timeInMs); + }); + } + +} +``` + +In the `incrementIfOdd()` action creator, we create a one-time +subscription to the counter's `currentValue` in the application state. From +there, we check to see if it's odd before dispatching an action. + +In the `incrementAsync()` action creator, we are delaying the actual call to +`dispatch()`. We created a `Promise` that will resolve after the delay. Once +the `Promise` resolves, we can then dispatch an action to increment the counter. + +## Actions that Depend on Other Services + +The ActionCreatorService pattern becomes necessary in cases where your action +creators must use other Angular services. Consider the following +`SessionActions` service that handles a remote API call: + +```typescript +import {Injectable} from '@angular/core'; +import {Store} from '@ngrx/store'; + +import {createAction} from '../createAction'; +import {AppState} from '../../models/appState'; + +@Injectable() +export class SessionActions { + + static LOGIN_USER_PENDING = 'LOGIN_USER_PENDING'; + static LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS'; + static LOGIN_USER_ERROR = 'LOGIN_USER_ERROR'; + static LOGOUT_USER = 'LOGOUT_USER'; + + constructor( + private store: Store, + private authService: AuthService + ) { + + } + + loginUser(credentials: any) { + this.store.dispatch(createAction(SessionActions.LOGIN_USER_PENDING)); + + this.authService.login(credentials.username, credentials.password) + .then(result => this.store.dispatch(createAction(SessionActions.LOGIN_USER_SUCCESS, result))) + .catch(() => this.store.dispatch(createAction(SessionActions.LOGIN_USER_ERROR))); + }; + + logoutUser() { + this.store.dispatch(createAction(SessionActions.LOGOUT_USER)); + }; + +} +``` diff --git a/handout/state-management/ngrx/reading_your_application_state_using_selectors.md b/handout/state-management/ngrx/reading_your_application_state_using_selectors.md new file mode 100644 index 00000000..1ac53b10 --- /dev/null +++ b/handout/state-management/ngrx/reading_your_application_state_using_selectors.md @@ -0,0 +1,69 @@ +# Reading your Application State using Selectors + +To read your application state in Redux, we need to use the `select()` method on +[@ngrx's](https://github.com/ngrx/store) `Store` class. This method creates and +returns an `Observable` that is bound to a specific property in your application +state. + +For example, here's how you would select the `counter` object: + +```typescript +store.select('counter'); // Returns Observable +``` + +And to fetch the counter's `currentValue`, we can pass in a `string` array, +where each string plucks a single property from the application state one at a +time in the order specified: + +```typescript +store.select(['counter', 'currentValue']); // Returns Observable +``` + +While `select()` allows for several variations of strings to be passed in, it +has it's shortcomings - namely you won't actually know if the plucking is +working properly until you execute your code. + +Because of that, `select()` allows you to select values using functions too, +which makes things more type-safe and your selectors will be more refactorable +by your IDE. + +```typescript +store.select(appState => appState.counter.currentValue); +``` + +## Creating a Counter Service + +While you could inject `Store` and select values directly in your Angular +components, it's considered to be a best practice to wrap this functionality +into separate services. This approach encapsulates all of the selection logic +and eliminates any duplication where the selection path is repeated +throughout your application. + +Let's tie everything together by building out a `CounterService` example: + +_app/services/counter.service.ts_ +```typescript +import {Injectable} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {Observable} from 'rxjs/Observable'; + +import {AppState} from '../models'; + +@Injectable() +export class CounterService { + + constructor(private store: Store) {} + + getCurrentValue(): Observable { + return this.store.select(appState => appState.counter.currentValue) + .filter(Boolean); + } + +} +``` + +Because `select()` returns an `Observable`, the `getCurrentValue()` method also +applies a `filter()` to ensure that subscribers do not receive any *falsy* +values. This greatly simplifies the code and templates in your components, since +they don't have to repeatedly consider the falsy case everywhere the value is +used. \ No newline at end of file diff --git a/handout/state-management/ngrx/reducers_and_pure_functions.md b/handout/state-management/ngrx/reducers_and_pure_functions.md new file mode 100644 index 00000000..189f0172 --- /dev/null +++ b/handout/state-management/ngrx/reducers_and_pure_functions.md @@ -0,0 +1,14 @@ +# Review of Reducers and Pure Functions + +One of the core concepts of Redux is the *reducer*. A reducer is a function with +the signature `(accumulator: T, item: U) => T`. Reducers are often used in +JavaScript through the `Array.reduce` method, which iterates over each of the +array's items and accumulates a single value as a result. Reducers should be +*pure functions*, meaning they don't generate any side-effects. + +A simple example of a reducer is the sum function: + +```javascript +let x = [1, 2, 3].reduce((sum, number) => sum + number, 0); +// x == 6 +``` diff --git a/handout/state-management/ngrx/reducers_as_state_management.md b/handout/state-management/ngrx/reducers_as_state_management.md new file mode 100644 index 00000000..eff200ef --- /dev/null +++ b/handout/state-management/ngrx/reducers_as_state_management.md @@ -0,0 +1,108 @@ +# Reducers as State Management + +Reducers are a simple idea that turns out to be very powerful. With Redux, you +replay a series of actions into the reducer and get your new application state +as a result. + +Reducers in a Redux application should not mutate the state, but *return a copy* +of it, and be side-effect free. This encourages you to think of your application +as UI that gets "computed" from a series of actions in time. + +## Simple Reducer + +Let's take a look at a simple counter reducer. + +_app/store/counter/counter.reducer.ts_ +```typescript +import {Action} from '@ngrx/store'; + +import {CounterActions} from './counter.actions'; + +export default function counterReducer(state: number = 0, action: Action): number { + switch (action.type) { + case CounterActions.INCREMENT: + return state + 1; + case CounterActions.DECREMENT: + return state - 1; + case CounterActions.RESET: + return 0; + default: + return state; + } +} +``` + +We can see here that we are passing in an initial state (the current number) and +an `Action`. To handle each action, a common approach is to use a `switch` +statement. Instead of each reducer needing to explicitly subscribe to the +dispatcher, every action gets passed into each reducer, which handles the +actions it's interested in and then returns the new state along to the next +reducer. + +Reducers should be side-effect free. This means that they should not modify +things outside of their own scope. They should simply compute the next +application state as a pure function of the reducer's arguments. + +For this reason, side-effect causing operations, such as updating a record in a +database, generating an id, etc. should be handled elsewhere in the application, +like in your action creators or using +[@ngrx/effects](https://github.com/ngrx/effects). + +## Complex Reducer + +Another consideration when creating your reducers is to ensure that they are +immutable and not modifying the state of your application. If you mutate your +application state, it can cause unexpected behavior. There are a few ways to +help maintain immutability in your reducers. One way is by using new ES6 +features such as `Object.assign()` or the spread operator for arrays. + +_app/models/counter.ts_ +```typescript +// ... + +export function setCounterCurrentValue(counter: Counter, currentValue: number): Counter { + return Object.assign({}, counter, { currentValue }); +} + +// ... +``` + +Here, the `setCounterCurrentValue()` function creates a new `Counter` object +that overwrites the `counter.currentValue` property with a new value while +maintaining the references and values of all of the other properties from +`counter`. + +Let's update our reducer to utilize this concept: + +```typescript +import {Action} from '@ngrx/store'; + +import {Counter, createDefaultCounter, setCounterCurrentValue} from '../../models/counter'; +import {CounterActions} from './counter.actions'; + +export function counterReducer( + counter: Counter = { currentValue: 0 }, + action: Action +): Counter { + switch (action.type) { + case CounterActions.INCREMENT: + return setCounterCurrentValue(counter, counter.currentValue + 1); + + case CounterActions.DECREMENT: + return setCounterCurrentValue(counter, counter.currentValue - 1); + + case CounterActions.RESET: + return setCounterCurrentValue(counter, 0); + + default: + return counter; + } +} +``` + +With each action, we take the existing `counter` state and create a new +state with the updated value (such as `counter.currentValue + 1`). + +When dealing with complex or deeply nested objects, it can be difficult to +maintain immutability in your application using this syntax. This is where a +library like [Ramda](http://ramdajs.com/) can help. diff --git a/handout/state-management/ngrx/side_effects.md b/handout/state-management/ngrx/side_effects.md new file mode 100644 index 00000000..42ac4060 --- /dev/null +++ b/handout/state-management/ngrx/side_effects.md @@ -0,0 +1,88 @@ +# Side Effects + +Often times, we need to perform some logic after an action has been dispatched +and the store has been updated. Because reducers should be side-effect free, we +need a way to handle these side-effects. Sometimes we can put this logic with +our action creator services, and that works for simple cases, but often times +the same block of logic needs to run in response to multiple action types. + +[@ngrx](https://github.com/ngrx) offers a library called +[@ngrx/effects](https://github.com/ngrx/effects) to solve these problems. + +## Creating your first effects service + +Let's say we have an application that supports customizable themes and other +customization options. To support this functionality, we'll need to fetch these +customizations from the server whenever a user logs into the application. + +Let's build out a `CustomizationEffects` service to accomplish this: + +```typescript +import {Injectable} from '@angular/core'; +import {Action} from '@ngrx/store'; +import {Actions, Effect} from '@ngrx/effects'; +import {Observable} from 'rxjs/Observable'; + +import {createAction} from '../createAction'; +import {CustomizationActions, SessionActions} from '../session/session.actions.ts'; +import {ApiService} from '../../services'; + +@Injectable() +export class CustomizationEffects { + + constructor( + private actions$: Actions, + private apiService: ApiService + ) { + + } + + @Effect() + login$ = this.actions$ + .ofType(SessionActions.LOGIN_SEND_SUCCESS) + .mergeMap(action => this.apiService.getCustomizations(action.payload.userId) + .map(result => createAction(CustomizationActions.CUSTOMIZATIONS_RETRIEVE_SUCCESS, result.json())) + .catch(error => Observable.of(createAction(CustomizationActions.CUSTOMIZATIONS_RETRIEVE_ERROR, error.json()))) + ); + +} +``` + +[@ngrx/effects](https://github.com/ngrx/effects) provides an Angular `actions$` +service (which is also an `Observable`) to emit every action that has been +dispatched by your application in a single stream. Its `ofType()` method can be +used to filter the one or more actions we're interesting in before adding a +side-effect. + +In this example, when the `LOGIN_SEND_SUCCESS` action occurs from anywhere in +the application, we make a request to the server to fetch the user's +customizations given their `userId`, which has been attached to the payload of +this action. Regardless if this requests succeeds or fails, we need to create +and return an `Observable` that is bound to the new action we'd like Redux to +perform, such as storing the customizations when the request is successful, or +processing the error returned by the server if there's an error. + +To tell [@ngrx/effects](https://github.com/ngrx/effects) which `Observable` +objects are side-effects to associate them with Redux, we need to provide a hint +using the `@Effect()` decorator. Without it, your side-effect will not run. + +## Configuring your effects service + +Lastly, we just need to add the `CustomizationEffects` service as a module to +the `@NgModule`'s imports to start the effects: + +```typescript +// ... +import {EffectsModule} from '@ngrx/effects'; + +import {CustomizationEffects} from './store/customization/customization.effects'; + +@NgModule({ + imports: [ + // ... + EffectsModule.run(CustomizationEffects) + ], + // ... +}) +export class AppModule { } +``` \ No newline at end of file diff --git a/handout/state-management/rxjs/README.md b/handout/state-management/rxjs/README.md new file mode 100644 index 00000000..a9d0b927 --- /dev/null +++ b/handout/state-management/rxjs/README.md @@ -0,0 +1,6 @@ +# Angular Services and RxJS + +Using Redux and immutable data is not the only way to manage state in an Angular +application. We can still benefit from a unidirectional flow of data using +[RxJS](https://github.com/Reactive-Extensions/RxJS) `Observable`s, under a +Façade of Angular Services. \ No newline at end of file