what is a Side-effect?
A side effect refers simply to the modification of some kind of state - for instance:
- Changing the value of a variable;
- Writing some data to disk;
- Enabling or disabling a button in the User Interface.
Table of contents
-
Why NgRx Effects?
- Service based design vs NgRx Effects based design
-
NgRx Effects
- Installation
- Implementation
- Register NgRx Effects in module
Why NgRx Effects?
A pure component has an immutable input and produces the events as output. These components are essentially dumb and most of the computations are done outside of it. A pure component has a single responsibility similar to pure functions.
Now, If the components are not doing any computations, most of the computations and side-effects would be moved inside the services. That is the first-hand use of angular services. It is easy to inject them into components and perform various computations.
Now here are a few things you should consider -
- First of all, if your application is growing fast and you are storing/managing state inside your components or services, you should consider a state management solution.
- The services are an ideal choice but you have to manually take care of storing the state, making sure the state remains immutable, consistent, and available for all the dependent components as well as services.
- Let’s assume, you have a service taking care of communicating via APIs to some remote server. You inject the service in a component and call the method of the service inside the component.
- …and if you have another service, which performs some computation which is supposed to be run when the user interacts with UI such as a click of a button. Again, you would inject the service in component, and on
click
of the button, a method of the service is called.
It works great… But if you notice, the component is tighly coupled with the service and it knows about what operations to perform when a user clicks a button or when an API should be called.
The very first disadvantage of this pattern you would come across is components are hard to test since they are dependent on many services. You would also notice that It is almost impossible to reuse the components.
So what is the alternative approach? …
The alternative approach is to let components be pure and not responsible for managing the state . Instead make them reactive so that when an input data is available, it renders it to UI and when there are UI interactions, it generates the relevant Events.
That is it…
The component does not have to know, how the Input data is made available or how the events are handled.
The services are nevertheless an important part of the application. They would still contain all the methods to do various computations or communicate via APIs. But now they are no longer being injected inside the component.
I have already written a post about reactive state management using NgRx store, actions, and selectors. You can go through it to have an understanding of NgRx state management.
Let’s see a comparison between the service-based design and NgRx effects.
You might notice fewer elements in play during service-based design but don’t let it fool you. It is better to have more elements in application than to have a lousy app.
Service based design
Suppose, our AppComponent
requires a list of users.
- We have a service
AppRemoteService
and it containsusers$ observable
which can be subscribed to get a list of users.
users$ = this.httpClient.get<User[]>(URL).pipe(take(1));
- We have injected the
AppRemoteService
in side theAppComponent
and we would subscribe toAppRemoteService.users$
observable .
@Component({
template: `
<div class="user-container"
*ngIf="localUsers">
<app-user *ngfor="let user of localUsers"
[inputUser]="user">
</div>
`
})
export class AppComponent{
//state inside component
localUsers: User[];
constructor(private remoteService: RemoteService){}
ngOnInit(){
//handle the subscription here
this.remoteService.users$.subscrible(
users => this.localUsers = users;
);
}
}
NgRx Effects based design
Here is how NgRx effects will change it -
- The AppComponent would only require
NgRx Store
to select thestate
or dispatchactions
.
export class AppComponent implements OnInit {
constructor(private store: Store<fromApp.AppState>) { }
}
- As soon as the component requires the list of users, it would dispatch an action
loadUsers
when the component is initialized.
export class AppComponent implements OnInit {
constructor(private store: Store<fromApp.AppState>) { }
ngOnInit(): void {
//action dispatched
this.store.dispatch(fromActions.loadUsers());
}
}
- The Component will use NgRx selector
selectUsers
and subscribe to its observable.
@Component({
template: `
<div class="user-container"
*ngIf="localUsers$ | async as users">
<app-user *ngfor="let user of users"
[inputUser]="user">
</div>
`
})
export class AppComponent implements OnInit {
localusers$ = this.store.select(fromSelectors.selectUsers);
constructor(private store: Store<fromApp.AppState>) { }
ngOnInit(): void {
this.store.dispatch(fromActions.loadUsers());
}
}
NgRx Effects will be listening to the stream of actions dispatched since the latest state change.
loadUsers$
effect is interested inloadUsers
action dispatched byAppComponent
. As such, when the component is initialized, theloadUsers
action is dispatched. The effect reacts to it and subscribesremoteservice.users$
.Once the data is fetched, the
loadUsers$
effect will dispatchaddUsers
action with associated metadata - users. The respective reducer function will transition the state. The latest state will contain recently feteched users.
//app.effects.ts
loadUsers$ = createEffect(
() => this.action$.pipe(
ofType(AppActions.loadUsers),
mergeMap(() => this.remoteService.users$
.pipe(
map(users => AppActions.addUsers({ users })),
catchError(error => {
return of(error);
})
)),
));
//app.reducer.ts
//addUsers action mapping
const theReducer = createReducer(
initialState,
on(AppActions.addUsers, (state, { users }) => ({
...state,
users: [...users]
}))
);
- As soon as the data is available, the
localusers$
observable subscription will have users list ready for the component to render.
In contrast with the service-based approach isolating the side-effects using NgRx Effects, the component is not concerned about how the data is loaded. Besides allowing a component to be pure, It also makes testing components easier and increases the chances of reusability.
If you have come this far, you might also want to know how to install and implement the NgRx Effects in an angular application. I have written a detailed post. Click here to check it out.
Top comments (0)