Tutorial

GraphQL Subscriptions in Angular using Apollo 2.0

Published on December 18, 2017
Default avatar

By Alligator.io

GraphQL Subscriptions in Angular using Apollo 2.0

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Subscriptions are a powerful GraphQL feature that make it easy to receive updates from a backend server in real time using a technology like WebSockets on the frontend. In this quick post we’ll go over how to setup the frontend for subscriptions in Angular using Apollo Client 2.0.

For the examples is this post, we’ll assume that you already have a GraphQL server up and running with subscriptions properly setup on the server-side. You can easily setup your own GraphQL server using a tool like graphql-yoga, or you can use the Graphcool framework and have Graphcool host your GraphQL service.

Required Packages

We’ll assume that you’ll want to build an app that has both GraphQL subscriptions as well as regular queries and mutations, so we’ll set things up with both a regular HTTP link and a WebSocket link. split, an utility from the apollo-link package, will make it easy to direct requests to the correct link.

First, we’ll need a whole bunch of packages to make everything work: apollo-angular, apollo-angular-link-http, apollo-cache-inmemory, apollo-link, apollo-link-ws, apollo-utilities, graphql, graphql-tag, apollo-client and subscriptions-transport-ws.

Let’s install all of them at once using npm or Yarn:

$ npm i apollo-angular apollo-angular-link-http apollo-cache-inmemory apollo-link apollo-link-ws apollo-utilities graphql graphql-tag apollo-client subscriptions-transport-ws

# or, using Yarn:
$ yarn add apollo-angular apollo-angular-link-http apollo-cache-inmemory apollo-link apollo-link-ws apollo-utilities graphql graphql-tag apollo-client subscriptions-transport-ws

Setup

We’ll create a module with our Apollo configuration and links. Let’s call it GraphQLConfigModule:

apollo.config.ts
import { NgModule } from '@angular/core';
import { HttpClientModule, HttpClient } from '@angular/common/http';

import { Apollo, ApolloModule } from 'apollo-angular';
import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
@NgModule({
  exports: [HttpClientModule, ApolloModule, HttpLinkModule]
})
export class GraphQLConfigModule {
  constructor(apollo: Apollo, private httpClient: HttpClient) {
    const httpLink = new HttpLink(httpClient).create({
      uri: 'REGULAR_ENDPOINT'
    });
const subscriptionLink = new WebSocketLink({
  uri:
    '___SUBSCRIPTION_ENDPOINT___',
  options: {
    reconnect: true,
    connectionParams: {
      authToken: localStorage.getItem('token') || null
    }
  }
});

const link = split(
  ({ query }) => {
    const { kind, operation } = getMainDefinition(query);
    return kind === 'OperationDefinition' && operation === 'subscription';
  },
  subscriptionLink,
  httpLink
);

apollo.create({
  link,
  cache: new InMemoryCache()
});

Here are a few things to note about our configuration:

  • In our module’s constructor, we first define an HTTP link using the apollo-angular-link-http package. Internally our link uses Angular’s HttpClient.
  • We then also define a WebSocket link with our GraphQL server’s subscription endpoint. Here you can see that we also get an authorization token from localStorage to authenticate our WebSocket connections. You’ll need this if your server only accepts authenticated subscription requests.
  • Next we use the split utility, which takes the query and returns a boolean. Here we check the query using another utility called getMainDefinition to extract the kind of query and operation name. From there, we can check if the query is a subscription and, if so, use the subscription link. Otherwise, the request will use the HTTP link.
  • Finally we simply create the link and make use of InMemoryCache for caching.

At this point, if the TypeScript compiler complains with something like Cannot find name 'AsyncIterator', you can add esnext to the list of libs in your tsconfig.json file:

tsconfig.json
{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    ...,
    "lib": [
      "es2017",
      "dom",
      "esnext"
    ]
  }
}

With this configuration module in place, all that’s left to do for our setup is to import the module in our main app module:

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { GraphQLConfigModule } from './apollo.config';
import { AppComponent } from './app.component';

Simple Subscription

We’re now ready to subscribe to different events in the frontend. Here’s a simple subscription query that we’ll run to automatically receive new todos created on the server:

subscription newTodos {
  Todo(filter: { mutation_in: [CREATED] }) {
    node {
      title
      description
      completed
    }
  }
}

For a real app, you’ll probably want to decouple your subscription logic in a service, but here we’ll do everything is our app component’s class for the sake of simplicity:

app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';

import { Apollo } from 'apollo-angular';
import gql from 'graphql-tag';
const subscription = gqlsubscription newTodos {
    Todo(filter: { mutation_in: [CREATED] }) {
      node {
        title
        description
        completed
      }
    }
  };
interface TodoItem {
  title: string;
  name: string;
  completed: boolean;
}
@Component({ ... })
export class AppComponent implements OnInit, OnDestroy {
  todoSubscription: Subscription;
  todoItems: TodoItem[] = [];
  constructor(private apollo: Apollo) {}
  ngOnInit() {
    this.todoSubscription = this.apollo
      .subscribe({
        query: subscription
      })
      .subscribe(({ data }) => {
        this.todoItems = [...this.todoItems, data.Todo.node];
      });
  }

Notice how it’s very similar to running a regular GraphQL query. With this in place, our frontend app will automatically receive new todo items.

We can display our todo items with something like this in our component’s template:

app.component.html
<ul>
  <li *ngFor="let item of todoItems">{{ item.title }} - {{ item.description }}</li>
</ul>

watchQuery + Subscriptions

Our example so far works well, but our app only gets new todos. As soon as we refresh, we get an empty list of todo items once again.

The simple solution is to first run a regular query and then use the subscription to receive additional todos automatically. That’s easy to do using the subscribeToMore method on an initial watchQuery:

app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';

import { Apollo, QueryRef } from 'apollo-angular';
import gql from 'graphql-tag';
const subscription = gqlsubscription newTodos {
    Todo(filter: { mutation_in: [CREATED] }) {
      node {
        title
        description
        completed
      }
    }
  };
const allTodosQuery = gqlquery getTodos {
    allTodos {
      title
      description
      completed
    }
  };
interface TodoItem {
  title: string;
  description: string;
  completed: boolean;
}
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
  todoSubscription: Subscription;
  todoItems: TodoItem[] = [];
  todoQuery: QueryRef<any>;
  constructor(private apollo: Apollo) {}
  ngOnInit() {
    this.todoQuery = this.apollo.watchQuery({
      query: allTodosQuery
    });
this.todoSubscription = this.todoQuery.valueChanges.subscribe(
  ({ data }) => {
    this.todoItems = [...data.allTodos];
  }
);

this.setupSubscription();  }
  setupSubscription() {
    this.todoQuery.subscribeToMore({
      document: subscription,
      updateQuery: (prev, { subscriptionData }) => {
        if (!subscriptionData.data) {
          return prev;
        }
    const newTodo = subscriptionData.data.Todo.node;

    return Object.assign({}, prev, {
      allTodos: [...prev['allTodos'], newTodo]
    });
  }
});  }

  • We first setup a watchQuery and subscribe to its valueChanges observable to get all the todo items when the component initializes.
  • We then setup our subscription by using subscribeToMore on our watchQuery.
  • subscribeToMore takes a query document (our subscription query), variables if needed and an update query function that gets the data from the previous query and an object that contains our subscription data (subscriptionData).
  • If the subscription data is empty we simply return the previous data, but if not we construct and return a new object that contains our previous data with our new data.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Alligator.io

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

I have a problem with the app.component.ts example. On line 2: import { Subscription } from 'rxjs/Subscription';

I’m using rxjs v6.5 and there is no Subscription export.

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel