- Vue.js 2 and Bootstrap 4 Web Development
- Olga Filipova
- 7640字
- 2021-07-08 10:01:05
Vue.js
The official Vue.js website suggests that Vue is a progressive JavaScript framework:

Screenshot from the official Vue.js website
What does that mean? In a very simplified way, I can describe Vue.js as a JavaScript framework that brings reactivity to web applications.
It's undeniable that each and every application has some data and some interface. Somehow, the interface is responsible for displaying data. Data might or might not change during runtime. The interface usually has to react somehow to those changes. The interface might or might not have some interactive elements that might or might not be used by the application's users. Data usually has to react to those interactions, and consequently, other interface elements have to react to the changes that have been done to the data. All of this sounds complex. Part of this complex architecture can be implemented on the backend side, closer to where data resides; the other part of it might be implemented on the frontend side, closer to the interface.
Vue.js allows us to simply bind data to the interface and relax. All the reactions that must happen between data and the interface will happen on their own. Let's look at a very simple example where we will bind a message to the page title. Start by defining a simple HTML structure:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Vue.js - binding data</title> </head> <body> <div id="app"> <h1>Hello, reader! Let's learn Vue.js</h1> </div> </body> </html>
Now, let's initialize a Vue.js instance on this page and bind its data to the <h1>
element. For this simple example, we will use a standalone Vue.js
file. Download it from the Vue.js official page at a Vue instance. The minimum that a Vue.js instance needs is the element to be attached to and the data
object. We want to attach our Vue instance to the main <div>
tag with the app
ID. Let's also create a data object containing an entry for the name:
var data = {name:'Olga'}
Let's create our Vue.js instance with this data:
new Vue({
el: '#app',
data
})
Let's now bind data
to our HTML element. We will do this using double curly brackets ({{}}
). Once the element has been attached to the Vue
instance, everything that is inside of it becomes special—even the curly brackets. Everything that you put inside the double curly brackets will be interpreted and evaluated. So, if you put, for example, 2 + 2
inside the curly brackets, 4
will be rendered on the page. Just try it. Any expression, any statement will be compiled and calculated. Don't be too excited though; don't start writing chunks of JavaScript code inside those brackets. Let's leave the computation to the script logic that is written where script resides. Use the brackets to access the data that you pass to your Vue
instance. So, in our case, if you insert {{name}}
anywhere inside your HTML markup, you will see the name that we passed to the Vue
instance within the data object. Let's, for example, replace the word reader
inside the <h1>
element by {{name}}
:
<h1>Hello, {{name}}! Let's learn Vue.js</h1>
If you refresh the page, you will see that the name we passed to the Vue instance is rendered. Try to change the data.name
attribute in the developer tools console. You will see the changes immediately propagated. What we see here is a one-way data binding—the changes that happen to data are reactively propagated to the element to which the data is bound. Vue.js also supports two-way data binding; so, the changes that happen to the element on the page are also propagated to the data to which the element is bound.
To achieve this, just bind the given piece of data to the element using the v-model
attribute. Let's, for example, add a text input to the page and bind it to the data attribute name
:
<input type="text"v-model="name">
Now, once you start typing in the text input, the change is immediately propagated to any other element bound to this piece of data:

The data changes are reactively propagated through all the bound elements
The complete code for the HTML markup and JavaScript code looks like this:
<body> <div id="app"> <div> <label for="name">What's your name? </label> <input id="name" type="text" v-model="name"> </div> <h1>Hello, <strong>{{name}}</strong>! Let's learn Vue.js</h1> </div> <script src="vue.js"></script> <script> var data = {name:'Olga'} new Vue({ el: '#app', data }) </script> </body>
As you can see, there is nothing difficult at all. All you need is to pass data
to the Vue
instance and bind it to the elements of your page. The Vue framework does everything else. In the upcoming chapters, we will find out what else is possible using Vue.js and how to Bootstrap a Vue.js project.
Vue project – getting started
So, now that we know what Vue.js is for and what its main focus is, we would like to get our hands dirty and start a Vue.js project and explore all the Vue.js features with it. There are plenty of ways of including Vue into the project. Let's explore all of them.
Including directly in script
You can use Vue.js by just downloading it and including it within the <script>
tag. Actually, we've done it in the previous section. So, if you have a project already running and want to use some Vue.js features, you can simply include the vue.js
file and use it.
CDN
If you don't want to bother downloading and managing Vue versions yourself, you can simply use the CDN version. Just include https://unpkg.com/vue script in your project and you are good to go! It will always be in sync with the latest Vue version:
<script src="https://unpkg.com/vue"></script>
NPM
If you are all into the Node.js development, you can simply add an npm
dependency to your package.json
file. Just run npm install
on your project's root:
npm install vue --save
Vue-cli
Vue provides a nice and clean command-line interface that is perfect for bootstrapping new projects. First of all, you must install vue-cli:
npm install --global vue-cli
Now, you can start a fresh new project using the Vue command-line interface. Check out the vue-cli repository for the detailed documentation at https://github.com/vuejs/vue-cli.
As you can see, it is possible to setup a project using different templates—starting from a simple single HTML page project and going to a complex webpack project setup. The command that should be used for scaffolding a Vue project is as follows:
vue init <template-name><project-name>
The following templates are available:
- webpack: This is a full-featured webpack setup with
vue-loader
. It supports hot reload, linting, testing, all kind of pre-processors, and so on. - webpack-simple: This is a simple webpack setup that is useful for quick prototyping.
- browserify: This is a full-featured browserify setup with vueify that also supports hot reload, linting, and unit testing.
- browserify-simple: This is a simple browserify setup with vueify that can be used for quick prototyping.
- simple: This generates a simple HTML page that includes Vue.js. It is perfect for quick feature exploration.
It is also possible to create custom templates. Check out the documentation at https://github.com/vuejs/vue-cli#custom-templates and try it.
In this book, we will use the webpack
template. We will include some loaders, and we will use linters, unit, and end-to-end testing techniques. To bootstrap a project using the webpack
template, simply run the following line of code:
vue init webpack my-project
Now that we know how to scaffold a project with vue-cli, let's check what Vue offers besides what we already explored in the previous section.
Vue directives
Vue directives are no more than just attributes attached to your HTML elements. These directives provide some extra functionality to your template.
All these directives start with the prefix v-
. Why? Because it's Vue! You have already used some of them in the previous section. Now, we will see what directives exist and what you can do with them.
Conditional rendering
Open our Hello page and remove user's input. Something not really beautiful is happening:

"Hello,!"
It would be interesting to render the Hello, name message conditionally, depending on the user input. If there is a name, render it; if there's no name, don't render.
For example, only render the Hello, name message if there's a name. Directives v-show
and v-if
are used exactly for the conditional render. Open the index.html
file of this example and let's change it. Wrap the Hello, <strong>{{name}}</strong>!
part into span
and add a v-show
attribute with the name
value:
<h1><span v-show="name">Hello, <strong>{{name}}</strong>! </span>Let's learn Vue.js</h1>
Now, if you refresh the page and remove the input completely, the message will only say Let's learn Vue.js:

The v-show attribute allows conditional rendering
Try to replace the v-show
directive with the v-if
directive. The end result will be quite the same. Why do both exist then? Check out the developer tools' elements tab and try to add or remove the text in the input. You will see that in the case of v-show
, the conditional span will just gain a display:none
property if the condition does not verify. In the case of v-if
, the element disappears completely:

Using the v-show attribute manipulates the display CSS property, whereas using the v-if attribute adds/removes an element completely
When do we use either attribute? If you have a lot of elements that should be visible depending on some data (this data is really dynamic, so it will happen a lot during the runtime), I would advise using the v-show
attribute, because adding or removing elements in DOM is a rather expensive operation that might affect the application's performance and even the DOM itself. On the other hand, if the elements should be conditionally rendered only once, let's say, at the application startup, use the v-if
attribute. If some elements should not appear, they will just not be rendered. Thus, the number of elements on the page will be reduced. Consequently, the computational cost of the application will be also reduced, as, now, it has fewer elements to go through and compute.
Text versus HTML
I am sure you know pretty well from the previous chapter how to bind some data using the mustache syntax {{}}
.
Since this is a technical book about programming, we have to have a cat here ! A cat is pretty easy to render. Its Unicode is
U+1F638
; thus, we just have to add the 😸
code to our HTML:
<div>😸</div>
And, surely, we will have a cat:

Emoji cat saying hello to us
It's nice, but if we want to replace the cat with a dog, we will have to use Google to look for another Unicode representing a dog and replace it. If at some point we want to replace it with a unicorn, we will have to run the same procedure. Moreover, just by looking at our code, we will not be able to say what we are actually rendering unless we know all emoji codes by ♥
. It might be a good idea to map the names of the emojis to their codes.
Let's add a map of some of them. Open your HTML file and add the following lines of code to the <script>
area:
//index.html <script> const animalCodes = { dog: '🐶', cat: '😸', monkey: '🐵', unicorn: '🦄' } const data = { animalCodes } new Vue({ el: '#app', data }) </script>
Now, you can bind the values of this map to your HTML elements. Let's try to do it using the mustache annotation:
<div>{{animalCodes.cat}}
</div>
Refresh the page. The result is not exactly the same as we expected, is it?

The code is rendered instead of the actual cat emoji
This is happening because mustache interpolation actually interpolates text. Using mustache interpolation is the same as using the v-text
directive:
<div v-text="animalCodes.cat"></div>
What we actually want to render here is not the text; we want the value of the Unicode for the emoji being rendered as HTML! This is also possible with Vue.js. Just replace a v-text
directive with the v-html
directive:
<div v-html="animalCodes.cat"></div>
Now, we will get our cat back, and we know exactly what we are rendering when we are looking at the code.
So, remember to use the v-text
directive or mustache annotation for text interpolation and the v-html
directive for interpolating pure HTML.
Loops
In the previous section, we put a cat on our page. In this section, I would like to have a whole zoo! Imagine that our zoo has a cat , a dog
, a monkey
, and, of course, a unicorn
.We would like to display our zoo in an ordered list. Of course, you can write a simple markup that will look like this:
<ol> <li>😸</li> <li>🐶</li> <li>🐵</li> <li>🦄</li> </ol>
However, this makes your code unreadable, and if you want to add more animals to your zoo or remove one of them, you would have to know all these codes by heart. In the previous section, we added a map for emoji animals Unicode. Let's use it in our markup. You already learned that we must use a v-html
directive so that the codes are interpolated as HTML. Hence, our markup will look like this:
<div id="app"> <ol> <li v-html="animalCodes.cat"></li> <li v-html="animalCodes.dog"></li> <li v-html="animalCodes.monkey"></li> <li v-html="animalCodes.unicorn"></li> </ol> </div>
It looks better, but still there's something we could improve. Imagine if you want to render all the animals from the emoji world! There are plenty of them. For each animal, you will have to repeat the code of the list item. Every time you would like to reorder the list, remove some elements, or add new ones, you will have to deal with this markup. Wouldn't it be nice if we just had an array of animals that we want to render and then somehow iterate over it and render what's inside of it? Of course, it would! It is possible using the v-for
directive. Create an array of animals using the following lines of code:
const animals = ['dog', 'cat', 'monkey', 'unicorn']
Export it in the vue data
object:
var data = {
name:'Olga',
animals,
animalCodes
}
Now, you can use this array in the v-for
directive and replace multiple <li>
elements by only one:
<ol> <h2><span>{{name}}! </span>Here's your Zoo</h2> <li v-for="animal in animals" v-html="animalCodes[animal]"></li> </ol>
The result will be quite nice:

Emoji zoo rendered using the v-for directive
Binding data
We dealt a lot with rendering different data using Vue.js in the previous section; so now, you are already familiar with different ways of binding it. You know how to interpolate data as text and as HTML, and you know how to iterate over arrays of data.
We've also seen that two-way data binding is achieved using the v-model
directive. We used it to bind a name to the input element:
<input id="name" type="text" v-model="name">
The v-model
directive can only be used with the input
, select
, and textarea
elements. It also accepts some modifiers to be used with. Modifiers are special keywords that affect the input in some way. There are three modifiers that can be used with this directive:
.lazy
: This will only update the data on a change event (try it with our input and you'll see that changes in the input will only affect other parts where the name is used when the Enter button is pressed and not on each key press).number
: This will cast your input to number.trim
: This will trim the user's input
It is also possible to chain the modifiers:
<input id="name" type="text"v-model.lazy.trim="name">
So now, we know nearly everything about binding data to the elements. What if we want to bind some data to the elements' properties? Imagine, for example, the dynamic value for the image's source property or class property depending on some data value. How could we do that?
For this, Vue provides a v-bind
directive. With this directive, you can bind whatever you want!
As an example, let's show a sad picture when the name is not defined and a glad picture when the name is defined. For this, I've created two pictures, glad.png
and sad.png
, and put them into the images
folder of my application. I will also export their paths into the data object:
//index.html var data = { name:'Olga', animals, animalCodes, sadSrc: 'images/sad.png', gladSrc: 'images/glad.png' }
Now, I can create an image and bind its source using v-bind:src
, and I'll provide a JavaScript expression as the value. This expression will check the value of the name. If it's defined, the glad
image will be applied, and if not, the sad
image will be applied instead:
<img width="100%" v-bind:src="name ? gladSrc : sadSrc">
The shortcut for the v-bind
directive is :
, so we can just write the following line of code:
<img width="100%" :src="name ? gladSrc : sadSrc">
Here is how our page looks when the value of name
is defined:

Happy face image appears when the name is defined
If you remove the name from the input field, the image will automatically change! Open the page and try to remove the text from the input field and add it again. Continue removing and adding, and you will see how fast the image is changed to the corresponding one. This is how the page looks when the name is undefined:

Once the input is cleaned, the image source is immediately changed
Basically, you can do exactly the same with any property binding, for example, class:
<label for="name" v-bind:class="{green: name, red: !name}">What's your name? </label>
You can also bind properties to pass to the children components. We will see how to do it in the section about components.
Handling events
Besides the direct form of data binding to the elements, we want to handle some events because this is what our users do on the page—trigger some events so that they happen. They click, they hover, they submit forms—and all these events must be handled somehow by us. Vue provides a very nice way of attaching listeners to events on any DOM element and provides methods that can handle those events. The good thing about these methods is that they have direct access to Vue data using the this
keyword. In this way, we can use methods to manipulate data, and since this data is reactive, all the changes will be immediately propagated to the elements to which this data is bound.
In order to create a method, you just have to add a methods
object to the export section of your Vue application. In order to attach this method to any event listener, use the v-on
directive with the corresponding event after the colon. Here is an example:
v-on:sumbit="handleSubmit" v-on:click="handleClick" v-on:hover="handleHover"
The shortcut for this directive is @
, so we could rewrite all these directives as follows:
@sumbit="handleSubmit" @click="handleClick" @hover="handleHover"
It should sound familiar to you. Do you remember the tutorial that we followed in the Chapter 1, Please Introduce Yourself – Tutorial? Do you remember that we were listening on the submit
method of the message, adding form
and calling addMessage
? Check it out. Our form with its submit
directive looked like this:
//please-introduce-yourself/src/App.vue
<template>
<form @submit="addMessage">
<...>
</form>
</template>
Then, inside the methods
section, we actually had the addMessage
method defined:
//please-introduce-yourself/src/App.vue <script> <...> export default { <...> methods: { addMessage (e) { <...> }, }, } </script>
Does it start to make more sense now?
Just to understand it better, let's add some methods to our zoo page! Wouldn't it be nice if you could compose your own zoo? Let's add a multiple select element that will contain all possible options, and your zoo will be populated from something that you actually choose! So, let's do the following:
- Add more animals to our
animalCodes
map - Add another array called
animalsForZoo
- Use this new array in our ordered list that displays the zoo
- Add a multiple
select
box composed of the keys of theanimalCodes
map - Attach a
@change
listener to this select box that will call thepopulateAnimalsForZoo
method - Create a
populateAnimalsForZoo
method that will populate theanimalsForZoo
array with the selected options from our multiple select element
Doesn't it sound easy? Of course, it does! Let's get started. So, at first, add more animals to our animalCodes
map:
var animalCodes = { dog : '🐶', cat : '😸', monkey : '🐵', unicorn : '🦄', tiger : '🐯', mouse : '🐭', rabbit : '🐰', cow : '🐮', whale : '🐳', horse : '🐴', pig : '🐷', frog : '🐸', koala : '🐼' }
Let's also rethink our animals
array and generate it out of our map. In this way, every time we need to add some new animal, we just add its key-value name-unicode to the mapping object instead of maintaining both object and array. So, our animals
array will look like this:
var animals = Object.keys(animalCodes)
Now, we need another empty array. Let's call it animalsForZoo
, and let's populate our zoo from this new array. Since it is empty, our zoo will also be empty. However, we are about to create a method that will populate this array. So, creating an array is easy, and don't forget to export it in a data object:
<script> <...> var animalsForZoo = [] var data = { name:'Olga', animals, animalCodes, animalsForZoo, sadSrc: 'images/sad.png', gladSrc: 'images/glad.png' } new Vue({ el: '#app', data }) </script>
Don't forget to replace the usage of the animals
array in our zoo display with the new animalsForZoo
array:
<ol>
<li v-for="animal in animalsForZoo"><span class="animal" v-html="animalCodes[animal]"></span></li>
</ol>
I know that now you are worried that your zoo on the page is empty, but give us a couple of minutes and we will take care of that!
First of all, let's create a multiple select
element that will be populated based on the animals
array:
<select multiple="multiple" name="animals" id="animals"> <option v-for="animal in animals" :value="animal">{{animal}}</option> </select>
Now, finally, we will attach an event listener to our select box. Let's attach a listener to the change event. Let's tell it to call the populateAnimalsForZoo
method. Our directive will look like this:
@change="populateAnimalsForZoo"
The whole select
element will obtain a new attribute:
<select @change="populateAnimalsForZoo" multiple="multiple" name="animals" id="animals">
<option v-for="animal in animals" :value="animal">{{animal}}</option>
</select>
Great! But there's no such method as populateAnimalsForZoo
. But there's us! Let's create it. This method will just iterate through the checked options of the animals selected as input and push them into the animalsForZoo
array:
new Vue({ el: '#app', data, methods: { populateAnimalsForZoo(ev) { this.animalsForZoo = [] const selected = document.querySelectorAll('#animals option:checked') for (var i = 0; i < selected.length; i++) { this.animalsForZoo.push(selected[i].value) } } } })
Check out how the whole HTML and JavaScript code look after all these changes in the chapter2/example1-vue-intro/index.html
file. This is how our testing page looks after the changes:

The zoo is being populated based on the user's choice
The page is messy, right? However, look how many things you have already learned just by using this page. And, admit it, it's a fun learning process! And we are not done with it yet.
Now that you have learned how to add methods and event listeners, I will teach you how we could have done the exact same thing without this method and v-bind:change
. Remove all the code we just added and just add v-model
to our select
element with the animalsForZoo
value :
<select v-model="animalsForZoo" multiple="multiple" name="animals" id="animals">
<option v-for="animal in animals" :value="animal">{{animal}}</option>
</select>
Now, everything we have just done inside the method is handled automatically by Vue! Isn't it great?
Vue components
We came to this chapter having a midsize HTML page in our hands that contains a lot of different parts. We could have thought of more things, for example, adding interactivity to each animal of our zoo, adding the possibility of feeding animals, or having some interesting facts about each animal showing up every time you hover over the animal's icon. At some point, let's face it, the HTML file along with its JavaScript will become unmaintainable.
Can you also see that our visualization layer (HTML) works along with our logical layer (JavaScript)? So, they kind of form blocks, items, bricks… For example, we have a piece of code that is responsible for the Hello name section. We have another block that contains our zoo. Each animal in the zoo is another item.
Call these things whatever you want, but they are undeniably separated pieces of structure and logic that, when brought together, form the whole puzzle. If you build a wall from a unique piece of material and decide to change some parts of the wall, it will not be the easiest task.
So, imagine, you build this wall and incorporate some yellow stars, blue polygons, red squares, and so on into it. Then, you decide that your yellow stars should be black. You have to change all your stars. Then, you decide that your green ellipsis should be a smiling face instead. What now? Change all ellipses, but first you have to find all the places in the wall that contain those ellipses. This is your wall, try to find all ellipses in it:

The wall built as a whole piece with incorporated parts of different colors and forms
Now, imagine that each piece actually resides on its individual brick. You can change them, add them, and remove them as much as you want. If you want to change the appearance of some of the wall elements, you just change this one brick and all the wall pieces containing this brick will change, because all in all, it's just another brick in the wall. So, instead of having the wall full of incorporated strange pieces, you have four bricks, and you change them whenever you need to change the piece of wall that relies on that brick:

If you need to change the appearance of an element in the wall, you just change the corresponding brick
The wall is composed of bricks. These bricks are our components. What if we could also have components built with HTML, CSS, and JavaScript and our application could be built of those components? Did I just say "what if"? There's no "what if." We already have it. Vue.js supports component-based application structure. It's really easy to create components with Vue.js. The only three things you have to do are as follows:
- Create a component, and give it a template, data, methods, and whatever you need to give to it.
- Register it in the Vue app under the
components
object. - Use it within the application's template.
For example, let's create a component that will simply render a header element saying Hello. Let's call it HelloComponent
. It will only contain the template string:
var HelloComponent = { template: '<h1>Hello!</h1>' }
Now, we can register this component inside our Vue application initialization code:
new Vue({
el: '#app',
components: {
HelloComponent
}
})
Now, this component can actually be used inside the HTML section of the Vue application's element:
<div id="app">
<hello-component></hello-component>
</div>
So, the whole section will look something like this:
<body> <div id="app"> <hello-component></hello-component> </div> <script src="vue.js"></script> <script> var HelloComponent = { template: '<h1>Hello!</h1>' } new Vue({ el: '#app', components: { HelloComponent } }) </script> </body>
Someone might ask, "What's so powerful in these components?" The amount of written code is actually the same as if I would have just written a piece of HTML that does the same. What's the point? Yes, sure, but in this example, our component had just one template inside. A template composed of one line only. We could have a huge template in there, and we could have some methods in this component and also its own data! Let's, for example, add an input for the name to this component and the name to its data object:
var HelloComponent = { template: '<div>' + '<input v-model="name" />' + '<h1>Hello! <strong>{{name}}</strong></h1>' + '</div>', data() { return { name: '' } } }
If you need to reuse this component, you can do it as many times as you want:
<div id="app"> <hello-component></hello-component> <hello-component></hello-component> <hello-component></hello-component> </div>
Then, you will end up with three independent components on your page:

Using components helps avoid repeated code
These components are very nice, but there's still a big amount of code written within the same JavaScript code block. We declare components all in one place, and if there are too many of them, the application will become unmanageable again. Besides that, this HTML code within the template string is also not the most maintainable thing ever.
Well, if you are thinking so, I have some good news for you. Each component can be stored in its own file with its own HTML, JavaScript, and CSS code. These are special files with the .vue
extension. Inside each file, there's a <script>
section for the JavaScript code, a <style>
section for the CSS code, and a<template>
section for the HTML code. Isn't it convenient? Such components are called single-file components. Have a look at the first chapter's code—there's a main component called App.vue
and there's also the MessageCard.vue
component created by us. Isn't it nice?
If you want to use single-file components in your application, you must scaffold this application using some modular bundler, for example, webpack
. We already talked about vue-cli
and how easy it is to bootstrap a Vue application using the webpack
template. Let's port the messy page with zoo to the webpack
bundled application. Run the initialization and installation scripts:
vue init webpack zoo cd zoo npm install npm run dev
Now, open the App.vue
file and let's fill it up with our messy zoo application. The <script>
section looks like this:
<script> <...> var data = { name: 'Olga', animals, animalCodes, animalsForZoo, sadSrc: '../static/images/sad.png', gladSrc: '../static/images/glad.png' } export default { name: 'app', data () { return data } } </script>
Note the highlighted areas. I've copied the images into the static
folder. Another important thing is that the data inside the component should be used as a function that returns an object and not as an object itself. Since the data object will still be one single instance across multiple components, the whole data object with its properties must be assembled in a dedicated function.
The rest of the script is completely the same.
The template area of the component is pretty much the same as the HTML structure from the previous example. Check out the code in the chapter2/example3-components-started
folder.
Let's extract some of the functionality into the individual component. What do you think if we extract the zoo to its individual component? Create a Zoo.vue
file in the components
folder. Copy the template for the animals list to this component's <template>
area:
//Zoo.vue <template> <div v-if="animals.length > 0"> <h2><span v-if="name">{{name}}! </span>Here's your Zoo</h2> <ol> <li v-for="animal in animals"><span class="animal"v-html="animalCodes[animal]"></span></li> </ol> </div> </template>
Now, we should tell this component that it will receive animals
, name
, and animalCodes
properties from the parent component that will call the following component:
//Zoo.vue
<script>
export default {
props: ['animals', 'animalCodes', 'name']
}
</script>
Now, open the main App.vue
component, import the Zoo
component, and export it in the components
object:
//App.vue <script> import Zoo from './components/Zoo' <...> export default { name: 'app', components: { Zoo } } </script>
Now, we can use this component inside the template! So, replace the whole div
tag that contains our zoo with just the following code:
//App.vue
<template>
<...>
<zoo :animals="animalsForZoo" :animalCodes="animalCodes":name="name"></zoo>
<...>
</template>
Check out the page! Everything works as it did earlier!
Exercise
Extract an animal to the individual component and call it inside the zoo within the v-for
directive. Each animal has to have a small functionality that will display a small description when clicking its face (on click
). I am pretty sure you will easily solve this exercise. If you need help, check out this chapter's code inside the example4-components/zoo
directory.
Vue router
Single Page Applications (SPA) are great. They came to make our life easier. And it definitely is. With a bit of JavaScript code, you can achieve all the functionality that had to be done on the server side before, and the whole page should have been replaced just to display the result of that functionality. It is a golden era for web developers now. However, there is a problem that SPAs are trying to solve—navigation. History API and the pushState
method (https://developer.mozilla.org/en-US/docs/Web/API/History_API) are already solving it, but it has been a long process until it became an established technology.
Our users are used to controlling their where I am and where I want to be using browsers' navigation buttons. If the whole functionality is located on the same page, how will these buttons help with the navigation? How do you use Google analytics to check which page (that, in fact, is the same) is being accessed more by your users? The whole concept is totally different. Of course, these kinds of applications are a lot faster because the number of requests is significantly reduced, and of course, our users are grateful for that, but they are not changing their web surfing habits just because we changed the way we implement things. They still want to go back. They expect that if they refresh the page, the page will open on exactly the same place where they were right before hitting the refresh button. They expect that they will understand where they are just by looking at the page's URL and checking what's behind the slash. For example, if it's http://mySite/store
then it's a store; if it's http://mySite/settings
, then most likely I'm somewhere where I can check my current settings and change them.
There are a lot of ways to achieve navigation without having to transform single-page applications into multiple-page applications. You can include an extra layer of logic on your application and change window.location.href
every time a different URL is required—this will cause the page to refresh, which is not nice. You can also use HTML5 history
API. It would not be the simplest thing to maintain, but it might work.
We all know that good developers are lazy, right? Being lazy means not solving problems that are already solved by someone else. This problem of navigation is being solved by many frameworks and libraries. Not only can you use some third-party libraries that help you deal with the routing in your application, but you can also use the mechanisms provided by the framework of your choice. Vue.js is one of the frameworks that offers a way of dealing with routing. You just map the URL path to your components and everything just works! Check out the official documentation of vue-router
library at https://router.vuejs.org/en/.
In order to be able to use vue-router
, you must install it for your project:
npm install vue-router –save
Optionally, vue-router
usage can be selected on the Vue project initialization with vue init
.
Now, you can use Vue router in your application. Just tell Vue to use it:
//main.js import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter)
Let's create a simple routing example. We will have three components, one of which we consider as the Home
component, meaning that it should be shown when someone navigates to the root route /
. Let's call the second one Hello
component and the third one Bye
component. Open the example5-router-started
code files from Chapter 2, Under the Hood – Tutorial Explained. You will find all the described components in the components
directory:

The structure of the example application where we are going to try Vue router
Now, we must create a router
instance. The constructor receives the options
object as a parameter. This object can contain different configurable values. The most important one is the array of routes
. Each entry of this array should consist of an object that indicates the path
of the route and its corresponding component
.
First, we will import all the needed components, and then, our router
instance will look like this:
//main.js import Home from '@/components/Home' import Hello from '@/components/Hello' import Bye from '@/components/Bye' <...> var router = new Router({ mode: 'history', routes: [ { name: 'home', component: Home, path: '/' }, { name: 'hello', component: Hello, path: '/hello' }, { name: 'bye', component: Bye, path: '/bye' } ] })
If you want to understand better what the mode:
history
option is, check out the documentation page at have to pass the router option to our Vue application. This option will point to our new router
instance:
//main.js
new Vue({
el: '#app',
template: '<App/>',
components: { App },
router
})
Now, the whole application knows that we use this router. One more important step: we need to include the router component into the main component's template. For this, it is enough to just include the <router-view>
tag in the App.vue
component's template:
//App.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view></router-view>
</div>
</template>
Check out in more detail the router-view
component at https://router.vuejs.org/en/api/router-view.html.
Voilà! Run the application if you haven't done so already:
npm run dev
Open the page at http://localhost:8080
and check that it is displaying our home page component. Then, type http://localhost:8080/hello
and http://localhost:8080/bye
in the browser's address bar. Check that the content of the page actually changes according to the URL path:

Basic routing with vue-router
Of course, you are already thinking about how to create a simple menu, pointing an anchor <a>
element to your defined paths in the router. Don't think too much. Just use a <router-link>
component with the to
attribute pointing to the path of your choice. For example, to display a simple navigational menu for our router example application, we could write something like this:
//App.vue <template> <div id="app"> <router-link to="/">Home</router-link> <router-link to="hello">Hello</router-link> <router-link to="bye">Bye</router-link> <router-view></router-view> </div> </template>
Alternatively, if you don't want to write your paths all over again, you can reference your routes by name and use the v-bind:to
directive or simply use :to
:
//App.vue <template> <div id="app"> <router-link :to="{name: 'home'}">Home</router-link> <router-link :to="{name: 'hello'}">Hello</router-link> <router-link :to="{name: 'bye'}">Bye</router-link> <router-view></router-view> </div> </template>
Check how the code looks in the example6-router
folder.
Open the page and check whether all the links actually work! Click on them several times and check whether you will actually go back if you click on the browser's go back button. Isn't it fantastic?
Vuex state management architecture
Do you remember our example with the Zoo
and animal
components? There was some data that had to be propagated from the main component to the child component of the child component. If this grandchild component had the possibility of somehow changing data, this change would have to be propagated from the child component to its parent component and so on until data reaches the main component. Don't think that you would do it simply with a v-model
binding attribute. Vue has some restrictions regarding binding data to the children components via props
. It is strictly one way. So, if the parent component changes the data, the child component's bindings will be affected, but it will never happen the other way around. Check out Vue's official documentation in regarding this at https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow.
If you don't believe me, let's try it. Imagine that in our zoo page example, we would extract the introduction part to the separate component. I am talking about this part of our messy zoo page:

What if we'd like to extract this part to the separate component?
It seems easy. We have to declare a component, let's say Introduction
, tell it that it will receive the name
property, and just copy-paste HTML from App.vue
to this new component. Inside App.vue
, we will import this new component and export it inside the components
object of the Vue instance. Of course, we will replace the HTML that we already copied to the new component with the <introduction>
tag and bind the name
property to it. Isn't it easy? Our Introduction.vue
file will look like this:
//Introduction.vue
<template>
<div>
<label for="name" :class="{green: name, red: !name}">What's your name? </label>
<input id="name" type="text" v-model.trim="name">
</div>
</template>
<script>
export default {
props: ['name']
}
</script>
Our App.vue
file will import, export, and call:
//App.vue <template> <div id="app" class="jumbotron"> <...> <introduction :name="name"></introduction> <...> </div> </template> <script> <...> import Introduction from './components/Introduction' <...> export default { components: { Zoo, Introduction } <...> } </script>
Check out this code in the code bundle of the Chapter 2, Under the Hood – Tutorial Explained in the example7-events-started/zoo
folder. Run npm install
and npm run
inside this folder:
cd example7-events-started/zoo npm install npm run dev
Check out the page. It looks like it did before. Try to change the name inside the input. First of all, it doesn't change in other places where it should change, and second, our dev tools console is full of warnings and errors:

The name is not updated where it should have been updated, and the console is full of errors
It seems the documentation is right: we can't change the value of data passed as property to the child component. What can we do then? We can emit events and attach event listeners to the component, and change the data on the event. How do we do this? It's simple. First of all, let's call the property being passed by something that is not name
, for example, initialName
. Then, open the Introduction
component and create a data
function that will bind this component's name
object to initialValueprops
. In this way, we are at least telling Vue that it is not our intention to try to change parent's data from the child. So, script
of the Introduction.vue
component will look like this:
//Introduction.vue <script> export default { props: ['initialName'], data () { return { name: this.initialName } } } </script>
We also have to change the way we bind name to the component inside App.vue
:
//App.vue
<introduction :initialName="name"></introduction>
Now, if you check the page, you will at least see that Vue doesn't complain anymore about something illegal that we try to do. However, still, if we try to change the name, the changes are not propagated to the parent, which is quite understandable; these changes only affect the data of the component itself. Now, we have to attach the event
to the input
element. This event will call a method that will finally emit the event to the parent component:
//Introduction.vue <template> <div> <...> <input id="name" type="text" v-model.trim="name"@input="onInput"> </div> </template> <script> export default { <...> methods: { onInput () { this.$emit('nameChanged', this.name) } } } </script>
Now, the only thing we have to do is to bind the nameChanged
event listener to the <introduction>
component and call the method that will change the name of the App.vue
data object:
//App.vue <template> <...> <introduction @nameChanged="onNameChanged" :initialName="name"></introduction> <...> </template> <script> export default { <...> methods: { onNameChanged (newName) { this.name = newName } } } </script>
Check the page. Now, everything works as before! Check the code for this solution inside the example7-events/zoo
code folder for this chapter.
Well, it was not very difficult, but do we want to emit all these events every time we need to update the state? And what if we have components inside the components? And what if we have other components inside those components? Will it be the events handling hell? And if we have to change something, will we have to go to all those components? Argh! Wouldn't it be great to have the application's data in some kind of centralized storage that would provide a simple API for its management and then we could just call this storage's methods in order to retrieve and update the data? Well, this is exactly what Vuex is for! Vuex is a centralized state management inspired by Redux. Check out its official documentation at http://vuex.vuejs.org/en/.
Now, in a nutshell, the three most important parts of a Vuex store are state, getters, and mutations:
- State: This is an initial state of the application, basically the data of the application
- Getters: These are exactly what you think, functions that return data from the store
- Mutations: These are functions that can mutate data on the store
A store can also have actions. These things are like wrappers for mutations with a bit more capacity. If you want to check what are they about, refer to the official documentation at http://vuex.vuejs.org/en/mutations.html.
Let's add the Vuex store to our Zoo
application to check how it works. First of all, we need to install vuex
. Open the code for Chapter 2, Under the Hood – Tutorial Explained from the example8-store-started/zoo
folder and run npm install
:
cd example8-store-started/zoo npm install vuex --save
Let's create our store. Start by creating a folder named store
with the index.js
file inside. We will put all our store data inside this file. Before doing this, tell Vue that we will use Vuex:
//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
Now, we can create a new Vuex instance. It should receive state
, getters
, and mutations
. Let's define them:
//store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const state = { } const getters = { } const mutations = { } export default new Vuex.Store({ state, getters, mutations })
Nice! Now, let's add all the data that resides in our application to the state:
//store/index.js const animalCodes = { dog: '🐶', <...> koala: '🐼' } const animalsDescriptions = { dog: 'I am a dog, I bark', <...> koala: 'I am a koala, I love eucalyptus!' } const animals = Object.keys(animalCodes) const state = { name: 'Olga', animals, animalCodes, animalsDescriptions, animalsForZoo: [], sadSrc: '../static/images/sad.png', gladSrc: '../static/images/glad.png' }
Now, if you inject the store on the Vue application initialization, all the components and their children will have access to the this.$store
instance. Let's inject it:
//main.js
import Vue from 'vue'
import App from './App'
import store from './store'
new Vue({
el: '#app',
template: '<App/>',
components: { App },
store
})
Now, if we replace all the data with computed properties from the store in App.vue
(except animalsForZoo
, which is bound as a property for our zoo), the application will look quite the same:
//App.vue <script> import Zoo from './components/Zoo' import Introduction from './components/Introduction' export default { name: 'app', components: { Zoo, Introduction }, data () { return { animalsForZoo: [] } }, computed: { name () { return this.$store.state.name }, animals () { return this.$store.state.animals }, animalCodes () { return this.$store.state.animalCodes }, sadSrc () { return this.$store.state.sadSrc }, gladSrc () { return this.$store.state.gladSrc } }, methods: { onNameChanged (newName) { this.name = newName } } } </script>
If you open the page, nothing has changed. However, our changing name interaction doesn't work again!
Let's add mutation
to change the name. Mutations are just methods that receive a state as first argument and anything you call them with as other parameters. So, let's call our mutation updateName
and pass newName
to it as a second argument:
//store/index.js const mutations = { updateName (state, newName) { state.name = newName } }
Now, we can use this mutation to access the this.$store.mutation
property inside the component responsible for updating the name—Introduction.vue
. We have to just change the onInput
method:
//Introduction.vue
methods: {
onInput (ev) {
this.$store.commit('updateName', ev.currentTarget.value)
}
}
By the way, we can also remove the properties and pass the name directly from the store, just like we did in the App.vue
component. Then, you can remove the name
binding to the introduction
component inside the App.vue
component's template. Now, you can replace the properties that are bound to the Zoo component by computed properties coming from the store. See how elegant the code becomes! For example, look at this line of code:
<introduction></introduction>
Doesn't it look better than the following line of code:
<introduction @nameChanged="onNameChanged" :initialName="name"></introduction>
Check out the final code for this chapter in the example8-store/zoo
code folder for not even used any getters. For a more sophisticated use, we would create getters
and actions
, and they would have been located in their own actions.js
and getters.js
files. We would also use mapGetters
and mapActions
helpers. However, for basic understanding, what we have done is enough. Refer to the official documentation to find out more about Vuex store and how to use it.
- 演進式架構(原書第2版)
- 密碼學原理與Java實現
- 編程卓越之道(卷3):軟件工程化
- CentOS 7 Linux Server Cookbook(Second Edition)
- 趣學Python算法100例
- Java Web及其框架技術
- Learning AWS Lumberyard Game Development
- Python Geospatial Development(Second Edition)
- JavaScript by Example
- C語言程序設計案例精粹
- Hands-On Microservices with Kotlin
- 全棧自動化測試實戰:基于TestNG、HttpClient、Selenium和Appium
- 從零開始學Linux編程
- Clojure for Java Developers
- Learning Concurrency in Python