Refactoring VueJS with Typescript- Part 9


VueJS Typescript stutorial

Other articles in the series

  • Making our UI user friendly - Part 15
  • Deploying Phoenix VueJS application using Docker - Part 14
  • Fixing failing Elixir tests - Part 13
  • Adding new features to Order Management - Part 12
  • Add Order Management - Part 11
  • Refactoring and adding tests for Phoenix - Part 10
  • Refactoring VueJS with Typescript- Part 9
  • Add Customer Management - Part 8
  • Writing tests with Jest and Typescript - Part 7
  • Adding Vuex to Vue using Typescript - Part 6
  • Building our Homepage view component - Part 5
  • Add Multi-language support to Vue Typescript - Part 4
  • Generate Vue Typescript Application - Part 3
  • Setting up Models with Ecto and Adding Routing . Part 2
  • Setting up Your Phoenix Application - Part 1
  • Tutorial Series for building a VueJS (Typescript) and Phoenix(Elixir) Shop Management Application - Part 0

  • The backend related code for this post is available here.
    The front-end related code for this post is available at here.

    Refactoring Vue with Typescript

    In last post, we added customer management functionality. As you’ve already noticed, we just copied and pasted most of the code for customer management from product management functionality. In this post, we will use Typescript generics to reduce the code duplication. We also will introduce a uniform interface for different models.

    Introducing a Base Service

    Let’s start with introducing a base class for src/services. We will move all generic functions to baseService.ts.

    import axios, { AxiosPromise } from 'axios';
    
    export abstract class BaseService<T> {
    
      /*
       * endpoint is the actual endpoint like http://localhost:8080/api/v1
       * entity is the identifier to find the entity we target. For eg: http://localhost:8080/{endpoint}
       * requestPrefix is used when we send data for creation and updating.
       * like {${requestPrefix}: data}
       */
      private endpoint: string;
      private entity: string;
      private requestPrefix: string;
    
      constructor(endpoint: string, entity: string, requestPrefix: string) {
        this.endpoint = endpoint;
        this.entity = entity;
        this.requestPrefix = requestPrefix;
      }
    
      public getAllRequest(): AxiosPromise<{ 'data': T[] }> {
        const response = axios.get(`${this.endpoint}${this.entity}`);
        return response;
      }
    
      public create(data: T): AxiosPromise<{ 'data': T}> {
        const request: {[key:string]: T} = {};
        request[this.requestPrefix] = data;
        return axios.post(`${this.endpoint}${this.entity}`, request);
      }
    
      public update(identifier: number, data: T): AxiosPromise<{ 'data': T}> {
        const request: {[key:string]: T} = {};
        request[this.requestPrefix] = data;
        return axios.put(`${this.endpoint}${this.entity}/${identifier}`, request);
      }
    
      public get(identifier: number): AxiosPromise<{ 'data': T}> {
        return axios.get(`${this.endpoint}${this.entity}/${identifier}`);
      }
    
      public delete(identifier: number): AxiosPromise<any> {
        return axios.delete(`${this.endpoint}${this.entity}/${identifier}`);
      }
    
      public abstract getEmpty(): T;
    }

    As you have notice rather than having a function called updateProduct or updateCustomer, in our baseService, we have a function named update. Also the return type is now always AxiosPromise<{‘data’ :T}>, rather than AxiosPromise<{‘product’ :Product}> or AxiosPromise<{‘customer’ :Customer}>. This provides a uniform interface for all models.

    Refactoring Services to use Base Service

    Now let’s rewrite our productService and customerService extending baseService.

    // productService.ts
    import { Product, getEmptyProduct } from '@/types/types';
    import { BaseService } from './baseService';
    
    export class ProductService extends BaseService<Product> {
    
      // constructors should call parent class with super()
      constructor(endpoint: string, entity: string, requestPrefix: string) {
        super(endpoint, entity, requestPrefix);
      }
    
      public getEmpty(): Product {
        return getEmptyProduct();
      }
    }
    // customerService.ts
    import { Customer, getEmptyCustomer } from '@/types/types';
    import { BaseService } from './baseService';
    
    export class CustomerService extends BaseService<Customer> {
    
      // constructors should call parent class with super()
      constructor(endpoint: string, entity: string, requestPrefix: string) {
        super(endpoint, entity, requestPrefix);
      }
    
      public getEmpty(): Customer {
        return getEmptyCustomer();
      }
    }

    As you have noticed we have significantly removed duplicate code. Now simply replace all createCustomer or updateProduct function calls with just create or update function calls. This will change a few files. Since the changes are rather trivial, you can look it up in the bitbucket repo.

    There are also a few changes in the structure of data returned by our service. Previously our responses had the structure response.data.product or response.data.customer. Since we changed the return type in baseService, to AxiosPromise<{‘data’: T}>, now all responses have the same structure response.data.data.

    Some of you will notice, that we previously changed our backend server to provide product or customer rather than data, due to personal preferences :D. Now we change it back :).

    Now our ms_web/views/product_view.ex will be as follows

    defmodule MsWeb.ProductView do
      use MsWeb, :view
      alias MsWeb.ProductView
    
      def render("index.json", %{products: products}) do
        %{data: render_many(products, ProductView, "product.json")}
      end
    
      def render("show.json", %{product: product}) do
        %{data: render_one(product, ProductView, "product.json")}
      end
    
      def render("product.json", %{product: product}) do
        %{id: product.id,
          price: product.price,
          stock: product.stock,
          name: product.name,
          tax: product.tax}
      end
    end

    Similarly our ms_web/views/customer_view.ex will become

    defmodule MsWeb.CustomerView do
      use MsWeb, :view
      alias MsWeb.CustomerView
    
      def render("index.json", %{customers: customers}) do
        %{data: render_many(customers, CustomerView, "customer.json")}
      end
    
      def render("show.json", %{customer: customer}) do
        %{data: render_one(customer, CustomerView, "customer.json")}
      end
    
      def render("customer.json", %{customer: customer}) do
        %{id: customer.id,
          name: customer.name,
          phone: customer.phone,
          pincode: customer.pincode,
          details: customer.details}
      end
    end

    Now running our tests, all tests should pass. In next section, we will refactor our Phoenix codebase and fix tests.