How To Build An Ionic App With A Graphcool Backend - Part 5

In the previous parts of this tutorial, we created a Shopping List app with Ionic which uses Apollo Client to query and mutate data in a Graphcool backend.

In this part, we'll set up a GraphQL subscription to get real-time updates from the backend.

This tutorial is split up into these parts:
Part 1 - Introduction to GraphQL, Graphcool, and Apollo Client
Part 2 - Creating a Graphcool backend
Part 3 - Querying data in an Ionic app
Part 4 - Mutations in an Ionic app
Part 5 - Real-time subscriptions in an Ionic app (this post)

Initialize the network interface

First, we'll need to install a couple of packages to be able to set up the subscriptions connection to the Graphcool backend.

$ npm install --save apollo-link-ws subscriptions-transport-ws

Note: I also updated the apollo-angular and apollo-angular-link-http packages to the latest version (1.0.1).

Update the file graphql.module.ts to add the code for the subscription connection. This code configures Apollo Client to use both the Simple API and the Subscription API.

import { NgModule } from '@angular/core';  
import { HttpClientModule } from '@angular/common/http';  
import { ApolloModule, Apollo } from 'apollo-angular';  
import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';  
import { InMemoryCache } from 'apollo-cache-inmemory';  
import { WebSocketLink } from 'apollo-link-ws';  
import { split } from 'apollo-link';  
import { getMainDefinition } from 'apollo-utilities';

const simpleAPI = 'PUT YOUR SIMPLE API ENDPOINT HERE';  
const subscriptionAPI = 'PUT YOUR SUBSCRIPTION API ENDPOINT HERE';

@NgModule({
  exports: [
    HttpClientModule,
    ApolloModule,
    HttpLinkModule
  ]
})
export class GraphQLModule {  
  constructor(apollo: Apollo,
              httpLink: HttpLink) {

    const http = httpLink.create({ 
      uri: simpleAPI 
    });

    const ws = new WebSocketLink({ 
      uri: subscriptionAPI,
      options: {
        reconnect: true
      }
    });

    const link = split(
      // split based on operation type
      ({ query }) => {
        const { kind, operation } = getMainDefinition(query);
        return kind === 'OperationDefinition' && operation === 'subscription';
      },
      ws,
      http,
    );

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

See the Apollo Client Docs for more information.

Add subscription GraphQL

Let's set up the subscription in ShoppingListProvider.

This particular subscription will fire for all types of mutations: CREATED, UPDATED and DELETED because I want to show how to sync these with the local cache. You can obviously choose yourself which mutations you want to handle per subscription.

We are specifying which data we want to receive, this will include the mutation type, the item data as node and any previous values that we're interested in. In this case, I only want to know the id so that in the case of a DELETED mutation, I'll know which item was deleted.

const subscription = gql`  
subscription changedItems {  
  Item(
    filter: {
      mutation_in: [CREATED, UPDATED, DELETED]
    }
  ) {
    mutation
    node {
      id
      name
      done
      category {
        id
      }
      createdAt
      updatedAt
    }
    previousValues {
      id
    }
  }
}
`;

Now let's create a method on the provider to use this subscription and handle the mutations. We use the subscribeToMore method on the query and pass it the subscription GraphQL and a function that gets as input the local cache and the data received from the subscription.

private subscribeToChanges(query: QueryRef<any>) {  
  query.subscribeToMore({
    document: subscription,
    updateQuery: (prev: any, { subscriptionData }) => {
      if (subscriptionData.Item.mutation == 'CREATED') {
        return Object.assign({}, prev, {
          allItems: prev.allItems.concat(subscriptionData.Item.node)
        });
      }
      else if (subscriptionData.Item.mutation == 'DELETED') {
        return Object.assign({}, prev, {
          allItems: prev.allItems.filter(i => i.id !== subscriptionData.Item.previousValues.id)
        });
      }
    }
  });    
}

We are only handling the CREATED and DELETED mutations because, as I mentioned in the previous part of this tutorial, we don't need to manually update our local cache for UPDATED mutations. Apollo Client takes care of that for us.

Change the getAllItems method on the provider and add the call to subscribeToChanges.

getAllItems(): Observable<any> {  
  const queryWatcher = this.apollo.watchQuery<any>({
    query: queryAllItems
  });

  this.subscribeToChanges(queryWatcher);

  return queryWatcher.valueChanges
    .map(result => result.data.allItems);;
}

The last thing we need to do now is removing the code for updating the local cache from the createItem and deleteItem methods because we are now updating the cache in the subscribeToChanges method.

So your createItem and deleteItem methods should now look like this:

createItem(name, categoryId): void {  
  this.apollo.mutate({
    mutation: mutationCreateItem,
    variables: {
      name: name,
      categoryId: categoryId
    }
  })
  .subscribe(response => console.log(response.data),
              error => console.log('Mutation Error:', error));
}

deleteItem(item: any): void {  
  this.apollo.mutate({
    mutation: mutationDeleteItem,
    variables: {
      id: item.id
    }
  })
  .subscribe(response => console.log(response.data),
             error => console.log('Mutation Error:', error));
}

All done, now fire up the app with ionic serve and change some data on the backend in the Graphcool Console and you should see that data updated in the app instantly.