Angular: 2 Simple Examples to understand how NgRx works- Part-II

Angular&NodeEnthusiast
18 min readDec 19, 2024

--

In this story, we will take another example to demonstrate Effects and Feature state.

You can check the Part-I of this series, where we have demonstrated Root state, Selectors,Actions and Reducers in details.

I. Demo Example

Its a ToDoApplication as you see below ,where we have 2 lazily loaded feature modules. These features modules load when we click on the Users and ToDo tab respectively.

Clicking on the Users tab, we are displaying the below screen, where a user(totally 10 users in the dropdown)can be selected from the dropdown.

When we click on the ToDos tab, we display a table which lists out the ToDo items created by the user selected in the dropdown. If no user is selected under the dropdown, the table will list out the ToDo items of all the 10 users. We have also given an option to update/delete a ToDo item.

II. What are Effects ?

Lets complete the workflow diagram from Part-I of this series. We have already discussed how the Selector, Store, Component, Actions and Reducers work together. Let’s now see how the Service and Effects fit in ?

  1. Whenever an event demands a state change, the component will dispatch an Action, say action-A.
  2. If the action-A requires any communication with external resources, the Effect will handle the action-A and dispatch another action-B. This action-B will be handled by the Reducer function to update the state slice in the Store.
  3. Effects will handle the action-A in scenarios where you handle tasks such as fetching data, long-running tasks that produce multiple events, and other external interactions. If action-A does not have any such requirements, the Reducer function should directly handle action-A and update the state slice as required.
  4. The component uses Selector functions to extract the latest value of the desired state slice from the Store and update the template.

III. Why Effects ?

In the absence of ngrx, components are responsible for interacting with external resources directly through services. Instead, Effects provide a way to interact with those services and isolate them from the components. Effects isolate side effects from components, allowing for more pure components that only select state and dispatch actions.

IV. Objectives:

  1. We will be maintaining state of the selected user in the dropdown and also any changes in the state of the ToDo items in the table, as we switch between the tabs i.e switch between the feature modules.

As you can see in the demo below, when I initially click on the ToDo tab, there are 200 ToDo items. Next I switch to the User tab and select 1 user and switch back to the ToDo tab. This time, it shows only 10 ToDo items belonging to the selected user.

Next, when I switch back to the Users tab, we can still see the user selected earlier. I have now changed the selected user and switched to the ToDo tab. This time too we have 10 ToDo items which are different and belong to the newly selected user.

2. We should be able to update/delete the ToDo item of any user in the table. The state of the table post updation/deletion must persist when we switch between the tabs.

Below is the demonstrations for updation of ToDo item.

Below is the demonstration for deletion of ToDo item. I have deleted the 2nd ToDo item in the table and switched to Users tab. When I get back to ToDo tab, the 2nd item remains deleted and is not visible in the table.

V. Configuration

In this example, we are going to maintain the state of the Users and ToDo tab. Each of these feature modules will have its own state. There will be no root state in the global application state. We will just have 2 slices of feature state in the application state.

This is the structure of the application. The Actions,Reducers,Effects and Selectors are defined inside a state folder within each feature module.

In order to use Ngrx Effects in the project, we need to install an additional npm package @ngrx/effects. In the Part-I of this series, we have already installed @ngrx/store.

ng add @ngrx/effects@16

In order to create an in-memory database of dummy Users and ToDos data, we need to install the below package.

npm install — save angular-in-memory-web-api@0.16.0

In the root AppModule, we have imported the StoreModule from @ngrx/store, the EffectsModule from the @ngrx/effects package and finally the HttpClientInMemoryWebApiModule from the angular-in-memory-web-api package.

Observe that we have passed an empty object {} to the forRoot() called on the StoreModule. This means that the root state in the total application state is an empty object.

No arguments are passed to the forRoot() called on the EffectsModule because we will be registering the Effects with the feature modules, where it is required and not with the root AppModule.

We have passed the AppData class as argument to the HttpClientInMemoryWebApiModule. We have written a class AppData which implements InMemoryDbService interface to create a in-memory database. The AppData class must implement createDb() which creates a "database" hash whose keys are collection names and whose values are arrays of collection objects to return or update. The keys in this example are users and todos. The values corresponding to these keys are an array of 10 user detail objects and 200 ToDo item objects.

Any changes to the data will persist as long as you don’t refresh the application.

VI. Services

Below are the 2 angular Services, we use to communicate with this in-memory database.

In the ToDoService we have written methods to Read, Update and Delete ToDo items from the database.

ToDoService

In the UsersService, we have written a method to Read the data of the 10 users from the database.

UsersService

VII. Implementation of Users Tab

  1. As mentioned earlier, we have created a new Feature Module: UsersModule for the Users tab. When we click on the Users tab, the feature module is lazily loaded.

Observe the imports of this module. We have imported the StoreModule and EffectsModule. Instead of forRoot(), we have called forFeature() on the StoreModule and EffectsModule class.

StoreModule.forFeature(userStateKey,userReducer)

2 arguments are passed to the forFeature() called on the StoreModule. The first argument is a constant userStateKey which holds a string “userState”. This string uniquely identifies the state slice. We have passed the Reducer function: userReducer which will manage the “userState” slice as the 2nd argument. It also implies that we have added a new state slice to the application state.

EffectsModule.forFeature(UsersEffect)

With the above statement, we have registered the UsersEffect class with the feature module. It is essential to register the class for the Effects defined within the class to execute.

2. The next step is to decide the shape of the state slice. This is how a sample user looks like. We will be just using the id and name properties in this example.

{
“id”: 1,
“name”: “Leanne Graham”,
“username”: “Bret”,
“email”: “Sincere@april.biz”,
“address”: {
“street”: “Kulas Light”,
“suite”: “Apt. 556”,
“city”: “Gwenborough”,
“zipcode”: “92998–3874”,
“geo”: {
“lat”: “-37.3159”,
“lng”: “81.1496”
}
},
“phone”: “1–770–736–8031 x56442”,
“website”: “hildegard.org”,
“company”: {
“name”: “Romaguera-Crona”,
“catchPhrase”: “Multi-layered client-server neural-net”,
“bs”: “harness real-time e-markets”
}
}

In the Users tab, we need to display the list of users inside the dropdown and also keep track of the selected user.

In the user.reducer.ts, the below statements define the state slice for the Users tab. The remaining contents of this file, will be discussed later in the story.

export interface UserState{
users:UserModel[],
selectedUser: UserModel| null,
error:string
}

export const userStateKey:string=”userState”; // state slice with 3 keys: users,error and selectedUser

const initialUserState: UserState={
users:[],
selectedUser:null,
error:””
}

The application state is initially a big empty object {}. When we add a state slice to this object, we are basically adding a key-value pair. We will be adding a key named “userState” and the value of the key will model the UserState interface.

In the UserState interface above, the field users will contain the details of all the 10 users, the field selectedUser will contain the detail of the user selected in the dropdown and the field error will contain the details of any errors encountered while fetching the user details.

I have stored the key “userState” in a constant userStateKey and exported it because it is required in multiple files.

The constant initialUserState will contain the initial value of the state slice.

Application state at this point will look like below, if I navigate to the Users tab, the moment I load the application(i.e I don’t load the ToDos tab first when the application loads). Its an object with a new key-value pair added to it.

{
“userState”:{
“users”:[],
”selectedUser”:null,
”error”:””
}
}

3. Component Template. Its a simple template with a dropdown for selecting the user and also we are displaying error messages if any.

<h4>Select the User</h4>
<p class=”error”>{{errors$|async}}</p>

<select (change)=”selectUser()” class=”form-select” name=”selectedUser” [(ngModel)]=”selectedUserId”>
<option value=”0">Select A User</option>
<option *ngFor=”let user of usersList$|async” [value]=”user.id”>{{user.name}}</option>
</select>

usersList$ is an observable of all the 10 user details. We have subscribed to it using the async pipe to populate the dropdown.

errors$ is an observable of error messages, which again we have subscribed using async pipe.

When I make a selection in the dropdown, the change event is triggered and the selectUser() in the class is called. selectedUserId property is the ngModel for the <select> element. It stores the id of the selected user. Recall that we have an id field for every user.

{
“id”: 1,
“name”: “Leanne Graham”,
“username”: “Bret”,
“email”: “Sincere@april.biz”,
“address”: {
“street”: “Kulas Light”,
“suite”: “Apt. 556”,
“city”: “Gwenborough”,
“zipcode”: “92998–3874”,
“geo”: {
“lat”: “-37.3159”,
“lng”: “81.1496”
}
},
“phone”: “1–770–736–8031 x56442”,
“website”: “hildegard.org”,
“company”: {
“name”: “Romaguera-Crona”,
“catchPhrase”: “Multi-layered client-server neural-net”,
“bs”: “harness real-time e-markets”
}
}

4. Component Class

=> We have injected a reference store of the Store class from @ngrx/store in the constructor. Observe that we have passed the UserState interface as type parameter to the Store class in the constructor.

selectedUserId is the ngModel attached to the <select> element. usersList$ is an observable of the 10 user details which will populate in the dropdown. errors$ is an observable of error messages.

constructor(private store:Store<UserState>){}

selectedUserId:number = 0;
usersList$?:Observable<UserModel[]>;
errors$?:Observable<string>;

=>When a user is selected in the dropdown, the below selectUser() is called, where we are dispatching a UserPageActions.setSelectedUser action, passing the id of the selected user as argument. We will check how this action is defined as we progress.

selectUser(){
this.store.dispatch(UserPageActions.setSelectedUser({userId:this.selectedUserId}))
}

=> Within the ngOnInit lifecycle hook, we are using the select() on the store reference to extract the desired state slice. We have passed Selector functions as arguments to each of the select().

ngOnInit(){

this.usersList$=this.store.select(usersListSelector); //populate the dropdown

this.store.select(currentUserSelector).pipe(
tap((result:number)=>{this.selectedUserId=result})
).subscribe(); //get the latest selected user from the state slice

this.errors$=this.store.select(userErrorSelector); //populate the error message

}

5. How are Selector functions defined ?

Lets first check the Selector functions we have defined in the users.selectors.ts to extract the desired state “userState” from the application state and then to further dig into the value of the state slice.

We have first used createFeatureSelector() which is a convenience method for returning a top level feature state. We are using it to extract the “userState” slice from the application state. Note that userStateKey is a constant containing the string “userState”, exported from the users.reducer.ts file.

const userFeatureSelector=createFeatureSelector<UserState>(userStateKey);

Recall that the value corresponding to the “userState” slice models the below UserState interface. So we want to capture the value of the users field in the “userState” slice to populate the dropdown. The value of the selectedUser field in the “userState” slice needs to be extracted to update the details of the current user selected in the dropdown. Finally the value of the error field in the “userState” slice needs to be extracted to display the errors(if any) while fetching the 10 user details from the DB we created in the beginning.

export interface UserState{
users:UserModel[],
selectedUser: UserModel| null,
error:string
}

The createFeatureSelector() returns a selector function for the state slice “userState”. We will now pass this selector function to the createSelector() to extract the value of the users,selectedUser and error fields from the state slice.

export const usersListSelector=createSelector(
userFeatureSelector,
(state)=>state.users
)

export const currentUserSelector=createSelector(
userFeatureSelector,
(state)=>state?.selectedUser?.id || 0
)

export const userErrorSelector=createSelector(
userFeatureSelector,
(state)=>state.error
)

The createSelector() return another selector function, which we have stored in constants usersListSelector, currentUserSelector and userErrorSelector and exported them, for using them in other files, wherever required.

6. How are the Actions defined ?

We have defined the actions for the User tab within the users.actions.ts file. We have defined 4 actions, which are put into 2 groups based on the source dispatching the action.

Instead of the createAction(), we have used the createActionGroup() to define and group actions based on the source of the action.

createActionGroup() keeps the actions well organized and I always prefer it over createAction(), if there are actions dispatched from multiple sources.

The constant UserPageActions contains a group of actions that are dispatched from the component. The constant UserApiActions contains a group of actions dispatched from the Effects.

The source field is a string describing the source which has dispatched the action. The events field is an object containing key-value pairs, where the key describes the action being dispatched and the value is the metadata passed to the action using the props(). If no metadata needs to be passed to the action, the value corresponding to the key will be emptyProps(), imported from @ngrx/store.

7. How are the Reducers defined ?

We have defined the reducer function: userReducer for the User tab within the users.reducers.ts file. The reducer function handles the 3 actions: UserApiActions.loadUsersSuccess, UserApiActions.loadUsersFailed and UserPageActions.setSelectedUser.

Please recall that we have registered the reducer function: userReducer to manage the “userState” slice of state with the below import in the feature module.

StoreModule.forFeature(userStateKey,userReducer),

8. How are Effects defined ?

We have defined the effects for the User tab within the users.effects.ts file.

=> We have injected in the constructor, a reference: action$ of the Actions class imported from the @ngrx/effects package. The reference action$ provides an observable stream of all actions dispatched. Effects is nothing but a service, which listens to the observable of each of the actions dispatched.

constructor(private actions$:Actions,private usersService:UsersService) { }

=> The ngrxOnInitEffects() is a lifecycle hook which executes after the Effect has registered when the feature module loads. Inside this hook, we have dispatched the UserApiActions.loadUsers action to fetch the 10 user details from the DB.

ngrxOnInitEffects(){
return UserApiActions.loadUsers();
}

Please recall that we have registered the UserEffect class in the imports section of the feature module as below.

EffectsModule.forFeature(UsersEffect)

=> Who is handling the UserApiActions.loadUsers action ? We have already seen the users.reducers.ts file. The reducer function is not handling it because a reducer only handles state transitions and does not call the service to make HTTP requests.

Thus we have created an Effect using the createEffect() and stored it in a constant loadUsersEffect$ as you see below. The loadUsersEffect$ effect is listening for all dispatched actions through actions$ reference, but is only interested in the UserApiActions.loadUsers action, which we have filtered out using the ofType operator.

loadUsersEffect$=createEffect(()=>{

return this.actions$.pipe(

ofType(UserApiActions.loadUsers),
mergeMap(()=>{
return this.usersService.fetchUsers().pipe(
map((result:UserModel[])=>{return UserApiActions.loadUsersSuccess({usersData:result})}),
catchError((err:HttpErrorResponse) => { console.log(err); return of(UserApiActions.loadUsersFailed({ errorMessage: err.statusText }))})
//of operator is required because catchError operator does not immediately return the response
)
})
)
})

We have called the fetchUsers() from the UsersService to fetch the 10 user details and then dispatched the UserApiActions.loadUsersSuccess action, passing the result as argument.

In case the HTTP request fails, the catchError operator dispatches the UserApiActions.loadUsersFailed action passing the error details as argument to the action.

Please recall that the reducer function listens to the UserApiActions.loadUsersSuccess action and UserApiActions.loadUsersFailed action and updates the state as you see below.

on(UserApiActions.loadUsersSuccess,(state,effectResult)=>{
return {
…state,
users:effectResult.usersData,
selectedUser:state.selectedUser,
error:””
}
}),
on(UserApiActions.loadUsersFailed,(state,effectsResult)=>{
return {
...state,
users:[],
error:effectsResult.errorMessage,
selectedUser:null
}
})

The component, extracts the desired state slice using the Selector function: usersListSelector and userErrorSelector as you see below and this will either populate the dropdown or the populate the error message in the template.

this.usersList$=this.store.select(usersListSelector);

this.errors$=this.store.select(userErrorSelector);

VIII. Implementation of the ToDo Tab

  1. We have created a new Feature Module: ToDoModule for the ToDo tab. When we click on the ToDo tab, the feature module is lazily loaded.

Observe the imports of this module. We have imported the StoreModule and EffectsModule. Instead of forRoot(), we have called forFeature() on the StoreModule and EffectsModule class.

 StoreModule.forFeature(todoStateKey,todosReducer)

2 arguments are passed to the forFeature() called on the StoreModule. The first argument is a constant todoStateKey which holds a string “todoState”. This string uniquely identifies the state slice. We have passed the Reducer function: todosReducer which will manage the “todoState” slice as the 2nd argument. It also implies that we have added a new state slice “todoState” to the application state.

    EffectsModule.forFeature(ToDoEffects)

With the above statement, we have registered the ToDoEffects class. It is essential to register the class for the Effects defined within the class to execute.

2. First step is to decide the shape of the state slice “todoState”. This is how a ToDo item looks like.

{
“userId”: 1,
“id”: 1,
“title”: “delectus aut autem”,
“completed”: false
}

In the todos.reducer.ts, the below statements define the state slice for the ToDo tab. The remaining contents of this file, will be discussed later in the story.

export interface ToDoState{
todos:ToDoModel[],
error:string
}

export const todoStateKey= “todoState”;

let initialToDosListState:ToDoState={
todos:[],
error:””
}

The application state is initially a big empty object. When we add a state slice to this object, we are basically adding a key-value pair. We will be adding a key named “todoState” and the value of the key will model the ToDoState interface.

In the ToDoState interface, the field todos will contain the details of the ToDo items created by all the 10 users and the field error will contain the details of any errors encountered while fetching the user details.

I have stored the key “todoState” in a constant todoStateKey and exported it because it is required in multiple files.

The constant initialToDosListState will contain the initial value of the state slice.

Initial application state at this point will look like below if I navigate to the ToDo tab the moment I load the application (i.e I navigate to the ToDo tab first, before the Users tab). Its an object with a new key-value pair added to it.

{
"todoState":
{
"todos":[],
"error":""
}
}

3. Component Class:

In this class, we are using a reactive form to construct the table and populate the ToDo items in the table.

=> We have injected in the constructor, a reference: store of the Store class from the @ngrx/store package. Observe that we have passed the ToDoState interface as type parameter to the Store class in the constructor.

constructor(private store:Store<ToDoState>,private fb:FormBuilder) {}

=> Within the ngOnInit lifecycle hook, we are using the select() on the store reference, passing the Selector function as argument to extract the desired data from the “todoState” slice. I am not going into details on how the reactive form is used to populate the table.

ngOnInit(){
this.error$=this.store.select(todoErrorSelector)

this.todoFormGroup=this.fb.group({
todoList: this.fb.array([])
})

//subscribing to the state slice to get the updated the todo list
this.store.select(todoListSelector).pipe(
tap((todos:ToDoModel[])=>{
this.todoFormGroup?.setControl(“todoList”,this.fb.array(this.returnToDoGroup(todos)))
})
).subscribe();

}

error$ is an observable, which we have subscribed to, via the async pipe in the template to display any error message.

this.error$=this.store.select(todoErrorSelector)

The below lines are code are used to update the ToDo items in the table based on the user selected in the User tab.

this.store.select(todoListSelector).pipe(
tap((todos:ToDoModel[])=>{
this.todoFormGroup?.setControl(“todoList”,this.fb.array(this.returnToDoGroup(todos)))
})
).subscribe();

=> We are calling the updateToDo() passing the index of the ToDo item as argument, when clicking on the “Update” button corresponding to a ToDo item in the table. In the updateToDo(), we have dispatched the ToDoTabActions.updateToDo action, passing the updated ToDo item and item index as argument.

updateToDo(todoIndex:number){
const updatedToDoItem:ToDoModel=(<FormArray>this.todoFormGroup?.get(‘todoList’)).at(todoIndex).value;
this.store.dispatch(ToDoTabActions.updateToDo({updatedToDo:updatedToDoItem,todoIndex:updatedToDoItem.id}))
}

deleteToDo(todoIndex:number){
const todoItemToBeDeleted:ToDoModel=(<FormArray>this.todoFormGroup?.get(‘todoList’)).at(todoIndex).value;
this.store.dispatch(ToDoTabActions.deleteToDo({todoIndex:todoItemToBeDeleted.id}));
}

We are calling the deleteToDo() passing the index of the ToDo item as argument, when clicking on the “Delete” button corresponding to a ToDo item in the table. In the deleteToDo(), we have dispatched the ToDoTabActions.deleteToDo action, passing the index of the ToDo item as argument.

3. How are the Selectors defined ?

We have defined 2 Selector functions within the todos.selectors.ts.

Please recall that the value of the “todoState” slice models the ToDoState interface.

export interface ToDoState{
todos:ToDoModel[],
error:string
}

Using the createFeatureSelector() below, we are first extracting the “todoState” slice of the application state. Recall that the constant todoStateKey contains the string “todoState”.

const todoFeatureSelector=createFeatureSelector<ToDoState>(todoStateKey);

We have composed 2 new Selector functions as you see below: todoListSelector and todoErrorSelector.

export const todoListSelector=createSelector(
todoFeatureSelector,currentUserSelector,
(todoState,currentUser)=>{
if(currentUser !== 0){
return todoState.todos.filter(todo=>todo.userId === currentUser)
}
return todoState.todos;
}
)

export const todoErrorSelector=createSelector(
todoFeatureSelector,
(state)=>state.error
)

The todoListSelector Selector function is used to filter the ToDo items in the table based on the user selected in the Users tab.

Thus we have passed 2 Selector functions: todoFeatureSelector and currentUserSelector as arguments to the createSelector() to compose a new Selector function. Recall that currentUserSelector function is exported from users.selectors.ts file.

The 3rd argument to the createSelector() is a callback function which has 2 arguments. These 2 arguments are nothing but the results of the todoFeatureSelector and currentUserSelector Selector functions. Inside the callback function we are checking if the id of the user selected in the dropdown is 0. If its 0, it means no user has been selected and all the 200 ToDo items can be displayed in the table. If its not 0, we know that a user has been selected in the dropdown and we filter out only those ToDo items to display in the table, which have been created by the selected user.

The todoErrorSelector Selector function is used to extract the error field from the state slice. The error field contains the error message, in case the Read, Update or Delete operation on the ToDo items fails.

4. How are the Actions defined ?

We have defined 9 actions inside the todos.actions.ts using the createActionGroup().

5. How are the Reducers defined ?

We have defined a reducer function todosReducer, within the todos.reducers.ts which will handle the below 6 actions and perform the state transition accordingly.

ToDoApiActions.loadingToDosSuccess, ToDoApiActions.loadingToDosFailed, ToDoApiActions.updatingToDosFailed, ToDoApiActions.deletingToDosFailed, ToDoApiActions.updatingToDosSuccess, ToDoApiActions.deletingToDosSuccess

6. How are the Effects defined ?

We have defined 3 effects within the todo.effects.ts file.

We have defined 3 Effects:

=>loadToDos$ Effect will handle the ToDoApiActions.loadToDos action, which is dispatched by the Effect itself.

loadToDos$=createEffect(()=>{
return this.actions$.pipe( //listens to all action types
ofType(ToDoApiActions.loadToDos), //filtering based on the action type dispatched by the component
mergeMap(()=>{
//execute the api call
return this.todoService.fetchToDos().pipe(
map((result:ToDoModel[])=>ToDoApiActions.loadingToDosSuccess({todoList:result})),
catchError((err:HttpErrorResponse)=>of(ToDoApiActions.loadingToDosFailed({message:err.statusText})))
)
})
)
})

Observe the below ngrxOnInitEffects() lifecycle hook which executes after the Effects class ToDoEffects has registered, when the feature module loads. Inside this hook, we have dispatched the ToDoApiActions.loadToDos action to fetch the 200 ToDo items from the database.

ngrxOnInitEffects(){
return ToDoApiActions.loadToDos();
}

Thus the loadToDos$ Effect handles the dispatched action by calling the fetchToDos() in the ToDoService. If the request succeeds, the Effect will dispatch another action: ToDoApiActions.loadingToDosSuccess, passing the result as argument. If the request fails, the Effect will dispatch the ToDoApiActions.loadingToDosFailed action, passing the error details as argument.

The todosReducer function listens to the actions dispatched by the Effect and updates the “todoState” slice as below.

on(ToDoApiActions.loadingToDosSuccess,(state,effectResult)=>{
return {
…state,
todos:effectResult.todoList,
error:””
}
}),
on(ToDoApiActions.loadingToDosFailed,(state,effectResult)=>{
return {
…state,
todos:[],
error:effectResult.message
}
}),

=> updateToDos$ Effect will handle the ToDoTabActions.updateToDo action dispatched by the component, when we click on the “Update” button corresponding to a ToDo item in the table.

 updateToDos$=createEffect(()=>{
return this.actions$.pipe(
ofType(ToDoTabActions.updateToDo),
mergeMap((state)=>{
return this.todoService.updateToDo(state.updatedToDo,state.todoIndex).pipe(
map(()=>ToDoApiActions.updatingToDosSuccess({updatedToDo:state.updatedToDo})),
catchError((err:HttpErrorResponse)=>of(ToDoApiActions.updatingToDosFailed({message:err.statusText})))
)

})
)
})

The Effect will call the updateToDo() in the ToDoService, passing the update the ToDo item and the ToDo item index as argument.

If the request succeeds, the Effect will dispatch another action: ToDoApiActions.updatingToDosSuccess, passing the updated ToDo item as argument. If the request fails, the Effect will dispatch the ToDoApiActions.updatingToDosFailed action, passing the error details as argument.

The todosReducer function listens to the actions dispatched by the Effect and updates the “todoState” slice as below. Observe that we are updating the state for ToDoApiActions.updatingToDosFailed and ToDoApiActions.deletingToDosFailed action in the same way. Hence we have accommodated them both within the same on function.

on(ToDoApiActions.updatingToDosFailed, ToDoApiActions.deletingToDosFailed,(state,effectResult)=>{
return {
…state,
error:effectResult.message
}
}),
on(ToDoApiActions.updatingToDosSuccess,(state,effectsResult)=>{
return {
…state,
todos: state.todos.map(todo=> todo.id == effectsResult.updatedToDo.id ? effectsResult.updatedToDo: todo),
error:””
}
}),

=> deleteToDos$ Effect will handle the ToDoTabActions.deleteToDo action dispatched by the component, when we click on the “Delete” button corresponding to a ToDo item in the table.

deleteToDos$=createEffect(()=>{
return this.actions$.pipe(
ofType(ToDoTabActions.deleteToDo),
mergeMap((state)=>{
return this.todoService.deleteToDo(state.todoIndex).pipe(
map(()=>ToDoApiActions.deletingToDosSuccess({todoIndex:state.todoIndex})),
catchError((err:HttpErrorResponse)=>of(ToDoApiActions.deletingToDosFailed({message:err.statusText})))
)
})
)
})

The Effect will call the deleteToDo() in the ToDoService, passing the index of the ToDo item to be deleted as argument.

If the request succeeds, the Effect will dispatch another action: ToDoApiActions.deletingToDosSuccess, passing the index of the deleted ToDo item as argument. If the request fails, the Effect will dispatch the ToDoApiActions.deletingToDosFailed action, passing the error details as argument.

The todosReducer function listens to the actions dispatched by the Effect and updates the “todoState” slice as below.

 on(ToDoApiActions.deletingToDosSuccess,(state,effectsResult)=>{
return {
...state,
todos: state.todos.filter(todo=>todo.id !== effectsResult.todoIndex),
error:""
}
})

=>Finally, the component uses the Selector functions to listen to any changes in the “todoState” slice of the application state and update the template accordingly as you see below. The below 2 statements are in the ngOnInit hook of the component class.

this.error$=this.store.select(todoErrorSelector)

this.store.select(todoListSelector).pipe(
tap((todos:ToDoModel[])=>{
this.todoFormGroup?.setControl(“todoList”,this.fb.array(this.returnToDoGroup(todos)))
})
).subscribe();

Below is the link to the git repository for this example.

--

--

Angular&NodeEnthusiast
Angular&NodeEnthusiast

Written by Angular&NodeEnthusiast

Loves Angular and Node. I wish to target issues that front end developers struggle with the most.

No responses yet