DEV Community

Cover image for Understanding and Implementing State Management with NGXS in Angular
Renan Ferro
Renan Ferro

Posted on • Updated on

Understanding and Implementing State Management with NGXS in Angular

Hey guys, how are you!?

Today I want to share how we can implement state management in Angular with NGXS and consuming a API to get some Advices(The study setting for the data).

So, let's start it!

State Management

In some words, State Management is the implementation of a Design Pattern that allows us to use the same data source(Single source of truth) in different components of our application, providing some benefits such as the reliability of the data provided, code maintenance, among others!

NGXS

NGXS is a State Management Pattern focused on Angular, It acts as a single source of truth for your application's state with simple implementations! In my opinion, NGXS is more simple to implement than NGRX!

NGXS there are 4 concepts to implement:
StoreActionsStateSelect

Store

The store is like the global state manager that will dispatch actions and provide us with the ability to select data slices outside of the global state.


Actions

Actions are like actions that can be done to achieve possible results, for example: we can "dispatch" a Get Books action, because it is "getting some books to us". And in our actions we need to define a type about our action, for example:

[Subject] The Action Name
Enter fullscreen mode Exit fullscreen mode

State

States are responsible for defining an application state container, we can have some basic states that are common in our applications, such as:

🆙 An initial state
🔄 A loading state
❌ An error state
✅ A success state
Enter fullscreen mode Exit fullscreen mode

Select

Selects are functions that allow us to select the data we want from our store, and we can do this in two ways:

With decorator:

export class YourComponent {

  @Select(AdvicesState) advices$: Observable<Advices[]>;
}
Enter fullscreen mode Exit fullscreen mode

With store selection function:

export class YourComponent {
advices$: Observable<Advices[]>;

  constructor(private _store: Store) {
    this.advices$ = this._store.select(state => state.advices.advices);
  }
}
Enter fullscreen mode Exit fullscreen mode

We now have a basic knowledge about State Management, NGXS and the concepts behind it. So, let's implement it in a real application!


Install the dependencies:

  1. First install the NGXS, NGXS Devtools plugin and the Redux DevTools Plugin(NOT OBLIGATORY) in the navigator(Chrome):
👇🏻 (NGXS): Install on the project
npm i @ngxs/store
Enter fullscreen mode Exit fullscreen mode
👇🏻 (NGXS Devtools): Install on the project
npm i @ngxs/devtools-plugin
Enter fullscreen mode Exit fullscreen mode
👇🏻 (Redux DevTools Plugin): Install on the Browser(Chrome)
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=pt
Enter fullscreen mode Exit fullscreen mode

The State Management Structure

  1. Let's create our Actions, so in our app folder let's create a new store folder and inside it we are going to create the advices.actions.ts file to create our Advices actions! We'll have three actions:
🆙 Get Advices: To dispatch and get our advices.
Enter fullscreen mode Exit fullscreen mode
❌ Error Advices: To dispatch when we have an error.
Enter fullscreen mode Exit fullscreen mode
✅ Success Advices: To dispatch when we have the success (our data).
Enter fullscreen mode Exit fullscreen mode

So, based on this information we will create our actions and remember! We always need to inform the type of our action, so our file will look like this:


export namespace AdvicesActions {

  export class GetAdvices{
    static readonly type = '[Advices] Get Advices'
  }

  export class getError {
    static readonly type = '[Advices] Error Advices'
  }

  export class getSuccess {
    static readonly type = '[Advices] Success Advices'
  }
}
Enter fullscreen mode Exit fullscreen mode

After that, we'll to create our state structure and our @Selectors too. So, inside of the store folder we need to create the new advices.state.ts file and the code is like below!

import { Action, Selector, State, StateContext } from '@ngxs/store';
import { Injectable } from '@angular/core';
import { Advices } from '../../models/advices';
import { AdvicesService } from '../../services/api.service';
import { AdvicesActions } from '../actions/advices.actions';

export interface AdvicesStateModel {
  advices: Advices[],
  hasError: boolean,
  isLoading: boolean
}

const defaultState: AdvicesStateModel = {
  advices: [],
  hasError: false,
  isLoading: true,
};

@State<AdvicesStateModel>({
  name: 'AdviceState',
  defaults: defaultState,
})

@Injectable()
export class AdvicesStates {
  constructor(private advicesService: AdvicesService) {}

  @Selector()
  static hasError(state: AdvicesStateModel): boolean {
    return state.hasError;
  }

  @Selector()
  static isLoading(state: AdvicesStateModel): boolean {
    return state.isLoading;
  }

  @Selector()
  static getAdvices(state: AdvicesStateModel): Advices[] {
    return state.advices;
  }

  @Action(AdvicesActions.GetAdvices)
  getAdvices(ctx: StateContext<AdvicesStateModel>) {
    return this.advicesService.getAdvicesAPI()
        .subscribe({
          next: value => {
            ctx.patchState({
              advices: value,
              hasError: false,
              isLoading: false,
            });
            ctx.dispatch(new AdvicesActions.getSuccess());
          },
          error: err => {
            ctx.patchState({
              hasError: true,
              isLoading: false,
            });
            ctx.dispatch(new AdvicesActions.getError());
          },
        });
  }
}
Enter fullscreen mode Exit fullscreen mode

Understanding the code!

...
export interface AdvicesStateModel {
  advices: Advices[],
  hasError: boolean,
  isLoading: boolean
}

const defaultState: AdvicesStateModel = {
  advices: [],
  hasError: false,
  isLoading: true,
};
...
Enter fullscreen mode Exit fullscreen mode

When we use State Management, we need to create a default state of our store! So, we create a simple interface with the advices property to get our advices, a hasError property to select of the error state and we create the loading property to select of the loading state.
And with the interface created, we declare the defaultState const with AdvicesStateModel type and the default values in our properties!


...
@State<AdvicesStateModel>({
  name: 'AdviceState',
  defaults: defaultState,
})
...
Enter fullscreen mode Exit fullscreen mode

In this block of code we defined our @State with the AdvicesStateModel type. In the name property we declared an exclusive name to the our state and in the defaults we set our default state!


...
@Selector()
static hasError(state: AdvicesStateModel): boolean {
  return state.hasError;
}

@Selector()
static isLoading(state: AdvicesStateModel): boolean {
  return state.isLoading;
}

@Selector()
static getAdvices(state: AdvicesStateModel): Advices[] {
  return state.advices;
}
...
Enter fullscreen mode Exit fullscreen mode

Here we create our @Selectors to select of the slice of our state (getAdvices, isLoading, hasError).


@Action(AdvicesActions.GetAdvices)
  getAdvices(ctx: StateContext<AdvicesStateModel>) {
    return this.advicesService.getAdvicesAPI()
        .subscribe({
          next: value => {
            ctx.patchState({
              advices: value,
              hasError: false,
              isLoading: false,
            });
            ctx.dispatch(new AdvicesActions.getSuccess());
          },
          error: err => {
            ctx.patchState({
              hasError: true,
              isLoading: false,
            });
            ctx.dispatch(new AdvicesActions.getError());
          },
        });
  }
Enter fullscreen mode Exit fullscreen mode

Here we get our GetAdvices action! And this return an Observable with our data. See that in our next: () and error: () we are dispatching our actions, remember that we always need to be dispatching the actions in the right places.


Now, we need to import the NgxsModule and the NgxsReduxDevtoolsPluginModule in our app.module.ts, so let's import it:

@NgModule({
  ...
  imports: [
    NgxsModule.forRoot([AdvicesState], {
      developmentMode: !environment.production
    }),
    NgxsReduxDevtoolsPluginModule.forRoot({
      name: 'Advices',
      disabled: environment.production,
    }),
  ],
  ...
})
Enter fullscreen mode Exit fullscreen mode

Coooool! We have our structure, import done! So, now let's dispatch the Get Advices action, get data with @select and finish it! In our home.component.ts or other component, let's do it:

export class HomeComponent implements OnInit {

  // Here we are selecting the getAdvices and creating an Observable<Advices[]>!
  @Select(AdvicesStates.getAdvices) advices$: Observable<Advices[]>;

  constructor(
      private _store: Store
  ) { }

  ngOnInit(): void {
    // Here we are dispatching the GetAdvices action!
    this._store.dispatch(new AdvicesActions.GetAdvices());
  }
}
Enter fullscreen mode Exit fullscreen mode

Aaaand dooone, FINAALLLY!!! 🤯🤩🥳🔥🤯🤩🥳🔥

If your repair in the ReduxDevTools plugin, we can see our actions dispatched like below:

Image description

Now our State Management with NGXS is working and we have a store in our component, making our application more robust, easier to work with and with confidence in the data!

State Management is powerful and there are other possibilities with NGXS, I always recommend looking at the documentation and practicing! There are many cool possibilities and new challenges 🤟🤘

That's all guys, I hope you like it and if any questions, suggestions or other topics, please comment!

See you later ✌🏼

Top comments (0)