Complete Vue.js 3 Guide

Complete Vue.js 3 Guide

Last updated: 24.12.2019

We at German IT Academy are very eager to see first official release of the third Version of Vue.js 3. Therefore we are watching the developments closely and will be publishing many Posts, Tutorials & Courses on Vue.js 3. It does pay off to be on our Newsletter list (scroll down to subscribe) or check this Vue.js 3 Guide once i a while.

Also, check out the first “awesome-vue3″ repository with an updated and curated list of all resources regarding Vue.js 3.

Currently the vuejs team is working on three projects: the 2.7 Version, Maintenance of current codebase and the Roadmap (mainly the third version of Vue.js).

Major Changes in Vue.js 3

Let’s explore the official plans for third Version of Vue.js. We will also derive a great lot of content from the vue/rfcs, where all the RFCs are developed and discussed (Note: RFC = Request For Comment). We picked some of the most interesting ones and included a basic code example. As the changes become more certain, we will update our Vue.js 3 Guide to have more detailed examples.

Official RFCs December 2019

Composition API

There is a long explanation of what the Composition API is. Generally, the motivation behind it is the reuse of Logic, better Code Structure and better Type Inference. Here is a basic Example:

<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>

<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    })

    function increment() {
      state.count++
    }

    return {
      state,
      increment
    }
  }
}
</script>

New syntax for scoped slots usage

  • New directive v-slot that unifies slot and slot-scope in a single directive syntax.
  • Shorthand for v-slot that can potentially unify the usage of both scoped and normal slots.
<!-- default slot -->
<foo v-slot="{ msg }">
  {{ msg }}
</foo>

<!-- named slot -->
<foo>
  <template v-slot:one="{ msg }">
    {{ msg }}
  </template>
</foo>

Shorthand syntax for the v-slot syntax

<foo>
  <template #header="{ msg }">
    Message from header: {{ msg }}
  </template>

   <template #footer>
    A static footer
  </template>
</foo>

Dynamic values in directive arguments

<div v-bind:[key]="value"></div>
<div v-on:[event]="handler"></div>

# instead of

<div v-bind="{ [key]: value }"></div>
<div v-on="{ [event]: handler }"></div>

Global API

Currently in 2.x, all global APIs are exposed on the single Vue object:

import Vue from 'vue'

Vue.nextTick(() => {})

const obj = Vue.observable({})

In 3.x, they can only be accessed as named imports:

import Vue, { nextTick, observable } from 'vue'

Vue.nextTick // undefined

nextTick(() => {})

const obj = observable({})

Re-design app bootstrapping and global API

Global APIs that globally mutate Vue’s behavior are now moved to app instances created the new createApp method, and their effects are now scoped to that app instance only.

// Before
import Vue from 'vue'
import App from './App.vue'

Vue.config.ignoredElements = [/^app-/]
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)

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


// After
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp()

app.config.ignoredElements = [/^app-/]
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

app.mount(App, '#app')

More Material on Vue.js 3

  • Github Repository ‘awesome-vue3‘ with a compilation of most recent and most popular Vue.js 3 Resources.
VueJS Tutorial – #6 Template Syntax

VueJS Tutorial – #6 Template Syntax

Before we start with another VueJS Tutorial, let me promote our VueJS Course with a Certificate right at the beginning. You’ll see more interactive and deeper Content about VueJS. At the end you’ll also receive a VueJS Certificate from German IT Academy. That’s it.

Problem / Task

While coding a solution for the following tasks, make sure to make use of every single v-directive at least once: v-text, v-html, v-once and v-model. Also, do not forget to use conditional and iterative v-directives, if needed.

  • Create a new Vue Component CustomizeDonut.vue
  • Use following JSON Sample data as ‘donuts‘ data property
  • In CustomizeDonut, create 4-step input process, in which a user has to perform one data-selection in each step. Use v-model as much as you can. The steps should have individual h1 titles and individual Button Values.
    • Select Donut by name (see Sample Data)
    • Select Batter Type
    • Select Topping Type
    • Last step: Show the summary and a dummy button “Order Donut”

Hint

Here is a sample Object of a Donut. Few things stand out: We have a type and a name. We have a list of “batters” and “topping”, which contain object with further details.

	{
		"id": "0001",
		"type": "donut",
		"name": "Cake",
		"ppu": 0.55,
		"batters":
			{
				"batter":
					[
						{ "id": "1001", "type": "Regular" },
						{ "id": "1002", "type": "Chocolate" },
						{ "id": "1003", "type": "Blueberry" },
						{ "id": "1004", "type": "Devil's Food" }
					]
			},
		"topping":
			[
				{ "id": "5001", "type": "None" },
				{ "id": "5002", "type": "Glazed" },
				{ "id": "5005", "type": "Sugar" },
				{ "id": "5007", "type": "Powdered Sugar" },
				{ "id": "5006", "type": "Chocolate with Sprinkles" },
				{ "id": "5003", "type": "Chocolate" },
				{ "id": "5004", "type": "Maple" }
			]
	}
  • To have a multi-step process, you have to track the status of the current process. You can do it with an object. Or simpler yet, a variable that will store the step number that the user is currently performing.

Solution

Here is the solution we came up with for this VueJS Tutorial. Please that is no definite solution, except that the v-directive are used correctly. We smashed the solution into one Vue Component, that way we make it simpler to understand by not having the code in 3-4 different files.

<template>
  <div id="CustomizeDonut">
    <h1 v-text="stepConfig[step].title"></h1>
      <div v-if="step == 1">
            <select v-model="userInput.donutId">
                <option v-for="donut in donuts" :value="donut.id" :key="donut.id" v-text="donut.name"/>
            </select>
      </div>
      <div v-else-if="step == 2">
            <select v-model="userInput.batterId">
                <option v-for="batter in batters" :value="batter.id" :key="batter.id" v-text="batter.type"/>
            </select>
      </div>
      <div v-else-if="step == 3">
            <select v-model="userInput.toppingId">
                <option v-for="topping in toppings" :value="topping.id" :key="topping.id" v-text="topping.type"/>
            </select>
      </div>
      <div v-else-if="step == 4">
            Donut Id: {{userInput.donutId}}<br>
            Batter Id: {{userInput.batterId}}<br>
            Topping Id: {{userInput.toppingId}}<br>
      </div>
      <button @click="stepConfig[step].nextStep" v-text="stepConfig[step].button"/>
  </div>
</template>

<script>
    export default {
        name: "CustomizeDonut",
        data() {
            return {
                donuts: [{
                        "id": "0001",
                        "type": "donut",
                        "name": "Cake",
                        "ppu": 0.55,
                        "batters": {
                            "batter": [{
                                    "id": "1001",
                                    "type": "Regular"
                                },
                                {
                                    "id": "1002",
                                    "type": "Chocolate"
                                },
                                {
                                    "id": "1003",
                                    "type": "Blueberry"
                                },
                                {
                                    "id": "1004",
                                    "type": "Devil's Food"
                                }
                            ]
                        },
                        "topping": [{
                                "id": "5001",
                                "type": "None"
                            },
                            {
                                "id": "5002",
                                "type": "Glazed"
                            },
                            {
                                "id": "5005",
                                "type": "Sugar"
                            },
                            {
                                "id": "5007",
                                "type": "Powdered Sugar"
                            },
                            {
                                "id": "5006",
                                "type": "Chocolate with Sprinkles"
                            },
                            {
                                "id": "5003",
                                "type": "Chocolate"
                            },
                            {
                                "id": "5004",
                                "type": "Maple"
                            }
                        ]
                    },
                    {
                        "id": "0002",
                        "type": "donut",
                        "name": "Raised",
                        "ppu": 0.55,
                        "batters": {
                            "batter": [{
                                "id": "1001",
                                "type": "Regular"
                            }]
                        },
                        "topping": [{
                                "id": "5001",
                                "type": "None"
                            },
                            {
                                "id": "5002",
                                "type": "Glazed"
                            },
                            {
                                "id": "5005",
                                "type": "Sugar"
                            },
                            {
                                "id": "5003",
                                "type": "Chocolate"
                            },
                            {
                                "id": "5004",
                                "type": "Maple"
                            }
                        ]
                    },
                    {
                        "id": "0003",
                        "type": "donut",
                        "name": "Old Fashioned",
                        "ppu": 0.55,
                        "batters": {
                            "batter": [{
                                    "id": "1001",
                                    "type": "Regular"
                                },
                                {
                                    "id": "1002",
                                    "type": "Chocolate"
                                }
                            ]
                        },
                        "topping": [{
                                "id": "5001",
                                "type": "None"
                            },
                            {
                                "id": "5002",
                                "type": "Glazed"
                            },
                            {
                                "id": "5003",
                                "type": "Chocolate"
                            },
                            {
                                "id": "5004",
                                "type": "Maple"
                            }
                        ]
                    }
                ],
                step: 1, // We start with step 1
                stepConfig: {
                    1: {
                        title: "Donut Type",
                        button: `Go to Step 2`,
                        nextStep: () => {this.step = 2;}
                    },
                    2: {
                        title: "Batter Type",
                        button: `Go to Step 3`,
                        nextStep: () => {this.step = 3;}
                    },
                    3: {
                        title: "Topping Type",
                        button: `Final Step`,
                        nextStep: () => {this.step = 4;}
                    },
                    4: {
                        title: "Your Donut",
                        button: `Send order and Start over`,
                        nextStep: () => {this.step = 1;}
                    },
                },
                userInput: {
                    donutId: 0,
                    batterId: 0,
                    toppingId: 0
                }
            }
        },
        computed: {
            batters: {
                get() {
                    const currentDonut = this.donuts.filter(item => item.id == this.userInput.donutId)
                    return currentDonut[0].batters.batter
                }
            },
            toppings: {
                get() {
                    const currentDonut = this.donuts.filter(item => item.id == this.userInput.donutId)
                    return currentDonut[0].topping
                }
            }
        }
    }
</script>

<style>
button, option, select {
    margin: 20px 20px 20px 20px;
    font-size: 30px;
    color: rgb(255, 255, 255);
    background: rgba(255, 106, 0, 0.972)
}
</style>

Here is quite a lot happening, let’s go trough it.

First we have created a very simple step process by having a step data property and showing conditional HTML content based on that property; we achieve this with v-if and v-else-if v-directives.

Our data property stepConfig tells our App what title, what button text and what the button-action should be. Notice that we use a function for stepConfig.nextStep; this is because we use the v-directive v-on on our button. This directive needs a function as a parameter. @click=”stepConfig[step].nextStep”

Our computed properties help us extract or filter the correct batters and topping for the donut selected by the user.

In order to store user Input we created an object userInput and we mapped the keys “userInput.donutId”, “userInput.batterId” and “userInput.toppingId” with v-model to the select.options HTML Tags. This way our App has always the most recent user input saved in this Object.

If you want to learn more about VueJS and become a VueJS Certified Developer, please take a look at our VueJS Course + Certificate.

Here is a visual version of our solution. Please note, we know VueJS, but we suck at Design.

VueJS Tutorial- #5 Component Relationships

VueJS Tutorial- #5 Component Relationships

Welcome to another VueJS Tutorial. Let’s explore a very important topic in VueJS: the relationship between your Vue-Components. It’s crucial because everything is importing everything from everywhere anytime…or something like this.

For those who are new to VueJS Tutorial / VueJS Practice: we create a fictional problem / task for you to solve. To go through it and try and code a solution that would solve the issue. Then you compare it with the solution provided by German IT Academy.

Problem / Task

We need

  • create DataSourceList.vue that reads from state ‘datasources’ in vuex store.
  • For every Entry in the ‘datasources’ list, embed a a DataSourceItem.vue in the DataSourceList.vue.
  • DataSourceItem Component receives the props ‘id’ from the Parent. With that id the Child fetches all the data from ‘datasources’ in vuex store and displays them.
  • DataSourceItem Component receives a slot from the Parent and displays the content in a random place.

Hint

  • When importing a Vue-Component, do not forget to also import it inside the Parent Instance
  • As reminder, props are those data values that you pass to a child as a HTML Tag Attribute (:propName=data) and slot is the data between the HTML Opening and Closing tag.
  • To create Vuex Getter that receives a parameter from the outside, read: https://vuex.vuejs.org/guide/getters.html

You can learn more about VueJS with our VueJS Course and get VueJS certified.

Solution

Source code:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    datasources: [
      {
        id:0,
        name:'Google Cloud Storage',
        interface: 'REST API',
        activated: true
      },
      {
        id:1,
        name:'AWS S3 Bucket',
        interface: 'REST API',
        activated: true
      },
      {
        id:2,
        name:'On-Deman Server Storage',
        interface: 'SSH',
        activated: false
      }
    ]
  },
  getters: {
    getAllDataSources(state) {
      return state.datasources
    },
    getDataSourceById: (state) => (id) => {
      for (let i = 0; i < state.datasources.length; i++) {
        if (state.datasources[i].id == id) {
          return state.datasources[i]
        }
      }
    }
  },
  mutations: {
  },
  actions: {
  }
})

We have the list as a state variable. We have to getters: one for every DataSource and one getter that will return only the Entry with the specified id.

<template>
  <div class="DataSourceItem">
    <h2 :style="activated()"> 
      {{this.datasource.name}}
    </h2>
    <h3> {{datasource.interface}}</h3>
  </div>
</template>

<script>
export default {
  name: 'DataSourceItem',
  props: {
    id: Number
  },
  computed: {
    datasource: {
      get(){
        return this.$store.getters.getDataSourceById(this.id)
      }
    }
  },
  methods: {
    activated() {
      if (this.datasource.activated) {
        return 'background-color:green;'
      }
      return 'background-color:red;'
    }
  }
}
</script>

<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

The DataSourceItem.vue receives the id as a props. With that id the Component will retrieve the DataSource from Vuex Store. It’ll use the Vuex getter that we’ll define below.

<template>
  <div class="home">
    <img 
      alt="German IT Academy"
      width="200"
      src="https://i0.wp.com/code.git-academy.com/wp-content/uploads/2019/10/400dpiLogo.jpg?zoom=1.100000023841858&fit=2077%2C1175&ssl=1">
    <div v-for="datasource in datasources" :key="datasource.id">  
      <DataSourceItem :id="datasource.id"/>
    </div>
  </div>
</template>

<script>
// @ is an alias to /src
import DataSourceItem from '@/components/DataSourceItem.vue'

export default {
  name: 'DataSourceList',
  components: {
    DataSourceItem
  },
  computed: {
    datasources: {
      get(){
        return this.$store.getters['getAllDataSources']
      }
    },
  }
}
</script>

German IT Academy hopes you learned something new with this VueJS Tutorial. You can learn more about VueJS with our VueJS Course and get VueJS certified.

The visual version of the solution looks like this.

VueJS Course and VueJS Certificate
VueJS Practice – #2 Routing

VueJS Practice – #2 Routing

Learning by doing – VueJS Practice part 2. Let’s take a look at how a possible school-like question on your VueJS Test would look like. And how you would go about solving it. Btw if you want to dive deep into VueJS 2, take a look at our VueJS Certificate Course.

Problem

You need to create routes for the Vue-Components that we created in the previous VueJS Practice. Create a vue-route (use Vue-Router) for each of the following Components:

  • AssetsDetail.vue and AssetsList.vue
  • ProductsDetail.vue and ProductsList.vue
  • UsersDetail.vue and UsersList.vue

Routes for “*Detail.vue” Components must have a url parameter “id”. Router for “*List.vue” Components must have a url parameter “page”. Also, create a Vue Component called “navigation/TopMenu.vue” where you generate a list of router-links to the previously created routes; use a programmatic approach.

Hint

  • Vue-Router is the obvious choice for a simple routing task like this.
  • Regarding URL Parameters you cheat by reading about Dynamic Routing at Vue-Router Official Docs.

VueJS Practice – Solution

To define your router with Vue-Router, you need to write your routings to router.js. Here is how our prototype looks like.

import AssetsDetail from './components/assets/AssetsDetail.vue'
import AssetsList from './components/assets/AssetsList.vue'
import UsersDetail from './components/assets/UsersDetail.vue'
import UsersList from './components/assets/UsersList.vue'
import ProductsDetail from './components/assets/ProductsDetail.vue'
import ProductsList from './components/assets/ProductsList.vue'


export default new VueRouter({
    mode: 'history',
    routes: [{
            path: '/asset/:id',
            name: 'AssetsDetail',
            component: AssetsDetail
        },{
            path: '/asset/list/:page',
            name: 'AssetsList',
            component: AssetsList
        },{
            path: '/user/:id',
            name: 'UsersDetail',
            component: UsersDetail
        },{
            path: '/user/list/:page',
            name: 'UsersList',
            component: UsersList
        },{
            path: '/product/:id',
            name: 'ProductsDetail',
            component: ProductsDetail
        },{
            path: '/product/list/:page',
            name: 'ProductsList',
            component: ProductsList
        }
    ]
})

The TopMenu.vue Component has two benefits. First, it helps you debug your router.js while you are creating your routes configuration. Second, you can import the TopMenu to the most Upper VueComponent. This ways, if a route changes, your will have adjust the TopMenu.vue only, and the whole App will be updated. This is what i came up with for the Menu.

<template>
  <div id="menu">
    <ul>
      <li v-for="(key, item) in menu" :key="key">
        <router-link :to="item.url">{{ item.name }}</router-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "TopMenu",
  data() {
    return {
      menu: [
        {name: "List of Assets", url: "/asset/list/1"},
        {name: "First Asset", url: "/asset/1"},
        {name: "List of Users", url: "/user/list/1"},
        {name: "First User", url: "/user/1"},
        {name: "List of Products", url: "/product/list/1"},
        {name: "First Product", url: "/product/1"},
      ]
    }
  }
}
</script>

That’s it for our VueJS Practice. If you want more, take a look at our more detailed VueJS Certificate Course.

VueJS Practice – #1 Project Structure

VueJS Practice – #1 Project Structure

In VueJS 2 Practice we will explore a possible solution to a simple Problem: How to structure your Project, Folders, Assets and Single File Components?

Task

Create a VueJS Project/Folder Structure, that will contain Components for Detail views of products, users and payment steps. It’ll also contain a file with vuex state definitions. The project will also include API fetching methods, that should be accessible from every Single File Component.

Hints

  • Take a look at the official recommendation for VueJS Project Structure.
  • The API fetching methods need to be centrally available. What else is centrally available in your App?
  • Read the detailed VueJS Style-Guide

Solution

Your Project Structure would look like the one presented in the screenshot.

You have two VueJS Components “*Detail.vue” and “*List.vue” for each products, assets and users.

Your vuex state definitions are obviously in the “vuex-” store.js file.

The hardest one was probably the placement of the API fetching methods. There is no right answer, but there is my answer that worked for me many times. Since your methods need to be available centrally, the vuex Store-Object in store.js is the perfect place for it. You combine the actions method in store.js Store-Object with your API fetching methods.

  actions: {
    getUsers(context) {
      const users = axios.get('https://myresturl.com/users/all')
      context.commit('increment', users)
    }
  }

Then you can call from every part of your Application the following line and execute your method. The best part of this structure is that you can directly insert your freshly fetched data into the Vuex Store.

this.$store.dispatch('getUsers')

And that is how you solve this VueJS 2 Practice . If you love VueJS like we do, please consider enrolling in our VueJS Certificate Course. It’s on-demand with lifetime access!