Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Separating State Into Angular Modules With Ngrx

DZone's Guide to

Separating State Into Angular Modules With Ngrx

In this post, I want to give you an explanation of the state tree of Ngrx if you are working with a state and how to separate it into different modules.

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

In this post, I want to give you an explanation of the state tree of Ngrx if you are working with a state and how to separate it into different modules.

In this blog

  1. One state for your entire application with forRoot(…)
  2. Separating state into modules with forFeature(…)
  3. Conclusion

If you are building smaller or large Angular application, you will sooner or later face the problem of managing the state of your application. It gets influenced by a lot of things around which can be a simple button triggering a service - maybe with an HTTP action - which will set your application in a different state.

One State for Your Entire Application With forRoot(…)

Let's take a state object for the application: 

{ customerList = [], currentlyLoading = false }

The app's application state interface then looks like:

interface ApplicationState {
        currentlyLoading: boolean;
customerList: any []
}

export const initialState: ApplicationState = {
        currentlyLoading: false,
customerList: []
};

A reducer could manipulate this state like:

export function appReducer(state: ApplicationState = initialState, action: Action) : ApplicationState {
    switch (action.type) {
        case ANYACTION:
        return {
            ...state,
            // modify properties here

        }
    // more actions

    default:
        return state;
    }
}

We can then use this in our as code as follows:

@NgModule({
    imports: [
        BrowserModule,
        StoreModule.forRoot({ applicationState: appReducer})
    ]
})
export class AppModule {}

So imagine you have the following Angular application structure:

app
├── // modules, components and so on
├── app.component.ts
├── app.component.html
├── app.module.ts
└── main.ts
...

With the forRoot statement, we apply the state which is the result of appReducer at the application level. Hence, the whole application is aware of it and can react to its changes.

StoreModule.forRoot({ applicationState: appReducer})

The state is now an object with a property called applicationState containing the value which appReducer gives us as an output.

However, if we console.log it out..

ngOnInit(): void {
    this.store.pipe(select<any>('applicationState'))
        .subscribe((appState: ApplicationState) => console.log(appState));
}

…we get the following result:

{currentlyLoading: false, customerList: Array(0)}

An object with two properties on it. But why is that? Shouldn’t it be an object (like the one from the forRoot(..)) with a property on it called applicationState?

Well, the select method in the code sample above is literally selecting the property applicationState and giving us back the result which is itself an object with two properties currentlyLoading and customerList on it.

If you want to get the entire state printed out to your console, use a statement such as:

ngOnInit(): void {
this.store
    .select<any>((state: any) => state) // the complete state this time!!!

    .subscribe((completeState: any) => console.log(completeState));
}

Separating State Into Modules With forFeature(…)

So our application state will be influenced by many things we do. We would divide those actions and areas of our application into different modules. This helps to keep everything more maintainable and it’s much easier to navigate through the codebase.

As a result, you would possibly introduce a module which encapsulates and abstracts all your customer related things. So your application could look like this:

app
└── customers
    ├── components
    ├── containers
    ├── customer.routing.ts
    └── customer.module.ts
├── app.component.ts
├── app.component.html
├── app.module.ts
└── main.ts
...

So we created a customers folder, where we have a place to put all the customer related stuff into. But now it would be an improvement if only the module itself could keep track of its own state, as it does this for its routes, and can thus fulfill the purpose of a completely separated part of your application. This would also concern the state.

So we can apply our module state in a separate module store which would be a separate folder in the customer's folder:

app
└── customers
    ├── components
    ├── containers
    └── store
        └── state/actions/reducers/effects...
    ├── customer.routing.ts
    └── customer.module.ts
├── app.component.ts
├── app.component.html
├── app.module.ts
└── main.ts
...

Similarly, we should handle our state object. The goal is that each feature module contributes it’s own little part to the global state. Hence, let’s refactor our application state accordingly.

Luckily, Ngrx already provides a good mechanism for us to apply parts of the state to our main state. We already know the forRoot() method which applies a state at the root level. The forFeature() method allows us to merge a part of the state to the global root-level one. So let’s try that out: our AppState is now an empty model (which could have properties, however) and the customer store gets the whole properties:

interface CustomerState {
    currentlyLoading: boolean;
    customerList: any []
}

export const initialState: CustomerState = {
    currentlyLoading: false,
    customerList: []
};

app.module.ts

@NgModule({
imports: [
        BrowserModule,
        StoreModule.forRoot({ /* an empty object here for this time */})
    ]
})
export class AppModule {}

customer.module.ts

@NgModule({
    imports: [
        StoreModule.forFeature('customerFeature', {
            customer: customerReducer
        })
    ],
    exports: [],
    declarations: [],
    providers: []
})
export class CustomerModule {}

The customer reducer - customer - which is just a feature module name here - manipulates the whole customer state now. As AppState did exactly before, we can just move the reducer into the customer feature module and rename all the things.

The interesting part is the forFeature method above. Let’s take a look at this:

StoreModule.forFeature('customerFeature', {
    customer: customerReducer
})

The forFeature(...) method merges an object to the root state which is only an empty object now. It takes the string and creates a property on the root state with that name which has an object itself with the property customer which itself again has the two properties from before. So our state would look like this now:

{
    customerFeature: {
        customer: {
            currentlyLoading: false,
            customerList: Array(0)
        }
    }
}

If we console log out the complete state this time:

ngOnInit(): void {
this.store
    .select<any>((state: any) => state) // the complete state this time!!!

    .subscribe((completeState: any) => console.log(completeState));
}

The we get exactly that result:

{
    customerFeature: {
        customer: {
            currentlyLoading: false,
            customerList: Array(0)
        }
    }
}

The select method now, again, gives us the possibility to get a part of the state like this:

ngOnInit(): void {
this.store
.select<any>('customerFeature')
.subscribe((customerState: CustomerState) => console.log(customerState));
}

We are selecting the property customerFeature and subscribing to that again. So the result of the console.log looks like:

{
    customer: {
        currentlyLoading: false,
        customerList: Array(0)
    }
}

This would be the same result as if we subscribed to the state like:

ngOnInit(): void {
this.store
    .select<any>((state: any) => state.customerFeature) // no strings here

    .subscribe((customerState: CustomerState) => console.log(customerState));
}

Conclusion

So with the forFeature(...) method, you can build your state object exactly as modular as you normally structure your application. That way we can follow the separation of concerns principle and make things easy to follow and nicely structured.

Of course all the anys can, and probably should, be replaced with interfaces to be type safe.

You can see examples of states with multiple modules here.

Take a look at the Indigo.Design sample applications to learn more about how apps are created with design to code software.

Topics:
web dev ,angular ,state management ,angular modules

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}