Vue.js Tutorial: Build a Tesla Battery Range Calculator in Vue 3
Get started building robust UIs with Vue.js (compatible with Vue 3)
Join the DZone community and get the full member experience.
Join For Free
This tutorial focuses entirely on the new driving experience. It is now clear that electric driving is the future. But how far can you actually ride on a full battery? And what influence does the speed, outside temperature, and size of the rims have on the range, for example? In this tutorial, we get started with Vue.js, an easy-to-understand JavaScript framework. We are going to make a dashboard with which we can calculate how much range the Tesla has under different circumstances.
As a starting point for the tutorial, clone this Github repository.
Then, move to the vuejs-app
directory.
cd workshop-reactjs-vuejs/vuejs-app
Read the README.md
for the tasks to be executed. The figure above is an example of the application that we are going to build. We initially start from a buggy application, which we must fix and further develop. Before we get started, I first explain how this application is structured.
Requirements
To start with this tutorial you must install the following:
Stable node version 8.9 or higher (https: // nodejs.org/en/download/).
Yarn (https://yarnpkg.com).
Project Structure
The project we are working on has the same structure as the aforementioned figure and shows the components that make up this application. The main.js
is the entry point of the application. App.vue
is the entry component of the application. The outer edge of the figure above shows the App.vue
component.
Project Entry Point Vue 2
An application in Vue2 is started in main.js
. In main.js
, you first create a new "root Vue instance." This goes as follows:
1. Import Vue: import Vue from "vue."
2. Import an entry component App.vue, through: import App from "./App. vue "
.
3. Create a "root Vue instance": new Vue ({....})
.
4. From this "root Vue instance," render the imported App.vue component (entry component).render: h => h (App)
.
5. Finally, this root Vue instance is mounted. This is the point where the application is started. This refers to an HTML element with the identification #app, which is defined in the template
in the App.vue
component. (See the code snippet in the section below.)
Listing 2a
Project Entry Point Vue 3
The way we bootstrap a new Vue3 app has changed. Rather than using new Vue(), we now need to import the new createApp() method.
We then call this method, passing the App.vue component (entry component).
Next, we'll call the mount method on createApp and pass a CSS selector indicating our mount element, just like we did with the $mount instance method in Vue 2.
Listing 2b
App.vue Component Vue 2
App.vue Component Vue 3
Listing 3b
This App.vue is the entry component of the application and consists of the following parts.
Script: this is the JavaScript part of this component. In this example, the name property
indicates the name of the component (the name is “app”). The child components that this component uses are defined in the components-property
. In this case, TeslaBattery is a child component of the App.vue component. To use the TeslaBattery component, it must first be imported ( import Tesla-Battery from "..."
).
In the data()-function
, you can define and initialize state variables, such as the imported logo and the greeting property. To render the logo and the greeting, they must be defined in the template. You must eventually export this entire component (via export default { }
), so that it can be imported again into other components and into main.js
.
Template: is responsible for defining the output that the component generates. Vue.js
uses an HTML-based template syntax. Data from the data ()-function
can be easily rendered through data binding. The simplest form of data binding is text interpolation using the Mustache syntax (double braces): {{greeting}}
In the example above,{{greeting}}
is replaced by the value Hello Tesla !!!
from the relevant data()-function
. Above this greeting, the logo is also rendered thanks to the img-tag
. To assign the logo to the img src-attribute
, use attribute binding. For this, you can use v-bind
,
<img :src="logo"
, or <img v-bind:src="logo">
. Attribute binding is often used in this application.
And finally, the TeslaBattery component is instantiated and rendered using the <tesla battery>-tag
. For this tag (also called "custom element"), you must use the Kebab case. How this component functions will be discussed later.
Style: in Vue, we use a SCSS file for styling the entire application.
Breaking Down the UI
Almost all Vue applications consist of a composition of components. This application consists of an entry App component with the TeslaBattery as a child component. And the TeslaBattery component contains the following child components:
TeslaCar: for rendering the TeslaCar image with wheel animation.
TeslaStats: for rendering the maximum battery range per Tesla model. This concerns the models: 60, 60D, 75, 75D, 90D and P100D.
TeslaCounter: for manually controlling the speed and the outside temperature.
TeslaClimate: this changes the heating to air conditioning when the outside temperature is more than 20 degrees.
TeslaWheels: for manually adjusting the wheel size from 19 inches to 20 inches and vice versa.
The user interface is represented by a component tree as follows.
The following code block shows that the "Tesla Battery component" is a Container component. The underlying child components are Presentation components. This is a useful pattern that can be used when developing a Vue application. Dividing components into two categories makes them more reusable.
Listing 4
Container components are characterized by the following:
They can contain both presentation and container components.
They take care of the creation and transfer of data to child components through "props."
They execute logic based on incoming events.
They are responsible for managing the state and know when a component must be rendered again.
They are often stateful because they tend to serve as data sources.
Presentation components are characterized by the following:
They are also called "dumb components." The focus is on the user interface. Almost all basic UI components must be regarded as dumb components. Examples of this are buttons, inputs, modals, etc.
The TeslaCar is also a dumb component, which ensures the rendering of the TeslaCar image.
They receive data via "props" and return data to parent components through an event.
They are often stateless and have no dependence on the rest of the application.
This approach has the following advantages:
Reusability.
Dumb components are easier to test because they only receive "props," emit events, and return a piece of UI.
Higher readability: the less code you have and the better it is organized, the easier it is to understand and adjust.
It offers consistency and prevents code duplication.
TeslaBattery service
The data we use is hard-coded and stored in tesla-battery.service.js
. This service has a getModel-Data()
method for retrieving the model data. View the structure of this model data in the code block below.
The maximum battery range per Tesla model is determined based on the following parameters:
Tesla model (60, 60D ...).
Wheel size (19/20 inch).
Climate (on / off).
Speed.
Temperature ( -10.0 ...).
TeslaBattery component
This component is responsible for defining, creating and passing data to child components through "props." It is also responsible for managing the state of the application.
When fully collapsed, we see that this component consists of the properties below.
The components property contains all child components that this component uses.
The computed property contains the functions that are cached. That is, such a function is only executed if it depends on a specific data property and when the state of this property changes. In the full version of the TeslaBattery component below, the stats()-function
is an example of a computed function.
This function filters the maximum battery range per Tesla model from the model data. The code block below is an example of the output of the stats()-function
. This maximum battery range is based on user input, such as the selected wheel size, climate, speed, and temperature. And this stats()-function
is only executed if this user input changes. The user input is recorded in the tesla object (state object), which is defined in the data()-function
.
Maximum battery range per model:
[
{"model":"60","miles":267},
{"model":"60D","miles":271},
{"model":"75","miles":323},
{"model":"75D","miles":332},
{"model":"90D","miles":365},
{"model":"P100D","miles":409}
]
The methods property contains all of the functions that are not cached. The changeClimate()
function is defined here because this function is triggered by an onClick-event
(and is not based on a data/state property).
The template of this TeslaBattery component has the same structure as Listing 4:
Passing Data to Child Components Through Props
In the following figure, stats-data (originating from the stats()-function) is passed from the TeslaBattery component to the TeslaStats component.
To pass data to a child component, you must use v-bind or : in the template of the TeslaBattery component.
<template>
<form>
<tesla-stats :stats="stats" />
</form>
</template>
Listing 9
Receive Data in the TeslaStats Component
This component contains a props-property in the scripts section for receiving the stats-data. These stats are of type Array. In the template we use a v-for directive from Vue.js, to iterate through the stats. The :key (in the v-for directive) indicates that this list must be rendered in a specific order.
You can define a custom filter in the filters-property. For example, the filter, "lowercase," with a pipe for rendering the model names in lowercase. There is also a custom filter defined for converting miles to kilometers.
Composition API in Vue 3
In Vue 3 we cannot use the filters-property any more. We can use the Compostion API to solve this.
The Compostion API is function-based APIs that allow flexible composition of component logic and reuse logic between components.
How do we use this Compostion API for a filter?
1. Create first a javascript file, referred to as a composable, which exports the data and methods you want your components to have access to. For this filter we create a javascript file = composable:
filters,js
2. Import the composable into a component.
3. And add a setup() method, in which you can import the data and methods exported by the composable. And finally use the filter in your template.
2-way data-binding in Vue 3
2-way data binding is possible with the v-model directive in your template.
We use this directive in the TeslaCounter component to pass the current speed and return the new selected speed via an emit.
Here we use the v-model directive in the template of the TeslaBattery Component to pass the speed to the TeslaCounter component.
To make this all work, the receiving component (the TeslaCounter component) has to accept a modelValue property in the props.
And second has to emit an @update:modelValue - event for the new value (is the new selected speed).
The emit take place in the increment() method. This method is triggered via the @click="increment" event, defined in the template of the TeslaCounter component.
Conclusion
With this introduction, you can start solving bugs and carrying out assignments described in the README.md of this project. In this Github project, a powerpoint has also been added, which elaborates on issues such as two-way data binding through the v-model directive, assigning an onClick
event to a button with @click
and creating other components.
Related Articles
How and Why We Moved to Vue.js.
Opinions expressed by DZone contributors are their own.
Comments