loading...

Ngrx Effects - Isolate side-effects in angular applications

igagrock profile image irshad sheikh Originally published at initgrep.com on ・4 min read

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.

source

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 AppComponentrequires a list of users.

  • We have a service AppRemoteService and it contains users$ 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 the AppComponent and we would subscribe to AppRemoteService.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 the state or dispatch actions.
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 in loadUsers action dispatched by AppComponent. As such, when the component is initialized, the loadUsers action is dispatched. The effect reacts to it and subscribes remoteservice.users$ .

  • Once the data is fetched, the loadUsers$ effect will dispatch addUsers 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.

Discussion

pic
Editor guide