Vue.js is simple. It is so simple that people often dismiss it as only suitable for small projects. While it is true the Vue.js core is just a view layer library, there are in fact a collection of tools that will enable you to build full-blown, large-scale SPAs (Single Page Applications) using Vue.js with a pleasant development experience.

If you are already familiar with the basics of Vue.js but feel that the world of SPA is scary, this series is for you. We will first introduce the concepts, tools and libraries needed in this first article, and then will walk you through the full process of building an example app in the rest of the series.

First Thing’s First: What is a SPA?

Single-Page Applications (SPAs) are web apps that load a single HTML page and dynamically update that page as the user interacts with the app. SPAs use AJAX and HTML5 to create fluid and responsive Web apps, without constant page reloads.

As stated in the above description taken from Wikipedia, the main advantage of SPAs is that the app can respond to user interactions without fully reloading the page, resulting in a much more fluid user experience.

As a nice side effect, a SPA also encourages the backend to focus on exposing data endpoints, which makes the overall architecture more decoupled and potentially reusable for other types of clients.

From the developer’s perspective, the main difference between SPAs and a traditional backend-rendered app is that we have to treat the client side as an application with its own structure. Typically we will need to handle routing, data fetching and persistence, view rendering and the necessary build setup to facilitate a modularized codebase.

The Building Blocks

For a Vue.js-based SPA, here are the tools and libraries that we will use to fill in these gaps:

  • View Layer: Vue.js, of course 馃檪
  • Routing: vue-router, the official router for Vue.js.
  • State Management: vuex, a state-management solution inspired by Flux/Redux, but designed specifically for Vue.
  • Server Communication: vue-resource, plugin for interfacing with a RESTful backend.
  • Build Tool: Webpack + vue-loader for modules, hot-reloading, ES2015, pre-processors and most importantly, single-file Vue components.

Let’s take a closer look at each part.

THE VIEW LAYER

This series assumes you are already familiar with the basics of Vue.js. If you are not, you should be able to quickly pick it up by going through official guide.

The core concept when using Vue.js for large SPAs is dividing your application into many nested, self-contained components. We also want to carefully design how these components interact with one another by leveraging component props for the data flow and custom events for communication. By doing so, we dissect the complexity into small, decoupled units that are tremendously easier to maintain.

ROUTING

The official vue-router library handles client side routing, and supports both hash mode and HTML5 history mode. It is a bit different from standalone routing libraries in that it deeply integrates with Vue.js and makes the assumption that we are mapping nested routes to nested Vue components.

When using vue-router, we implement components that serve as “pages“, and within these components we can implement hook functions that are called when the route changes.

STATE MANAGEMENT

State management is a topic that only arises when your application’s complexity grows beyond a certain level. When you have multiple components that need to share and mutate application state, it can get very hard to reason about and maintain if you don’t have a layer in your application that is dedicated to managing such shared state.

This is where Vuex comes in. You don’t necessarily need Vuex if you application is relatively simple – but if you are interested, here’s an excellent intro on what problem it solves by Anirudh Sanjeev.

SERVER COMMUNICATION

We will be working with a RESTful backend in the example, so we are using the vue-resource plugin which is maintained by the PageKit team. Do note that Vue.js SPAs are backend-agnostic and can basically work with any data fetching solution you prefer, for example fetch, restful.js, Firebase or even Falcor.

BUILD TOOL

This is probably the biggest hurdle that you’ll have to jump through if you are not familiar with the frontend build tool scene, and we will try to explain it here. Feel free to skip this section if you are already experienced with Webpack.

First, the entire build tool chain relies on Node.js, and we will be managing all our library and tool dependencies using NPM. Although NPM started out as the package manager for Node.js backend modules, it is now widely used for frontend package management too. Because all NPM packages are authored using the CommonJS module format, we need special tooling to “bundle” these modules into files that are suitable for final deployment. Webpack is exactly such a tool, and you may have also heard of a similar tool called Browserify.

We will be using Webpack for the series because it provides more advanced functionalities out of the box, such as hot-reloading, bundle-splitting and static asset handling.

Both Webpack and Browserify exposes APIs that allows us to load more than just CommonJS modules: for example, we can directly require() an HTML file by transforming it into a JavaScript string.

By treating everything for your frontend including HTML, CSS and even image files as module dependencies that can be arbitrarily transformed during the bundling process, Webpack actually covers most of the build tasks that you will encounter when building a SPA. We are primarily going to build the example using Webpack and plain NPM scripts, without the need for a task runner like Gulp or Grunt.

We will also be using vue-loader which enables us to author Vue components in a single file:

// app.vue
<template>
  <h1 class="red">{{msg}}</h1>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>
.red {
  color: #f00;
}
</style>

In addition, the combination of Webpack and vue-loader gives us:

  1. ES2015 by default. This allows us to use future JavaScript syntax today, which results in more expressive and concise code.
  2. Embedded pre-processors. You can use your pre-processors of choice inside single-file Vue components, for example using Jade for the template and SASS for the styles.
  3. CSS output inside Vue components are autoprefixed. You can also use any PostCSS plugins you like.
  4. Scoped CSS. By adding a scoped attribute to the <style>, vue-loader will simulate scoped CSS by rewriting the template and style output so that the CSS for a specific component will not affect other parts of your app.
  5. Hot Reload. When editing a Vue component during development, the component will be “hot swapped” into the running app, maintaining the app state without having the reload the page. This greatly improves the development experience.

Setting it Up

Now with all these fancy features, it could be a really daunting task to assemble the build stack yourself! Luckily, Vue provides vue-cli, a command-line interface that makes it trivially easy to get started:

npm install -g vue-cli
vue init webpack my-project

Answer the prompts, and the CLI will scaffold a project with all the aforementioned features working out of the box. All you need to do next is:

cd my-project
npm install # install dependencies
npm run dev # start dev server at http://localhost:8080

For full details on what is included in the generated project, check out the project template documentation.

One of my favorite things about Vue.js is how approachable it is. We can simply drop the library into an existing project, create a Vue instance with an element or ID of our choosing as a selector, and we’re all set to add reactivity to the page. This simplicity is great and comes in handy if we just want to use a few of Vue’s features, but there’s actually a lot more we can do with the library that some people may not be aware of.

Surrounding the core Vue.js library is a rich ecosystem of tools and plugins that allow us to create full single page applications. Vue also offers full support for ES2015 and comes with its own file type: the .vue component, which is great because it allows us to have our template, scripts, and styles all in the same file. While some might say that this could be cumbersome and file sizes could get huge, I would argue that the number of clicks and amount of mental bandwidth (even if small) that we save by using this kind of format makes it quite valuable.

This is the second in a series of articles that will cover how to build a single page app with Vue.js.

We’re going to use several libraries and plugins from the Vue ecosystem to:

  • Create a Node.js backend so we can see how to work with data from a remote source
  • Implement single page app routing
  • Implement HTTP calls to our Node.js backend
  • Add unidirectional data flow
  • Use the aforementioned .vue file type to have our templates, scripts, and styles all in one spot

WHAT ARE WE GOING TO BUILD?

Heads up though: the app we’re going to build is going to be pretty simplisitic as far as tracking time goes. That’s because we want to focus more on how all the pieces of a Vue.js app fit together and less on making a real-world time tracker.

vuejs tutorial time-tracker

So let’s get to work–you’ll be logging your hours like a champ in no time.

Installation

The app we build will use Webpack for module bundling, preprocessing, and hot module reloading. If you’re not familiar with Webpack, it basically gives us a way to automatically string together and then serve our various bits of JavaScript from a single file. This is great for production because it means we don’t need to worry about making multiple HTTP requests for our various component files. But there’s more than that going on here: we need Webpack so that we can have our .vue files handled properly. Without a loader to convert the files to proper JavaScript, HTML, and CSS, the browser won’t be able to understand what’s going on. However, with the appropriate Webpack loaders in place, the .vue files get converted to something the browser can understand.

Hot module reloading is a powerful feature that gives us a lot of convenience, and it’s available to us out of the box with Vue. Normally when we refresh the page after making a change to our code, all of the application’s state is lost. This can be a pain when we’re working on a part of the app that requires us to be a few clicks deep or otherwise have some kind of state be active. With hot module reloading, the code that we change is swapped in automatically and our state is preserved.

Vue also makes CSS preprocessing available to us automatically, so we can choose to write LESS or SASS instead of plain CSS if we like.

It used to be the case that we had to install a bunch of dependencies from npm to get a proper Webpack setup, but thankfully we now have the option of using vue-cli. This is a great addition to the Vue ecosystem because it means we no longer need to scaffold our projects by hand, but rather can have them generated for us very quickly.

First, install vue-cli.

npm install -g vue-cli

Then create a new webpack project and install the dependencies for it.

vue init webpack vue-time-tracker
cd vue-time-tracker
npm install

The generated app comes with a command that lets us run the application with hot module reloading.

npm run dev

This command is an alias for node build/dev-server.js, and is found in package.json on the scripts object. The dev-server.js file that gets run is basically what makes Webpack do its thing. That is, it configures all of the Webpack pieces, parses and compiles all the project files, and serves everything up to be viewed in the browser. We can take a look at localhost:8080 to see the starter app.

vuejs tutorial time-tracker

Everything is scaffolded, but we’re actually going to need a couple more dependencies for our time tracker app. We need a way to set up routing and make XHR requests, and for that we can use some additional libraries from the Vue ecosystem.

npm install vue-resource vue-router --save

Initial Setup

Taking a look at the app file, we can see that the src directory root has an App.vue file and a main.js file. It’s the main.js file that is the starting point where things get kicked off. Inside that file, we’re importing both Vue and App, and then creating a new Vue instance on the body element. While this is fine if we don’t want to use any routing, we’re going to need to change things up a bit to support the setup we want.

// src/main.js

import Vue from 'vue'
import App from './App.vue'
import Hello from './components/Hello.vue'

import VueRouter from 'vue-router'
import VueResource from 'vue-resource'

// We want to apply VueResource and VueRouter
// to our Vue instance
Vue.use(VueResource)
Vue.use(VueRouter)

const router = new VueRouter()

// Pointing routes to the components they should use
router.map({
  '/hello': {
    component: Hello
  }
})

// Any invalid route will redirect to home
router.redirect({
  '*': '/hello'
})

router.start(App, '#app')

To support this new setup, we just need to wrap the <app></app> element in index.html within a div.

  <!-- index.html -->
  <div id="app">
    <app></app>
    <!-- built files will be auto injected -->
  </div>

Our application now supports routing, and if we refresh, we see that the default /hello route is reached which displays the same message as before.

The initial setup is done, now let’s get on to creating our own components!

Creating the Home View

Let’s kick things off here by applying a navigation bar to the app. But first, we’ll need to add Bootstrap for some styling. A quick way to do this is to grab the CSS from Bootstrap’s CDN.

  <head>
    <meta charset="utf-8">
    <title>Vue Time Tracker</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  </head>

The best place for the navbar is our App.vue file, so let’s edit it to have a new template. At the same time, we can get rid of the script and stylesections that are there right now.

// src/App.vue

<template>
  <div id="wrapper">
    <nav class="navbar navbar-default">
      <div class="container">
        <a class="navbar-brand" href="#">
          <i class="glyphicon glyphicon-time"></i>
          Vue Time Tracker
        </a>
        <ul class="nav navbar-nav">
          <li><a v-link="'/home'">Home</a></li>
          <li><a v-link="'/time-entries'">Time Entries</a></li>
        </ul>
      </div>
    </nav>
    <div class="container">
      <div class="col-sm-3">

      </div>
      <div class="col-sm-9">
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

In addition to the navigation bar, we’ve also got a container for the rest of the application. There’s a smaller div that will be used for our sidebar, and a larger one that will show other components through the router-viewtag. Just in the same way that other frameworks, such as AngularJS, will display partial content through a special tag, vue-router does this as well through router-view.

The Home component for our app really just needs to show a simple message. Let’s create a new file called Home.vue and give it a template with that message.

// src/components/Home.vue

<template>
  <div class="jumbotron">
    <h1>Vue Time Tracker</h1>
    <p>
      <strong>
        Get started by <a v-link="'/time-entries'">creating a time entry</a>.
      </strong>
    </p>  
  </div>
</template>

We also need to tell the router about this new component in App.vue by importing it and mapping it. This is an easy change because essentially all we need to do here is replace Hello with Home and we’re good to go.

vuejs tutorial time-tracker

So what’s going on with that blank space in the App component? Well, we’ll later have a sidebar going in there which will display the value for the sum of all of our time entries, but first we need to create some. For that, we’ll need to get started on components which let users log and display individual entries.

Creating the Time Entries Component

Here’s the plan: we’ll make a component that lists out existing time entries, called TimeEntries, and then another one for entering new time entries, called LogTime. We’ll add a link from TimeEntries to LogTime so the user can jump to the spot for adding new time entries quickly. The TimeEntries component is going to be a little large, so let’s approach the template, script, and style sections separately. Keep in mind that all of these parts will go in the same file in the end, but we’ll split them out here.

Note: The data that is used in this template hasn’t been set up yet, but we’ll do that afterward.

// src/components/TimeEntries.vue

<template>
  <div>
    <button
      v-if="$route.path !== '/time-entries/log-time'"
      v-link="'/time-entries/log-time'"
      class="btn btn-primary">
      Log Time
    </button>

    <div v-if="$route.path === '/time-entries/log-time'">
      <h3>Log Time</h3>     
    </div>

    <hr>

    <router-view></router-view>

    <div class="time-entries">
      <p v-if="!timeEntries.length"><strong>No time entries yet</strong></p>

      <div class="list-group">

        <a class="list-group-item" v-for="timeEntry in timeEntries">
          <div class="row">

            <div class="col-sm-2 user-details">
              <img :src="timeEntry.user.image" class="avatar img-circle img-responsive" />
              <p class="text-center">
                <strong>
                  {{ timeEntry.user.firstName }} 
                  {{ timeEntry.user.lastName }}
                </strong>
              </p>
            </div>

            <div class="col-sm-2 text-center time-block">
              <h3 class="list-group-item-text total-time">
                <i class="glyphicon glyphicon-time"></i> 
                {{ timeEntry.totalTime }}
              </h3>
              <p class="label label-primary text-center">
                <i class="glyphicon glyphicon-calendar"></i> 
                {{ timeEntry.date }}
              </p>
            </div>

            <div class="col-sm-7 comment-section">
              <p>{{ timeEntry.comment }}</p>
            </div>

            <div class="col-sm-1">
              <button 
                class="btn btn-xs btn-danger delete-button"
                @click="deleteTimeEntry(timeEntry)">
                X
              </button>
            </div>

          </div>        
        </a>

      </div>
    </div>    
  </div>
</template>

Let’s take a look at the Vue.js-specific items in the template to get a feel for what’s going on. The first one we encounter is a v-if on both the Log Time button and an h3 that says “Log Time”. The v-if is checking where we are in our routing, and we’re saying that we don’t want the button to be displayed if the route we’re currently at is /time-entries/log-time.

We’re going to create the LogTime component later and we’ll see how this all fits together at that point, but essentially once we’re at this route, we don’t want to show the button for navigating to it. The same goes for the “Log Time” h3, but in reverse. We’ve also got a v-link in the button at the top, which we’ve actually seen previously. This directive is from vue-routerand will instruct the router to go to the /time-entries/log-time route when clicked.

The next thing we see is another router-view below the hr tag, and this is because we’ll be registering a sub-route for logging time entries. Essentially we’re nesting one route within another, so the LogTimecomponent will be two levels deep. This is the cool thing about routing–we can just keep putting router-view elements in our templates, and as long as we register a component for them, they will keep nesting further down.

The first p tag we see has a v-if condition on it which checks for whether an array called timeEntries has anything in it. If it’s empty, a message indicating so will be displayed. Next we get to our first repeater. The form for Vue.js repeaters is v-for="alias in collection", which is very similar to the pattern we see in other frameworks.

If you’re unfamiliar with repeaters, what this does is loops over an array and outputs some HTML for each item in the array, starting with the HTML element that the repeat statement is placed on, up to the element that closes it.

We’re using a new syntax shorthand in the img tag for binding the user’s profile picture to the src attribute. Using the colon is the same as doing v-bind and saves us a few keystrokes. By binding the image URL to the src attribute like we are, we don’t need to template it out with the curly braces. Rather, the binding gets evaluated and resolves to the URL provided in our data (which we’ll see below).

We’re templating out the information for the time entry throughout the rest of the template, and near the end we have a button that uses another of Vue’s shorthands–@click. This binds a click event to the button element which calls the deleteTimeEntry method (which we’ve yet to create), passing in the currently iterated-over time entry.

Next, let’s put in the script.

// src/components/TimeEntries.vue

<script>
  export default {
    data () {
      // We want to start with an existing time entry
      let existingEntry = {
        user: {
          firstName: 'Ryan',
          lastName: 'Chenkie',
          email: 'ryanchenkie@gmail.com',
          image: 'https://1.gravatar.com/avatar/7f4ec37467f2f7db6fffc7b4d2cc8dc2?s=250'
        },
        comment: 'First time entry',
        totalTime: 1.5,
        date: '2016-04-08'
      }
      return {
        // Start out with the existing entry
        // by placing it in the array
        timeEntries: [existingEntry]
      }
    },
    methods: {
      deleteTimeEntry (timeEntry) {
        // Get the index of the clicked time entry and splice it out
        let index = this.timeEntries.indexOf(timeEntry)
        if (window.confirm('Are you sure you want to delete this time entry?')) {
          this.timeEntries.splice(index, 1)
          this.$dispatch('deleteTime', timeEntry)
        }
      }
    },
    events: {
      timeUpdate (timeEntry) {
        this.timeEntries.push(timeEntry)
        return true
      }
    }
  }
</script>

We’re starting out with one time entry that has a user object, as well as a comment, totalTime value, and date. The method for deleting time entries is fairly simple–we’re just looking for the index of the clicked time entry in the array and then splicing it out. Remember that we’re just working with local data in this part of the tutorial series, but in later installments we’ll see how to work with remote data that is persisted to a database. We’re also calling $dispatch as part of the delete method. We’ll see more on this later, but essentially what we want to do here is dispatch an event with the data that we deleted so we can listen for it in other components. This will come into play when we get to coding the sidebar.

Finally, we’ve got a timeUpdate method on the events key. This method is going to come into play when we make the LogTime component and is what will be emitted when the user saves a time entry. When that happens, a timeEntry object will come through and be pushed onto the timeEntries array in this component. This is how we will be facilitating communication between different components for now.

Let’s not forget some simple styles for this component.

// src/components/TimeEntries.vue

<style>
  .avatar {
    height: 75px;
    margin: 0 auto;
    margin-top: 10px;
    margin-bottom: 10px;
  }
  .user-details {
    background-color: #f5f5f5;
    border-right: 1px solid #ddd;
    margin: -10px 0;
  }
  .time-block {
    padding: 10px;
  }
  .comment-section {
    padding: 20px;
  }
</style>

We just need to add routing for this component to main.js and we’ll be able to see it in the browser.

// src/main.js

import TimeEntries from './components/TimeEntries.vue'

...

router.map({
  '/home': {
    component: Home
  },
  '/time-entries': {
    component: TimeEntries
  }
})

...

vuejs tutorial time-tracker

Creating the Log Time Component

Next we need a component that provides a screen for the user to log time entries. This one is a bit smaller, so let’s take a look at the whole file all at once.

// src/components/LogTime.vue

<template>
  <div class="form-horizontal">
    <div class="form-group">
      <div class="col-sm-6">
        <label>Date</label>
        <input 
          type="date"
          class="form-control"
          v-model="timeEntry.date"
          placeholder="Date"
        />
      </div>
      <div class="col-sm-6">
        <label>Hours</label>
        <input 
          type="number" 
          class="form-control"
          v-model="timeEntry.totalTime"
          placeholder="Hours"
        />
      </div>      
    </div>    
    <div class="form-group">
      <div class="col-sm-12">
        <label>Comment</label>
        <input 
          type="text" 
          class="form-control"
          v-model="timeEntry.comment"
          placeholder="Comment"
        />
      </div>        
    </div>    
    <button class="btn btn-primary" @click="save()">Save</button>
    <button v-link="'/time-entries'" class="btn btn-danger">Cancel</button>    
    <hr>
  </div>

</template>

<script>
  export default {
    data () {
      return {
        // We default the user object on
        // the timeEntry to have some user details
        timeEntry: {
          user: {
            firstName: 'Ryan',
            lastName: 'Chenkie',
            email: 'ryanchenkie@gmail.com',
            image: 'https://1.gravatar.com/avatar/7f4ec37467f2f7db6fffc7b4d2cc8dc2?s=250&d=retro&r=g'
          }
        }
      }
    },
    methods: {
      save () {
        let timeEntry = this.timeEntry
        // We dispatch the timeEntry so it can be pushed
        // onto the timeEntries array in the parent component
        this.$dispatch('timeUpdate', timeEntry)
        this.timeEntry = {}
      }
    }
  }
</script>

We’ve got input elements in place for the user to enter a date, number of hours, and a comment for a new time entry. In the data() function, we’re initializing the timeEntry model with some data for the user so that we can have a name and profile photo. There’s an obvious limitation with this simple app: it’s only really good for one user. That’s by design at this stage though, and we’ll adapt the application to multiple users with login in a later installment.

We’ve got a single method in this component called save which takes the object that has the user’s input and dispatches it out of the component with $dispatch. Now in our other components we can listen for the dispatched timeUpdate event and grab the timeEntry object that comes through with it. So why are we doing this? So that we can have some method of communicating data between our components. Since data for a given component is scoped to it, we need some mechanism to communicate from that component to the outside world. This method works well enough, but can get messy when our app grows and needs to communicate a lot of data between many components. We’ll see how to fix this by using Vuex for state management later on in the series. We’ve already got our event listener set up on the TimeEntriescomponent, so when we save a new entry, we can see it get added to the list.

We need to add the LogTime component as a sub-route of TimeEntries in our router configuration. By doing this, the router will know that LogTime is a child of TimeEntries, and the appropriate URI structure will be generated when we navigate to it.

// src/main.js

import LogTime from './components/LogTime.vue'

...

router.map({
  '/home': {
    component: Home
  },
  '/time-entries': {
    component: TimeEntries,
    subRoutes: {
      '/log-time': {
        component: LogTime
      }
    }
  }
})

...

vuejs tutorial time-tracker

Creating the Sidebar Component

We’re almost done for the first part of this series. The only thing left to do now is to add a Sidebar component which will hold the total number of hours for all of our time entries. Before we code the actual component, we need to make some adjustments to App.vue so that we can handle the data that is dispatched from the LogTime component when new entries are saved, and from the TimeEntries component when they are deleted.

First, place the sidebar element into the template.

// src/App.vue

  ...

  <div class="container">
    <div class="col-sm-3">
      <sidebar :time="totalTime"></sidebar>      
    </div>
    <div class="col-sm-9">
      <router-view></router-view>
    </div>
  </div>

  ...

We’ve got a property binding on sidebar called time that points to totalTime. By binding the property in this way, we’ll be able to pick it up in the actual Sidebar component as a prop. If you’ve used React at all, passing properties into elements in this way will no doubt feel familiar.

We need some logic to calculate the values to pass down in the timeproperty.

<script>
  import Sidebar from './components/Sidebar.vue'

  export default {
    components: { 'sidebar': Sidebar },
    data () {
      return {
        // Start with the same value as our
        // first time entry. Hard-coded for now
        // because we'll use a different approach
        // in the next article anyway
        totalTime: 1.5
      }
    },
    events: {
      // Increment the totalTime value based on the new
      // time entry that is dispatched up
      timeUpdate (timeEntry) {
        this.totalTime += parseFloat(timeEntry.totalTime)
      },
      // Decrement totalTime when a time entry is deleted
      deleteTime (timeEntry) {
        this.totalTime -= parseFloat(timeEntry.totalTime)
      }
    }
  }
</script>

First we import the Sidebar component (which we’ll create next) and then we initialize the totalTime property with the value from our hard-coded time entry. This obviously isn’t a very robust way of picking up that initial value, but we’ll just leave it like this for now because things will change when we modify the app to consume remote data in the next article anyway. We’re listening for two events: timeUpdate and deleteTime. Then we’re simply incrementing and decrementing totalTime based on the values that come through from the data in those events.

With that in place, we can create the actual Sidebar.vue component.

<template>
  <div class="panel panel-default">
    <div class="panel-heading">
      <h1 class="text-center">Total Time</h1>   
    </div>

    <div class="panel-body">
      <h1 class="text-center">{{ time }} hours</h1>
    </div>

  </div>
</template>

<script>
  export default {
    props: ['time']
  }
</script>

The props array is where we can specify any properties that we want to use which are passed into the component, and here we are getting the time prop which is placed on the sidebar element in App.vue. The template simply prints out the total number of hours.

vuejs tutorial time-tracker

Anuncios

Write your comment