Sunday, September 03, 2017

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


Comments: Post a Comment

Subscribe to Post Comments [Atom]





<< Home

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

Subscribe to Posts [Atom]