How are the different concepts organised?
Now that you have gained familiarity with the various concepts of NGXS, let's explore how they interact with each other through a schema, which provides a clear visual representation of their relationships and interactions.
Schema explaining NGXS functioning
After we have set up NGXS, Now lets dive deep into our example:
add the following code in the imports of the @NgModule:
NgxsModule.forRoot([TodoState], {
developmentMode: !environment.production
}),
like this:
@NgModule({
declarations: [
AppComponent
],
imports: [
...
NgxsModule.forRoot([TodoState], {
developmentMode: !environment.production
}),
]
})
export class AppModule { }
As we discussed earlier in the theoretical part, everything begins with a component. Therefore, let's proceed by writing the code for the app.component.ts
file:
import { Component } from '@angular/core';
import {Select, Store} from '@ngxs/store';
import {AddTodo, EmptyTodo} from '../store/todo.actions';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
newTodo = '';
constructor(private readonly store: Store) {}
onAddTodo(): void {
if (this.newTodo.length > 0) {
this.store.dispatch(new AddTodo(this.newTodo));
}
this.newTodo = '';
}
onEmptyList(): void {
this.store.dispatch(new EmptyTodo());
}
Let’s create the Actions in the todo.actions.ts
:
export class AddTodo {
static readonly type = '[Todo] Add';
constructor(public newTodo: string) {
}
}
export class EmptyTodo {
static readonly type = '[Todo] Empty';
}
Since the addTodo
action requires the new todo as an input, we need to declare the constructor with newTodo
as a parameter.
Following the triggering of these actions, certain code will be executed to modify the state. This is where the todo.state.ts
file comes into play.
import {Action, State, StateContext} from '@ngxs/store';
import {AddTodo, EmptyTodo} from './todo.actions';
export interface TodoStateModel {
todoList: string[];
}
@State<TodoStateModel>({
name: 'todo',
defaults: {
todoList: [],
}
})
export class TodoState {
@Action(AddTodo)
addTodo({patchState, getState}: StateContext<TodoStateModel>, {newTodo}: AddTodo): void {
patchState({todoList: [...getState().todoList, newTodo]});
}
@Action(EmptyTodo)
emptyTodo({patchState}: StateContext<TodoStateModel>): void {
patchState({todoList: []});
}
}
The @State
decorator allows us to define the initial state of the state container.
The functions preceded by the @Action
annotation are associated with an action and will be executed when triggered.
Let's take a closer look at the more complex addTodo()
function to understand its components:
First, it takes two parameters:
{patchState, getState}: StateContext<TodoStateModel}
, which provides patchState()
and getState()
functions to interact with the state defined in the same file.
{newTodo}: AddTodo
, which allows us to access data from the triggered action.
As mentioned earlier, the state is immutable. To modify the value of one or more state elements, NGXS provides the patchState()
function. Here's an example of how it's used:
patchState({todoList: [...getState().todoList, newTodo]});
This function enables developers to specify parameters only for the fields they intend to modify, without needing to create a copy of the entire state.
To allow our component to access the todoList
value from the state, we can add a getter function. Therefore, the todo.state.ts
file is updated as follows:
import {Action, Selector, State, StateContext} from '@ngxs/store';
import {AddTodo, EmptyTodo} from './todo.actions';
export interface TodoStateModel {
todoList: string[];
}
@State<TodoStateModel>({
name: 'todo',
defaults: {
todoList: [],
}
})
export class TodoState {
@Selector()
static getTodoList(state: TodoStateModel): string[] {
return state.todoList;
}
@Action(AddTodo)
addTodo({patchState, getState}: StateContext<TodoStateModel>, {newTodo}: AddTodo): void {
patchState({todoList: [...getState().todoList, newTodo]});
}
@Action(EmptyTodo)
emptyTodo({patchState}: StateContext<TodoStateModel>): void {
patchState({todoList: []});
}
}
As you can observe, we have added the getTodoList()
function, which takes the state as a parameter and is annotated with @Selector()
.
Whenever the value of todoList
changes, the code within the selector function is executed, and any component that listens to todoList through getTodoList()
will automatically receive the updated value.
As mentioned earlier, this function acts as a decorator for the returned value. Therefore, if you decide to sort todoList in alphabetical order within the selector, each component listening to todoList through this function will receive the sorted list.
To complete the loop, we need to return to our App component to allow it to access the value of todoList from the state using the newly created selector. To achieve this, we add a new variable in our component preceded by the @select() annotation, with the selector function as the parameter.
Here is the final version of app.component.ts
:
import { Component } from '@angular/core';
import {Select, Store} from '@ngxs/store';
import {AddTodo, EmptyTodo} from '../store/todo.actions';
import {TodoState} from '../store/todo.state';
import {Observable} from 'rxjs';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@Select(TodoState.getTodoList) todoList$?: Observable<string[]>;
newTodo = '';
constructor(private readonly store: Store) {}
onAddTodo(): void {
if (this.newTodo.length > 0) {
this.store.dispatch(new AddTodo(this.newTodo));
}
this.newTodo = '';
}
onEmptyList(): void {
this.store.dispatch(new EmptyTodo());
}
}
Conclusion:
🥳🙌👏Congratulations! You have successfully learned how to set up and utilize NGXS. Your next task, should you decide to accept it, is to adapt the example we have worked on to meet the requirements of your own project.
For further information on NGXS, you can refer to the official documentation, available HERE.
Well done, and best of luck with your NGXS implementation! 💫👋👋
Top comments (0)