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
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 we'll have a look at how to implement real-time updates with GraphQL Subscriptions.