Ionic 4 + AppSync: Use GraphQL mutations in Ionic - Part 4

In the previous parts of this tutorial we've set up a GraphQL backend with AWS AppSync and created Ionic pages to display the data from the backend. We will now use GraphQL mutations to create/update/delete Cards from our Ionic app with a cool new feature from the Amplify CLI.

This tutorial is split up into these parts:
Part 1 - Introduction to GraphQL & AWS AppSync
Part 2 - Create a GraphQL API
Part 3 - Add Ionic pages and GraphQL queries
Part 4 - Use GraphQL mutations in Ionic (this post)
Part 5 - Set up Authentication with Amazon Cognito
Part 6 - Add ElasticSearch to GraphQL API
(more parts will be added later)

You can find the source code for this tutorial on my Github.

New Amplify CLI feature

Before we go to the GraphQL mutations, I want to show you a new feature of the Amplify CLI which was released a few days ago.

When we created our AppSync API in part 2, we were asked if we wanted to generate code and to choose which language to create code for.

We opted for typescript, but now we have a new option. The CLI will now give you the option to choose angular and this will generate an Angular service which implements all the code for handling the GraphQL queries and mutations for your schema.

First, make sure you have the latest version of the Amplify CLI.

$ npm install -g @aws-amplify/cli

We'll use the Ionic CLI to create an Angular service for us.

$ ionic g service api

Next, we'll need to configure the codegen to use angular instead of typescript. The CLI will ask you some questions, choose as shown below.

$ amplify codegen configure
? Choose the code generation language target angular
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.graphql
? Enter the file name for the generated code src/app/api.service.ts

You might want to delete any previously generated code before you continue. Delete the directory src/graphql/ and the file src/API.ts.

And now we can use the Amplify codegen command to generate the code for our GraphQL queries and mutations.

$ amplify codegen

Have a look at the generated code in /src/app/api.service.ts, we'll use this service now to implement the create/update/delete functionality in our app.

Create/Update/Delete mutations

Go to src/app/card-details/card-details.page.ts and add an import for the generated service.

import { APIService } from '../api.service';  

Modify the constructor and add the service.

constructor(private modalController: ModalController,  
            private navParams: NavParams,
            private apiService: APIService)  { }

And now comes the actual mutation part which is super simple with the generated service.

save() {  
  if (this.isNew) {
    this.apiService.CreateCard({
      cardDeckId: this.deck.id,
      question: this.card.question,
      answer: this.card.answer
    })
  } 
  else {
    this.apiService.UpdateCard({
      id: this.card.id,
      question: this.card.question,
      answer: this.card.answer
    })
  }

  this.modalController.dismiss();
}

As you can see, this generated service is very easy to use and gives you the ability to build your app very fast. Let's move on to the delete mutation.

delete() {  
  if (!this.isNew) {
    this.apiService.DeleteCard({
      id: this.card.id
    });
  }

  this.modalController.dismiss();
}

So now we have implemented our mutations, but we're still seeing old data on the Cards page after the mutations happen. We could choose to simply refetch our query upon dismissal of the modal dialog like below.

Add this line to cards.page.ts in loadModal after the modal is created.

modal.onDidDismiss().then(() => this.getQuestions());  

Another way to get notified of changes in data is by using GraphQL subscriptions. The generated Angular service has methods for these as well.

We'll look into subscriptions another time, but if you want to go that route, I would recommend you have a look at using the GraphQL client from the AWS AppSync JavaScript SDK which is more advanced than the GraphQL client we're using now, as I explained in the first part of this tutorial.

Quiz Page

Screenshot Quiz Page

We still need to implement the Quiz page we created in the previous part. We'll be using the Ionic Slides component to give the user the ability to swipe through the cards during the quiz.

Ionic Slides uses Swiper under the hood which has a lot of cool features, check out the docs.

Go to scr/app/quiz/quiz.page.html and copy in the following code:

<ion-header no-border>  
  <ion-toolbar color="secondary">
    <ion-buttons slot="start">
      <ion-button (click)="stop()">
        <ion-icon slot="icon-only" name="close"></ion-icon>
      </ion-button>
    </ion-buttons>
    <ion-title class="swiper-pagination"></ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>  
  <ion-slides [options]="slideOptions">
    <ion-slide *ngFor="let card of cards">
      <ion-card padding>
        <p class="question">{{ card.question }}</p>
        <ion-button color="secondary" (click)="showAnswer(card)" *ngIf="!card.displayAnswer">Answer</ion-button>
        <p class="answer" *ngIf="card.displayAnswer">
          {{ card.answer }}
        </p>
      </ion-card>
    </ion-slide>
  </ion-slides>
</ion-content>  

We're could create our own GraphQL query again here, as we did in the previous part of this tutorial, or we could use the API service. The service has the ListCards method which gets all the cards, you can see the exact query when you go to the implementation of that method.

Go to scr/app/quiz/quiz.page.ts and copy in the following code:

import { Component, OnInit } from "@angular/core";  
import { ModalController } from "@ionic/angular";  
import { APIService } from "../api.service";

@Component({
  selector: "app-quiz",
  templateUrl: "./quiz.page.html",
  styleUrls: ["./quiz.page.scss"]
})
export class QuizPage implements OnInit {  
  public cards;

  public cardIndex = 1;

  public slideOptions = {
    slidesPerView: 1.2,
    spaceBetween: -5,
    centeredSlides: true,
    pagination: {
      el: ".swiper-pagination",
      type: "fraction"
      }
  };

  constructor(
    public modalController: ModalController
    public apiService: APIService
  ) { }

  ngOnInit() {
    this.apiService
      .ListCards({
        question: {
          contains: 'GraphQL'
        }
      })
      .then(query => this.cards = query.items);
  }

  showAnswer(card) {
    card.displayAnswer = true;
  }

  async stop() {
    await this.modalController.dismiss();
  }
}

The ListCards query can also filter the cards that are returned from the backend. In this case I've defined that I only wanted the cards that contain the word GraphQL in their questions. You can filter on all the fields in the card, have a look at the types that are generated to see what is possible.

FYI: Searching this way on data stored in DynamoDB is not very efficient. In part 6 of this tutorial we are going to replace this query with ElasticSearch.

To finish it off, let's add some styling to this page. Go to scr/app/quiz/quiz.page.scss and copy in the following code:

ion-card {  
    min-height: 70%;
    width: 100%;
}

ion-card p {  
    font-size: 16px;
    color: #000;
}

ion-slides {  
    height: 100%;
}

.question {
    font-weight: bold;
}

.answer {
    text-align: left;
}

You can find the source code for this tutorial on my Github.

What's next?

We've looked at GraphQL queries and mutations and how to use these with the GraphQL client and with the generated Angular service.

Right now the app is really only usable by just one person, since we don't have any functionality for users to sign up and create an account. In the next part we'll have a look at adding authentication with Amazon Cognito.

If you have any questions or if you're having problems following the steps in this tutorial, please leave a comment below and I'll try to help you out.