Last update on Wednesday, September 6th 2017

Implementing GraphQL using Apollo in an Ionic Application: CRUD

Apollo for Angular is quite new so this article will be quite experimental and (I hope) it might change after the release of the incoming apollo-client 2.0.
Thanks to Angular, Ionic applications benefit from two-way data bindings and this feature will affect how our CRUD is built.
As starting point, we take the previous introduction article on Apollo and GraphQL and start by setting up the server side.

Server Side

Until now, we have only seeing how to access the data through Queries. In order to make some modifications, Mutations are used.

Let's start by adding the mutations to our Schema at the end of the user.schema.js file:

var MutationType = new GraphQLObjectType({
  name: "Mutation",
  fields: {
    create: MutationCreate,
    update: MutationUpdate,
    deleteUser: MutationDelete
  }
});

exports.userSchema = new GraphQLSchema({
  query: queryType,
  mutation: MutationType
});

Next step, creating the following MutationCreate, MutationUpdate and MutationDelete objects.

Starting with MutationCreate:

var MutationCreate = {
  type: new GraphQLList(UserType),
  description: "Create a User",
  args: {
    name: {
      name: "User name",
      type: new GraphQLNonNull(GraphQLString)
    }
  },
  resolve: (root, { name }) => {
    USERs.push({
      id: new Date().getTime(),
      name: name
    });
    return USERs;
  }
};

We are going to return an array of users.
This mutation will receive the user's name as a string.
As usual the resolve property is where the work is done. A new user object will be pushed in the USERs array.
The id will be randomly created and the name will be the one we will get from the client. At the end, we return the list of users to the client.

The MutationUpdate object is slightly more complex:

var MutationUpdate = {
  type: new GraphQLList(UserType),
  description: "Update a User",
  args: {
    id: {
      name: "User id",
      type: new GraphQLNonNull(GraphQLFloat)
    },
    newName: {
      name: "New user name",
      type: new GraphQLNonNull(GraphQLString)
    }
  },
  resolve: (root, { id, newName }) => {
    var selectedUser = USERs.filter(function(user) {
      return user.id === id;
    })[0];
    selectedUser.name = newName;
    return USERs;
  }
};

The id and newName are received.
The built-in filter method is used on the USERs array to acquire the corresponding user. This method returns an array so we just grab the first result.
This user will have its name replaced by the one we get from the client.
As usual the new USERs array is returned to the client.

Finally, the last mutation:

var MutationDelete = {
  type: new GraphQLList(UserType),
  description: "Delete a User",
  args: {
    id: {
      name: "User id",
      type: new GraphQLNonNull(GraphQLFloat)
    }
  },
  resolve: (root, { id }) => {
    USERs = USERs.filter(function(user) {
      return user.id !== id;
    });
    return USERs;
  }
};

Once again the filter method is used. This time we grab every users except the one matching the ID to delete.

We are now done for the server side.

We can now directly test our mutations with GraphiQL!

GraphiQL tests

Those mutations can now be tested with GraphiQL.

The create mutation:

mutation create {
  create(name: "Nicolas") {
    id
    name
  }
}

ionic graphql node apollo CRUD create mutation graphiql

The deleteUser mutation:

mutation deleteUser {
  deleteUser(id: 1504624415942) {
    id
    name
  }
}

ionic graphql node apollo CRUD delete mutation graphiql

The update mutation:

mutation update {
  update(id: 1446412740883, newName: "Jean updated") {
    id
    name
  }
}

ionic graphql node apollo CRUD update mutation graphiql

The Ionic Application

Let's start by changing the home.ts file:

interface Response {
  users: any;
  create: any;
  deleteUser: any;
  update: any;
}

const createUser = gql`
  mutation create($name: String!) {
    create(name: $name) {
      id
      name
    }
  }
`;

const Users = gql`
  query users {
    users {
      id
      name
    }
  }
`;

const updateUser = gql`
  mutation update($id: Float!, $newName: String!) {
    update(id: $id, newName: $newName) {
      id
      name
    }
  }
`;

const deleteUser = gql`
  mutation deleteUser($id: Float!) {
    deleteUser(id: $id) {
      id
      name
    }
  }
`;

A Response can have more information now. The mutations results will be located in a field named after the mutation (deleteUser, create or update).

createUser, deleteUser and updateUser are constants that will contain the GraphQL mutation documents.

The syntax is as follow:

mutation mutationName($var1: type!) {
    mutationName(var: $var1) {
    ...

A small detour

Before diving into the real solution, let me show you one interesting thing about Apollo and Ionic.

First the ngInit is redesigned:

  ngOnInit() {
    this.apollo.watchQuery<Response>({ query: Users })
    .subscribe(({data}) => {
      this.data = data.users;
    });
  }

Using the data from the subscribe method will lead us to a surprise later 😉. The users from the data response are acquired and the data property is updated.

Following with the home.html file:

  <ion-list>

    <ion-item *ngFor="let user of data">

      <button item-left ion-button (click)="updateUser(user.id, user.name)">
        <ion-icon name="brush"></ion-icon>
      </button>

        <input type="text" name="name" [(ngModel)]="user.name">

    </ion-item>

  </ion-list>

The users names are now editable through a simple input thanks to the traditional ngModel and a button will trigger the updateUser method.

The updateUser method is as follow:

  updateUser(id, name) {
    this.apollo.mutate<Response>({
      mutation: updateUser,
      variables: {
        id: id,
        newName: name
      }
    }).subscribe(({data}) => {
      this.data = data.update;
    });
  }

The mutate method is used with the updateUser GraphQL document, the id and newName variables. We then subscribe and update the data with the mutation's result.

Everything should be good right?

Running the Ionic application:

ionic graphql node apollo CRUD not changing

Well that's that's odd right? Our data bindings aren't working properly!

Let's see if we can act on the data property when the input changes in the HomePage:

<input type="text" name="name" [(ngModel)]="user.name" (ngModelChange)="onInputChanged(user.id, $event)">

By passing the user's id and the event, we will have enough information.

For now, we only modify the first item's name:

 onInputChanged(id, name) {
    this.data[0].name = name;
  }

Here is what we get:

ionic graphql node apollo CRUD read only

A new error:

Cannot assign to read only property ‘name’ of object ‘[object Object]’.

Our data are read-only! The information returned by a GraphQL Query are supposed to be read, not modified.

If you have followed the latest tutorials on Redux, you should be familiar with this icon in Chrome:

ionic graphql node apollo CRUD store icon

This means that Apollo has its own Redux Store!

Here it is:

ionic graphql node apollo CRUD store

Let’s get back to the good solution.

The Good Way

The correct way to acquire Apollo’s Redux Store data is as follow:

  ngOnInit() {
    this.data = this.apollo.watchQuery<Response>({ query: Users })
  }

The main Users query is watched and we get the data from this query.

The concept is simple:

  1. Mutations are going to be run
  2. The result from those mutations will be acquired
  3. This main Users Query will be read then modified
  4. The UI will automatically get updated because we are watching the changes

We put back the correct ngFor:

    <ion-item *ngFor="let user of data | async | select:'users'">

Handling multiple inputs, the state of the data on the server and Apollo’s Redux Store at the same time can be tricky.

A new Component will be used to update a user one at a time.

This one is from a previous tutorial on the ngDragDrop project:

import {Component, Input, EventEmitter, Output } from '@angular/core';

@Component({ selector: 'editable-text', template:`
  <span *ngIf='isEditable'> <input type='text' (blur)="updateParent()" [(ngModel)]='writableData.name'></span>
  <span *ngIf='!isEditable' (click)='setEditable(true)'> {{writableData.name}} </span>
  `
})
export class XEditableText {
  writableData = {};
  @Input() data;

  @Output('updateUser') updateUser: EventEmitter<any> = new EventEmitter<any>();

  isEditable:boolean;

  ngOnInit() {
    Object.assign(this.writableData, ...this.data);
    this.isEditable = false;
  }

  setEditable(isEditable){
    this.isEditable = isEditable;
  }

  updateParent() {
    this.setEditable(false);
    this.updateUser.emit(this.writableData);
  }
}

This Component will receive the user from its parent: the HomePage.
Those information will be copied into a new property by using the built-in assign method. When the user leaves the input, this component will tell the HomePage to update the corresponding user.

Here is the data copy:

ionic graphql node apollo CRUD data copy

All we need now is adding the component to the app.module.ts file:

import { XEditableText } from './xeditable-text/xeditable-text.component';
...

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    XEditableText
  ],
  ...
})

And using it in the home.html:

      <editable-text (updateUser)="updateUser($event)" [data]="user"></editable-text>

While we are here, let’s add the buttons for the create and delete methods, here is the final home.html file:

<ion-content padding>

  <input type="text" [(ngModel)]="newUsername">

  <button ion-button (click)="createUser(newUsername)">
    <ion-icon name="add"></ion-icon>
  </button>

  <ion-list>
    <ion-item *ngFor="let user of data | async | select:'users'">

      <button item-left ion-button (click)="deleteUser(user.id)">
        <ion-icon name="remove"></ion-icon>
      </button>

      <editable-text (updateUser)="updateUser($event)" [data]="user"></editable-text>

    </ion-item>

  </ion-list>
</ion-content>

One input with an ngModel to stock the newUsername, coupled with a button that will trigger the createUser method using this newUsername.

One button will be displayed for each <ion-item>. Tapping on it will trigger the deleteUser method using the user’s ID.

We need to change the updateUser method:

  updateUser(user) {
    this.apollo.mutate<Response>({
      mutation: updateUser,
      variables: {
        id: user.id,
        newName: user.name
      },
      update: (store, { data: { update }}) => {
        let data = store.readQuery({ query: Users });
        (<any> data).users = update;
        store.writeQuery({ query: Users, data });
      }
    })
  }

The user is received from the XEditable Component, it has everything up-to-date.
The mutate method from the apollo property will update the data on the server side. In order to update the data in Apollo’s Redux Store, it is recommended using the update** field.
We acquire here the store and the data from the mutation.

Two important methods are used here: readQuery and writeQuery.
The readQuery method allows us to access the current data from the Users query, it’s the information that we started watching earlier.
We stock it in a variable and modify the users property with the mutation’s result. Finally by using the writeQuery, the store updates the information returned by the Users query and the UI is updated.

What’s left now: the create and delete features.

This is a piece of cake.

Those two methods are as follow:

  createUser(name) {
    this.apollo.mutate<Response>({
      mutation: createUser,
      variables: {
        name: name
      },
      update: (store, {data: { create }}) => {
        let data = store.readQuery({ query: Users });
        (<any> data).users = create;
        store.writeQuery({ query: Users, data });
      }
    })
  }

  deleteUser(id) {
    this.apollo.mutate<Response>({
      mutation: deleteUser,
      variables: {
        id: id
      },
      update: (store, { data: { deleteUser } }) => {
        let data = store.readQuery({ query: Users });
        (<any> data).users = deleteUser;
        store.writeQuery({ query: Users, data});
      }
    })
  }

They do the same thing:

  1. Using a GraphQL mutation document
  2. Passing the variables
  3. Updating Apollo’s Redux Store

Here is the final result:

ionic graphql node apollo CRUD final result

Conclusion

Queries aren’t Mutations.
The data returned by a Query is read-only so be prepared to create a writable copy. GraphiQL is once again very helpful for testing the mutations before diving into the Ionic application.
Once we understand how Apollo works, it become a bit repetitive. It’s mainly about watching a query and updating it once new information are available.

Implementing GraphQL using Apollo in an Ionic Application: Introduction

Introduction to Angular 2 Observables

Implementing Redux Time Travel in an Ionic Application

Stay up to date


Join over 4000 other developers who already receive my tutorials and their source code out of the oven + other free stuff!