Tech insights: Configuring “Effect”-ive Communication with Angular, ngRx and SocketIO

Configuring “Effect”-ive Communication with Angular, ngRx and SocketIO

By Albert Janse van Rensburg

Having set up Redux, the next challenge you might face is effectively communicating changes from the outside world to your frontend application: Rather than having to manually ask every time something changes, we want to defer responsibility to the server so that it automatically updates and notifies you of changes. This tutorial will explain why, and guide you through configuring that communication using Redux effects and web sockets.

Albert_Pigeon-messenger-with-notification_Effective-communication_Inner-article-image-02

So, you have this awesome Angular frontend running, complete with state management and all the bells and whistles a client would normally ask for. As more and more users are adopting or using the system, you start experiencing scenarios where multiple users are editing the same policy (insurance industry) or medical history. How is a user supposed to know that somebody else is already editing something?

This would be a great scenario to have real-time notifications or alerts: If somebody updates a piece of information that I am working on, either I get notified that I am out of sync, or my work is simply updated ‘automagically!’ This is achieved using websockets, which allows the server to take on the responsibility of notifying connected clients of changes or events that took place, effectively given the server a voice.

When working in the web and programming worlds in general, there are a billion different ways to achieve the same thing, using endless combinations of tech. For this article, though, I will be looking at communication between the server and a front end application using a pattern that can be applied to an enterprise scale application.

Enter sockets, our saviour

To illustrate the communication concepts, I chose SocketIO as the ‘real-time engine’. It’s super easy to understand, and it integrates into my Node and Angular environments with minimal setup (see the end of the article for references to other socket implementations).

This kind of implementation is useful when you need to have multiple reactions for every action that occurs within your application. It’s also great for instances when there are multiple stakeholders interested in changes incurred by such an action. For example, when a user logs in (action), you want to set his roles in your app state (reaction), get his / her allowed menu items (reaction), and notify the connected dashboard of a user connecting (reaction in a whole separate system).

Whoa, hold up… before you continue!

To get the most out of this article, you should have a working knowledge of Angular, ngRx and websockets. If not, seek enlightenment from my previous article to get you going! Additionally, get the source code for the article and play with the tech on your machine.

As for websockets, there are links at the end of the article to assist in further learning on the subject. In a nutshell, though, websockets allow a long-held, single TCP socket connection to be established between a client and server, which in turn allows for bi-directional (full duplex) messages to be distributed instantly or “real-time.”

Preparations for awesomeness

If you’re all good with the above, we can look ahead at what needs to be done:

  1. Set up server-side SocketIO: Firstly, we will be looking at the SocketIO setup on the server side, and creating the global commands that the server emits and listens for.
  2. Set up client-side SocketIO service: Here, we will create a place to handle the events or emits from the server and the payload it delivers
  3. Create ngRx Effects to update the store: With the SocketIO stuff running, we create the Effects that fire when an action (such as AddTodo) is dispatched to the store.
  4. Register the Effects: After creating the Effects, we need to register them in all the right places.

Sound cool? Onwards! :)

Step 1: Set up server-side SocketIO

Firstly let’s get the ball rolling with the relevant bits from the server-side code. Remember: your server-side could be written in something completely different, but the CONCEPT will remain the same - it’s just implementation that will differ!

If you are using Node, however, download the source code for a working example that you can change or mould to your liking. In this example, I create a socket on port 3000, which will listen for the following events: todoCreated, todoUpdated, todoDeleted. These are custom defined names to correspond to Create, Update and Delete actions performed. The names can be whatever you like, though; I chose these as they made sense within the context of the application at hand.

Firstly, here is a snippet of the relevant part of the server-side websockets implementation (for the full file, see GitHub resource at the end of the article). Here we define the handles for the events that fire on the socket. If you are Node.js, add the following snippet to your server.js file:

server.js

// Some setup code here (see GitHub resource)

// Set up socket event handlers
io.on('connection', socket => {
 console.log('New client connected');

 socket.on('todoCreated', (todo) => {
   io.sockets.emit('todoCreated', todo);
 });

 socket.on('todoUpdated', (todo) => {
   io.sockets.emit('todoUpdated', todo);
 });

 socket.on('todoDeleted', (todo) => {
   io.sockets.emit('todoDeleted', todo);
 });

 socket.on('disconnect', () => {
   console.log('Client disconnected');
 });
});

// Some further setup

From the above, it’s worth noting that when an event is received, an emit occurs on io.sockets. This lets all clients connected to this socket know that this event occurred, ensuring all connected clients have the same consistent state (in a perfect world with 0 disconnects, etc.).

Next we need to emit the relevant events on the socket in the corresponding controller action. We need to add an emit call to our POST, PATCH and DELETE HTTP calls. Here is a snippet of the structure of my POST method in my todo controller (the PATCH and DELETE calls can be found in the source for the article, under Resources):

todo.routes.js

// Rest of the controller
 todoRouter.post('/', async (req, res) => {
   try {
     const todo = new Todo({
       text: req.body.text
     });

     const savedTodo = await todo.save();

     const newTodo = {
       id: savedTodo._id,
       text: savedTodo.text,
       completed: false,
       completedAt: savedTodo.completedAt
     };

     io.sockets.emit('todoCreated', newTodo);

     res.send(newTodo);
   } catch (error) {
     return res.status(400).send(error);
   }
 });
 // Rest of the controller

For this snippet, when a person creates a Todo, the created event is emitted to all connected clients to update their states as it has changed. This is specific to the todoCreated event, and the implementation for the Update and Delete methods are almost identical (see the source for the article, under Resources). However, this should be at the end of the function, which - if placed in a try-catch block as demonstrated - assumes that the save happened successfully.

Cool, that’s all that is relevant to the topic on the server-side code. We are now ready to implement the client-side goodness that you have been patiently waiting for!

Step 2: Set up client-side SocketIO service

Time to dive into the UI…

First up, we need a place to handle the events or emits coming in via the socket, and do something with the payload that the emit event delivers. For this, I wrote my implementation in a service (for reusability and maintainability) to dispatch messages to the server via the socket, subscribe to events that can be emitted from the server (todoCreated, todoUpdated, todoDeleted), and do something with the payload for that event.

Ensure that you have installed the ngx-socket-io package via NPM with the following command in your terminal:

npm install --save ngx-socket-io

Here is a snippet of the todo socket service within the source. It extends the Socket class from the ngx-socket-io package to generalise the dispatch and subscribe functionality. I found that it was cleaner to implement this than repeating the code throughout the components. Create your socket service as follows:

todo-socket.service.ts

import { Injectable } from '@angular/core';
import { Socket } from 'ngx-socket-io';
import { Observable } from 'rxjs/internal/Observable';

@Injectable({
 providedIn: 'root'
})
export class TodoSocketService extends Socket {
 constructor() {
   super({url: 'http://localhost:3000', options: { origin: '*', transport : ['websocket'] }});
 }

 public dispatch(messageType: string, payload: any) {
   this.emit(messageType, payload);
 }

 public subscribeToMessage(messageType: string): Observable<any> {
   return this.fromEvent(messageType);
 }
}

Here the **messageType** would be the event name, and the payload in this case it’s a **TodoModel** - the payload can, however, be anything

So with this in place, all that’s left to do is implement the Effects and string all these pieces together.

Step 3: Create Effects

Now we can create the effects that fire when an action (such as AddTodo) is dispatched to the store. This will be the mechanism that is triggered to do the update magic advertised throughout this article. Here, there are many options regarding what is possible to do in the effect:

  • You could navigate to another url
  • You could dispatch other actions
  • You could even call other APIs

There are, however, certain ways to set things up to interact correctly with the Action stream, so I would recommend reading this article for more information about getting this right.

Create a todo.effects.ts file and paste the following code (be sure to resolve any needed references in the file):

todo.effects.ts


@Injectable()
export class TodoEffects {
 @Effect({dispatch: false})
 public serverTodoAdd$ = this.actions$
   .pipe(
     ofType(TodoActionType.AddTodo),
     tap((payload) => console.log('Do something here as [AddTodo] effect', payload))
   );

 @Effect({dispatch: false})
 public serverTodoUpdate$ = this.actions$
   .pipe(
     ofType(TodoActionType.UpdateTodo),
     tap((payload) => console.log('Do something here as [UpdateTodo] effect', payload))
   );

 @Effect({dispatch: false})
 public serverTodoRemove$ = this.actions$
   .pipe(
     ofType(TodoActionType.RemoveTodo),
     tap((payload) => console.log('Do something here as [RemoveTodo] effect', payload))
   );

 constructor(
   private actions$: Actions
 ) {}
}

Firstly, the most noteworthy piece to address here is the injection of ‘Actions’. Actions is the action stream provided by ngRx that you subscribe to, to handle the dispatched events. Essentially, what we are doing above is creating multiple observers to the observable, with each effect being an observer. The ofType operator is used to specify the event type that should be handled by this effect, which is defined by the user in files such as todo.actions.ts in the provided source code.

Here, I also use tap to fire an event and return the observable to the stream unchanged. For more reading about Effects and what is possible with the stream please see the additional info at the end of the article.

Step 4: Register Effects with the store

Now that we have all the building blocks in place, the next step would be to hook all of this up in the relevant modules. We will need to register the todo-socket.service.ts as well as the TodoEffects.

Ensure that you set-up the EffectsModule in the feature module as demonstrated below:

todo.module.ts

// Rest of the file above
@NgModule({
 imports: [
   // .. Rest of the imports here
   EffectsModule.forFeature([TodoEffects])
 ],
 exports: [],
 declarations: [
   TodoComponent,
   TodoItemComponent,
   TodoDetailComponent,
   TodoListComponent
 ],
 providers: []
})
export class TodoModule {
}

Something worth mentioning here is that my effects are part of my todo feature. So, when the effects are registered, it is done so with the forFeature option provided by the effects module. This allows you to implement Effects in lazily loaded modules, which will be loaded and made available with the feature module.

Next, it is important that even if you don’t have effects in the root context of your application that you still call the forRoot function on the effects module as follows:

app.module.ts

// Rest of the file above
@NgModule({
 declarations: [
   AppComponent,
   HomeComponent
 ],
 imports: [
   // Rest of the imports here
   EffectsModule.forRoot([]),
   StoreRouterConnectingModule,
   !environment.production ? StoreDevtoolsModule.instrument() : []
 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }

… And that’s it! If you have done everything correctly and you have the backend running correctly you should see the console logs from the Effects firing when you add/update a todo.

I would recommend throwing in a few breakpoints or logs in the code to follow through the flow to get a better understanding if it is still not clear. I also recommend using Redux DevTools to visualise and debug the store interactions, as well as setting up the tools in your main module in this manner, and then using the tool as described here.

Why did I go about it in this way?

Firstly, I would like to reiterate that there are indeed many ways to skin a cat (so to speak): The concept that I am trying to illustrate is that websockets are the answer to how to build in real-time updates for your clients.

In this tutorial, we are doing this in an environment where we have a Redux implementation through ngRx in an Angular client. This is quite a common setup of enterprise frontends. The purpose was to demystify websockets and to showcase the ease of integrating it into your communication flow to ensure consistent state among your connected clients.

What I learned

Through developing the code for this article, I learned that most of the time ideas will transform or even change through implementation as your understanding of a problem grows. As I was building this, removing redundancies and streamlining the communication pipeline along the way, my initial approach kept developing into better implementations.

That said, there are many things that can still be implemented to better this approach, such as catering for disconnects, or setting up something like a polling service to check that the connection is still active and inform a client to refresh if not (or consider silent reconnect and verify the state). I had not considered these when I started out but they became apparent through working on the problem.

Basically, don’t assume that your first iteration will be perfect - it probably won’t! :)

Resources for further reading

SocketIO: The official documentation for the SocketIO library used in this article.

WebSockets conceptual deep dive: For those wanting to dive deep into WebSockets.

Angular: Assumed knowledge for this article, without which you will be totally lost.

ngRx Effects: All the info required to write awesome Effects in the Angular framework.

GitHub source for this post: In case you would like to inspect the code in greater detail.


Albert Janse van Rensburg is a full-stack software developer at Entelect, wielding experience in a broad variety of technologies and industries - which is totally not part of an elaborate plan to build his own death star (cough cough). He has built and optimised a vast number of enterprise solutions and, when not plugged in to the world of .NET and Angular, he tinkers with a variety of different technologies and ideas. Find him on Github to see more of what he does!

Source-banner--1-

Cat eyes@2x

Subscribe to our blog

Don’t miss out on cool content. Every week we add new content to our blog, subscribe now.

By subscribing you agree to our Ts & Cs and our Privacy Policy, including our use of cookies.