Room with a Vue Part 2: Building an App with Vue.js

Room with a Vue Part 2: Building an App with Vue.js

Introduction

In Part 1 in this series, we learned about the basic concepts and features of the Vue.js library. Now it is time to put that knowledge to use and build an application.

Our goal is to build a simple library app for managing books and authors. We will also use the "official" extensions for Vue: Vuex for state-management and Vue Router for client-side routing.

While this project will necessarily be quite simplistic, we will also include a RESTful backend to persist and retrieve our data, as would be typical in real-world projects.

Our backend happens to be written in Grails, but you could substitute an app using your preferred framework if you wish, as long as the API endpoints match (we will see what the API looks like shortly). The backend application will be downloaded from Github, so no knowledge or experience with Grails is required to follow along with this article.

Requirements

You will need Java 7 or later in order to run the backend project (Grails does not need to be installed).

You should also have npm installed and available on your machine; if you don’t have npm installed, nvm is a useful utility to install and manage Node/npm versions.

The Webpack dev server is configured to reload the app whenever changes are made to the source files, so you can leave the app running while you work, and the changes will automatically be loaded in the browser. The only time you will need to restart the Vue app is when you install a new dependency (e.g., with npm install)

Port Conflict

If you’ve already started up the backend server (which runs on port 8080), the Vue app will probably run on port 8081. This is because the app is configured to default to port 8080, and then to increment the port number to avoid conflicts.

Let’s change this default to avoid this behavior and ensure a consistent port.

Stop the client app and edit the file config/index.js. Find the reference to port 8080 (usually around line 17) and change it to 3000.

The next component will be BookList, which does what you’d expect – it accepts a list of book objects and renders a <table> element using the <Book> component we just created to render out rows for each book.

Create a new file under src/components named BookList.vue, and add the following content:

client/src/components/BookList.vue

<template><tableclass="book-list"><thead><td>Title</td><td>Pages</td><td>Author</td></thead><tbody><!-- Iterate over 'books' prop, using the book.id as a "key", and rendering an instance of the Book component with the current book bound as a prop --><book v-for="book in books" :key="book.id" :book="book"/></tbody></table></template><script>
import Book from './Book.vue'
export default {
name: 'BookList',
props: ['books'],
//In addition to importing the Book component, we must declare which components
//we use in our templates within the `components` object:
components: {
//The "keys" in this object are arbitrary - you can use this to avoid name conflicts
//by "renaming" an imported component: { myBook: Book }
book: Book
}
}
</script><style scoped>
.book-list {
min-width: 1000px
}
</style>

Now we’ll replace the existing HelloWorld component with our own "home" page.

This component will eventually be responsible for retrieving data from our REST API, although we’ll be hardcoding some data initially.

Create a new file under src/components named Home.vue, and add the following content:

We need to edit one more file from the project template to use our new Home component. Edit the index.js file under src/router/, as shown below:

client/src/router/index.js

import Vue from 'vue'import Router from 'vue-router'import Home from '@/components/Home'//CHANGE: change this line to import our Home component
Vue.use(Router)exportdefaultnew Router({
routes:[{
path:'/',
name:'Home',//CHANGE: set the name to 'Home'
component: Home //CHANGE: set the component to 'Home'}]})

We’ll learn more about Vue Router later on in this article, but for now you can see that we’ve replaced references to the original HelloWorld component with our new Home component.

Getting Dynamic

At this point there’s not really any dynamic behavior in our app. We’ll be adding some more interesting features shortly, but first, let’s add a simple "hover" mouseover effect to the rows in our book list. We will use Vue’s shorthand syntax for adding event handlers to elements, which was discussed in the last article.

To recap, the shorthand uses the @ symbol together with the name of the event being targeted as an attribute. The value of the attribute can be a function call or even simple manipulation of the component’s data, as shown in the following example.

Connecting to the API

Now that we’ve gotten our basic table UI working with hardcoded data, it’s time to plug into our backend and retrieve books directly from our API.

Vue doesn’t provide a mechanism for making REST calls (although there are helpers that add this functionality), but we can use any library for this purpose; Vue doesn’t mind which one we choose.

In this app, we will use the fetch API to make our REST calls.

As is the case in many frontend frameworks, Vue provides lifecycle hooks, which allow the developer to specify behavior that should happen at certain points in the "life" of a component (or an app). One of these hooks is the created method. Any code in this method will be executed when the component is instantiated, which makes it an ideal place to make our REST call to obtain our list of books.

Deleting Books

Now it’s time to add some interactivity to our app. We want our users to be able to delete books from our list, as well as create new entries.

We could do this visually in the UI by simply removing or adding new rows to the table, but we’re going to take things a step further and make DELETE and POST requests to our API, so that our changes will be persisted to the database.

This interaction with the API will be done using methods on our component.

Methods are arbitrary functions that can be added to a Vue instance and executed (or passed around as references) from templates or from other methods. Methods have access to the instance’s data, as well.

Let’s add a removeBook method first, as it will be the simplest to implement in our UI.

At this point, if the app is running, you should see a simple 'X' button on each item in the list of books. Click that button, and the book will be removed from the table. Refresh the page to reload the data from the API – sure enough, the book is deleted!

(Note: you can restore the list of books by restarting the server app; the database will be recreated on each startup.)

Creating Books

We will create a simple form for creating new entries in our book table. Again, we will make use of our REST API by making a POST request to add the new book and then insert the new book in our list.

In addition, since books have a relationship with authors, we will also need to query our API for a list of authors, which can be selected in the form.

Forms are a common place where two-way binding is preferred by many developers, as it reduces the tediousness of explicitly binding event handlers and values to form elements.

Two-way binding allows the developer to specify the relationship between an element and a "model" object, allowing the framework to take care of "syncing" the state on both sides of the relationship. However, two-way binding has its downsides, as it reduces visibility and the developer’s control of state changes within the app.

Libraries like React have emphasized explicit one-way binding for its simplicity and predictability, and other frameworks have taken similar cues.

Vue makes it a point to support both binding approaches. We've already seen one-way binding (using directives like :book or @click), and now we'll make use of Vue's two-way binding with the v-model directive.

Create a new component named BookCreateForm under src/components and add the following content:

As noted above, the v-model directive will cause Vue to update the values in our state based on the inputs on the form. We then call our addBook function prop when the submit button is clicked, passing our book state as an argument.

Back in our Home component, we have a few things to change. Edit the file as shown below:

In the browser, you should now see the new form and table listing authors in the database.

You can add a new author via the form; notice that the new author is also added to the authors <select> element in the BookCreateForm.

Usability Improvements

We’ve now implemented the basic functionality of our app (creating/deleting books and authors).

There are still a number of improvements we can make, of course, and these will allow us to see a few more features of Vue in action.

Clear & Focus Forms

After adding a new book or author, the form inputs still contain the values they had when the form was submitted. In addition, the focus remains on the "save" button, rather than returning to the first input element as a user typically would expect.

Deleting an Author's books

Our backend server is configured to perform cascading deletes, meaning that if an author is deleted, all books "belonging" to that author are also deleted. However, currently our UI does not reflect the deleted books; until you refresh the page (and fresh data is retrieved from the API), the "orphaned" books remain in the list.

Managing State with Vuex

As an app grows in complexity, managing its state can become a time-consuming and confusing task. Unless a consistent strategy is followed, code for setting and modifying state can become spread across the codebase, making it difficult to trace bugs and leading to duplication of code.

State-management libraries can help mitigate these problems, and the Vue team has provided an official solution in Vuex.

Here’s how we described Vuex in the previous article in this series:

Like Redux, Vuex provides a single store for all of your app state. You define mutations, which are functions that transform the state based on some input. These mutations are called by actions, which are functions that can be dispatched from your components in order to trigger state changes on their behalf.

Installing Vuex

Install Vuex in your project by running the following command within the project directory (client):

npm install vuex

This will add the dependency for Vuex to the package.json file. It should look something like this:

Go ahead and try out the app in the browser; it should behave just as it did before, as all we’ve done is move the state of our Home component into the Vuex store and add the logic to change that state into mutations.

At this point, it may not seem we’ve gained much readability with our refactored code, but that will change once we complete our Vuex migration.

Actions

In Vuex, similar to Flux/Redux, actions are methods that coordinate the committing of mutations to the store. Unlike mutations, which simply accept incoming data and update the store, actions are able to perform asynchronous operations (such as Ajax calls), dispatch other actions, and potentially call (commit) multiple mutations.

Use of actions is completely optional; as we saw in the previous code change, components can commit mutations directly without using actions. However, actions provide a sensible organization for arbitrary logic that is not directly concerned with updates to the store.

Routing

Our final step in this article will be to split our book and author components into separate routes using Vue Router.

We’ve already seen Vue Router’s config file, but we haven’t taken advantage of any of its features. Before we can do that, we need to separate our book and author components with their own "view" components, so that each can be rendered independently.

Create a new file named BookView.vue under components/book and edit it as shown below:

Summary

This concludes our tutorial on building an app with Vue.js.

You’ve been able to use a wide range of features, not only of Vue itself, but Vuex and Vue Router.

Of course this is a simplistic example, and we’ve only scratched the surface of what can be done with these libraries, but by this point, you should feel confident enough to continue to experiment and learn on your own or follow along with the many resources available online.

In the meantime, keep learning and have fun coding!

Part 3 in this series will compare Vue with its primary competitors, React and Angular, and provide some perspective on what the future holds for Vue and its ecosystem.