Basic Vue JS tutorial for beginners

So nowadays there are three main front end frameworks: Vue js, React js and Angular. Among them Vue js is the most easy to learn. This tutorial assumes intermediate experience with HTML, CSS and JavaScript.

We will dive right in by building a component that fetches a NASA API and displays some if its data.

So to start head over to JetBrains and download the free trial of Webstorm. Webstorm is an IDE that will let you work easier with Vue js.Of course you can use any other IDE for this tutorial.

Now you need to install Node JS and NPM. Here is the guide for installation. I will provide guidance for Linux install as my machine runs Ubuntu (I’m an Ubuntu fan).

For Ubuntu you can run these commands:

curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt install nodejs

For the purpose of this tutorial we need to install Vue CLI. And to install it globally you can run this command:

npm install -g @vue/cli

Next you need to create a Vue app. This can be achieved with the next command. An option prompt will appear. Press enter to continue with default options.

vue create asteroids

Now head over to the newly created asteroids folder and run the development server:

npm run serve

The last command will start a development server on http://localhost:808*/. You can access this link to see the default Vue js app together with its default component.

This tutorial is using Bootstrap for more pleasing design and Axios package for NASA API fetching. To add them cd to the asteroids folder and run the following commands:

npm install --save bootstrap jquery popper.js
npm install --save axios

Now we are finally ready to start. Head over to /src/components and delete HelloWorld.vue. This is the default component and we don’t need it. Head over to /src/App.vue and delete the following line:

import HelloWorld from './components/HelloWorld.vue'

In /src/components create a new file called AsteroidGrid.vue. This will be our component. Webstorm is smart enough to create a basic skeleton of this component with the following structure:

<template>

</template>

<script>
export default {
name: "AsteroidGrid"
}
</script>

<style scoped>

</style>

Add also the empty <p></p> tags inside the template tags above. This will avoid a eslint error like “The template root requires exactly one element”. Now in App.vue we have:

<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

Replace this with:

<template>
  <div id="app">
    <AsteroidGrid header="Near-Earth Objects" />
  </div>
</template>

This will display the new component that we created. We also need to import it and add it to components. So in App.vue replace the following code snippet between the script tags:

<script>
  import AsteroidGrid from "./components/AsteroidGrid";

  export default {
    name: 'App',
    components: {
      AsteroidGrid
    }
}
</script>

We’re done with the basic skeleton of the component. If we access the development server (http://localhost:808*/) we will see a blank screen. This happens because the template is empty. Let’s fix this. In AsteroidGrid.vue add the following:

<template>
<div class="card mt-5">
<h2 class="card-header">
{{header}}
</h2>
<table class="table table-stripped">
<thead class="thead-light">
<th>#</th>
<th>Name</th>
<th>Close Approach Date</th>
<th>Miss Distance</th>
<th>Remove</th>
</thead>
<tbody is="transition-group" name="neo-list">
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td><button class="btn btn-warning">Remove</button></td>
</tr>
</tbody>
</table>
</div>
</template>

<script>
export default {
props: ['header'],
name: "AsteroidGrid"
}
</script>

This adds a default template and also makes available the header property. Remember when you created:

<AsteroidGrid header="Near-Earth Objects" />

You can reuse this component with different headers. But no sight of Bootstrap yet. In /src/main.js add the following at the beginning of the file:

import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'

The file should now look like:

import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
render: h => h(App),
}).$mount('#app')

Now if you access the development server you will see something that looks better. Lets add some asteroids information to it. First create an account at NASA API and retrieve API credentials. Store them in a safe place. After that in add App.vue replace the content inside <script> with the following:

<script>
  import AsteroidGrid from "./components/AsteroidGrid";
  import axios from 'axios'

  export default {
    name: 'App',
    components: {
      AsteroidGrid
    },
    data() {
      return {
        asteroids: []
      }
    },
    created() {
      this.fetchAsteroids();
    },
    methods: {
      fetchAsteroids: function () {
        var apiKey = '*************************************';
        var url = 'https://api.nasa.gov/neo/rest/v1/neo/browse?api_key=' + apiKey;
        axios.get(url)
                .then(response => {
                  this.asteroids = response.data.near_earth_objects;
                })
      }
    }
  }
</script>

Let’s brake down what changed. First we are importing the axios library. Next we are adding a default property to data called asteroids which is an empty array. Next in created method we are calling the next method: fetchAsteroids. fetchAsteroids uses axios library to perform a GET on the NASA API url and populate the previously defined default property asteroids with near earth objects. But if we look at the development server we will notice nothing has changed. We need some extra work to pass the asteroids property to the AsteroidGrid component and to display the data. So head over to App.vue and replace what is inside <template> tags with:

<template>
<div id="app">
<AsteroidGrid :asteroids="asteroids" header="Near-Earth Objects" />
</div>
</template>

Now go to AsteroidGrid.vue and replace what is inside <script> tags with:

<script>
export default {
props: ['asteroids', 'header'],
name: "AsteroidGrid"
}
</script>

We can notice that another property appeared which is called asteroids. This is the property that we previously populated and we need to pass it to the view. Now replace the <tr> tags inside <template> of AsteroidGrid.vue with:

<tr v-for="(asteroid, index) in asteroids" :key="asteroid.neo_reference_id">
<td>{{index + 1}}</td>
<td>{{asteroid.name}}</td>
<td>close approach date</td>
<td>
<ul v-if="asteroid.close_approach_data.length > 0">
<li v-for="(value, key) in asteroid.close_approach_data[0].miss_distance" :key="value.id">
{{key}}: {{value}}
</li>
</ul>
</td>
<td><button class="btn btn-warning">Remove</button></td>
</tr>

Let’s see what changed. We can notice the v-for directive used to render a list of items based on an array: v-for=”(asteroid, index) in asteroids”. We also added a key for the array: :key=”asteroid.neo_reference_id”. Next we have the v-if directive together with another v-for directive. The v-if is used to conditionally render a block. In our case only if the property close_approach_data has values. And in the last v-for we are displaying the first object of close_approach_data array. Head over to the development server to see the new code in action.

Let’s do something more interesting. How about displaying a summary with a count of all items, which asteroid has the shortest miss distance and a show/hide link? This can be achieved very easy using computed properties. To get stared add the following code in the template of AsteroidGrid.vue right after </h2>:

<div class="m-3" v-if="numberAsteroids > 0 && showSummary">
    <p>showing {{numberAsteroids}} items</p>
    <p>{{closestObject}} has the shortest miss distance</p>
</div>
<div class="m-3">
    <a href="#" @click="showSummary = !showSummary">Show/Hide summary</a>
</div>

To brake it down the v-if will call numberAsteroids and showSummary. Further on we also call closestObject. And we added a new @click directive which listens to DOM events and runs some JavaScript when they’re triggered.

Next in the <script> tag of AsteroidGrid.vue add the property showSummary and the computed functions. The script should look like:

<script>
export default {
props: ['asteroids', 'header'],
name: "AsteroidGrid",
data() {
return {
showSummary: true
}
},
computed: {
numberAsteroids() {
return this.asteroids.length;
},
closestObject() {
var neosHavingData = this.asteroids.filter(function (neo) {
return neo.close_approach_data.length;
});
var simpleNeos = neosHavingData.map(function (neo) {
return {name: neo.name, miles: neo.close_approach_data[0].miss_distance.miles}
});
var sortedNeos = simpleNeos.sort(function (a, b) {
return a.miles - b.miles;
});

return sortedNeos[0].name;
}
},
}
</script>

The closestObject function is a little more complicated because it extracts the closest object from the asteroids property. For the moment we hardcoded close approach date. Let’s make it dynamic. Replace “close approach date” in the <td> of AsteroidGrid.vue template with:

<td>{{getCloseApproachDate(asteroid)}}</td>

And in the script tag of the same file add the following at the end:

methods: {
getCloseApproachDate(asteroid) {
if (asteroid.close_approach_data.length > 0) {
return asteroid.close_approach_data[0].close_approach_date;
}
return 'N/A';
},
}

Pretty straightforward. The template calls the getCloseApproachDate function from the script. Go to the development server to see what changed.

How about highlighting with a red border the asteroids which don’t have a close approach date? This would be good exercise to see how to add classes dynamically. In AsteroidGrid.vue template add :class=”{highlight: isMissingData(asteroid)}” to the tr tag. The tr should now look like:

<tr v-for="(asteroid, index) in asteroids" :key="asteroid.neo_reference_id"
:class="{highlight: isMissingData(asteroid)}">
<td>{{index + 1}}</td>
<td>{{asteroid.name}}</td>
<td>{{getCloseApproachDate(asteroid)}}</td>
<td>
<ul v-if="asteroid.close_approach_data.length > 0">
<li v-for="(value, key) in asteroid.close_approach_data[0].miss_distance" :key="value.id">
{{key}}: {{value}}
</li>
</ul>
</td>
<td><button class="btn btn-warning">Remove</button></td>
</tr>

In the script of the same file add isMissingData to the methods. They should look like:

methods: {
getCloseApproachDate(asteroid) {
if (asteroid.close_approach_data.length > 0) {
return asteroid.close_approach_data[0].close_approach_date;
}
return 'N/A';
},
isMissingData: function (asteroid) {
return asteroid.close_approach_data.length == 0;
}
}

And again in the AsteroidGrid.vue style add:

<style scoped>
.highlight {
border: solid 3px red;
color: red;
}
</style>

Go to the development server again to check it out.

We’re almost done. All we have to do left is to make the remove button work. In AsteroidGrid.vue template replace the td of the remove button with:

<td>
<button @click="remove(index)" class="btn btn-warning">Remove</button>
</td>

You can see we added a click event handler that calls the remove function. Now add the remove function to AsteroidGrid.vue component to the methods;

remove(index) {
this.$emit('remove', index);
}

This will emit a remove event. We need to listen to this event so in App.vue you need to change the template to:

<template>
<div id="app">
<AsteroidGrid @remove="remove" :asteroids="asteroids" header="Near-Earth Objects" />
</div>
</template>

So when the event is triggered what happens? We need to add the remove method to App.vue. Add to methods:

remove(index) {
this.asteroids.splice(index, 1);
}

With a little bit of refactoring the App.vue should look like:

<template>
  <div id="app">
    <AsteroidGrid @remove="remove" :asteroids="asteroids" header="Near-Earth Objects" />
  </div>
</template>

<script>
  import AsteroidGrid from "./components/AsteroidGrid";
  import axios from 'axios'

  export default {
    name: 'App',
    components: {
      AsteroidGrid
    },
    data() {
      return {
        asteroids: []
      }
    },
    created() {
      this.fetchAsteroids();
    },
    methods: {
      fetchAsteroids() {
        var apiKey = 'ay7ydhbNUYBeBTfvbJ97eDyr9P1gKqBaVXJkBnDo';
        var url = 'https://api.nasa.gov/neo/rest/v1/neo/browse?api_key=' + apiKey;
        axios.get(url)
                .then(response => {
                  this.asteroids = response.data.near_earth_objects;
                })
      },
      remove(index) {
        this.asteroids.splice(index, 1);
      }
    }
  }
</script>

<style>
</style>

And AsteroidGrid.vue should look like:

<template>
<div class="card mt-5">
<h2 class="card-header">
{{header}}
</h2>
<div class="m-3" v-if="numberAsteroids > 0 && showSummary">
<p>showing {{numberAsteroids}} items</p>
<p>{{closestObject}} has the shortest miss distance</p>
</div>
<div class="m-3">
<a href="#" @click="showSummary = !showSummary">Show/Hide summary</a>
</div>
<table class="table table-stripped">
<thead class="thead-light">
<th>#</th>
<th>Name</th>
<th>Close Approach Date</th>
<th>Miss Distance</th>
<th>Remove</th>
</thead>
<tbody is="transition-group" name="neo-list">
<tr v-for="(asteroid, index) in asteroids" :key="asteroid.neo_reference_id"
:class="{highlight: isMissingData(asteroid)}">
<td>{{index + 1}}</td>
<td>{{asteroid.name}}</td>
<td>{{getCloseApproachDate(asteroid)}}</td>
<td>
<ul v-if="asteroid.close_approach_data.length > 0">
<li v-for="(value, key) in asteroid.close_approach_data[0].miss_distance" :key="value.id">
{{key}}: {{value}}
</li>
</ul>
</td>
<td>
<button @click="remove(index)" class="btn btn-warning">Remove</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>

<script>
export default {
props: ['asteroids', 'header'],
name: "AsteroidGrid",
data() {
return {
showSummary: true
}
},
computed: {
numberAsteroids() {
return this.asteroids.length;
},
closestObject() {
var neosHavingData = this.asteroids.filter(function (neo) {
return neo.close_approach_data.length;
});
var simpleNeos = neosHavingData.map(function (neo) {
return {name: neo.name, miles: neo.close_approach_data[0].miss_distance.miles}
});
var sortedNeos = simpleNeos.sort(function (a, b) {
return a.miles - b.miles;
});

return sortedNeos[0].name;
}
},
methods: {
getCloseApproachDate(asteroid) {
if (asteroid.close_approach_data.length > 0) {
return asteroid.close_approach_data[0].close_approach_date;
}
return 'N/A';
},
isMissingData(asteroid) {
return asteroid.close_approach_data.length == 0;
},
remove(index) {
this.$emit('remove', index);
}
}
}
</script>

<style scoped>
.highlight {
border: solid 3px red;
color: red;
}
</style>

That’s it. If you head on to the development server and click remove you will notice that the asteroid is removed. And the summary is also updated.

If you want to deploy the app you have to run the following command:

sudo npm run build

You will notice that in the asteroid folder a new folder has been created called dist. This folder contains the entire app. To see it in action without the development server you either add it to a vhost or you go to ./asteroids/dist/index.html and replace “=/” with “=” in all occurrences of index.html. Now open index.html in your favorite browser and you will see the Vue app.

I hope you enjoyed this tutorial that shows the basics of Vue js. Happy coding 🙂

Leave a Reply

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.