Simple Example to demonstrate Server Sent Events in Angular and Node

Angular&NodeEnthusiast
5 min readApr 7, 2020

--

Adding a Server Sent Event API to your angular application means that it can get data updates automatically from the server whenever available. The components/services need not repeatedly ask the server for updates.

Consider an example where I want the real-time exchange rate of 2 currencies USD and JPY at regular intervals.I have used a 3rd party API ALPHA VANTAGE which gives me this information free of cost.

How can this be achieved with SSE?

Lets begin with the Angular Project. Its a simple one with a component and a service.

Component Template:

<button (click)=”GetExchangeData()”>Get Exchange Rate Updates</button><button (click)=”stopExchangeUpdates()”>Stop Exchange Rate Updates</button>

There are 2 buttons. 1 button to open a connection with the server and another button to close this connection.

Component Class:

export class SSEComponent implements OnInit {constructor(private serv:ServiceA) { }ngOnInit() {
this.serv.returnAsObservable().subscribe(data=>console.log(data));
}
GetExchangeData()
{
this.serv.GetExchangeData();
}
stopExchangeUpdates()
{
this.serv.stopExchangeUpdates();
}
}

The component has subscribed to a Subject so that it can receive and log the data as soon as the service retrieves it from the server.

ServiceA:

export class ServiceA {constructor(private http:HttpClient) { }evs:EventSource;private subj=new BehaviorSubject([]);returnAsObservable()
{
return this.subj.asObservable();
}
GetExchangeData()
{
let subject=this.subj;
if(typeof(EventSource) !==’undefined’)
{
this.evs=new EventSource(‘//localhost:8080/getStockUpdate’);
this.evs.onopen=function(e)
{
console.log(“Opening connection.Ready State is “+this.readyState);
}
this.evs.onmessage=function(e)
{
console.log(“Message Received.Ready State is “+this.readyState);
subject.next(JSON.parse(e.data));
}
this.evs.addEventListener("timestamp",function(e)
{
console.log("Timestamp event Received.Ready State is "+this.readyState);
subject.next(e["data"]);
})
this.evs.onerror=function(e)
{
console.log(e);
if(this.readyState==0)
{
console.log(“Reconnecting…”);
}
}
}
}
stopExchangeUpdates()
{
this.evs.close();
}
}

The server-sent event API is contained in the EventSource interface.

In order to open a connection with the server, first you need to create an object of EventSource and pass the url of the script that will generate the events in text/event-steam format.

this.evs=new EventSource(‘//localhost:8080/getStockUpdate’);

Once the connection is opened, the onopen event handler is called.

this.evs.onopen=function(e)
{
console.log(“Opening connection.Ready State is “+this.readyState);
}

We are just logging a message and also printing the readyState. readyState just returns a number which tells us about the state of the connection.

If it is 0, it means the client is connecting to the server.

If it is 1, the connection is open.

If it is 2, the connection is closed.

When the server sends back data with a message event or no specific event name, the onmessage event handler is called.

this.evs.onmessage=function(e)
{
console.log(“Message Received.Ready State is “+this.readyState);
subject.next(JSON.parse(e.data));
}

The server also sends back data with a custom timestamp event, then the below handler is called.

this.evs.addEventListener("timestamp",function(e)
{
console.log("Timestamp event Received.Ready State is "+this.readyState);
subject.next(e["data"]);
})

We are calling next() on the subject instance passing the received data as argument. The subscribed component thus receives the data.

In case of any error eg:connection with the server broken,the onerror event handler is called.

The main advantage of EventSource is that keeps trying to reconnect to the server in case of any break.There’s a small delay between reconnections, a few seconds by default. This delay can also be set from the server side(will be seen later).

If the server wants the browser to stop reconnecting, it should respond with HTTP status 204.

When this reconnection happens, you will notice the below piece of code inside the onerror handler getting executed.

if(this.readyState==0)
{
console.log(“Reconnecting…”);
}

When close() is called on the EventSource object, the connection to the server is closed and the client no longer monitors the server url for events.

Now lets move to the Node server.

const express=require(‘express’);
const cors=require(‘cors’);
const axios = require(“axios”);
const app=express();
app.use(cors());
app.get(‘/getStockUpdate’,function(req,res,next)
{
res.set(‘Content-Type’,’text/event-stream;charset=utf-8');
res.set(‘Cache-Control’,’no-cache’);
setInterval(function()
{
(function(req,res,next)
{
let date=new Date(); //to capture the timestamp of the request
console.log(“executing request”);
axios({“method”:”GET”,“url”:”https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=USD&to_currency=JPY&apikey=some_key",“headers”:{“content-type”:”application/json”}
}).
then((response)=>{
res.write('event:'+'timestamp\n');
res.write('data:'+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds()+'\n\n');res.write('data:'+JSON.stringify(response.data)+'\n\n');})
.catch((error)=>{
console.log(error)
})
})(req,res,next);
},6000);
})
app.listen(8080,function()
{
console.log(“app listening on port 8080”);
})

It is essential to set the below 2 response headers because EventSource recevies events from the server only in text/event-stream format.

res.set(‘Content-Type’,’text/event-stream;charset=utf-8');
res.set(‘Cache-Control’,’no-cache’);

We have used the axios module to send a GET request to the 3rd party API.

We are executing this request every 6 seconds. This means the server is sending data updates every 6 seconds.

The format of the response to be sent is also very important.

res.write('event:'+'timestamp\n');res.write('data:'+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds()+'\n\n');res.write('data:'+JSON.stringify(response.data)+'\n\n');

The server sends messages, delimited by \n\n.

A message may have following fields:

data: — message body.A sequence of multiple data is interpreted as a single message, with \n between the parts.

id: — renews lastEventId, sent in Last-Event-ID on reconnect.

retry: — recommends a retry delay for reconnections in ms. There’s no way to set it from JavaScript.

event: — event name, must precede data:.

‘\n’ indicates the end of a part of the message and ‘\n\n’ indicates the end of the complete message.

Thus the below is considered as 1 message which will be captured under timestamp event. The message contains the details of the timestamp of the request.

res.write('event:'+'timestamp\n');res.write('data:'+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds()+'\n\n');

The 2nd message is captured under the message event contains the JSON response from the 3rd party API.

res.write('data:'+JSON.stringify(response.data)+'\n\n');

Below is a snapshot of the example:

Snapshot example

When I click on the Get Exchange Rate Updates Button, the connection with the server is opened and the readyState is 1.

I start receiving 2 events:message and timestamp from the server every 6 secs, as you can observe from the timestamp in the console.

When I click on Stop Exchange Rates button, the connection to the server is closed. Eventhough I have closed the connection, the server can continue to send updates but the application will not be able to receive them.

Consider a situation, where the server goes down and the connection is broken.

Reconnection

As you can see, the EventSource attempts to reconnect with a delay.

The retry interval is a few seconds by default but it can be set with the “retry:” field in the message sent by the server.

Please let me know your comments:)

--

--

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.

Responses (3)