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

In the previous part of this tutorial, we created a project on Graphcool and defined the schema for our Shopping List app.

In this part, we'll create our Ionic Shopping List app and add Apollo Client to it to connect to our Graphcool 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 (this post)
Part 4 - Mutations in an Ionic app
Part 5 - Real-time subscriptions in an Ionic app (coming soon)

Create Ionic App

First, we'll create the Ionic app. This tutorial should work for Ionic 2+ versions. The version used in this tutorial is Ionic 3.

$ ionic start ionic3-tutorial-graphcool blank
$ cd ionic3-tutorial-graphcool

Install Apollo Client

The Apollo Client library takes care of the communication with the GraphQL backend. In this tutorial, we're going to use Appolo Client 2, which was released just a week ago.

First, we'll install the apollo-client-preset package. This bundles all the Apollo Client packages we need into one.

$ npm install graphql apollo-client-preset --save

Next, we'll install the apollo-angular integration packages. These make it easier for us to use Apollo Client in Angular/Ionic apps. Hopefully these packages will be available as a stable release soon, but for now, we'll have to use the beta versions.

$ npm install apollo-angular@beta apollo-angular-link-http@beta --save

The type definitions for zen-observable (used by Apollo Client) are missing at the moment, so when you run the app you'll probably get an error. Execute this command to install them:

$ npm install @types/zen-observable

Configure Apollo Client

We'll create a new module to contain our Apollo Client configuration.

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';

const simpleAPI = 'SIMPLE API ENDPOINT OF YOUR GRAPHCOOL PROJECT';

@NgModule({
  exports: [
    HttpClientModule,
    ApolloModule,
    HttpLinkModule
  ]
})
export class GraphQLModule {  
  constructor(
    apollo: Apollo,
    httpLink: HttpLink
  ) {
    apollo.create({
      link: httpLink.create({ uri: simpleAPI }),
      cache: new InMemoryCache()
    });
  }
}

The code above initializes Apollo Client with the endpoint for the Simple API.

We're also instructing Apollo Client to use an in-memory cache for the data that is received from the backend. This is configurable so you can change it to another cache implementation like Hermes or even write your own one if needed.

In the imports section in app.module.ts we'll add our new module:

import { GraphQLModule } from './graphql.module';

@NgModule({
  declarations: [
    MyApp
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    GraphQLModule
  ],

Create Provider

We'll create a provider to access our Graphcool backend.

Execute the following command to add the provider:

$ ionic g provider shopping-list

In this provider, we'll add our GraphQL query for selecting all items from the backend and use the Apollo Client to send this query to Graphcool.

import { Injectable } from '@angular/core';  
import 'rxjs/add/operator/map';

import { Observable } from 'rxjs/Observable';

import { Apollo } from 'apollo-angular';  
import gql from 'graphql-tag';

const queryAllItems = gql`  
  query allItems {
    allItems {
      id
      name
      done
      category {
        id
      }
    }
  }
`;

@Injectable()
export class ShoppingListProvider {

  constructor(private apollo: Apollo) { }

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

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

As you can see, the query looks exactly like the queries we made in the previous part in the Graphcool console, so nothing new there.

We are using the graphql-tag function here to parse the query and turn it into a GraphQL document before we pass it on to Apollo Client.

We then use the watchQuery method to send the query to Graphcool. The response from the backend can then be accessed on the valueChanges property which is an Observable.

To get to the actual data we then use map to get to the allItems list.

We're now ready to create pages for our app, but first, let's set up the tabs.

Create Tabs

We'll use the ionic generate or ionic g command to generate the tabs for us.

$ ionic g tabs

What should the name be? tabs

How many tabs? 2

Name of this tab: items

Name of this tab: categories  

Make sure to set the rootPage in app.component.ts to TabsPage.

export class MyApp {  
  rootPage:any = TabsPage;

Don't forget to include the new page modules in app.module.ts

We created 2 pages, one that will display items and one that will display all categories. Let's go and implement ItemsPage.

Implement ItemsPage

Add the following code to items.ts.

import { Component } from '@angular/core';  
import { IonicPage, NavController, NavParams } from 'ionic-angular';  
import { Observable } from 'rxjs/Observable';  
import { ShoppingListProvider } from '../../providers/shopping-list/shopping-list';

@IonicPage()
@Component({
  selector: 'page-items',
  templateUrl: 'items.html',
})
export class ItemsPage {

  items$: Observable<any>;

  constructor(public navCtrl: NavController, 
              public navParams: NavParams,
              public provider: ShoppingListProvider) {

    this.items$ = provider.getAllItems();
  }
}

Add the following code to items.html:

<ion-header>  
  <ion-navbar>
    <ion-title>Items</ion-title>
  </ion-navbar>
</ion-header>

<ion-content>  
  <ion-list inset>
    <ion-item *ngFor="let item of items$ | async">
      <ion-label>{{ item.name }}</ion-label>
      <ion-checkbox [(ngModel)]="item.done"></ion-checkbox>
      <ion-badge item-end>{{ item.quantity }}</ion-badge>
    </ion-item>
  </ion-list>
</ion-content>  

Fire up the app with ionic serve and you should see some data coming back from the Graphcool backend.

Get Categories Data

Let's do the second part and write the code to get the categories. Add the code below to the ShoppingListProvider.

const queryAllCategories = gql`  
  query allCategories {
    allCategories {
      id
      name
    }
  }
`;

And add the getAllCategories method as defined below. This is very similar to the getAllItems method.

public getAllCategories() : Observable<any> {  
  const queryWatcher = this.apollo.watchQuery<any>({
    query: queryAllCategories
  });

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

Implement CategoriesPage

The CategoriesPage will display all the categories and when the user selects a category it will display all the items in that category.

Add the following code to categories.html:

<ion-header>  
  <ion-navbar>
    <ion-title>Categories</ion-title>
  </ion-navbar>
</ion-header>

<ion-content>  
  <ion-list inset>
    <button ion-item *ngFor="let category of categories$ | async" (click)="showItems(category)">
      <ion-icon name="cart" item-end></ion-icon>  
      <ion-label>{{ category.name }}</ion-label>
    </button>
  </ion-list>
</ion-content>  

Add the following code to categories.ts:

import { Component } from '@angular/core';  
import { IonicPage, NavController, NavParams } from 'ionic-angular';  
import { Observable } from 'rxjs/Observable';  
import { ItemsPage } from '../items/items';  
import { ShoppingListProvider } from '../../providers/shopping-list/shopping-list';

@IonicPage()
@Component({
  selector: 'page-categories',
  templateUrl: 'categories.html',
})
export class CategoriesPage {

  public categories$: Observable<any>;

    constructor(public navCtrl: NavController, 
                public navParams: NavParams,
                public provider: ShoppingListProvider) {

      this.categories$ = provider.getAllCategories();
    }

    showItems(category) {
      this.navCtrl.push(ItemsPage, { category: category });
    }
}

Run ionic serve and you should now see the categories.

Modify ItemsPage

Now all that's left to do is change ItemsPage so that it can display either all items or only items that belong to the selected category.

constructor(public navCtrl: NavController,  
  public navParams: NavParams,
  public provider: ShoppingListProvider) {

  const category = navParams.get("category");

  if (category) {
    this.items$ = provider.getItems(category);
  }
  else {
    this.items$ = provider.getAllItems()
  }
}

And add the getItems(category) method to the ShoppingListProvider.

public getItems(category: any): Observable<any> {  
  return this.getAllItems()
    .map(data => data.filter(i => i.category && i.category.id == category.id));
}

Load the app again and you should now be able to select a category and see the items in it.

Caching

We didn't write any code ourselves to cache the data from the server locally, but if you inspect the network traffic, you'll see that when we request the items per category, there is no new request sent off to the server.

This is because we configured Apollo to use the in-memory cache. Apollo caches the results based on the queries you send through it and the next time you execute the query it will give you the cached results.

Install the Apollo Client Developer Tools Chrome extension to see the cache.

Apollo Client Developer Tools

What's Next?

You now know how to use Apollo Client to query data from the backend. In the next part, we'll have a look at how to do mutations.