Vue: Contact Detail

Last week’s post covered adding a Vue project to the ASP.NET Core Basics example solution. This post is going to cover adding a read-only view of a contact’s detail to being the Vue sample a step closer to being in line with the Angular, Aurelia and React samples. The code before any changes can be found here.

Contact Class

The Contact interface in the contactlist.ts file should be deleted. Add a contact.ts file to the ClientApp/components/contacts/ directory. This will hold the replacement for the interface we just deleted. The full class definition follows.

export class Contact {
    id: number;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;

    constructor(data?: any) {
        if (data == null) return;
        (<any>Object).assign(this, data);
    }

    getAddress(): string {
        return `${this.address} ${this.city}, ${this.state} ${this.postalCode}`;
    }

}

This class is very simple and will be used later to show how a function call can be used as part of a view.

Contact Service

Since the application will now have two places that need to access the ASP.NET Core API lets to refactor the API access into a service. This is the same style used by the Aurelia, Angular and React applications. Add a contactService.ts file to the contacts directory. The service will provide functions to get all contacts or a single contact an ID. The following is the full class.

import { Contact } from './contact';

export class ContactService {
    private baseUrl = 'http://localhost:13322/api/contactsApi/';

    getAll(): Promise<Contact[]> {
        return fetch(this.baseUrl)
            .then(response => response.json() as Promise<Contact[]>)
            .then(contacts => Array.from(contacts, c => new Contact(c)));
    }

    getById(id: string): Promise<Contact> {
        return fetch(`${this.baseUrl}${id}`)
            .then(response => response.json())
            .then(contact => new Contact(contact));
    }
}

Now the API access in the ContactListComponent can be refactored to use the service. First, add imports for contact and service.

import { Contact } from './contact';
import { ContactService } from './contactService';

Next, replace the fetch call with a service call.

Before:
fetch('http://localhost:13322/api/contactsApi/')
    .then(response => response.json() as Promise<Contact[]>)

After:
let contactService = new ContactService();
contactService.getAll()

Contact Detail Component

Add a contactDetail.ts which will be the backing component for the contact detail view. The following is the full class. Pretty much all it is doing is using get contact service to get contact detail by ID when it is mounted.

import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import { Contact } from './contact';
import { ContactService } from './contactService';

@Component
export default class ContactDetailComponent extends Vue {
    contact: Contact = new Contact();
    @Prop() id: string;

    mounted() {
        let contactService = new ContactService();
        contactService.getById(this.id)
            .then(data => {
                this.contact = data;
            });
    }
}

Now add a contactDetail.vue.html file for the UI bits of the contact detail. This is basically just HTML with the same binding syntax as before. Notice that the binding of address is a function call. The other Vue things to note are that the details will only show if contact is truthy using the v-if.

<template>
    <div>
        <h1>Contact Details</h1>
        <hr />
        <div v-if="contact">
            <dl class="dl-horizontal">
                <dt>ID</dt>
                <dd>{{ contact.id }}</dd>
                <dt>Name</dt>
                <dd>{{ contact.name }}</dd>
                <dt>Address</dt>
                <dd>{{ contact.getAddress() }}</dd>
                <dt>Phone</dt>
                <dd>{{ contact.phone }}</dd>
                <dt>Email</dt>
                <dd>{{ contact.email }}</dd>
            </dl>
        </div>
        <router-link to="/contactlist">Back to List</router-link>
        <hr />
    </div>
</template>

<script src="./contactDetail.ts"></script>

Routing with a Parameter

Finding the proper way to route with at parameter in Vue was not easy. The way it is done has evolved over time making find the right way a little challenging. The first step is to add a route for the contact detail by adding a new item to the routes array in the boot.ts file.

{ path: '/contactdetail/:id', component: require('./components/contacts/contactDetail.vue.html'), props: true }

The :id in the path denotes a parameter. The props: true bit is also key to getting the ID when in the contact detail component. The contactlist.vue.html needs to be updated to link to the contact detail route.

Before:
<td>{{ contact.id }}</td>

After:
<td>
  <router-link :to="'/contactdetail/' +  contact.id">{{ contact.id }}
  </router-link>
</td>

Finally, in the ContactDetailComponent we need to use the ID. To do so we need an import to allow the use of the Prop decorator.

import { Component, Prop } from 'vue-property-decorator';

Now all that is needed is to apply the decorator to a property of the class that matches the name of the parameter in the route.

@Prop() id: string;

Now the ID can be used to get the proper contact from the contact service. The following is code that was already shown, but the full context of how the prop decorator is used is important so I am repeating it here.

@Component
export default class ContactDetailComponent extends Vue {
    contact: Contact = new Contact();
    @Prop() id: string;

    mounted() {
        let contactService = new ContactService();
        contactService.getById(this.id)
            .then(data => {
                this.contact = data;
            });
    }
}

Wrapping Up

This brings the projects one step closer to being on the same level feature-wise. Look for one more post to get the Vue project features lined up with the other samples in the solution.

Keep in mind that this is my first look at Vue and my examples may or may not be idiomatic.

The code in a finished state can be found here.


Also published on Medium.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.