Angular: Creating An Automatic Polling Service

When showing information in your UI, you often want to refresh the data when something changes to provided the most accurate information to the user. The most efficient way to do this is by having the server push new data to the WebUI by using Server Sent Events, WebSockets or a similar technique. A lot of APIs however do not offer this, but only support the basic HTTP operations. This means that if you want to refresh the data the only choice you have is to poll the server. This happened to me recently, we wanted to auto-refresh the data on our pages, but our backend application only supports simple GETs. While in the long term we should definitely change this to support sending data from the server, but for now this is what we have. In this blog post I will discuss my solution to doing this.

First of all, I was a bit shocked that I didn’t find any good resources that solve this problem already. The most common way to do this seems to be by just calling the endpoint again (after a timeout) after handling the data of the previous call.

private getData() {
this.httpClient.get("api").subscribe(
(data) => {
...
setTimeout(() => this.getData(), 1000);
},
(error) => {
...
setTimeout(() => this.getData(), 1000);
}
);
}

While this works fine for a single client wanting to get the data, it will cause duplicated calls if there are multiple parts of your application interested in the data. Moreover, the logic of the automatic polling is mixed with the actual business logic and that is not a good design. This can however be improved by splitting it a bit.

private getData() {
this.httpClient.get("api").do(
(data) => setTimeout(() => this.getData(), 1000),
(error) => setTimeout(() => this.getData(), 1000)
).subscribe(
(data) => {
...
},
(error) => {
...
}
);
}

A much bigger problem is that this code can not be moved to a service because with every new call, we get a new subscription. And when doing auto-refreshing it is essential to stop the subscription or you will have a memory leak and keep calling the server even if the application no longer needs this data. The client that has subscribed to this http call, must always have the latest subscription such that it can unsubscribe when it is no longer relevant.

There are a couple of reasons why you want to have all of this logic in a central service:

    1. It allows re-useability.
    2. It hides the details from your component, resulting in cleaner code.
    3. It is much easier to make any changes as they will all be centralised.

So after some experimenting I designed an approach that would allow this. It works by having 2 different observables, the first one is a timer observable that will trigger the other observable, which will call the server to get the data. When the client subscribes for data, he makes a call that causes the second observable to be subscribed to the first. And instead of giving a subscription to the second observable, the client gets back a subscription of the first. On each timer trigger, the second observable is subscribed, and thus the call to the server is made.

In the end the client code looks like this:
this.subscription = this.service.subscribeToData(
(data) => {
...
},
(error) => {
...
}
);

It is clear that the client does not have any knowledge about the details, it doesn’t even know how often the server will be called. And this is exactly what we want, because we may want to easily change this later on or have the server push the data. Also note that the client itself no longer explicitly subscribes, instead it passed on the function that have to be called.

The service code is as follows:
private readonly timerObservable: Observable = Observable.timer(0, 3000).share();
private readonly dataObservable: Observable = this.getData().share();

public getData() {
return this.httpClient.get("api");
}

public subscribeToData(
next?: (value: any) => void,
error?: (error: any) => void,
complete?: () => void): Subscription {
let subscription: Subscription;
const timerSubscription = this.timerObservable.subscribe(i => {
subscription = this.dataObservable.subscribe(next, error, complete);
});
timerSubscription.remove(subscription);
return timerSubscription;
}

Important is to note that the .share() at the end of both subscribers is essential to avoid creating an new stream of number or call to the server for each subscription. Also the timerSubscription.remove(subscription) is important, although not essential. This makes sure that if the client unsubscribes from the timer, the call to the server is also unsubscribed from. If we would not do this, then the client would get an extra call it may not want. While this will not cause too much problems, it is much cleaner to do it.

That is the solution I came up with, if you have any comments or other alternative solutions, let me know in the comments.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s