Exploring VueJS, TypeScript and Google FireStore[part 1]

VueJS

A friend of mine from our local Google developer group(GDG) requested that we cover content that would help during a hackathon. In particular, he had become curious about learning patterns for rapid back-end prototyping. With this in mind, I wanted to share some content around Vue and Google FireStore.

Motivations for using VueJS+FireStore

Vue has become respected for it’s simplicity and speed. In contrast with Angular, it has a lower concept count making it easier to learn.

https://vuejs.org/

Google FireStore, a no-SQL platform as a service, provides a robust and scalable tool for building web and mobile apps. The platform provides features for data storage, analytics, identity, authorization, and more. I’m convinced that it’s a great prototyping platform since you don’t have to think about VM’s or containers. FireStore has a very generous free tier. It’s worth checking out. In this post, we’ll walk through the process of building a small todo app using VueJS, FireStore, VueCLI, and TypeScript.

https://firebase.google.com/docs/firestore/

Setup of vue/typescript/cli

To get started, install VueCLI. This command line tool makes it easy to scaffold Vue projects. You can explore the Vue setup process using the following resource. It should be noted that I selected TypeScript for my project setup.

https://cli.vuejs.org/guide/creating-a-project.html#vue-create

Major parts of Todo.vue

Please note that you can review the completed demo code at the following github repo: https://github.com/michaelprosario/fireTodo

In the ‘src\views’ directory of the project, we created a template called Todo.vue. I like keeping my TypeScript in a different file from the markup. In the last line of the Todo.vue, we implement a script reference to ‘Todo.ts’ and created the file.

<template>
  <div>
    ...
  </div>
</template>
<script lang='ts' src="./Todo.ts"></script>

Saving a Todo

In the following code, we set up a simple form in Todo.vue to capture a new item. In this context, a todo item has action, complexity, and priority as strings. You’ll notice the ‘v-model’ attributes that bind the content of the text boxes to their Vue respective component properties. We’ve also added a ‘saveTask’ method to commit the todo item to the database.

<div>Task</div>
<div><input type="text" id="txtAction" v-model="action"></div>
<div>Priority</div>
<div><input type="text" id="txtPriority"  v-model="priority"></div>
<div>Complexity</div>
<div><input type="text" id="txtComplexity" v-model="complexity"></div>
<div><button v-on:click="saveTask()">Save task</button></div>

Adding behavior to Todo.vue

Let’s checkout the implementation of Todo.ts to see how we manage data at a high level. In the data section, we initialize the state of our component. We set the tasks collection to an empty array. We also initialize our form properties to empty strings. In the architecture of Angular, I really appreciate how data operations get encapsulated into services. I’ve tried to model this practice in this demo code. All the FireStore database operations are encapsulated in a class called TodoDataServices. In the ‘saveTask’ method, we create an instance of this service, populate a todo record, and store it using the data service. When the add operation completes, we re-load the list.

import Vue from 'vue';
import { TodoDataServices, TodoRecord } from './FireStoreDataServices';

export default Vue.extend({
  name: 'HelloWorld',
  data() {
    return {
      tasks: [],
      action: '',
      complexity: '',
      priority: ''
    }
  },
  created() {
    this.loadTasks();
  },
  methods: {
    saveTask() {
      let todoDataService = new TodoDataServices();
      let todo = new TodoRecord();
      todo.action = this.action;
      todo.complexity = this.complexity;
      todo.priority = this.priority;
      let context = this;
      todoDataService.Add(todo).then(function () {
        context.loadTasks();
      })
    },
    loadTasks() {
      let todoDataService = new TodoDataServices();
      todoDataService.GetAll().then(listData => {
        this.tasks = listData;
      });
    },
    removeTask(record) {
      let todoDataService = new TodoDataServices();
      let context = this;
      todoDataService.Delete(record.id).then(function () {
        context.loadTasks();
      });
    }
  },
});

TodoDataService

There’s not a lot of complexity in the TodoDataService. The ‘add’ method passes the todo record to a class called FireStoreDataServices. This class helps create re-usable code fore Google FireStore operations. In the add call, we pass the todo record and the name of the database table or collection.

export class TodoDataServices{

    dataServices: FireStoreDataServices;
    constructor(){
        this.dataServices = new FireStoreDataServices();
    }

    Add(todo: TodoRecord){
        // Not sure why this hack is needed. I'm still researching why the type information causes issues.
        var data = JSON.parse(JSON.stringify(todo));  
        return this.dataServices.addRecord(data, "tasks");
    }

    Delete(recordId: string){
        return this.dataServices.deleteRecord(recordId, "tasks");
    }

    GetAll(){
        return this.dataServices.getAll("tasks", DocToTodoRecordMap);
    }
}

FireStoreDataServices

The FireStore data services class encapsulates the major database operations of FireStore. For each database operation(add, update, get, delete, list), we return data using promises. This helps us communicate success and failures to the caller.

import db from './FirebaseConfig';

export class FireStoreDataServices {

    addRecord(recordObject: any, tableName: string) {
        return new Promise(function (resolve, reject) {  
            // implement add
        });
    }

    updateRecord(recordObject: any, tableName: string) {
        return new Promise(function (resolve, reject) {
            // implement update
        });
    }

    getRecord(recordID: string, tableName: string, docToRecordMap) {
        return new Promise(function (resolve, reject) {
            // implement get record
        });
    }

    deleteRecord(recordID: string, tableName: string) {
        return new Promise(function (resolve, reject) {
            // implement delete

        });
    }

    getAll(tableName: string, docToRecordMap) {
        return new Promise(function (resolve, reject) {
            // implement get all
        });
    }
}

In ‘views/FirebaseConfig.ts’, you can configure your connection to your FireStore database. Check out this checklist to establish your FireStore db. You will want to follow the directions for web.

https://firebase.google.com/docs/firestore/quickstart

import firebase from 'firebase'
import 'firebase/firestore'

// Initialize Firebase
  var config = {
    apiKey: "...",
    authDomain: "fixme.firebaseapp.com",
    databaseURL: "https://fixme.firebaseio.com",
    projectId: "fixme",
    storageBucket: "fixme.appspot.com",
    messagingSenderId: "..."
};

const firebaseApp = firebase.initializeApp(config);

export default firebaseApp.firestore();

Adding Stuff

In the implementation of ‘addRecord’, we accept parameters of the record to store and the database table name. (i.e. document collection) When the database add operation works, we call resolve with the id of the new document created. If an error happens, we share the error details with the caller using ‘reject.’

addRecord(recordObject: any, tableName: string) {
    return new Promise(function (resolve, reject) {            
        db.collection(tableName).add(recordObject).then(function (docRef) {
            console.log("Document written with ID: ", docRef.id);
            resolve(docRef.id);
        })
        .catch(function (error) {
            console.error("Error adding document: ", error);
            reject(error);
        });
    });
}

In a future blog post, we’ll explore some of the details for listing and removing data from Google FireStore.

We love to hear from our readers!  Feel free to leave a comment to ask questions or provide feedback.  All the best!

Be the first to comment

Leave a Reply

Your email address will not be published.


*