Vue.js + Typescript best practices

Vue.js + Typescript best practices

  • 339

Vue.js + Typescript best practices. Ok, it is actually not only about Vue because you can actually use this architecture in angular or react also, but I will show an example in Vue.js just because Vue is the best.

Ok, it is actually not only about Vue because you can actually use this architecture in angular or react also, but I will show an example in Vue.js just because Vue is the best.

What is Class-based fetching

Before I will start explanation I would like to introduce you to a problem. Let’s assume, we have a model User**,** which is a simple example, but with more complex example it’s getting more sense. Our User.ts looks like this:

export interface IUser {
  id?: number;
  email: string;
  first_name: string;
  last_name: string;
  avatar: string;
}

and now we want to create a list of users so we need to fetch our Users. I’m using simple and free API that I found for this article and I’m using getter that returns this interface:

interface RequestInterface {
  page: number,
  per_page: number,
  total: number,
  total_pages: number,
  data: IUser[]
}
/// Endpoint response interface

Next, I’m creating a UsersApi service to easily manage my endpoints:

export abstract class UsersApi {
  private static usersAxios = Axios.create();

  static async getAllUsers(): Promise<IUser[]>{
    let url = 'https://reqres.in/api/users'
    let response = await this.usersAxios.get<RequestInterface>(url);
    return response.data.data;
  }
}
/// Api service

Ok, so the last thing for me to do is to use this service so inside my component in mounted function (it is crucial to use it in mounted because this hook can be async!):

export default class HomeComponent extends Vue {
  private users: IUser[] = [];

  async mounted(): Promise<void> {
    this.users = await UsersApi.getAllUsers();
  }
};
/// HomeComponent.vue

Ok so now I want to create my list by displaying first_name and last_name, so we have 3 options:

Option 1 — the bad one

The easiest way and the first thing that comes to mind is to display it in the simplest way possible just write the logic in HTML:

<v-list-item :key="index" v-for="(user, index) in users">
  {{user.first_name}} {{user.last_name}}
</v-list-item>

And It is ok. It will do the job, but like I said in the beginning, imagine that your model is more complex. This method has lots of disadvantages.

The first one and in my opinion the biggest is that in HTML you are forced to use simple Javascript instead of typescript, so it is simple to do a typo.

The second huge problem is let’s say you want to use this string of firstName and lastName in lot’s of places, you will need to write this code
{{user.first_name}} {{user.last_name}} every time you want to display a user’s full name.

So it lacks types and reusability.

Option 2 — the ok one

You can create a method in your component that returns the full name of a user:

getUserFullName(user: User): string{
  return `${user.first_name} ${user.last_name}`;
}
/// HomeComponent.vue - typescript

and use it in HTML like this:

<v-list-item :key="index" v-for="(user, index) in users">
  {{getUserFullName(user)}}
</v-list-item>
/// HomeComponent.vue - html

And this is ok because I’ve managed to eliminate the first problem by moving logic to Typescript instead of writing it in simple Javascript inside HTML.

Wait, but what about reusability? Hmm… It’s better, for sure, but what about displaying user full name in other components? I will need to create the same function over and over again… ugh…

So this solution is ok, but we can make it better!

Option 3 — the best one

I believe that sometimes to create an intuitive mechanism, implementation of it can be complex but it will pay off in the long run.

So the simplest way to use it would be just writing a getter inside a User object, that would allow us to use it like this:

<v-list-item :key="index" v-for="(user, index) in users">
  {{user.fullName}}
</v-list-item>
/// HomeComponent.vue - html

To do this you will need a Class-based fetching data architecture that implements more Model-Driven Design to the frontend. The assumption is simple, UserApi fetch function returns not an Interface but a Class, and that’s about it, but how to achieve it?

I like to have lots of interfaces and have an interface for every class. It may be unnecessary, but I will leave IUser interface alone and implement in User class, So my User.ts file looks like this:

export interface IUser {
  id?: number;
  email: string;
  first_name: string;
  last_name: string;
  avatar: string;
}

export class UserDTO implements IUser{
  id?: number;
  avatar: string = '';
  email: string = '';
  first_name: string = '';
  last_name: string = '';

}

export default class User extends UserDTO {
  constructor(dto: UserDTO){
    super();
    Object.assign(this, dto);
  }

  get fullName(): string {
    return `${this.first_name} ${this.last_name}`;
  }
}
/// User.ts

as you can see I’ve created 2 classes:
1. UserDTO
2. User

The first one I’m using just to declare default values and represent a model that is returning in response from backend so this is Data Transfer Object.

And to parse Dto object to have all of the property set by UserDto I need to do one trick and it is this Object.assign(this, dto) line. I know it stinks but it is the best solution I found.

The second one represents a class that I want to have and use when referring to the User Model. This adds an additional layer of abstraction to our architecture, so It makes it easier to mock something here, but also allow us to add functions and getters or whatever we want to the model. And as you can see there is our fullName getter there.

But! And this is an important part, we need to add mapping to UserApi service because backend returns an array of UserDTO objects and we want an array of User objects. So this mapping is ultra simple and looks like this:

static async getAllUsers(): Promise<User[]>{
  let url = 'https://reqres.in/api/users'
  let response = await this.usersAxios.get<RequestInterface>(url);
  return response.data.data.map(userDto => new User(userDto));
}

So now getAllUsers() function returns Promise<User[]> instead of Promise<IUser[]>.

And that’s it now you can create new properties and new getters inside a class and have a better clearer look on your Model.

If you want to check full repo click here.