Skip to main content

Display real-time data in Angular

5 min read

Older Article

This article was published 8 years ago. Some information may be outdated or no longer applicable.

Two ways to push real-time data into an Angular application. One uses sockets. The other leans on the AsyncPipe and Observables. Both get the job done, but they feel very different to build.

Setting the scene

Most Angular apps follow a familiar pattern. A component calls a service, the service calls an API, data comes back, and the template renders it. Simple enough.

But what happens when that data changes constantly? Think stock tickers, live radio displays showing the current artist and song, or any feed that refreshes every few seconds. You need a way to keep the component in sync with whatever the API is sending.

Async Pipe & Observables

The first approach doesn’t require touching the API at all. We’ll use the Async Pipe.

Pipes in Angular work the same way pipes work in Linux. They take an input and produce an output. The Async Pipe specifically accepts a promise or an observable, and it updates the template whenever the promise resolves or the observable emits a new value. Like all pipes, you apply it in the template.

Let’s say we’ve got a list of products returned by an API and this service available:

// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class ApiService {
  constructor(private http: HttpClient) {}

  getProducts() {
    return this.http.get('http://localhost:3000/api/products');
  }
}

The getProducts() method returns the HTTP GET call. Nothing fancy.

Now we consume this service in the component. We’ll create an Observable and assign the result of getProducts() to it. Then we’ll fire that call every 1 second, so any update at the API level gets picked up:

// some.component.ts
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { ApiService } from './../api.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/switchMap';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css'],
})
export class ProductsComponent implements OnInit {
  @Input() products$: Observable<any>;
  constructor(private api: ApiService) {}

  ngOnInit() {
    this.products$ = Observable.interval(1000)
      .startWith(0)
      .switchMap(() => this.api.getProducts());
  }
}

And then we wire up the async pipe in the template:

<!-- some.component.html -->
<ul>
  <li *ngFor="let product of products$ | async">
    {{ product.prod_name }} for {{ product.price | currency:'£'}}
  </li>
</ul>

Push a new item to the API (or remove one), and the update shows up in the component within 1 second.

Sockets

The second approach uses sockets. This one requires changes on both the API and the client side.

API level modifications

At the API level, we need to enable sockets. The most widely used package for this is socket.io, installed via npm i socket.io.

Here’s a server implementation using Restify and Socket.io:

const restify = require('restify');
const server = restify.createServer();
const products = require('./products');

const io = require('socket.io')(server.server);
let sockets = new Set();

const corsMiddleware = require('restify-cors-middleware');
const port = 3000;

const cors = corsMiddleware({
  origins: ['*'],
});

server.use(restify.plugins.bodyParser());
server.pre(cors.preflight);
server.use(cors.actual);

io.on('connection', (socket) => {
  sockets.add(socket);
  socket.emit('data', { data: products });
  socket.on('clientData', (data) => console.log(data));
  socket.on('disconnect', () => sockets.delete(socket));
});

server.get('/', (request, response, next) => {
  response.end();
  next();
});

server.post('/api/products', (request, response) => {
  const product = request.body;
  products.push(product);
  for (const socket of sockets) {
    console.log(`Emitting value: ${products}`);
    socket.emit('data', { data: products });
  }
  response.json(products);
});
server.listen(port, () => console.info(`Server is up on ${port}.`));

Note how Restify requires us to use server.server when requiring socket.io.

The products file contains an array of objects representing some data. On the first connection we send that data to the requester and stash the socket in a JavaScript Set:

io.on('connection', (socket) => {
  sockets.add(socket);
  socket.emit('data', { data: products });
  socket.on('clientData', (data) => console.log(data));
  socket.on('disconnect', () => sockets.delete(socket));
});

When someone adds a new product (just a simple push to the products array), we emit the updated array to every connected client:

server.post('/api/products', (request, response) => {
  const product = request.body;
  products.push(product);
  for (const socket of sockets) {
    console.log(`Emitting value: ${products}`);
    socket.emit('data', { data: products });
  }
  response.json(products);
});

We’re only covering the basics here, so the API stays deliberately simple.

Client side modifications

On the client side, we need to connect to the socket from our Angular application. We’ll use socket.io-client along with its typing. Install both via npm: npm i socket.io-client @types/socket.io-client.

Once installed, update the Angular service:

// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as socketIo from 'socket.io-client';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class ApiService {
  observer: Observer<any>;

  getProducts() {
    const socket = socketIo('http://localhost:3000/');
    socket.on('data', (response) => {
      return this.observer.next(response.data);
    });
    return this.createObservable();
  }

  createObservable() {
    return new Observable((observer) => (this.observer = observer));
  }
}

We create an observer, connect to the socket server on port 3000, and listen for emitted data (which fires on first load and whenever someone adds a new product). That data flows into an observable, which gets passed to the component and then to the template. The template still uses the async pipe. The rest of the code stays the same.

Adding a new product now triggers a live update across all connected clients.

Conclusion

Two approaches, same result. The polling method is simpler to set up but hammers the API. Sockets are more work upfront but push updates instantly. Pick whichever fits.