Ionic Vue JS AWS Amplify Authentication CRUD Tutorial Pt 3, Create, Update And Delete with Datastore

Aaron K Saunders - May 4 '21 - - Dev Community

This is a continuation of a video series on using AWS Amplify Datastore with Vue JS and Ionic Framework for the user interface. In the first two parts of the video we did setup, user authentication/account creation and querying data.

In the third video we cover the remaining CRUD actions of creating, updating and deleting data from AWS Amplify Datastore.

This blog post is to provide the source code from the project.

Please see the entire tutorial videos by using the link below

ENTRYFORM.VUE

<template>
  <ion-header :translucent="true">
    <ion-toolbar>
      <ion-title>Create New Entry</ion-title>
    </ion-toolbar>
  </ion-header>
  <ion-content class="ion-padding">
    <ion-item>
      <ion-label>Title</ion-label>
      <ion-input type="" v-model="formData.title"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label>Description</ion-label>
      <ion-textarea rows="3" v-model="formData.description"></ion-textarea>
    </ion-item>
    <ion-item>
      <ion-label position="fixed">Status</ion-label>
      <ion-checkbox
        slot="end"
        :modelValue="formData.status"
        @update:modelValue="formData.status = $event"
      ></ion-checkbox>
    </ion-item>
    <ion-item>
      <ion-label>Start Date</ion-label>
      <ion-datetime disabled v-model="formData.start_date"></ion-datetime>
    </ion-item>
    <ion-item>
      <ion-label>Completion Date</ion-label>
      <ion-datetime disabled v-model="formData.end_date"></ion-datetime>
    </ion-item>
  </ion-content>
  <ion-footer :translucent="true">
    <ion-toolbar>
      <ion-button @click="saveData()">SAVE</ion-button>
      <ion-button @click="$emit('closeModal', null)">CANCEL</ion-button>
    </ion-toolbar>
  </ion-footer>
</template>

<script lang="ts">
import {
  IonContent,
  IonItem,
  IonLabel,
  IonButton,
  IonInput,
  IonToolbar,
  IonTitle,
  IonHeader,
  IonFooter,
  IonTextarea,
  IonCheckbox,
  IonDatetime
} from "@ionic/vue";
import { ref } from "vue";

export default {
  props: ["initialData"],
  emits: ["closeModal"],
  setup(props, ctx) {
    const formData = ref<any>({
      status: false,
      ["start_date"]: new Date().toLocaleString(),
      ...props.initialData
    });

    /**
     *
     */
    const saveData = () => {
      if ( formData.value.status === true && props.initialData.status !== true) {
        // set the completion date
        formData.value['end_date'] = new Date().toLocaleString();
      }
      ctx.emit("closeModal", { ...formData.value });
    };

    return {
      formData,
      saveData
    };
  },
  components: {
    IonContent,
    IonItem,
    IonLabel,
    IonButton,
    IonInput,
    IonToolbar,
    IonTitle,
    IonHeader,
    IonFooter,
    IonTextarea,
    IonCheckbox,
    IonDatetime
  }
};
</script>

<style lang="css" scoped>
</style>
Enter fullscreen mode Exit fullscreen mode

HOME.VUE

<template>
  <!-- amplify-ui -->
  <amplify-authenticator username-alias="email">
    <ion-page>
      <ion-header :translucent="true">
        <ion-toolbar>
          <ion-title>HOME</ion-title>
          <ion-buttons slot="end">
            <ion-button @click="showInputModal(null)">NEW</ion-button>
          </ion-buttons>
        </ion-toolbar>
      </ion-header>
      <ion-content :fullscreen="true" class="ion-padding">
        <p>TASKS: {{ user?.attributes?.email }}</p>
        <div>
          <ion-list>
            <ion-item v-for="t in tasks" :key="t.id">
              <ion-label class="ion-text-wrap">
                <p>{{ t.title }}</p>
                <p>{{ t.description }}</p>
                <p id="id">{{ t.id }}</p>

              </ion-label>
            </ion-item>
          </ion-list>
        </div>
        <ion-modal
          :is-open="showModal?.isOpen"
          @onDidDismiss="showModal.isOpen = false"
        >
          <entry-form
            :initialData="showModal?.data"
            @closeModal="handleCloseModal"
          ></entry-form
        ></ion-modal>
      </ion-content>
      <ion-footer class="ion-padding">
        <amplify-sign-out></amplify-sign-out>
      </ion-footer>
    </ion-page>
    <!-- [end] amplify-ui -->
  </amplify-authenticator>
</template>

<script lang="ts">
import {
  IonContent,
  IonPage,
  IonTitle,
  IonToolbar,
  IonHeader,
  IonFooter,
  IonList,
  IonItem,
  IonLabel,
  IonButton,
  IonButtons,
  IonModal
} from "@ionic/vue";
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
import { onAuthUIStateChange } from "@aws-amplify/ui-components";

import { DataStore } from "@aws-amplify/datastore";
import { Task } from "../models";
import EntryForm from "@/components/EntryForm.vue";

export default defineComponent({
  name: "Home",
  setup() {
    let unsubscribeAuth: any = null;
    let unsubscribeData: any = null;

    const curAuthState = ref<any>(null);
    const user = ref<any>(null);
    const showModal = ref<{
      isOpen: boolean;
      data: any;
    }>({ isOpen: false, data: null });

    // results from query
    const tasks = ref<any>([]);

    onUnmounted(() => {
      try {
        unsubscribeAuth();
        if (unsubscribeData) unsubscribeData();
      } catch (e) {
        console.log(e);
      }
    });

    const getData = async () => {
      const models = await DataStore.query(Task);
      console.log(models);
      tasks.value = models;
    };

    /**
     *
     */
    const createData = async (data: any) => {
      console.log(data);
      return await DataStore.save(new Task({ ...data }));
    };

    /**
     *
     */
    const deleteData = async (data: any) => {
      return await DataStore.delete(Task, t => t.id("eq", data.id));
    };
    /**
     *
     */
    const saveData = async (data: any) => {
      const original = await DataStore.query(Task, data.id);
      if (original === undefined) return;
      return await DataStore.save(
        Task.copyOf(original, (updated: any) => {
          updated.title = data.title;
          updated.description = data.description;
          updated.status = data.status;
          updated["end_date"] = data.end_date;
        })
      );
    };

    const handleCloseModal = async (payload: any) => {
      showModal.value = {
        isOpen: false,
        data: null
      };

      // save data
      console.log(payload);
      if (payload) {
        try {
          await (payload?.id !== undefined
            ? saveData(payload)
            : createData(payload));
        } catch (e) {
          debugger;
          console.log(e);
        }
      }
    };

    const showInputModal = (value: any) => {
      if (value === null) {
        showModal.value = {
          isOpen: true,
          data: null
        };
      } else {
        showModal.value = {
          isOpen: true,
          data: value
        };
      }
    };

    onMounted(() => {
      unsubscribeAuth = onAuthUIStateChange((authState, authData) => {
        curAuthState.value = authState;
        user.value = authData;
        if (user.value) {
          getData();

          const { unsubscribe } = DataStore.observe(Task).subscribe(msg => {
            console.log(msg.model, msg.opType, msg.element);
            getData();
          });
          unsubscribeData = unsubscribe;
        }
      });
    });

    return {
      user,
      tasks,
      showModal,
      //functions
      showInputModal,
      handleCloseModal,
      deleteData
    };
  },
  components: {
    IonContent,
    IonPage,
    IonTitle,
    IonToolbar,
    IonHeader,
    IonFooter,
    IonList,
    IonItem,
    IonLabel,
    IonButton,
    IonModal,
    IonButtons,
    EntryForm
  }
});
</script>
<style scoped>
#id {
  font-size: x-small;
}
</style>
Enter fullscreen mode Exit fullscreen mode

MAIN.TS

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

import { IonicVue } from "@ionic/vue";

/* Core CSS required for Ionic components to work properly */
import "@ionic/vue/css/core.css";

/* Basic CSS for apps built with Ionic */
import "@ionic/vue/css/normalize.css";
import "@ionic/vue/css/structure.css";
import "@ionic/vue/css/typography.css";

/* Optional CSS utils that can be commented out */
import "@ionic/vue/css/padding.css";
import "@ionic/vue/css/float-elements.css";
import "@ionic/vue/css/text-alignment.css";
import "@ionic/vue/css/text-transformation.css";
import "@ionic/vue/css/flex-utils.css";
import "@ionic/vue/css/display.css";

/* Theme variables */
import "./theme/variables.css";

/* AMPLIFY */
import {
  applyPolyfills,
  defineCustomElements,
} from "@aws-amplify/ui-components/loader";
import Amplify from "aws-amplify";
import awsconfig from "./aws-exports";

Amplify.configure(awsconfig);

applyPolyfills().then(() => {
  defineCustomElements(window);
});

const app = createApp(App)
  .use(IonicVue)
  .use(router);

app.config.isCustomElement = (tag) => tag.startsWith("amplify-");

router.isReady().then(() => {
  app.mount("#app");
});
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .