Monday, September 04, 2017

NGRX Store and State Management 3

In previous articles I described the basic Store and Reducer layout of the pattern.

Reducers are the only way state is modified, and the modifications should be pure, meaning synchronous functions. Anything asynchronous should be handled elsewhere. Other desired side effects may be chaining of actions, where one action sets in motion a chain of changes and asynchronous calls.

The NGRX solution is Effects. These are essentially a subscription to the Action observable. Every action can be looked at, some side effect applied, and a new action mapped into the stream to be dispatched.

Here are the actions.

export const ARTICLES_LOAD = 'Load Articles';
export const ARTICLES = 'Article list';

export class ArticlesLoadAction: Action {
   type = ARTICLES_LOAD;
}

export class ArticlesAction: Action {
   type = ARTICLES;
   constructor(public payload: Articles[]) {}

export type Action 
   = ArticlesLoadAction
   | ArticlesAction;

The ARTICLES type action was handled in the articlesreducer, inserting the array of articles into the state. But the ARTICLES_LOAD action was not. It is a command action, and here is where Effects do their magic.

@Injectable()
export class ArticlesEffects {
   constructor(private actions$: Actions,
                      private http: Http
) {}

@Effect() loadarticles$ = this.actions$
   .ofType(ARTICLES_LOAD)
   .mergeMap(() => this.http.get(api)
      .map(response => response.json())
      .map(articlearray => new ArticlesAction(articlearray))
      .catch(err => Observable.of(new ErrorAction(err))
   );

Effects are an injectable service. Actions, the action observable stream is injected in the constructor, along with any services required. 

The effect is an observable chain, starting with ofType, which is similar to .filter. We are only interested in ARTICLES_LOAD. Multiple actions can be listed, 

.ofType(ARTICLES_LOAD, ARTICLES_RELOAD)

mergeMap calls a function that returns an observable, merging the results back into the actions$ stream. The response is mapped to an action with the array of articles as payload. The reducer is listening for this action, and the array will be inserted into the state.

Note the .catch is off the http.get observable? Effects in v2 of NGRX would complete if the .catch was off the actions$ observable. As well, if you .map the values off the http.get you can return a type that the actions$ observable likes.

If you want to dispatch multiple actions, replace the .map with
.mergeMap(data => [new Action1(data), new Action2(data)])

and each action will be merged into the stream for dispatch.

In some situations you may not want to dispatch a new action. 

@Effect({dispatch: false}) = ...

The possibilities are almost endless, and this is where much of the work of getting and posting data will occur.

To initialize the effects,

EffectsModule.forRoot([ArticlesEffects]),

in the import: [] section of the app.module.ts. It takes an array of injectable effect classes.



Sunday, September 03, 2017

NGRX Store and State Management 2

In the first post I described what application state is and how to use it in your components.

Here we will get into modifying the state.

If you remember, our application State looked like this.

export interface Article {
   author: string;
   title: string;
}

export interface ArticleState {

   articlelist: Article[];
}

export interface State {
    articles: ArticleState;
    auth: AuthState;
    preferences: PreferencesState;

}

And we defined the reducers.


export function articlesreducer(state: ArticleState = InitialArticleState, action: Action) {}

On startup of the application we want the State to be usable as is in components, otherwise we would need to jump through hoops to check the existence of properties etc. On initialization the reducers are called with an undefined state parameter, so we can pass it an initialization value, here called InitialArticleState.

export const InitialArticleState: ArticleState = {
   articlelist: []

};

The ngFor in our template can handle an empty array without error.

State is modified by dispatching an action. How you define your actions is how your application logic will be executed. Victor Savkin wrote a blog post describing the types of messages you will use in your application. As your application grows having an established pattern for naming your actions makes it much easier to use.

So what actions do we need for our articles? We need to get them from somewhere. So we need a command action. And the articles need to be inserted into the store by a document type action.

An Action looks like this.

export interface Action {
  type: string;
}

To define them.

export const ARTICLES_LOAD = 'Load Articles';
export const ARTICLES = 'Article list';

export class ArticlesLoadAction: Action {
   type = ARTICLES_LOAD;
}

export class ArticlesAction: Action {
   type = ARTICLES;
   constructor(public payload: Articles[]) {}

export type Action 
   = ArticlesLoadAction
   | ArticlesAction;

The Articles reducer function would respond to these actions.

export const articlesreducer(state: ArticleState = InitialArticleState, action: fromArticles.Action) {
   switch (action.type) {
      case ARTICLES:
         return Object.assign({}, state, {
               articles: action.payload;
            }
         });
      default: return state;
   }
}

The ARTICLES action is responded by assigning the articles property with the array of articles. Each reducer is called with every action, so return state as the default case or the state will disappear into undefined. I don't respond to ArticlesLoad here because the state isn't modified by that action. A loading indicator could watch for Load actions if desired.

How to dispatch an action? 

constructor(private store: Store<State>) {
    this.store.dispatch( new ArticlesLoadAction());
}

An important point. State should be immutable; don't modify the state slice, return a new instance. If you are working with items in an array, don't push or pop, return a new instance of the array with the changes.

Reducers are very easy to test. Pass it an object and a type, then see what it spits out.

How do we get the articles?
And how do we break up our state/reducers into modules?

NGRX Store and State Management

I have seen a couple of times experienced developers finding NGRX difficult to grasp. That was my experience as well. So I'll try to explain what it is about.

https://gitter.im/ngrx/platform

This is a great chat site for getting assistance. The angular/angular chat is active as well.

The best was to start is to lay out the problem that it solves. The Redux pattern implemented in NGRX is a way to maintain application state.

What is application state? All the data that the application requires to build and render the view over time.

Starting from the view, or Component level in Angular, here is how the data is presented by NGRX.

@Component({
   selector: 'article-view',
   template: `<div ngfor="let a of articlelist$ | async">
                  {{a.author}}{{a.title}}
              </div>`  
})
export class ArticleListView {
   articlelist$ = this.store.select(getArticles);

   constructor(private store: Store) {}
}

First you inject the store using the constructor.
Then you assign a value with a selector, in this example, getArticles.

Then in the template articlelist$, an observable is piped through async, returning an array, passed to ngFor which loops through each item and displays the author and title.

State is a list of articles each with a title and author.

export interface Article {
   author: string;
   title: string;
}

export interface ArticleState {
   articlelist: Article[];
}

Then define that application state

export interface State {
   articles: ArticleState;
}

You may hit the first objection to this pattern right here. All you have is a list of objects, why the excessive boilerplate? Believe me it will get worse. In fact for such a simple application, NGRX doesn't really make sense. But fill out this example into a full content display application with users, multiple data sources, complex content structures, multiple developers. Then the boilerplate, the very clear definition of everything in a way that is easy to read becomes very important. Embrace the Boilerplate!

Adding a second state slice will help illustrate the pattern.

export interface State {
    articles: ArticleState;
    auth: AuthState;
    preferences: PreferencesState;
}

Authentication and authorization, and a way to track preferences are added to the State. I won't define them here for brevity's sake, but it will help you understand the Store structure when it comes to initialization.

A basic rule of this pattern is that the state is changed in one way only, with a reducer function. The function that looks like this:

export function reducer(state, action) {}

It gets passed state and an action. Here are the reducer functions for the three state slices.

export function articlesreducer(state: ArticleState = InitialArticleState, action: Action) {}

export function authreducer(state: AuthState = InitialAuthState, action: Action) {}

export function preferencesreducer(state: PreferencesState = InitialPreferenceState, action: Action) {}

Here with types. As you can see, the articlesreducer doesn't get State as a parameter, but State.articles. How does that work? This is how you define your reducers.

export const reducers: ActionReducerMap<State> = {
   articles: articlesreducer,
   auth: authreducer,
   preferences: preferencesreducer
}

Then to initialize the state, in your app.module.ts file

imports: [
   StoreModule.forRoot(reducers),
]

The reducers object and the State interface properties match. When a reducer is called, the auth reducer is called with State.auth.

In the reducer function, if state is undefined it is assigned the value of the default. On initialization all reducers are called with an undefined state, initializing the state on startup.

What about that selector used in the component? Selectors are composed of a series of selectors that extract a specific property from the State.

export const getArticleState = (state: State) => state.articles;
export const getArticleList = (state: ArticleState) => state.articlelist;
export const getArticles = createSelector(getArticleState, getArticleList);

More boilerplate. Why not something like this?

export const getArticles = (state: State) => state.articles.articlelist;

Because every time any changes happen in State, the subscription in your component would fire.

This is one way of the two way data flows in the NGRX pattern. But it shows promise. A change in the state values will show up in the view. The data acquisition and handling is outside of the component. The Store structure is extensible, adding another state slice breaks nothing, making adding a feature to your app or component almost trivial.

How do you modify State? See here.
How do you break down the State into pieces that are modular?
How do you deal with api calls to get data from elsewhere? Here

For a suggestion on how to layout the files and folder structure, ngrx/example-app auth implementation


This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]