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:
Store • Actions • State • Select
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
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
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[]>;
}
With store selection function:
export class YourComponent {
advices$: Observable<Advices[]>;
constructor(private _store: Store) {
this.advices$ = this._store.select(state => state.advices.advices);
}
}
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:
- 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
👇🏻 (NGXS Devtools): Install on the project
npm i @ngxs/devtools-plugin
👇🏻 (Redux DevTools Plugin): Install on the Browser(Chrome)
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=pt
___
## 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.
❌ Error Advices: To dispatch when we have an error.
✅ Success Advices: To dispatch when we have the success (our data).
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:
```typescript
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'
}
}
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());
},
});
}
}
Understanding the code!
...
export interface AdvicesStateModel {
advices: Advices[],
hasError: boolean,
isLoading: boolean
}
const defaultState: AdvicesStateModel = {
advices: [],
hasError: false,
isLoading: true,
};
...
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,
})
...
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;
}
...
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());
},
});
}
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,
}),
],
...
})
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());
}
}
Aaaand dooone, FINAALLLY!!! 🤯🤩🥳🔥🤯🤩🥳🔥
If your repair in the ReduxDevTools plugin, we can see our actions dispatched like below:
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 ✌🏼