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

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

In this part, we are going to write code to insert, update and delete items in the shopping list.

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 (this post)
Part 5 - Real-time subscriptions in an Ionic app (coming soon)

I will first show you how to set up and send these mutations in the ShoppingListProvider and then we'll implement the code for the views.

Update Item Mutation

We're going to do an update mutation when an item in the list is marked as "done".

Let's add the mutation query to the provider.

const mutationToggleItem = gql`  
  mutation($id: ID!, $done: Boolean) {
    updateItem(
      id: $id
      done: $done
    ) {
      id
      done
    }
  }
`;

Next, we'll add the code to send the mutation to the backend with Apollo Client.

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

Apollo Client has a mutate method that is similar to the watchQuery method we used before. We provide it with the mutation mutationToggleItem that we defined above and we also give it the variables for the mutation.

In this case, we set the id of the item and we'll set the done variable to either true or false depending on what the previous value was.

The return value of the mutate method is an Observable so we need to subscribe to it to trigger it. I'm logging the response so you can see it in the Console and if there is an error that will be logged as well.

We don't need to manually update our in-memory cache because Apollo Client takes care of that for us. We defined in our mutation that the backend should send us back the id and the done properties. Apollo Client will automatically identify the item in the cache by the id and subsequently update the done property.

Create Item Mutation

Here is the mutation for creating an item.

const mutationCreateItem = gql`  
mutation($name: String!, $categoryId: ID) {  
  createItem(
    name: $name,
    done: false,
    categoryId: $categoryId
  ) {
    id,
    name,
    done,
    category {
      id
    }
  }
}
`;

And here is the code that sends this mutation to the backend. Notice the update function where the cache is updated for the allItems query. We have to manually update the cache now to add the new item, Apollo Client does not do that automatically for you in the case of an insert or delete.

createItem(name, categoryId): void {  
  this.apollo.mutate({
    mutation: mutationCreateItem,
    variables: {
      name: name,
      categoryId: categoryId
    },
    update: (proxy, { data: { createItem } }) => {

      // Read the data from the cache for the allItems query
      const data: any = proxy.readQuery({ query: queryAllItems });

      // Add the new item to the data
      data.allItems.push(createItem);

      // Write the data back to the cache for the allItems query
      proxy.writeQuery({ query: queryAllItems, data });
    }
  })
  .subscribe(response => console.log(response.data),
             error => console.log('Mutation Error:', error));
}

Delete Item Mutation

And finally, here is the mutation for deleting an item.

const mutationDeleteItem = gql`  
mutation($id: ID!) {  
  deleteItem(id: $id) {
    id
  }
}
`;

As with the create mutation, we need to manually update the cache to remove the deleted item.

deleteItem(item: any): void {  
  this.apollo.mutate({
    mutation: mutationDeleteItem,
    variables: {
      id: item.id
    },
    update: (proxy, { data: { deleteItem } }) => {
      // Read the data from the cache for the allItems query
      let data: any = proxy.readQuery({ query: queryAllItems });

      // Remove the item from the data
      data.allItems = data.allItems.filter(i => i.id !== deleteItem.id);

      // Write the data back to the cache for the allItems query
      proxy.writeQuery({ query: queryAllItems, data });
    }
  })
  .subscribe(response => console.log(response.data),
             error => console.log('Mutation Error:', error));
}

That's it for the GraphQL part, we are done with the provider now, so now all that's left to do is change the views to be able to update the data.

Add NewItemPage

We're going to create a modal dialog to add new items to the list. Execute the following command to create the NewItemPage view.

$ ionic g page new-item

Here is the code for new-item.html, we're only asking for the name and the category in this example, but you can add the other properties here as well.

<ion-header>  
  <ion-navbar>
    <ion-title>New Item</ion-title>
    <ion-buttons end>
        <button ion-button (click)="save()">Save</button>
      </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>  
  <ion-list>
    <ion-item>
      <ion-label>Name</ion-label>
      <ion-input [(ngModel)]="name" type="text" text-right></ion-input>
    </ion-item>
    <ion-item>
      <ion-label>Category</ion-label>
      <ion-select [(ngModel)]="categoryId">
        <ion-option *ngFor="let category of categories$ | async" [value]="category.id">{{ category.name }}</ion-option>
      </ion-select>
    </ion-item>
  </ion-list>
</ion-content>  

And here is the code for new-item.ts.

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

@IonicPage()
@Component({
  selector: 'page-new-item',
  templateUrl: 'new-item.html',
})
export class NewItemPage {

  categories$: Observable<any>;
  name: string;
  categoryId: string;

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

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

  save(){
    this.provider.createItem(this.name, this.categoryId);
    this.viewCtrl.dismiss();
  }
}

Change ItemsPage

We'll add a button in the nav bar to go to the NewItemPage dialog and we'll add the code to initiate the update and the delete mutations.

Add the following code to items.html. We'll have an Add (+) button in the header to add new items. To Delete an item, the user can slide to the left to see the Delete button and we'll update existing items when the checkbox value is changed.

<ion-header>  
  <ion-navbar>
    <ion-title>Items</ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="goToAddItem()">
        <ion-icon name="add"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content>  
  <ion-list inset>
    <ion-item-sliding *ngFor="let item of items$ | async">
      <ion-item>
        <ion-label>{{ item.name }}</ion-label>
        <ion-checkbox [ngModel]="item.done" (ionChange)="toggle(item)"></ion-checkbox>
        <ion-badge item-end>{{ item.quantity }}</ion-badge>
      </ion-item>
      <ion-item-options>
        <button ion-button (click)="delete(item)" color="danger">Delete</button>
      </ion-item-options>
    </ion-item-sliding>
  </ion-list>
</ion-content>  

Add the following code to items.ts.

toggle(item) {  
  this.provider.toggleItem(item);
}

goToAddItem() {  
  const modal = this.modalCtrl.create(NewItemPage);
  modal.present();
}

delete(item) {  
  this.provider.deleteItem(item);
}

And finally, let's add some styling for the checked items.

page-items {  
    .item-checkbox-checked {
        text-decoration: line-through;
    }
}

Run the app with ionic serve and everything should work now, you can check if your mutations are updating data on the server in the Graphcool Console and you can use the Apollo Client Developer Tools Chrome extension to inspect the mutations and the cache on the client-side.

What's Next?

In the next part (coming soon) we'll have a look at how to implement real-time updates with GraphQL Subscriptions.