Vue is a JavaScript framework. It can be added to an HTML page with a <script> tag.
Vue extends HTML attributes with Directives, and binds data to HTML with Expressions.
Vue is a JavaScript Framework
Vue is a front-end JavaScript framework written in JavaScript.
Similar frameworks to Vue are React and Angular, but Vue is more lightweight and easier to start with.
Vue is distributed as a JavaScript file, and can be added to a web page with a script tag:
<script
src="https://unpkg.com/vue@3/dist/vue.global.js">
</script>
Why Learn Vue?
- It is simple and easy to use.
- It is able to handle both simple and complex projects.
- Its growing popularity and open-source community support.
- In normal JavaScript we need to write HOW HTML and JavaScript is connected, but in Vue we simply need to make sure that there IS a connection and let Vue take care of the rest.
- It allows for a more efficient development process with a template-based syntax, two-way data binding, and a centralized state management.
If some of these points are hard to understand, don't worry, you will understand at the end of the tutorial.
The Options API
There are two different ways to write code in Vue: The Options API and The Composition API.
The underlying concepts are the same for both the Options API and Composition API, so after learning one, you can easily switch to the other.
The Options API is what is written in this tutorial because it is considered to be more beginner-friendly, with a more recognizable structure.
Take a look at this page at the end of this tutorial to learn more about the differences between the Options API and the Composition API.
My first page
We will now learn how we can create our very first Vue web page, in 5 basic steps:
- Start with a basic HTML file.
- Add a
<div>
tag withid="app"
for Vue to connect with. - Tell the browser how to handle Vue code by adding a
<script>
tag with a link to Vue. - Add a
<script>
tag with the Vue instance inside. - Connect the Vue instance to the
<div id="app">
tag.
These steps are described in detail below, with the full code in a 'Try It Yourself' example in the end.
Step 1: HTML page
Start with a simple HTML page:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My first Vue page</title>
</head>
<body>
</body>
</html>
Step 2: Add a <div>
Vue needs an HTML element on your page to connect to.
Put a <div>
tag inside the <body>
tag and give it an id:
<body>
<div id="app"></div>
</body>
Step 3: Add a link to Vue
To help our browser to interpret our Vue code, add this <script>
tag:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
Step 4: Add the Vue instance
Now we need to add our Vue code.
This is called the Vue instance and can contain data and methods and other things, but now it just contains a message.
On the last line in this <script>
tag our Vue instance is connected to the <div id="app">
tag:
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
message: "Hello World!"
}
}
})
app.mount('#app')
</script>
Step 5: Display 'message' with Text Interpolation
Finally, we can use text interpolation, a Vue syntax with double curly braces {{ }}
as a placeholder for data.
<div id="app"> {{ message }} </div>
The browser will exchange {{ message }}
with the text stored in the 'message' property inside the Vue instance.
Here is our very first Vue page:
Example: My first Vue page!Get your own Vue Server
Test this code with the 'Try it Yourself' button below.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My first Vue page</title>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
message: "Hello World!"
}
}
})
app.mount('#app')
</script>
</body>
</html>
Text Interpolation
Text interpolation is when text is taken from the Vue instance to show on the web page.
The browser receives the page with this code inside:
<div id="app"> {{ message }} </div>
Then the browser finds the text inside the 'message' property of the Vue instance and translates the Vue code into this:
<div id="app">Hello World!</div>
JavaScript in Text Interpolation
Simple JavaScript expressions can also be written inside the double curly braces {{ }}
.
Example
Use JavaScript syntax to add a random number to the message inside the div element:
<div id="app">
{{ message }} <br>
{{'Random number: ' + Math.ceil(Math.random()*6) }}
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
message: "Hello World!"
}
}
})
app.mount('#app')
</script>
Vue directives are special HTML attributes with the prefix v-
that give the HTML tag extra functionality.
Vue directives connect to the Vue instance to create dynamic and reactive user interfaces.
With Vue, creating responsive pages is much easier and requires less code compared to traditional JavaScript methods.
Different Vue Directives
The different Vue directives we use in this tutorial are listed below.
Directive | Details |
---|---|
v-bind | Connects an attribute in an HTML tag to a data variable inside the Vue instance. |
v-if | Creates HTML tags depending on a condition. Directives v-else-if and v-else are used together with the v-if directive. |
v-show | Specifies if an HTML element should be visible or not depending on a condition. |
v-for | Creates a list of tags based on an array in the Vue instance using a for-loop. |
v-on | Connects an event on an HTML tag to a JavaScript expression or a Vue instance method. We can also define more specifically how our page should react to a certain event by using event-modifiers. |
v-model | Used in HTML forms with tags like <form> , <input> and <button> . Creates a two way binding between an input element and a Vue instance data property. |
Example: The v-bind
DirectiveGet your own Vue Server
The browser finds what class to connect the <div> element to from the Vue instance.
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.pinkBG {
background-color: lightpink;
}
</style>
</head>
<body>
<div id="app">
<div v-bind:class="vueClass"></div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
vueClass: "pinkBG"
}
}
})
app.mount('#app')
</script>
</body>
</html>
Note: The example above could be written much simpler without the Vue code, but be patient. The real benefits of Vue can be seen in later examples when we make pages that are responsive.
Learning Vue at W3Schools
When learning Vue at W3Schools.com, you can use our "Try it Yourself" tool, which shows both the code and the result. This will make it easier for you to understand every part as we move forward.
You have already seen that a basic Vue setup consists of a Vue instance and that we can access it from the <div id="app">
tag with {{ }}
or the v-bind
directive.
On this page we will explain the v-bind
directive in more detail.
The v-bind
Directive
The v-bind
directive lets us bind an HTML attribute to data in the Vue instance. This makes it easy to change the attribute value dynamically.
SyntaxGet your own Vue Server
<div v-bind:[attribute]="[Vue data]"></div>
Example
The src
attribute value of an <img>
tag is taken from the Vue instance data property 'url':
<img v-bind:src="url">
CSS Binding
We can use the v-bind
directive to do in-line styling and modify classes dynamically. We will show you briefly how to do that in this section, and later in this tutorial, on the CSS Binding page, we will explain this in more detail.
Bind style
In-line styling with Vue is done by binding the style attribute to Vue with v-bind
.
As a value to the v-bind directive, we can write a JavaScript object with the CSS property and value:
Example
The font size depends on the Vue data property 'size'.
<div v-bind:style="{ fontSize: size }">
Text example
</div>
We can also separate the font size number value from the font size unit if we want to, like this:
Example
The font size number value is stored the Vue data property 'size'.
<div v-bind:style="{ fontSize: size + 'px' }">
Text example
</div>
We could also write the CSS property name with CSS syntax (kebab-case) in hyphens, but it is not recommended:
Example
The CSS property fontSize is referred to as 'font-size'.
<div v-bind:style="{ 'font-size': size + 'px' }">
Text example
</div>
Example
The background color depends on the 'bgVal' data property value inside the Vue instance.
<div v-bind:style="{ backgroundColor: 'hsl('+bgVal+',80%,80%)' }">
Notice the background color on this div tag.
</div>
Example
The background color is set with a JavaScript conditional (ternary) expression depending on whether the 'isImportant' data property value is 'true' or 'false'.
<div v-bind:style="{ backgroundColor: isImportant ? 'lightcoral' : 'lightgray' }">
Conditional background color
</div>
Bind class
We can use v-bind
to change the class attribute.
The value of v-bind:class
can be a variable:
Example
The class
name is taken from the 'className' Vue data property:
<div v-bind:class="className">
The class is set with Vue
</div>
The value of v-bind:class
can also be an object, where the class name will only take effect if it is set to 'true':
Example
The class
attribute is assigned or not depending on if the class 'myClass' is set to 'true' or 'false':
<div v-bind:class="{ myClass: true }">
The class is set conditionally to change the background color
</div>
When the value of v-bind:class
is an object, the class can be assigned depending on a Vue property:
Example
The class
attribute is assigned depending on the 'isImportant' property, if it is 'true' or 'false':
<div v-bind:class="{ myClass: isImportant }">
The class is set conditionally to change the background color
</div>
Shorthand for v-bind
The shorthand for 'v-bind:
' is simply ':
'.
Example
Here we just write ':
' instead of 'v-bind:
':
<div :class="{ impClass: isImportant }">
The class is set conditionally to change the background color
</div>
We will continue to use v-bind:
syntax in this tutorial to avoid confusion.
It is a lot easier to create an HTML element depending on a condition in Vue with the v-if
directive than with plain JavaScript.
With Vue you just write the if-statement directly in the HTML element you want to create conditionally. It's that simple.
Conditional Rendering in Vue
Conditional rendering in Vue is done by using the v-if
, v-else-if
and v-else
directives.
Conditional rendering is when an HTML element is created only if a condition is true, i.e. create the text "In stock" if a variable is 'true', or 'Not in stock' if that variable is 'false'.
ExampleGet your own Vue Server
Write different messages depending on whether there are any typewriters in stock or not:
<p v-if="typewritersInStock">
in stock
</p>
<p v-else>
not in stock
</p>
Conditions in Vue
A condition, or "if-statement", is something that is either true
or false
.
A condition is often a comparison check between two values like in the example above to see if one value is greater than the other.
We use comparison operators like
<
,>=
or!==
to do such checks.Comparison checks can also be combined with logical operators such as
&&
or||
.Go to our JavaScript tutorial page to find out more about JavaScript comparisons.
We can use the number of typewriters in storage with a comparison check to decide if they are in stock or not:
Example
Use a comparison check to decide whether to write "In stock" or "Not in stock" depending on the number of typewriters in storage.
<p v-if="typewriterCount > 0">
in stock
</p>
<p v-else>
not in stock
</p>
Directives for Conditional Rendering
This overview describes how the different Vue directives used for conditional rendering are used together.
Directive | Details |
---|---|
v-if | Can be used alone, or with v-else-if and/or v-else . If the condition inside v-if is 'true', v-else-if or v-else are not considered. |
v-else-if | Must be used after v-if or another v-else-if . If the condition inside v-else-if is 'true', v-else-if or v-else that comes after are not considered. |
v-else | This part will happen if the first part of the if-statement is false. Must be placed at the very end of the if-statement, after v-if and v-else-if . |
To see an example with all three directives shown above, we can expand the previous example with v-else-if
so that the user sees 'In stock', 'Very few left!' or 'Out of stock':
Example
Use a comparison check to decide whether to write "In stock", "Very few left!" or "Not in stock" depending on the number of typewriters in storage.
<p v-if="typewriterCount>3">
In stock
</p>
<p v-else-if="typewriterCount>0">
Very few left!
</p>
<p v-else>
Not in stock
</p>
Use The Return Value from a Function
Instead of using a comparison check with the v-if
directive, we can use the 'true' or 'false' return value from a function:
Example
If a certain text contains the word 'pizza', create a <p> tag with an appropriate message. The 'includes()' method is a native JavaScript method that checks if a text contain certain words.
<div id="app">
<p v-if="text.includes('pizza')">The text includes the word 'pizza'</p>
<p v-else>The word 'pizza' is not found in the text</p>
</div>
data() {
return {
text: 'I like taco, pizza, Thai beef salad, pho soup and tagine.'
}
}
The example above can be expanded to show that v-if
also can create other tags like <div> and <img> tags:
Example
If a certain text contains the word 'pizza', create a <div> tag with a pizza image and a <p> tag with a message. The 'includes()' method is a native JavaScript method that check if a text contain certain words.
<div id="app">
<div v-if="text.includes('pizza')">
<p>The text includes the word 'pizza'</p>
<img src="img_pizza.svg">
</div>
<p v-else>The word 'pizza' is not found in the text</p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
text: 'I like taco, pizza, Thai beef salad, pho soup and tagine.'
}
}
})
app.mount('#app')
</script>
Below the example is expanded even more.
Example
If a certain text contains the word 'pizza' or 'burrito' or none of these words, different images and texts will be created.
<div id="app">
<div v-if="text.includes('pizza')">
<p>The text includes the word 'pizza'</p>
<img src="img_pizza.svg">
</div>
<div v-else-if="text.includes('burrito')">
<p>The text includes the word 'burrito', but not 'pizza'</p>
<img src="img_burrito.svg">
</div>
<p v-else>The words 'pizza' or 'burrito' are not found in the text</p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
text: 'I like taco, pizza, Thai beef salad, pho soup and tagine.'
}
}
})
app.mount('#app')
</script>
With Vue we can now write code that create elements under certain conditions in a much easier way than with traditional JavaScript.
Learn how to make an element visible or not with v-show
.
v-show
is easy to use because the condition is written right in the HTML tag attribute.
Conditional Visibility
The v-show
directive hides an element when the condition is 'false' by setting the CSS 'display' property value to 'none'.
After writing v-show
as an HTML attribute we must give a conditon to decide to have the tag visible or not.
SyntaxGet your own Vue Server
<div v-show="showDiv">This div tag can be hidden</div>
In the code above, 'showDiv' represents a boolean Vue data property with either 'true' or 'false' as property value. If 'showDiv' is 'true' the div tag is shown, and if it is 'false' the tag is not shown.
Example
Display the <div> element only if the showDiv property is set to 'true'.
<div id="app">
<div v-show="showDiv">This div tag can be hidden</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
showDiv: true
}
}
})
app.mount('#app')
</script>
v-show
vs. v-if
The difference between v-show
and v-if
is that v-if
creates (renders) the element depending on the condition, but with v-show
the element is already created, v-show
only changes its visibility.
Therefore, it is better to use v-show
when switching visibility of an object, because it is easier for the browser to do, and it can lead to a faster response and better user experience.
A reason for using v-if
over v-show
is that v-if
can be used with v-else-if
and v-else
.
In the example below v-show
and v-if
are used separately on two different <div> elements, but based on the same Vue property. You can open the example and inspect the code to see that v-show
keeps the <div> element, and only sets the CSS display property to 'none', but v-if
actually destroys the <div> element.
Example
Display the two <div> elements only if the showDiv property is set to 'true'. If the showDiv property is set to 'false' and we inspect the example page with the browser we can see that the <div> element with v-if
is destroyed, but the <div> element with v-show
has just CSS display property set to 'none'.
<div id="app">
<div v-show="showDiv">Div tag with v-show</div>
<div v-if="showDiv">Div tag with v-if</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
showDiv: true
}
}
})
app.mount('#app')
</script>
With normal JavaScript you might want to create HTML elements based on an array. You use a for-loop, and inside you need to create the elements, adjust them, and then add each element to your page. And these elements will not react to a change in the array.
With Vue you start with the HTML element you want to create into a list, with v-for
as an attribute, refer to the array inside the Vue instance, and let Vue take care of the rest. And the elements created with v-for
will automatically update when the array changes.
List Rendering
List rendering in Vue is done by using the v-for
directive, so that several HTML elements are created with a for-loop.
Below are three slightly different examples where v-for
is used.
ExampleGet your own Vue Server
Display a list based on the items of an array.
<ol>
<li v-for="x in manyFoods">{{ x }}</li>
</ol>
Loop Through an Array
Loop through an array to display different images:
Example
Show images of food, based on an array in the Vue instance.
<div>
<img v-for="x in manyFoods" v-bind:src="x">
</div>
Loop Through Array of Objects
Loop through an array of objects and display the object properties:
Example
Show both images and names of different types of food, based on an array in the Vue instance.
<div>
<figure v-for="x in manyFoods">
<img v-bind:src="x.url">
<figcaption>{{ x.name }}</figcaption>
</figure>
</div>
Get the index
The index number of an array element can be really useful in JavaScript for-loops. Luckily we can get the index number when using v-for
in Vue as well.
To get the index number with v-for
we need to give two comma separated words in parentheses: the first word will be the array element, and the second word will be the index of that array element.
Example
Show index number and food name of elements in the 'manyFoods' array in the Vue instance.
<p v-for="(x, index) in manyFoods">
{{ index }}: "{{ x }}" <br>
</p>
We can also display both array element index and information from the array element itself, if the array elements are objects:
Example
Show both the array element index number, and text from the objects in the 'manyFoods' array.
<p v-for="(x, index) in manyFoods">
{{ index }}: "{{ x.name }}", url: "{{ x.url }}" <br>
</p>
Event handling in Vue is done with the v-on
directive, so that we can make something happen when for example a button is clicked.
Event handling is when HTML elements are set up to run a certain code when a certain event happens.
Events in Vue are easy to use and will make our page truly responsive.
Vue methods are code that can be set up to run when an event happens.
With v-on
modifiers you can describe in more detail how to react to an event.
Get startet with events
Lets start with an example to show how we can click a button to count moose in a forest.
We need:
- A button
v-on
on the <button> tag to listen to the 'click' event- Code to increase the number of moose
- A property (variable) in the Vue instance to hold the number of moose
- Double curly braces
{{}}
to show the increased number of moose
ExampleGet your own Vue Server
Click the button to count one more moose in the forest. The count property increases each time the button is clicked.
<div id="app">
<img src="img_moose.jpg">
<p>{{ "Moose count: " + count }}</p>
<button v-on:click="count++">Count moose</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
count: 0
}
}
})
app.mount('#app')
</script>
Note: A benefit that comes with Vue is that the number of moose in the <p> tag is updated automatically. With plain JavaScript we would need to update the number the user sees with an additional line of code.
Events
There are lots of events we can use as triggers for running code, among the most common ones are: 'click', 'mouseover', 'mouseout', 'keydown' and 'input'.
For a complete list of events to use you can visit our HTML DOM Events page.
'v-on'
The v-on
directive allows us to create pages that respond to what the user does.
The Vue v-on
works by telling the browser what event to listen to, and what to do when that event occurs.
Methods
If we want to run more complex code when an event occurs we can put the code in a Vue method and refer to this method from the HTML attribute, like this:
<p v-on:click="changeColor">Click me</p>
Event Modifiers
In addition to v-on
and Vue methods we can use something called event modifiers to modify an event so that it for example only happens once after a page is loaded, or modify an event like 'submit' to prevent a form from being submitted.
Learn More
As we can see, there are three techniques we need to learn about to use events in Vue:
- The Vue
v-on
directive - Vue methods
- Vue
v-on
modifiers
Like event handling in plain JavaScript, the v-on
directive in Vue tells the browser:
- which event to listen to ('click', 'keydown', 'mouseover', etc)
- what to do when that event occurs
Examples using v-on
Let's take a look at some examples to see how v-on
can be used with different events and different code to run when these events occur.
Note: To run more advanced code when an event occurs we need to introduce Vue methods. Learn about Vue methods on the next page in this tutorial.
onclick Event
The v-on directive allows us to perform actions based on specified events.
Use v-on:click
to perform action when the element is clicked.
ExampleGet your own Vue Server
The v-on
directive is used on the <button> tag to listen to the 'click' event. When the 'click' event occurs the 'lightOn' data property is toggled between 'true' and 'false', making the yellow <div> behind the lightbulb visible/hidden.
<div id="app">
<div id="lightDiv">
<div v-show="lightOn"></div>
<img src="img_lightBulb.svg">
</div>
<button v-on:click="lightOn = !lightOn">Switch light</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
lightOn: false
}
}
})
app.mount('#app')
</script>
oninput Event
Use v-on:input
to perform action when the element gets an input, like a keystroke inside a text field.
Example
Count the number of keystroke for a input text field:
<div id="app">
<input v-on:input="inpCount++">
<p>{{ 'Input events occured: ' + inpCount }}</p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
inpCount: 0
}
}
})
app.mount('#app')
</script>
mousemove Event
Use v-on:mousemove
to perform action when the mouse pointer moves over an element.
Example
Change the background color of a <div> element whenever the mouse pointer moves over it:
<div id="app">
<p>Move the mouse pointer over the box below</p>
<div v-on:mousemove="colorVal=Math.floor(Math.random()*360)"
v-bind:style="{backgroundColor:'hsl('+colorVal+',80%,80%)'}">
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
colorVal: 50
}
}
})
app.mount('#app')
</script>
Use v-on in a v-for Loop
You can also use the v-on
directive inside a v-for
loop.
The items of the array are available for each iteration inside the v-on
value.
Example
Display a list based on the food array and add an click event for each item that will use a value from the array item to change the source of an image.
<div id="app">
<img v-bind:src="imgUrl">
<ol>
<li v-for="food in manyFoods" v-on:click="imgUrl=food.url">
{{ food.name }}
</li>
</ol>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
imgUrl: 'img_salad.svg',
manyFoods: [
{name: 'Burrito', url: 'img_burrito.svg'},
{name: 'Salad', url: 'img_salad.svg'},
{name: 'Cake', url: 'img_cake.svg'},
{name: 'Soup', url: 'img_soup.svg'}
]
}
}
})
app.mount('#app')
</script>
Shorthand for v-on
The shorthand for 'v-on
' is simply '@
'.
Example
Here we just write '@
' instead of 'v-on
':
<button @:click="lightOn = !lightOn">Switch light</button>
We will start using @
syntax a little later in this tutorial.
Like event handling in plain JavaScript, the v-on
directive in Vue tells the browser:
- which event to listen to ('click', 'keydown', 'mouseover', etc)
- what to do when that event occurs
Examples using v-on
Let's take a look at some examples to see how v-on
can be used with different events and different code to run when these events occur.
Note: To run more advanced code when an event occurs we need to introduce Vue methods. Learn about Vue methods on the next page in this tutorial.
onclick Event
The v-on directive allows us to perform actions based on specified events.
Use v-on:click
to perform action when the element is clicked.
ExampleGet your own Vue Server
The v-on
directive is used on the <button> tag to listen to the 'click' event. When the 'click' event occurs the 'lightOn' data property is toggled between 'true' and 'false', making the yellow <div> behind the lightbulb visible/hidden.
<div id="app">
<div id="lightDiv">
<div v-show="lightOn"></div>
<img src="img_lightBulb.svg">
</div>
<button v-on:click="lightOn = !lightOn">Switch light</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
lightOn: false
}
}
})
app.mount('#app')
</script>
oninput Event
Use v-on:input
to perform action when the element gets an input, like a keystroke inside a text field.
Example
Count the number of keystroke for a input text field:
<div id="app">
<input v-on:input="inpCount++">
<p>{{ 'Input events occured: ' + inpCount }}</p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
inpCount: 0
}
}
})
app.mount('#app')
</script>
mousemove Event
Use v-on:mousemove
to perform action when the mouse pointer moves over an element.
Example
Change the background color of a <div> element whenever the mouse pointer moves over it:
<div id="app">
<p>Move the mouse pointer over the box below</p>
<div v-on:mousemove="colorVal=Math.floor(Math.random()*360)"
v-bind:style="{backgroundColor:'hsl('+colorVal+',80%,80%)'}">
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
colorVal: 50
}
}
})
app.mount('#app')
</script>
Use v-on in a v-for Loop
You can also use the v-on
directive inside a v-for
loop.
The items of the array are available for each iteration inside the v-on
value.
Example
Display a list based on the food array and add an click event for each item that will use a value from the array item to change the source of an image.
<div id="app">
<img v-bind:src="imgUrl">
<ol>
<li v-for="food in manyFoods" v-on:click="imgUrl=food.url">
{{ food.name }}
</li>
</ol>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
imgUrl: 'img_salad.svg',
manyFoods: [
{name: 'Burrito', url: 'img_burrito.svg'},
{name: 'Salad', url: 'img_salad.svg'},
{name: 'Cake', url: 'img_cake.svg'},
{name: 'Soup', url: 'img_soup.svg'}
]
}
}
})
app.mount('#app')
</script>
Shorthand for v-on
The shorthand for 'v-on
' is simply '@
'.
Example
Here we just write '@
' instead of 'v-on
':
<button @:click="lightOn = !lightOn">Switch light</button>
We will start using @
syntax a little later in this tutorial.
Vue methods are functions that belong to the Vue instance under the 'methods' property.
Vue methods are great to use with event handling (v-on
) to do more complex things.
Vue methods can also be used to do other things than event handling.
The Vue 'methods' Property
We have already used one Vue property in this tutorial, the 'data' property, where we can store values.
There is another Vue property called 'methods' where we can store functions that belong to the Vue instance. A method can be stored in the Vue instance like this:
const app = Vue.createApp({
data() {
return {
text: ''
}
},
methods: {
writeText() {
this.text = 'Hello Wrold!'
}
}
})
Tip: We need to write this.
as prefix to refer to a data property from inside a method.
To call the 'writeText' method when we click the <div>
element we can write the code below:
<div v-on:click="writeText"></div>
The example looks like this:
ExampleGet your own Vue Server
The v-on
directive is used on the <div>
element to listen to the 'click' event. When the 'click' event occurs the 'writeText' method is called and the text is changed.
<div id="app">
<p>Click on the box below:</p>
<div v-on:click="writeText">
{{ text }}
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
text: ''
}
},
methods: {
writeText() {
this.text = 'Hello World!'
}
}
})
app.mount('#app')
</script>
Call a Method with the Event Object
When an event occurs so that a method is called, the event object is passed with the method by default. This is very convenient because the event object contains a lot of useful data, like for example the target object, the event type, or the mouse position when the 'click' or 'mousemove' event occured.
Example
The v-on
directive is used on the <div>
element to listen to the 'mousemove' event. When the 'mousemove' event occurs the 'mousePos' method is called and the event object is sent with the method by default so we can get the mouse pointer position.
We must use the this.
prefix to refer to "xPos" inside the Vue instance data property from the method.
<div id="app">
<p>Move the mouse pointer over the box below:</p>
<div v-on:mousemove="mousePos"></div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
xPos: 0,
yPos: 0
}
},
methods: {
mousePos(event) {
this.xPos = event.offsetX
this.yPos = event.offsetY
}
}
})
app.mount('#app')
</script>
If we expand the example above by just one line, we can also make the background color change based on the mouse pointer position in the x-direction. The only thing we need to add is v-bind
to change the backgound-color in the style attribute:
Example
The difference here from the example above is that the background color is bound to 'xPos' with v-bind
so that hsl 'hue' value is set equal to 'xPos'.
<div
v-on:mousemove="mousePos"
v-bind:style="{backgroundColor:'hsl('+xPos+',80%,80%)'}">
</div>
In the example below the event object carries a text from the <textarea>
tag to make it look like we are writing inside a notebook.
Example
The v-on
directive is used on the <textarea>
tag to listen to the 'input' event which occurs whenever there is a change in the text inside the textarea element.
When the 'input' event occurs the 'writeText' method is called and the event object is sent with the method by default so we can get the text from the <textarea>
tag. The 'text' property in the Vue instance is updated by the 'writeText' method. A span element is set up to show the 'text' value with the double curly braces syntax, and this is updated automatically by Vue.
<div id="app">
<textarea v-on:input="writeText" placeholder="Start writing.."></textarea>
<span>{{ text }}</span>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
text: ''
}
},
methods: {
writeText(event) {
this.text = event.target.value
}
}
})
app.mount('#app')
</script>
Passing Arguments
Sometimes we want to pass an argument with the method when an event occurs.
Lets say you work as a forest ranger, and you want to keep count of moose sightings. Sometimes one or two moose are seen, other times over 10 moose might be seen during a day. We add buttons to count sightings '+1' and '+5', and a '-1' button in case we have counted too many.
In this case we can use the same method for all three buttons, and just call the method with a different number as an argument from the different buttons. This is how we can call a method with an argument:
<button v-on:click="addMoose(5)">+5</button>
And this is how the 'addMoose' method looks like:
methods: {
addMoose(number) {
this.count = this.count + number
}
}
Lets see how passing an argument with a method works in a full example.
Example
<div id="app">
<img src="img_moose.jpg">
<p>{{ "Moose count: " + count }}</p>
<button v-on:click="addMoose(+1)">+1</button>
<button v-on:click="addMoose(+5)">+5</button>
<button v-on:click="addMoose(-1)">-1</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
count: 0
}
},
methods: {
addMoose(number) {
this.count+=number
}
}
})
app.mount('#app')
</script>
Passing both an Argument and The Event Object
If we want to pass both the event object and another argument, there is a reserved name '$event
' we can use where the method is called, like this:
<button v-on:click="addAnimal($event, 5)">+5</button>
And this is how the method in the Vue instance looks like:
methods: {
addAnimal(e, number) {
if(e.target.parentElement.id==="tigers"){
this.tigers = this.tigers + number
}
}
}
Now let us look at an example to see how to pass both the event object and another argument with a method.
Example
In this example our method receives both the event object and a text.
<div id="app">
<img
src="img_tiger.jpg"
id="tiger"
v-on:click="myMethod($event,'Hello')">
<p>"{{ msgAndId }}"</p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
msgAndId: ''
}
},
methods: {
myMethod(e,msg) {
this.msgAndId = msg + ', '
this.msgAndId += e.target.id
}
}
})
app.mount('#app')
</script>
Larger Example
In this example we see that it is possible to use only one method to count three different animals with three different increments for each animal. We acheive this by passing both the event object and the increment number:
Example
Both the increment size and the event object are passed as arguments with the method when a button is clicked. The reserved word '$event
' is used to pass the event object with the method to tell what animal to count.
<div id="app">
<div id="tigers">
<img src="img_tiger.jpg">
<button v-on:click="addAnimal($event,1)">+1</button>
<button v-on:click="addAnimal($event,5)">+5</button>
<button v-on:click="addAnimal($event,1)">-1</button>
</div>
<div id="moose">
<img src="img_moose.jpg">
<button v-on:click="addAnimal($event,1)">+1</button>
<button v-on:click="addAnimal($event,5)">+5</button>
<button v-on:click="addAnimal($event,1)">-1</button>
</div>
<div id="kangaroos">
<img src="img_kangaroo.jpg">
<button v-on:click="addAnimal($event,1)">+1</button>
<button v-on:click="addAnimal($event,5)">+5</button>
<button v-on:click="addAnimal($event,1)">-1</button>
</div>
<ul>
<li>Tigers: {{ tigers }} </li>
<li>Moose: {{ moose }} </li>
<li>Kangaroos: {{ kangaroos }} </li>
</ul>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
tigers: 0,
moose: 0,
kangaroos: 0
}
},
methods: {
addAnimal(e,number) {
if(e.target.parentElement.id==="tigers") {
this.tigers+=number
}
else if(e.target.parentElement.id==="moose") {
this.moose+=number
}
else {
this.kangaroos+=number
}
}
}
})
app.mount('#app')
</script>
Event modifiers in Vue modify how events trigger the running of methods and help us handle events in a more efficient and straightforward way.
Event modifiers are used together with the Vue v-on
directive, to for example:
- Prevent the default submit behavior of HTML forms (
v-on:submit.prevent
) - Make sure that an event can only run once after the page is loaded (
v-on:click.once
) - Specify what keyboard key to use as an event to run a method (
v-on:keyup.enter
)
How To Modify The v-on
Directive
Event modifiers are used to define how to react on an event in more detail.
We use event modifiers by first connecting a tag to an event like we have seen before:
<button v-on:click="createAlert">Create alert</button>
Now, to define more specifically that the button click event should only fire one time after the page loads, we can add the .once
modifier, like this:
<button v-on:click.once="createAlert">Create alert</button>
Here is an example with the .once
modifier:
ExampleGet your own Vue Server
The .once
modifier is used on the <button>
tag to only run the method the first time the 'click' event happens.
<div id="app">
<p>Click the button to create an alert:</p>
<button v-on:click.once="creteAlert">Create Alert</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
methods: {
createAlert() {
alert("Alert created from button click")
}
}
})
app.mount('#app')
</script>
Note: It is also possible to handle an event inside the method instead of using event modifiers, but event modifiers make it a lot easier.
Different v-on
Modifiers
Event modifiers are used in different situations. We can use event modifiers when we listen to keyboard events, mouse click events, and we can even use event modifiers in combination with each other.
The event modifier .once
can be used after both keyboard and mouse click events.
Keyboard Key Event Modifiers
We have three different keyboard event types keydown
, keypress
, and keyup
.
With each key event type, we can specify exactly what key to listen to after a key event occurs. We have .space
, .enter
, .w
and .up
to name a few.
You can write the key event to the web page, or to the console with console.log(event.key)
, to find the value of a certain key yourself:
Example
The keydown
keyboard event triggers the 'getKey' method, and the value 'key' from the event object is written to the console and to the web page.
<input v-on:keydown="getKey">
<p> {{ keyValue }} </p>
data() {
return {
keyValue = ''
}
},
methods: {
getKey(evt) {
this.keyValue = evt.key
console.log(evt.key)
}
}
We can also choose to limit the event to happen only when a mouse click or a key press happens in combination with system modifier keys .alt
, .ctrl
, .shift
or .meta
. The system modifier key .meta
represents the Windows key on Windows computers, or command key on Apple computers.
Key Modifier | Details |
---|---|
.[Vue key alias] | The most common keys have their own aliases in Vue:
|
.[letter] | Specify the letter that comes when you press the key. As an example: use the .s key modifier to listen to the 'S' key. |
.[system modifier key] | .alt , .ctrl , .shift or .meta . These keys can be used in combination with other keys, or in combination with mouse clicks. |
Example
Use the .s
modifier to create an alert when the user writes an 's' inside the <textarea> tag.
<div id="app">
<p>Try pressing the 's' key:</p>
<textarea v-on:keyup.s="createAlert"></textarea>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
methods: {
createAlert() {
alert("You pressed the 'S' key!")
}
}
})
app.mount('#app')
</script>
Combine Keyboard Event Modifiers
Event modifiers can also be used in combination with each other so that more than one thing must happen simultaneous for the method to be called.
Example
Use the .s
and .ctrl
modifiers in combination to create an alert when 's' and 'ctrl' are pressed simultaneously inside the <textarea>
tag.
<div id="app">
<p>Try pressing the 's' key:</p>
<textarea v-on:keydown.ctrl.s="createAlert"></textarea>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
methods: {
createAlert() {
alert("You pressed the 'S' and 'Ctrl' keys, in combination!")
}
}
})
app.mount('#app')
</script>
Mouse Button Modifiers
To react on a mouse click, we can write v-on:click
, but to specify which mouse button that was clicked, we can use .left
, .center
or .right
modifiers.
Trackpad users: You might need to click with two fingers, or in the lower right hand side of the trackpad on your computer to create a right click.
Example
Change the background color when a user right-clicks in the <div>
element:
<div id="app">
<div v-on:click.right="changeColor"
v-bind:style="{backgroundColor:'hsl('+bgColor+',80%,80%)'}">
<p>Press right mouse button here.</p>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
bgColor: 0
}
},
methods: {
changeColor() {
this.bgColor+=50
}
}
})
app.mount('#app')
</script>
Mouse button events can also work in combination with a system modifier key.
Example
Change the background color when a user right-clicks in the <div>
element in combination with the 'ctrl' key:
<div id="app">
<div v-on:click.right.ctrl="changeColor"
v-bind:style="{backgroundColor:'hsl('+bgColor+',80%,80%)'}">
<p>Press right mouse button here.</p>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
bgColor: 0
}
},
methods: {
changeColor() {
this.bgColor+=50
}
}
})
app.mount('#app')
</script>
The event modifier .prevent
can be used in addition to the .right
modifier to prevent the default drop-down menu to appear when we right click.
Example
The drop-down menu is prevented from appearing when you right click to change the background color of the <div>
element:
<div id="app">
<div v-on:click.right.prevent="changeColor"
v-bind:style="{backgroundColor:'hsl('+bgColor+',80%,80%)'}">
<p>Press right mouse button here.</p>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
bgColor: 0
}
},
methods: {
changeColor() {
this.bgColor+=50
}
}
})
app.mount('#app')
</script>
It would be possible to prevent the drop-down menu from appearing after right click by using event.preventDefault()
inside the method, but with the Vue .prevent
modifier the code becomes more readable and easier to maintain.
You can also react on left button mouse clicks in combination with other modifiers, like click.left.shift
:
Example
Hold the 'shift' keyboard key and press left mouse button on the <img>
tag to change image.
<div id="app">
<p>Hold 'Shift' key and press left mouse button:</p>
<img v-on:click.left.shift="changeImg" v-bind:src="imgUrl">
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
imgUrlIndex: 0,
imgUrl: 'img_tiger_square.jpeg',
imgages: [
'img_tiger_square.jpeg',
'img_moose_square.jpeg',
'img_kangaroo_square.jpeg'
]
}
},
methods: {
changeImg() {
this.imgUrlIndex++
if(this.imgUrlIndex>=3){
this.imgUrlIndex=0
}
this.imgUrl = this.images[this.imgUrlIndex]
}
}
})
app.mount('#app')
</script>
Vue gives us an easy way to improve the user experience with forms by adding extra functionality like responsiveness and form validation.
Vue uses the v-model
directive when handling forms.
Our First Form with Vue
Lets start with a simple shopping list example to see how Vue can be used when creating a form.
For more information about forms in HTML, with related tags and attributes, see our HTML Forms tutorial.
1. Add standard HTML form elements:
<form>
<p>Add item</p>
<p>Item name: <input type="text" required></p>
<p>How many: <input type="number"></p>
<button type="submit">Add item</button>
</form>
2. Create the Vue instance with the current item name, number and the shopping list, and use v-model
to connect our inputs to it.
<div id="app">
<form>
<p>Add item</p>
<p>Item name: <input type="text" required v-model="itemName"></p>
<p>How many: <input type="number" v-model="itemNumber"></p>
<button type="submit">Add item</button>
</form>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
itemName: null,
itemNumber: null,
shoppingList: [
{ name: 'Tomatoes', number: 5 }
]
}
}
})
app.mount('#app')
</script>
3. Call the method to add an item to the shopping list, and prevent the default browser refresh on submit.
<form v-on:submit.prevent="addItem">
4. Create the method that adds the item to the shopping list, and clears the form:
methods: {
addItem() {
let item = {
name: this.itemName,
number: this.itemNumber
}
this.shoppingList.push(item);
this.itemName = null
this.itemNumber = null
}
}
5. Use v-for
to show an automatically updated shopping list below the form:
<p>Shopping list:</p>
<ul>
<li v-for="item in shoppingList">{{item.name}}, {{item.number}}</li>
</ul>
Below is the final code for our first Vue form.
ExampleGet your own Vue Server
In this example we can add new items to a shopping list.
<div id="app">
<form v-on:submit.prevent="addItem">
<p>Add item</p>
<p>Item name: <input type="text" required v-model="itemName"></p>
<p>How many: <input type="number" v-model="itemNumber"></p>
<button type="submit">Add item</button>
</form>
<p>Shopping list:</p>
<ul>
<li v-for="item in shoppingList">{{item.name}}, {{item.number}}</li>
</ul>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
itemName: null,
itemNumber: null,
shoppingList: [
{ name: 'Tomatoes', number: 5 }
]
}
},
methods: {
addItem() {
let item = {
name: this.itemName,
number: this.itemNumber
}
this.shoppingList.push(item)
this.itemName = null
this.itemNumber = null
}
}
})
app.mount('#app')
</script>Notice the two-way binding v-model
provides in the example above:
v-model
updates the Vue instance data when the HTML input changev-model
also updates the HTML input when the Vue instance data changes
Compared to normal JavaScript, it is easier to work with forms in Vue because the v-model
directive connects with all types of input elements in the same way.
v-model
creates a link between the input element value
attribute and a data value in the Vue instance. When you change the input, the data updates and when the data changes, the input updates as well (two-way binding).
Two-way Binding
As we have already seen in the shopping list example on the previous page, v-model
provides us with a two-way binding, meaning that the form input elements update the Vue data instance, and a change in the Vue instance data updates the inputs.
The example below also demonstrates the two-way binding with v-model
.
ExampleGet your own Vue Server
Two-way binding: Try to write inside the input field to see that the Vue data property value gets updated. Try also to write directly in the code to change the Vue data property value, run the code, and see how the input field is updated.
<div id="app">
<input type="text" v-model="inpText">
<p> {{ inpText }} </p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
inpText: 'Initial text'
}
}
})
app.mount('#app')
</script>
Note: The v-model
two-way binding functionality could actually be achieved with a combination of v-bind:value
and v-on:input
:
v-bind:value
to update the input element from the Vue instance data,- and
v-on:input
to update the Vue instance data from the input.
But v-model
is much easier to use so that is what we will do.
A Dynamic Checkbox
We add a checkbox to our shopping list on the previous page to mark if an item is important or not.
Next to the checkbox we add a text that allways reflects the current 'important' status, changing dynamically between 'true' or 'false'.
We use v-model
to add this dynamic checkbox and text to improve user interaction.
We need:
- a boolean value in the Vue instance data property called 'important'
- a checkbox where the user can check if the item is important
- a dynamic feedback text so that the user can see if the item is important
Below is how the 'important' feature looks, isolated from the shopping list.
Example
The checkbox text is made dynamic so that the text reflects the current checkbox input value.
<div id="app">
<form>
<p>
Important item?
<label>
<input type="checkbox" v-model="important">
{{ important }}
</label>
</p>
</form>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
important: false
}
}
})
app.mount('#app')
</script>Let's include this dynamic feature in our shopping list example.
Example
<div id="app">
<form v-on:submit.prevent="addItem">
<p>Add item</p>
<p>Item name: <input type="text" required v-model="itemName"></p>
<p>How many: <input type="number" v-model="itemNumber"></p>
<p>
Important?
<label>
<input type="checkbox" v-model="itemImportant">
{{ important }}
</label>
</p>
<button type="submit">Add item</button>
</form>
<hr>
<p>Shopping list:</p>
<ul>
<li v-for="item in shoppingList">{{item.name}}, {{item.number}}</li>
</ul>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
itemName: null,
itemNumber: null,
important: false,
shoppingList: [
{ name: 'Tomatoes', number: 5, important: false }
]
}
},
methods: {
addItem() {
let item = {
name: this.itemName,
number: this.itemNumber
important: this.itemImportant
}
this.shoppingList.push(item)
this.itemName = null
this.itemNumber = null
this.itemImportant = false
}
}
})
app.mount('#app')
</script>
Mark Found Items in The Shopping List
Let's add functionality so that items added to the shopping list can be marked as found.
We need:
- the list items to react on click
- to change the status of the clicked item to 'found', and use this to visually move the item away and strike it through with CSS
We create one list with all items we need to find, and one list below with items found striked through. We can actually put all the items in the first list, and all the items in the second list, and just use v-show
with the Vue data property 'found' to define whether to show the item in the first or second list.
Example
After adding items to the shopping list we can pretend to go shopping, clicking the items away after finding them. If we click an item by mistake we can take it back to the 'not found' list by clicking the item once more.
<div id="app">
<form v-on:submit.prevent="addItem">
<p>Add item</p>
<p>Item name: <input type="text" required v-model="itemName"></p>
<p>How many: <input type="number" v-model="itemNumber"></p>
<p>
Important?
<label>
<input type="checkbox" v-model="itemImportant">
{{ important }}
</label>
</p>
<button type="submit">Add item</button>
</form>
<p><strong>Shopping list:</strong></p>
<ul id="ulToFind">
<li v-for="item in shoppingList"
v-bind:class="{ impClass: item.important }"
v-on:click="item.found=!item.found"
v-show="!item.found">
{{ item.name }}, {{ item.number}}
</li>
</ul>
<ul id="ulFound">
<li v-for="item in shoppingList"
v-bind:class="{ impClass: item.important }"
v-on:click="item.found=!item.found"
v-show="item.found">
{{ item.name }}, {{ item.number}}
</li>
</ul>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
itemName: null,
itemNumber: null,
important: false,
shoppingList: [
{ name: 'Tomatoes', number: 5, important: false, found: false }
]
}
},
methods: {
addItem() {
let item = {
name: this.itemName,
number: this.itemNumber,
important: this.itemImportant,
found: false
}
this.shoppingList.push(item)
this.itemName = null
this.itemNumber = null
this.itemImportant = false
}
}
})
app.mount('#app')
</script>
Use v-model to make The Form Itself Dynamic
We can make a form where the customer orders from a menu. To make it easy for the customer, we only present the drinks to choose from after the customer chooses to order drinks. This is can be argued to be better than presenting the customer with all items from the menu at once. In this example we use v-model
and v-show
to make the form itself dynamic.
We need:
- A form, with relevant input tags and 'Order' button.
- Radio-buttons to select 'Dinner', 'Drink' or 'Dessert'.
- After category is chosen, a dropdown menu appears with all the items in that category.
- When an item is chosen you see an image of it, you can choose how many and add it to the order. The form is reset when the item is added to the order.
Example
This form is dynamic. It changes based on user choices. The user must first choose category, then product and how many, before the order button becomes visible and the user can order it.
Learn more about how to use v-bind
to modify CSS with the style
and class
attributes.
While the concept to change the style
and class
attributes with v-bind
is fairly straight forward, the syntax might need some getting used to.
Dynamic CSS in Vue
You have already seen how we can use Vue to modify CSS by using v-bind
on the style
and class
attributes. It has been explained briefly in this tutorial under v-bind
and several examples with Vue changing CSS has also been given.
Here we will explain in more detail how CSS can be changed dynamically with Vue. But first lets look at two examples with techniques we have already seen in this tutorial: in-line styling with v-bind:style
and assigning a class with v-bind:class
Inline Styling
We use v-bind:style
to do in-line styling in Vue.
ExampleGet your own Vue Server
An <input type="range">
element is used to change the opacity of a <div>
element with the use of in-line styling.
<input type="range" v-model="opacityVal">
<div v-bind:style="{ backgroundColor: 'rgba(155,20,20,'+opacityVal+')' }">
Drag the range input above to change opacity here.
</div>
Assign a Class
We use v-bind:class
to assign a class to an HTML tag in Vue.
Example
Select images of food. Selected food is highlighted with the use of v-bind:class
to show what you have selected.
<div v-for="(img, index) in images">
<img v-bind:src="img.url"
v-on:click="select(index)"
v-bind:class="{ selClass: img.sel }">
</div>
Other Ways to Assign Classes and Style
Here are different aspects regarding the use of v-bind:class
and v-bind:style
that we have not seen before in this tutorial:
- When CSS classes are assigned to an HTML tag with both
class=""
and v-bind:class=""
Vue merges the classes. - An object containing one or more classes is assigned with
v-bind:class="{}"
. Inside the object one or more classes might be toggled on or off. - With in-line styling (
v-bind:style
) camelCase is preferred when defining a CSS property, but 'kebab-case' can also be used if it is written inside quotes. - CSS classes can be assigned with arrays / with array notation / syntax
These points are explained in more detail below.
1. Vue Merges 'class' And 'v-bind:class'
In cases when an HTML tag belongs to a class assigned with class=""
, and is also assigned to a class with v-bind:class=""
, Vue merges the classes for us.
Example
A <div>
element belongs to two classes: 'impClass' and 'yelClass'. The 'important' class is set the normal way with the class
attribute, and 'yellow' class is set with v-bind:class
.
<div class="impClass" v-bind:class="{yelClass: isYellow}">
This div belongs to both 'impClass' and 'yelClass'.
</div>
2. Assign More Than One Class With 'v-bind:class'
When assigning an HTML element to a class with v-bind:class="{}"
, we can simply use comma to separate and assign multiple classes.
Example
A <div>
element can belong to both 'impClass' and 'yelClass' classes, depending on the boolean Vue data properties 'isYellow' and 'isImportant'.
<div v-bind:class="{yelClass: isYellow, impClass: isImportant}">
This tag can belong to both the 'impClass' and 'yelClass' classes.
</div>
3. Camel case vs kebab case notation with 'v-bind:style'
When modifying CSS in Vue with in-line styling (v-bind:style
), it is recommended to use camelCase notation for the CSS property, but 'kebab-case' can also be used if the CSS property is inside quotes.
Example
Here, we set CSS properties background-color
and font-weight
for a <div>
element in two different ways: the recommended way with camelCase backgroundColor
, and the not recommended way with 'kebab-case' in quotes 'font-weight'
. Both alternatives work.
<div v-bind:style="{ backgroundColor: 'lightpink', 'font-weight': 'bolder' }">
This div tag has pink background and bold text.
</div>'Camel case' and 'kebab case' notation are ways of writing a series of words without space or punctuation.
- Camel case notation is when the first word starts with a small letter, and every word after starts with a capital letter, like 'backgroundColor' or 'camelCaseNotation'. It is called camel case because we can imagine every capital letter resembling a hump on a camels back.
- Kebab case notation is when the words are separated with a dash
-
, like 'background-color' or 'kebab-case-notation'. It is called kebab case because we can imagine the dashes resembling the skewer in a 'shish kebab'.
4. Array Syntax with 'v-bind:class'
We can use array syntax with v-bind:class
to add multiple classes. With array syntax we can use both classes that depend on a Vue property and classes that do not depend on a Vue property.
Example
Here, we set CSS classes 'impClass' and 'yelClass' with array syntax. The class 'impClass' depends on a Vue property isImportant
and the class 'yelClass' is always attached to the <div>
element.
<div v-bind:class="[{ impClass: isImportant }, 'yelClass' ]">
This div tag belongs to one or two classes depending on the isImportant property.
</div>Computed properties are like data properties, except they depend on other properties.
Computed properties are written like methods, but they do not accept any input arguments.
Computed properties are updated automatically when a dependency changes, while methods are called on when something happens, like with event handling for example.
Computed properties are used when outputting something that depends on something else.
Computed Properties are Dynamic
The big advantage with a computed property is that it is dynamic, meaning it changes depending on for example the value of one or more data properties.
Computed properties is the third configuration option in the Vue instance that we will learn. The first two configuration options we have already looked at are 'data' and 'methods'.
As with 'data' and 'methods' computed properties also has a reserved name in the Vue instance: 'computed'.
SyntaxGet your own Vue Server
const app = Vue.createApp({
data() {
...
},
computed: {
...
},
methods: {
...
}
})
Computed Property 'yes' or 'no'
Let's say we want a form to create items in a shopping list, and we want to mark if a new item is important or not. We could add a 'true' or 'false' feedback when the checkbox gets checked, like we have done in an example before:
Example
An input element is made dynamic so that the text reflects the status.
<input type="checkbox" v-model="chbxVal"> {{ chbxVal }}
data() {
return {
chbxVal: false
}
}
However, if you you ask someone if something is important, they will most likely answer 'yes' or 'no' instead of 'true' or 'false'. So to make our form more fitting with normal language (more intuitive) we should have 'yes' or 'no' as feedback on the checkbox instead of 'true' or 'false'.
And guess what, a computed property is a perfect tool to help us with that.
Example
With a computed property 'isImportant' we can now customize the text feedback to the user when the checkbox is toggled on and off.
<input type="checkbox" v-model="chbxVal"> {{ isImportant }}
data() {
return {
chbxVal: false
}
},
computed: {
isImportant() {
if(this.chbxVal){
return 'yes'
}
else {
return 'no'
}
}
Computed Properties vs. Methods
Computed properties and methods are both written as functions, but they are different:
- Methods runs when called from HTML, but computed properties updates automatically when a dependency change.
- Computed properties are used the same way we use data properties, but they are dynamic.
A watcher is a method that watches a data property with the same name.
A watcher runs every time the data property value changes.
Use a watcher if a certain data property value requires an action.
The Watcher Concept
Watchers is the fourth configuration option in the Vue instance that we will learn. The first three configuration options we have already looked at are 'data', 'methods' and 'computed'.
As with 'data', 'methods' and 'computed' watchers also has a reserved name in the Vue instance: 'watch'.
SyntaxGet your own Vue Server
const app = Vue.createApp({
data() {
...
},
watch: {
...
},
computed: {
...
},
methods: {
...
}
})As mentioned in the green area at the top, a watcher monitors a data property with the same name.
We never call a watcher method. It is only called automatically when the property value changes.
The new property value is always available as an input argument to the watcher method, and so is the old value.
Example
An <input type="range">
element is used to change a value 'rangeVal'. A watcher is used to prevent the user from choosing values between 20 and 60 that are considered illegal.
<input type="range" v-model="rangeVal">
<p>{{ rangeVal }}</p>
const app = Vue.createApp({
data() {
rangeVal: 70
},
watch: {
rangeVal(val){
if( val>20 && val<60) {
if(val<40){
this.rangeVal = 20;
}
else {
this.rangeVal = 60;
}
}
}
}
})
A Watcher with New and Old Values
In addition to the new property value, the previous property value is also automatically available as an input argument to watcher methods.
Example
We set up click event on a <div>
element to record mouse pointer x-position 'xPos' with a method 'updatePos'. A watcher calculates the difference in pixels between the new x-position and the previous with the use of old and new input arguments to the watcher method.
<div v-on:click="updatePos"></div>
<p>{{ xDiff }}</p>
const app = Vue.createApp({
data() {
xPos: 0,
xDiff: 0
},
watch: {
xPos(newVal,oldVal){
this.xDiff = newVal-oldVal
}
},
methods: {
updatePos(evt) {
this.xPos = evt.offsetX
}
}
})We can also use new and old values to give feedback to the user the exact moment the input goes from being invalid to valid:
Example
The value from an <input>
element is connected to a watcher. If the value includes a '@' it is considered a valid e-mail address. The user gets a feedback text to inform if the input is valid, invalid, or if it just got valid with the last keystroke.
<input v-type="email" v-model="inpAddress">
<p v-bind:class="myClass">{{ feedbackText }}</p>
const app = Vue.createApp({
data() {
inpAddress: '',
feedbackText: '',
myClass: 'invalid'
},
watch: {
inpAddress(newVal,oldVal) {
if( !newVal.includes('@') ) {
this.feedbackText = 'The e-mail address is NOT valid';
this.myClass = 'invalid';
}
else if( !oldVal.includes('@') && newVal.includes('@') ) {
this.feedbackText = 'Perfect! You fixed it!';
this.myClass = 'valid';
}
else {
this.feedbackText = 'The e-mail address is valid :)';
}
}
}
})
Watchers vs. Methods
Watchers and methods are both written as functions, but there are many differences:
- Methods are called from HTML.
- Methods are often called when an event happens.
- Methods automatically receives the event object as an input.
- We can also send other values we choose as an input to a method.
- Watchers are only called when the watched data property value changes, and this happens automatically.
- Watchers automatically receives the new and old value from the watched property.
- We cannot choose to send any other values with a watcher as an input.
Watchers vs. Computed Properties
Watchers and computed properties are both written as functions.
Watchers and computed properties are both called automatically when a dependency change, and never called from HTML.
Here are some differences between computed properties and watchers:
- Watchers only depend on one property, the property they are set up to watch.
- Computed properties can depend on many properties.
- Computed properties are used like data properties, except they are dynamic.
- Watchers are not referred to from HTML.
A template in Vue is what we call the HTML part of our Vue application.
The <template> tag will later be used in *.vue files to structure our code in a better way.
It is possible to use template as a configuration option in the Vue instance, and put the HTML code inside.
The Vue Template
Let's look at an example where we use 'template' as a configuration option. This is a simple example where we have just moved the HTML part into the configuration option 'template':
ExampleGet your own Vue Server
The HTML content from inside the <div id="app">
is moved to the configuration option 'template', encapsulated in backtick quotes `...`
. We can write many lines of HTML inside a backtick quote.
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
template:
`<h1>{{ message }}</h1>
<p>This is a second line of HTML code, inside backtick quotes</p>`,
data() {
return {
message: "Hello World!"
}
}
})
app.mount('#app')
</script>
Single File Components (SFCs)
As you can see in the code example above, also the HTML part of our Vue application can be gathered inside the Vue instance, but this does not make it easier to get an overview in the HTML file.
To get a better overview, to make it easier to handle larger projects, and to get a better development environment, we will now switch to write our Vue code in SFCs, or *.vue files.
All *.vue files only consist of three parts:
<template>
where the HTML content is.
<script>
for our Vue code.
<style>
where we write the CSS styling.
But before we can use *.vue files in our project we need to set up our computer in a different way. Scroll down in this tutorial will explain this.
Using *.vue files for our Vue project makes sense because:
- it becomes easier to handle larger projects with the use of templates and components.
- we can see and test our project through the https protocol, like the users will see the page.
- the page updates immediately when changes are saved, without reloading.
- this is how real web pages in Vue are built.
- it is how developers work.
Why?
As we saw on the previous page about templates and components in Vue, there is now a need for different way to work because we want to:
- have larger projects
- gather all Vue related code in one place
- use components in Vue (we will come to this soon)
- have highlighting and auto-completion support in the editor
- auto-update the browser
And to make all this possible we much switch to *.vue files.
How?
SFCs (Single File Components), or *.vue files, are easier to work with but cannot run directly in the browser, so we need to set up our computer to compile our *.vue files to *.html, *.css and *.js files so that the browser can run our Vue application.
To build our web page based on SFCs we use a program called Vite as the build tool, and we write our code in the VS Code editor with the Volar extension for Vue 3 language features.
Setup
Follow the three steps below to install what you need to run Vue SFC applications on your computer.
The "VS Code" Editor
There are many different editors that can be used for Vue projects. We use the VS Code editor. Download VS Code and install it.
The VS Code "Volar" Extension
To get highlighting and auto-completion with *.vue files in the editor, open VS Code, go to "Extensions" on the left hand side. Search for "Volar" and install the extension with the most downloads and the description "Language support for Vue 3".
Node.js
Download and install the latest version of Node.js, as the Vue build tool "Vite" runs on top of this.
Node.js is an open-source server-side JavaScript runtime environment.
Create The Default Example Project
Follow the steps below to create the default Vue example project on your computer.
Create a folder for your Vue projects on your computer.
In VS Code, open a terminal by choosing Terminal -> New Terminal from the menu:
Use the terminal to navigate to the Vue folder you just created by using commands like cd <folder-name>
, cd ..
, ls
(Mac/Linux) and dir
(Windows). If you are not familiar with writing commands in the terminal, see our introduction to Command Line Interface (CLI) here.
After you have navigated to your Vue folder in the terminal, write:
npm init vue@latest
Create your first project, with project name "firstsfc":
Answer "No" to all options:
Now you should be presented with this in your terminal:
We will now run the commands as suggested above.
Run this command to change directory to your new project inside the 'firstsfc' folder:
cd firstsfc
Install all required dependencies so that the Vue project works:
npm install
Start the development server:
npm run dev
The terminal window should now look like this:
And your browser should open the example project automatically:
If you cannot find the example project in the browser, use the link from the terminal. The link you find in your terminal window might have a different address than the address in the screenshot above.
Now the example project is running on your machine in development mode by the Vite build tool.
The Project Files
The example project that has automatically been created contains many files, and we will take a quick look at a few of them.
main.js
Go to your Vue project in the VS Code editor, find the "main.js" file in the "src" folder:
"main.js" tells Vite how to build the Vue project based on the "App.vue" file. This is similar to how we previously gave a CDN link with the script tag to tell the browser how to run our Vue code, and how we mounted the Vue instance to the <div id="app">
tag.
App.vue
In the same example project folder, find the "App.vue" file and open it. Like all other *.vue files, "App.vue" contains three parts: a <script>
part, a <template>
part and a <style>
part.
App.vue
:
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
</div>
</header>
<main>
<TheWelcome />
</main>
</template>
<style scoped>
header {
line-height: 1.5;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
}
</style>
As you can see in the script part of "App.vue", other *.vue files are referred to: those are 'components' and are located in the 'components' folder. If you take a look in the 'template' part of the 'App.vue' file, you can see tags that are not normal HTML tags: <HelloWorld>
and <TheWelcome>
. This is how the components are referred to. Components are like apps within the app. We will learn more about components soon.
To create our first SFC web page from scratch we will:
- Create a new clean Vue project
- Write code in the 'App.vue' file
- See how the web page updates automatically during development
- Build the page for production
Create a Clean Project
Now we will remove all content in the example project we made on the previous page to create our own simple web page in Vue.
Before we start to write code, remove all content inside the <template>
, <script>
and <style>
tags, and remove any attributes like 'setup' or 'scoped'.
Your 'App.vue' file should now look like this:
App.vue:
<script></script>
<template></template>
<style></style>
Also remove the folders 'assets' and 'components' inside the 'src' folder.
Remove the line where assets are imported inside the 'main.js' file so that 'main.js' looks like this:
main.js:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
We now have an empty project to work with.
Write code in 'App.vue'
Now that we have a clean project, add a header inside the <template>
tag, like this:
<template>
<h1>Hello World!</h1>
</template>
<script></script>
<style></style>
Save the 'App.vue' file, go to your browser by following the localhost link in the terminal. Do you see the result? The browser should now update automatically every time you save a change in VS Code, without having to manually refresh the browser.
Now lets take a look at a slightly larger Vue example:
ExampleGet your own Vue Server
App.vue
:
<template>
<h1>{{ message }}</h1>
</template>
<script>
export default {
data() {
return {
message: 'This is some text'
};
}
};
</script>
<style></style>
Note: In the example above, export default
makes it possible for 'main.js' to catch the data with the import App from './App.vue'
so that it can be mounted on the <div id="app">
tag inside 'index.html'.
Components in Vue lets us decompose our web page into smaller pieces that are easy to work with.
We can work with a Vue component in isolation from the rest of the web page, with its own content and logic.
A web page often consists of many Vue components.
What are Components?
Components are reusable and self-contained pieces of code that encapsulates a specific part of the user interface, so that we can make Vue applications that are scalable and easier to maintain.
We can make components in Vue ourselves, or use built-in components that we will learn about later, like <Teleport>
or <KeepAlive>
. Here we will focus on components we make ourselves.
Creating a Component
Components in Vue is a very powerful tool because it lets our web page become more scalable and bigger projects become easier to handle.
Let's make a component and add it to our project.
Create a new folder components
inside the src
folder.
Inside the components
folder, create a new file FoodItem.vue
. It is common to name components with PascalCase naming convention, without spaces and where all new words starts with a capital letter, also the first word.
Make sure the FoodItem.vue
file look like this:
Code inside the FoodItem.vue
component:
<template>
<div>
<h2>{{ name }}</h2>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
name: 'Apples',
message: 'I like apples'
}
}
};
</script>
<style></style>
As you can see in the example above, components also consist of <template>
, <script>
and <style>
tags, just like our main App.vue
file.
Adding The Component
Notice that the <script>
tag in the example above start with export default
. This means that the object containing the data properties can be received, or imported, in another file. We will use this to implement the FoodItem.vue
component into our existing project by importing it with the main.js
file.
First, rewrite the last line into two lines in your original main.js
file:
main.js
:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
Now, add the FoodItem.vue
component by inserting lines 4 and 7 in your main.js
file:
main.js
:
import { createApp } from 'vue'
import App from './App.vue'
import FoodItem from './components/FoodItem.vue'
const app = createApp(App)
app.component('food-item', FoodItem)
app.mount('#app')
On line 7, the component is added so that we can use it as a custom tag <food-item/>
inside the <template>
tag in our App.vue
file like this:
App.vue
:
<template>
<h1>Food</h1>
<food-item/>
<food-item/>
<food-item/>
</template>
<script></script>
<style></style>
And, let's add some styling inside the <style>
tag in the App.vue
file. Make sure the development server is running, and check out the result.
ExampleGet your own Vue Server
App.vue
:
<template>
<h1>Food</h1>
<food-item/>
<food-item/>
<food-item/>
</template>
<script></script>
<style>
#app > div {
border: dashed black 1px;
display: inline-block;
margin: 10px;
padding: 10px;
background-color: lightgreen;
}
</style>
Development mode: When working with your Vue projects, it is useful to always have your project in development mode by running the following code line in the terminal:
npm run dev
Individual Components
A very useful and powerful property when working with components in Vue is that we can make them behave individually, without having to mark elements with unique IDs like we must do with plain JavaScript. Vue automatically takes care to treat each component individually.
Let's make the <div>
elements count when we click them.
The only thing added to our main application file App.vue
is in CSS to have the cursor look like a hand pointing during hover to imply that there is some sort of click functionality.
CSS code added to the <style>
tag in App.vue
:
#app > div:hover {
cursor: pointer;
}
In our component file FoodItem.vue
we must add a data property count
, a click listener to the <div>
element, a method to run when click happens to increment the counter, and text interpolation {{}}
to show the count.
Example
FoodItem.vue
:
<template>
<div v-on:click="countClicks">
<h2>{{ name }}</h2>
<p>{{ message }}</p>
<p id="red">You have clicked me {{ clicks }} times.</p>
</div>
</template>
<script>
export default {
data() {
return {
name: 'Apples',
message: 'I like apples',
clicks: 0
}
},
methods: {
countClicks() {
this.clicks++;
}
}
}
</script>
<style>
#red {
font-weight: bold ;
color: rgb(144, 12, 12);
}
</style>
We don't have to define unique IDs or do any extra work for Vue to handle the counting individually for each <div>
element, Vue just does this automatically.
But except for the different counter values, the content of the <div>
elements is still the same. In the next page we will learn more about components so that we can use components in a way that makes more sense. For example it would make more sense to display different kind of food in each <div>
element.
Props is a configuration option in Vue.
With props we can pass data to the components via custom attributes to the component tag.
Pass Data To a Component
Do you remember the example on the previous page where all three components said 'Apple'? With props we can now pass data down to our components to give them different content and make them look different.
Let's make a simple page to show 'Apples', 'Pizza' and 'Rice'.
In the main application file App.vue
we create our own attribute 'food-name' to pass a prop with the <food-item/>
component tags:
App.vue
:
<template>
<h1>Food</h1>
<food-item food-name="Apples"/>
<food-item food-name="Pizza"/>
<food-item food-name="Rice"/>
</template>
<script></script>
<style>
#app > div {
border: dashed black 1px;
display: inline-block;
width: 120px;
margin: 10px;
padding: 10px;
background-color: lightgreen;
}
</style>
Receive Data Inside a Component
To receive the data sent via the 'food-item' attribute from App.vue
we use this new 'props' configuration option. We list the attributes received so that our component *.vue file knows about them, and now we can use the props wherever we want in the same way as we use a data property.
FoodItem.vue
:
<script>
export default {
props: [
'foodName'
]
}
</script>
Props attributes are written with a dash -
to separate words (kebab-case) in the <template>
tag, but kebab-case is not legal in JavaScript. So instead we need to write the attribute names as camelCase in JavaScript, and Vue understands this automatically!
Finally, our example with <div>
elements for 'Apples', 'Pizza' and 'Rice' looks like this:
ExampleGet your own Vue Server
App.vue
:
<template>
<h1>Food</h1>
<food-item food-name="Apples"/>
<food-item food-name="Pizza"/>
<food-item food-name="Rice"/>
</template>
FoodItem.vue
:
<template>
<div>
<h2>{{ foodName }}</h2>
</div>
</template>
<script>
export default {
props: [
'foodName'
]
}
</script>
<style></style>
Soon we will see how to pass different data types as props attributes to components, but before we do that, let's expand our code with a description of each type of food, and put the food <div>
elements inside a Flexbox wrapper.
Example
App.vue
:
<template>
<h1>Food</h1>
<div id="wrapper">
<food-item
food-name="Apples"
food-desc="Apples are a type of fruit that grow on trees."/>
<food-item
food-name="Pizza"
food-desc="Pizza has a bread base with tomato sauce, cheese, and toppings on top."/>
<food-item
food-name="Rice"
food-desc="Rice is a type of grain that people like to eat."/>
</div>
</template>
<script></script>
<style>
#wrapper {
display: flex;
flex-wrap: wrap;
}
#wrapper > div {
border: dashed black 1px;
margin: 10px;
padding: 10px;
background-color: lightgreen;
}
</style>
FoodItem.vue
:
<template>
<div>
<h2>{{ foodName }}</h2>
<p>{{ foodDesc }}</p>
</div>
</template>
<script>
export default {
props: [
'foodName',
'foodDesc'
]
}
</script>
<style></style>
Boolean Props
We can achieve different functionality by passing props of different data types, and we are able to define rules for how attributes are given when components are created from App.vue
.
Let's add a new prop 'isFavorite'. This should be a boolean prop with value either true
or false
so that we can use it directly with v-show
to display a favorite stamp <img>
tag if the food is considered a favorite.
To pass props with a data type different to String, we must write v-bind:
in front of the attribute we want to pass.
This is how we pass the boolean 'isFavorite' prop from App.vue
as an attribute 'is-favorite':
App.vue
:
<template>
<h1>Food</h1>
<p>My favorite food has a diploma image attached to it.</p>
<div id="wrapper">
<food-item
food-name="Apples"
food-desc="Apples are a type of fruit that grow on trees."
v-bind:is-favorite="true"/>
<food-item
food-name="Pizza"
food-desc="Pizza has a bread base with tomato sauce, cheese, and toppings on top."
v-bind:is-favorite="false"/>
<food-item
food-name="Rice"
food-desc="Rice is a type of grain that people like to eat."
v-bind:is-favorite="false"/>
</div>
</template>
We receive the boolean 'isFavorite' prop inside FoodItem.vue
and show a favorite stamp if the food is considered favorite:
Example
FoodItem.vue
:
<template>
<div>
<h2>
{{ foodName }}
<img src="/img_quality.svg" v-show="isFavorite">
</h2>
<p>{{ foodDesc }}</p>
</div>
</template>
<script>
export default {
props: ['foodName','foodDesc','isFavorite']
}
</script>
<style>
img {
height: 1.5em;
float: right;
}
</style>
Images: To make the image in the example above work locally in the project on your machine, open the example above, right click the image, choose "Save Image As..." and save it in the "public" folder in your project.
Props Interface
In the example above, based on the code inside FoodItem.vue
, we cannot know for sure that we receive the 'isFavorite' prop, and we cannot know for sure if it is a boolean value. To help us with this we can define the data-type of props we receive, we can set props to be required, and we can even make validation functions to validate the props we receive.
Defining props we receive serves as a documentation for other people if you work in a team, and it provides us with warnings in the console if the rules that we have defined are broken.
Props as an Object
In FoodItem.vue
, we comment out how we defined the props in an array to have it as reference, and instead define the props in an object. We can also define the data type of each prop in addition to the prop name, like this:
FoodItem.vue
:
<script>
export default {
// props: ['foodName','foodDesc','isFavorite']
props: {
foodName: String,
foodDesc: String,
isFavorite: Boolean
}
}
</script>
With props defined in this way, other people can look inside FoodItem.vue
and easily see what the component expects.
If a component is created from the parent element (in our case App.vue
) and given a prop with the wrong data type, you get a warning in the console, like this:
Such warnings are useful to let ourselves and others know that a component is not used how it is supposed to, and to tell what is wrong so that we can correct the mistake.
Required Props
To tell Vue that a prop is required we need to define the prop as an object. Let's make the prop 'foodName' required, like this:
FoodItem.vue
:
<script>
export default {
// props: ['foodName','foodDesc','isFavorite']
props: {
foodName: {
type: String,
required: true
},
foodDesc: String,
isFavorite: Boolean
}
}
</script>
If a component is created from the parent element (in our case App.vue
) and a required prop is not defined, you get a warning in the console, like this:
Such warnings are useful to let ourselves and others know that a component is not used how it is supposed to, and to tell what is wrong so that we can correct the mistake.
Default Value
We can set a default value for a prop.
Let's create a default value for the 'foodDesc' prop in the 'FoodItem' component, and then create such an item for rice without defining the 'foodDesc' prop:
Example
App.vue
:
<template>
<h1>Food</h1>
<p>My favorite food has a diploma image attached to it.</p>
<div id="wrapper">
<food-item
food-name="Apples"
food-desc="Apples are a type of fruit that grow on trees."
v-bind:is-favorite="true"/>
<food-item
food-name="Pizza"
food-desc="Pizza has a bread base with tomato sauce, cheese, and toppings on top."
v-bind:is-favorite="false"/>
<food-item
food-name="Rice"
food-desc="Rice is a type of grain that people like to eat."
v-bind:is-favorite="false"/>
</div>
</template>
FoodItem.vue
:
<script>
export default {
props: {
foodName: {
type: String,
required: true
},
foodDesc: {
type: String,
required: false,
default: 'This is the default description.'
}
isFavorite: {
type: Boolean,
required: false,
default: false
}
}
}
</script>
Props Validator Function
We can also define a validator function that decides if the prop value is valid or not.
Such validator functions must return either true or false. When the validator returns false, it means the prop value is invalid. An invalid prop value generates a warning in the browser console when we run our page in developer mode, and the warning is a useful hint to make sure the components are used as intended.
Let's say we want the food description to be a certain length, between 20 and 50 characters. We can add a validator function to make sure the food description provided has a valid length.
FoodItem.vue
:
<script>
export default {
props: {
foodName: {
type: String,
required: true
},
foodDesc: {
type: String,
required: false,
default: 'This is the default description.',
validator: function(value) {
if( 20<value.length && value.length<50 ) {
return true;
}
else {
return false;
}
}
}
isFavorite: {
type: Boolean,
required: false,
default: false
}
}
}
</script>
Note: If you add the validator code above to your local project, you will get a warning in development mode because the food description for pizza is 65 characters, which is 15 characters longer than the validator function allows.
Modify Props
When a component is created in the parent element we are not allowed to change the value of the prop received in the child element. So inside FoodItem.vue
we cannot change the value of the 'isFavorite' prop we get from App.vue
. The prop is read-only from the parent, which is App.vue
in our case.
But let's say we want the user to be able to change what food is considered favorite by clicking a button. Now there is a need to change the 'isFavorite' prop, but we cannot do it because it is read only.
We are not allowed to change 'isFavorite'. This will generate an error.
methods: {
toggleFavorite() {
this.isFavorite = !this.isFavorite;
}
}
To get around this we can use the prop to initialize a new data value 'foodIsFavorite', inside FoodItem.vue
, like this:
data() {
return {
foodIsFavorite: this.isFavorite
}
}
And now we can add a method so the user can toggle this new data value:
methods: {
toggleFavorite() {
this.foodIsFavorite = !this.foodIsFavorite;
}
}
We must also add the toggle button to each food item, and change v-show
in the <img>
tag to depend on the new data property 'foodIsFavorite'. And to make our example simpler we also slim down the props declaration to just an array.
Example
FoodItem.vue
:
<template>
<div>
<h2>
{{ foodName }}
<img src="/img_quality.svg" v-show="foodIsFavorite">
</h2>
<p>{{ foodDesc }}</p>
<button v-on:click="toggleFavorite">Favorite</button>
</div>
</template>
<script>
export default {
props: ['foodName','foodDesc','isFavorite'],
data() {
return {
foodIsFavorite: this.isFavorite
}
},
methods: {
toggleFavorite() {
this.foodIsFavorite = !this.foodIsFavorite;
}
}
}
</script>
<style>
img {
height: 1.5em;
float: right;
}
</style>
Components can be reused with v-for
to generate many elements of the same kind.
When generating elements with v-for
from a component, it is also very helpful that props can be assigned dynamically based on values from an array.
Create Component Elements with v-for
We will now create component elements with v-for
based on an array with food item names.
ExampleGet your own Vue Server
App.vue
:
<template>
<h1>Food</h1>
<p>Components created with v-for based on an array.</p>
<div id="wrapper">
<food-item
v-for="x in foods"
v-bind:food-name="x"/>
</div>
</template>
<script>
export default {
data() {
return {
foods: ['Apples','Pizza','Rice','Fish','Cake']
};
}
}
</script>
FoodItem.vue
:
<template>
<div>
<h2>{{ foodName }}</h2>
</div>
</template>
<script>
export default {
props: ['foodName']
}
</script>
v-bind Shorthand
To bind props dynamically we use v-bind
, and because we will use v-bind
much more now than before we will use the v-bind:
shorthand :
in the rest of this tutorial.
The 'key' Attribute
If we modify the array after the elements are created with v-for
, errors can emerge because of the way Vue updates such elements created with v-for
. Vue reuses elements to optimize performance, so if we remove an item, already existing elements are reused instead of recreating all elements, and element properties might not be correct anymore.
The reason for elements being reused incorrectly is that elements do not have a unique identifier, and that is exactly what we use the key
attribute for: to let Vue tell the elements apart.
We will generate faulty behavior without the key
attribute, but first let's build a web page with foods using v-for
to display: food name, description, image for favorite food and button to change favorite status.
Example
App.vue
:
<template>
<h1>Food</h1>
<p>Food items are generated with v-for from the 'foods' array.</p>
<div id="wrapper">
<food-item
v-for="x in foods"
:food-name="x.name"
:food-desc="x.desc"
:is-favorite="x.favorite"/>
</div>
</template>
<script>
export default {
data() {
return {
foods: [
{ name: 'Apples',
desc: 'Apples are a type of fruit that grow on trees.',
favorite: true },
{ name: 'Pizza',
desc: 'Pizza has a bread base with tomato sauce, cheese, and toppings on top.',
favorite: false },
{ name: 'Rice',
desc: 'Rice is a type of grain that people like to eat.',
favorite: false }
{ name: 'Fish',
desc: 'Fish is an animal that lives in water.',
favorite: true }
{ name: 'Cake',
desc: 'Cake is something sweet that tastes good.',
favorite: false }
]
};
}
}
</script>
<style>
#wrapper {
display: flex;
flex-wrap: wrap;
}
#wrapper > div {
border: dashed black 1px;
flex-basis: 120px;
margin: 10px;
padding: 10px;
background-color: lightgreen;
}
</style>
FoodItem.vue
:
<template>
<div>
<h2>
{{ foodName }}
<img src="/img_quality.svg" v-show="foodIsFavorite">
</h2>
<p>{{ foodDesc }}</p>
<button v-on:click="toggleFavorite">Favorite</button>
</div>
</template>
<script>
export default {
props: ['foodName','foodDesc','isFavorite'],
data() {
return {
foodIsFavorite: this.isFavorite
}
},
methods: {
toggleFavorite() {
this.foodIsFavorite = !this.foodIsFavorite;
}
}
}
</script>
<style>
img {
height: 1.5em;
float: right;
}
</style>
To see that we need the key
attribute, let's create a button that removes the second element in the array. When this happens, without the key
attribute, the favorite image is transferred from the 'Fish' element to the 'Cake' element, and that is NOT correct:
Example
The only difference from the previous example is that we add a button:
<button @click="removeItem">Remove Item</button>
and a method:
methods: {
removeItem() {
this.foods.splice(1,1);
}
}
in App.vue
.
As mentioned before: this fault, that the favorite image changes from 'fish' to 'cake' when an element is removed, has to do with Vue optimizing the page by reusing elements, and at the same time Vue cannot fully tell the elements apart. That is why we should always include the key
attribute to uniquely mark each element when generating elements with v-for
. When we use the key
attribute, we no longer get this problem.
We do not use the array element index as the key
attribute value because that changes when array elements are removed and added. We could create a new data property to keep a unique value for each item, like an ID number, but because the food items already have unique names we can just use that:
Example
We only need to add one line in App.vue
to uniquely identify each element created with v-for
and fix the problem:
<food-item
v-for="x in foods"
:key="x.name"
:food-name="x.name"
:food-desc="x.desc"
:is-favorite="x.favorite"
/>
With the built-in $emit()
method in Vue we can create a custom event in the child component that can be captured in the parent element.
Props are used to send data from the parent element to the child component, and $emit()
is used to do the oposite: to pass information from the child component to the parent.
The purpose of the things we will do next is to end up with the 'favorite' status of a food item to be changed in the parent App.vue
instead of in the the FoodItem.vue
child component where the change is currently happening.
The reason for changing the favorite status in App.vue
instead of in FoodItem.vue
is that App.vue
is where the favorite status is stored in the first place, so that needs to be updated. In a larger project the data might come from a database we have connection to in App.vue
, and we want a change happening from the component to make a change in the database, so we need to communicate back to the parent from the child component.
Emit a Custom Event
There is a need to send information from the component to the parent, and we use the built-in method $emit()
to do that.
We already have the toggleFavorite
method inside the FoodItem.vue
component that runs when the toggle button is clicked. Now let's remove the existing line and add a line to emit our custom event 'toggle-favorite':
FoodItem.vue
:
methods: {
toggleFavorite() {
this.foodIsFavorite = !this.foodIsFavorite;
this.$emit('toggle-Favorite');
}
}
We can choose the name of our custom event, but it is normal to use kebab-case for emit events.
Receive an Emit Event
The custom emit event 'toggle-favorite' is now emitted from the FoodItem.vue
component, but we need to listen to the event in the App.vue
parent and call a method that does something so that we can see that the event happened.
We listen to the event with the shorthand @
instead of v-on:
in App.vue
where the component is created:
ExampleGet your own Vue Server
Listen to the 'toggle-favorite' event in App.vue
:
<food-item
v-for="x in foods"
:key="x.name"
:food-name="x.name"
:food-desc="x.desc"
:is-favorite="x.favorite"
@toggle-favorite="receiveEmit"
/>
When our custom 'toggle-favorite' event happens, we need to create the 'testEmit' method in App.vue
so that we can see that the event happened:
methods: {
receiveEmit() {
alert('Hello World!');
}
}
Change The Food Item 'favorite' Status in The Parent
We now have an event that notifies App.vue
when the 'Favorite' button is clicked from the child component.
We want to change the 'favorite' property in the 'foods' array in App.vue
for the correct food item when a 'Favorite' button is clicked. To do that we send the food item name from FoodItem.vue
to App.vue
because that is unique for each food item:
FoodItem.vue
:
methods: {
toggleFavorite() {
this.$emit('toggle-favorite', this.foodName);
}
}
We can now receive the food item name in App.vue
as an argument to the method called when the 'toggle-favorite' event happens, like this:
Example
App.vue
:
methods: {
receiveEmit(foodId) {
alert( 'You clicked: ' + foodId );
}
}
Now that we know what food item that was clicked we can update the 'favorite' status for the correct food item inside the 'foods' array:
App.vue
:
methods: {
receiveEmit(foodId) {
const foundFood = this.foods.find(
food => food.name === foodId
);
foundFood.favorite = !foundFood.favorite;
}
}
In the code above, the array method 'find' goes through the 'foods' array and looks for an object with name property equal to the food item we have clicked, and returns that object as 'foundFood'. After that we can set 'foundFood.health' to be oposite to what it was before so that it toggles between true
and false
.
Learn more about the JavaScript array method 'find' here.
Learn more about JavaScript arrow functions here.
The correct food inside the 'foods' array now gets its 'favorite' status updated. The only thing remaining is to get the image indicating favorite food updated.
Because the food item components are already created with the 'favorite' status from the 'foods' array and sent as a prop 'is-favorite' from App.vue
, we just need to refer to this 'isFavorite' prop in FoodItem.vue
from v-show
where the <img>
element is to update the image:
<img src="/img_quality.svg" v-show="isFavorite">
We can also delete the 'foodIsFavorite' data property in FoodItem.vue
because it is no longer in use.
Example
In this final example code the favorite status of the food items can be toggled in a similar way as before, but now the favorite status is modified in the correct place, inside App.vue
.
The 'emits' Option
In the same way that we declare props inside the FoodItem.vue
component, we can also document what the component emits by using the Vue 'emits' option.
Props must be declared in the component, while emits are just recommended to be documented.
This is how we can document our emit in the FoodItem.vue
component:
<script>
export default {
props: ['foodName','foodDesc','isFavorite'],
emits: ['toggle-favorite'],
methods: {
toggleFavorite() {
this.$emit('toggle-favorite', this.foodName);
}
}
};
</script>
The component becomes easier for others to use when the emits are documented.
A component can be called with attributes that are not declared as props, and they will simply fall through to the root element in the component.
With fallthrough attributes you get a better overview from the parent where the component is created, and it simplifies our code because we don't need to declare the attribute as a prop.
Typical attributes used to fall through are class
, style
and v-on
.
Fallthrough Attributes
It can be nice to for example control the component styling from the parent rather than having the styling hidden away inside the component.
Let's create a new example, a basic todo list in Vue, and see how the style attribute falls through to the components representing things to do.
So, our App.vue
should contain the list of things to do, and an <input>
element and a <button>
to add new things to do. Each list item is a <todo-item />
component.
App.vue
:
<template>
<h3>Todo List</h3>
<ul>
<todo-item
v-for="x in items"
:key="x"
:item-name="x"
/>
</ul>
<input v-model="newItem">
<button @click="addItem">Add</button>
</template>
<script>
export default {
data() {
return {
newItem: '',
items: ['Buy apples','Make pizza','Mow the lawn']
};
},
methods: {
addItem() {
this.items.push(this.newItem),
this.newItem = '';
}
}
}
</script>
And TodoItem.vue
just receives the description of what to do as a prop:
TodoItem.vue
:
<template>
<li>{{ itemName }}</li>
</template>
<script>
export default {
props: ['itemName']
}
</script>
To build our application correctly we also need the right setup in main.js
:
main.js
:
import { createApp } from 'vue'
import App from './App.vue'
import TodoItem from './components/TodoItem.vue'
const app = createApp(App)
app.component('todo-item', TodoItem)
app.mount('#app')
To see the point of this section, that properties can fall through to the root element inside the <template>
of our component, we can give the list items some styling from App.vue
:
ExampleGet your own Vue Server
We give styling to the <li>
elements inside the component, from App.vue
:
<template>
<h3>Todo List</h3>
<ul>
<todo-item
v-for="x in items"
:key="x"
:item-name="x"
style="background-color: lightgreen;"
/>
</ul>
<input v-model="newItem">
<button @click="addItem">Add</button>
</template>
To confirm that the style attribute has actually fallen through we can right click an <li>
element in our todo list in the browser, choose 'Inspect', and we can see the style attribute is now on the <li>
element:
Merging 'class' and 'style' Attributes
If 'class' or 'style' attributes are already set, and 'class' or 'style' attributes also comes from the parent as fallthrough attributes, the attributes will be merged.
Example
In addition to the existing styling from the parent, we add a margin to the <li>
elements inside the TodoItem.vue
component:
<template>
<li style="margin: 5px 0;">{{ itemName }}</li>
</template>
<script>
export default {
props: ['itemName']
}
</script>
If we right click an <li>
element in the browser we can see that the attributes have been merged. Margin is set directly on the <li>
element inside the component, and is merged with the background-color that falls through from the parent:
$attrs
If we have more than one element on the root level of the component, it is no longer clear which element the attributes should fall through to.
To define which root element gets the fallthrough attributes we can mark the element with the built-in $attrs
object, like this:
Example
TodoItem.vue
:
<template>
<div class="pinkBall"></div>
<li v-bind="$attrs">{{ itemName }}</li>
<div class="pinkBall"></div>
</template>
Styling defined inside the <style>
tag in a component, or in App.vue
, is actually available globally in all components.
To keep the styling limited locally to just the component, we can use the scope
attribute on that component: <style scoped>
Global Styling
CSS written inside the <style>
tag in any *.vue
file works globally.
This means that if we for example set <p>
tags to have pink background color inside the <style>
tag in one *.vue
file, this will affect <p>
tags in all of the *.vue
files in that project.
ExampleGet your own Vue Server
In this application we have three *.vue
files: App.vue
, and two components CompOne.vue
and CompTwo.vue
.
The CSS styling inside CompOne.vue
affects <p>
tags in all three *.vue
files:
<template>
<p>This p-tag belongs to 'CompOne.vue'</p>
</template>
<script></script>
<style>
p {
background-color: pink;
width: 150px;
}
</style>
Scoped Styling
To avoid that the styling in one component affects the styling of elements in other components we use the 'scoped' attribute on the <style>
tag:
Example
The <style>
tag in CompOne.vue
is given the scoped
attribute:
<template>
<p>This p-tag belongs to 'CompOne.vue'</p>
</template>
<script></script>
<style scoped>
p {
background-color: pink;
width: 150px;
}
</style>
The way we have included components so far makes them accessible from all *.vue
files in a project.
Components can be made to be local, meaning that they are only accessible inside a specific *.vue
file.
Global Components
The way we have included components inside main.js
so far make the components accessible inside the <template>
of all other *.vue
files in that project.
ExampleGet your own Vue Server
We use the CompOne.vue
component inside both CompTwo.vue
and App.vue
to show that components are accessible to each other with our current main.js
setup.
main.js
:
import { createApp } from 'vue'
import App from './App.vue'
import CompOne from './components/CompOne.vue'
import CompTwo from './components/CompTwo.vue'
const app = createApp(App)
app.component('comp-one', CompOne)
app.component('comp-two', CompTwo)
app.mount('#app')
Local Components
We can include a component directly in the <script>
tag in a *.vue
file instead of including it in main.js
.
If we include a component directly in a *.vue
file, the component beomes accessible only locally in that file.
Example
To make CompOne.vue
local to App.vue
, and only accessible there, we remove it from main.js
.
main.js
:
import { createApp } from 'vue'
import App from './App.vue'
import CompOne from './components/CompOne.vue'
import CompTwo from './components/CompTwo.vue'
const app = createApp(App)
app.component('comp-one', CompOne)
app.component('comp-two', CompTwo)
app.mount('#app')
And include CompOne.vue
directly in the <script>
tag of App.vue
instead.
App.vue
:
<template>
<h3>Local Component</h3>
<p>The CompOne.vue component is a local component and can only be used inside App.vue.</p>
<comp-one />
<comp-two />
</template>
<script>
import CompOne from './components/CompOne.vue';
export default {
components: {
'comp-one': CompOne
}
}
</script>
The CompOne.vue
component is now only available in App.vue
.
Slots are a powerful feature in Vue that allow for more flexible and reusable components.
We use slots in Vue to send content from the parent into the <template>
of a child component.
Slots
So far we have just used components inside <template>
as self-closing tags like this:
App.vue
:
<template>
<slot-comp />
</template>
Instead, we can use opening and closing tags, and put some content inside, like for example a text:
App.vue
:
<template>
<slot-comp>Hello World!</slot-comp>
</template>
But to receive 'Hello World!' inside the component and display it on our page, we need to use the <slot>
tag inside the component. The <slot>
tag acts as a placeholder for the content, so that after the application is built the <slot>
will be replaced by the content sent to it.
ExampleGet your own Vue Server
SlotComp.vue
:
<template>
<div>
<p>SlotComp.vue</p>
<slot></slot>
</div>
</template>
Slots as Cards
Slots can also be used to wrap around larger chunks of dynamic html content to get a card-like appearance.
Earlier we have sent data as props to create content inside compoents, now we can just send the HTML content directly inside the <slot>
tag as it is.
Example
App.vue
:
<template>
<h3>Slots in Vue</h3>
<p>We create card-like div boxes from the foods array.</p>
<div id="wrapper">
<slot-comp v-for="x in foods">
<img v-bind:src="x.url">
<h4>{{x.name}}</h4>
<p>{{x.desc}}</p>
</slot-comp>
</div>
</template>
As the content enters the component where the <slot>
is, we use a div around the <slot>
and style the <div>
locally to create a card-like appearance around the content without affecting other divs in our application.
SlotComp.vue
:
<template>
<div> <!-- This div makes the card-like appearance -->
<slot></slot>
</div>
</template>
<script></script>
<style scoped>
div {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
border-radius: 10px;
margin: 10px;
}
</style>
Components that produce a card-like frame around content can be reused to create different elements, but with the same card-like frame around.
In this example we use the same component as for the food items to create a footer.
Example
App.vue
:
<template>
<h3>Reusable Slot Cards</h3>
<p>We create card-like div boxes from the foods array.</p>
<p>We also create a card-like footer by reusing the same component.</p>
<div id="wrapper">
<slot-comp v-for="x in foods">
<img v-bind:src="x.url">
<h4>{{x.name}}</h4>
</slot-comp>
</div>
<footer>
<slot-comp>
<h4>Footer</h4>
</slot-comp>
</footer>
</template>
Fallback Content
If a component is created without content we can have fallback content in the <slot>
.
Example
The first component in this application has no content provided, so the fallback content is rendered.
App.vue
:
<template>
<h3>Slots Fallback Content</h3>
<p>A component without content provided can have fallback content in the slot tag.</p>
<slot-comp>
<!-- Empty -->
</slot-comp>
<slot-comp>
<h4>This content is provided from App.vue</h4>
</slot-comp>
</template>
SlotComp.vue
:
<template>
<div>
<slot>
<h4>This is fallback content</h4>
</slot>
</div>
</template>
We need the v-slot
directive to refer to named slots.
Named slots allow for more control over where the content is placed within the child component's template.
Named slots can be used to create more flexible and reusable components.
Before using v-slot
and named slots, let's see what happens if we use two slots in the component:
ExampleGet your own Vue Server
App.vue
:
<h1>App.vue</h1>
<p>The component has two div tags with one slot in each.</p>
<slot-comp>'Hello!'</slot-comp>
SlotComp.vue
:
<h3>Component</h3>
<div>
<slot></slot>
</div>
<div>
<slot></slot>
</div>
With two slots in a component, we can see that the content simply appears both places.
v-slot and Named Slots
If we have more than one <slot>
in a component, but we want to control in which <slot>
the content should appear, we need to name the slots and use v-slot
to send the content to the right place.
Example
To be able to differentiate the slots we give the slots different names.
SlotComp.vue
:
<h3>Component</h3>
<div>
<slot name="topSlot"></slot>
</div>
<div>
<slot name="bottomSlot"></slot>
</div>
And now we can use v-slot
in App.vue
to direct the content to the right slot.
App.vue
:
<h1>App.vue</h1>
<p>The component has two div tags with one slot in each.</p>
<slot-comp v-slot:bottomSlot>'Hello!'</slot-comp>
Default Slots
If you have a <slot>
with no name, that <slot>
will be default for components marked with v-slot:default
, or components that are not marked with v-slot
.
To see how this works we just need to make two small changes in our previous example:
Example
SlotComp.vue
:
<h3>Component</h3>
<div>
<slot name="topSlot"></slot>
</div>
<div>
<slot name="bottomSlot"></slot>
</div>
App.vue
:
<h1>App.vue</h1>
<p>The component has two div tags with one slot in each.</p>
<slot-comp v-slot:bottomSlot>'Hello!'</slot-comp>
As already mentioned, we can mark content with the default value v-slot:default
to make it even more clear that the content belongs to the default slot.
Example
SlotComp.vue
:
<h3>Component</h3>
<div>
<slot></slot>
</div>
<div>
<slot name="bottomSlot"></slot>
</div>
App.vue
:
<h1>App.vue</h1>
<p>The component has two div tags with one slot in each.</p>
<slot-comp v-slot:default>'Default slot'</slot-comp>
v-slot in <template>
As you have seen the v-slot
directive can be used as an attribute in the component tag.
v-slot
can also be used in a <template>
tag to direct larger parts of content to a certain <slot>
.
Example
App.vue
:
<h1>App.vue</h1>
<p>The component has two div tags with one slot in each.</p>
<slot-comp>
<template v-slot:bottomSlot>
<h4>To the bottom slot!</h4>
<p>This p tag and the h4 tag above are directed to the bottom slot with the v-slot directive used on the template tag.</p>
</template>
<p>This goes into the default slot</p>
</slot-comp>
SlotComp.vue
:
<h3>Component</h3>
<div>
<slot></slot>
</div>
<div>
<slot name="bottomSlot"></slot>
</div>
We use the <template>
tag to direct some content to a certain <slot>
because the <template>
tag is not rendered, it is just a placeholder for the content. You can see this by inspecting the built page: you will not find the template tag there.
v-slot Shorthand #
The shorthand for v-slot:
is #
.
This means that:
<slot-comp v-slot:topSlot>'Hello!'</slot-comp>
Can be written as:
<slot-comp #topSlot>'Hello!'</slot-comp>
Example
App.vue
:
<h1>App.vue</h1>
<p>The component has two div tags with one slot in each.</p>
<slot-comp #topSlot>'Hello!'</slot-comp>
SlotComp.vue
:
<h3>Component</h3>
<div>
<slot name="topSlot"></slot>
</div>
<div>
<slot name="bottomSlot"></slot>
</div>
A Scoped slot provides local data from the component so that the parent can choose how to render it.
Send Data to Parent
We use v-bind
in the component slot to send local data to the parent:
SlotComp.vue
:
<template>
<slot v-bind:lclData="data"></slot>
</template>
<script>
export default {
data() {
return {
data: 'This is local data'
}
}
}
</script>
The data inside the component can be referred to as 'local' because it is not accessible to the parent unless it is sent up to the parent like we do here with v-bind
.
Receive Data from Scoped Slot
The local data in the component is sent with v-bind
, and it can be received in the parent with v-slot
:
ExampleGet your own Vue Server
App.vue
:
<slot-comp v-slot:"dataFromSlot">
<h2>{{ dataFromSlot.lclData }}</h2>
</slot-comp>
In the example above, 'dataFromSlot' is just a name we can choose ourselves to represent the data object we receive from the scoped slot. We get the text string from the slot by using the 'lclData' property, and we use interpolation to finally render the text in an <h2>
tag.
Scoped Slot with an Array
A scoped slot can send data from an array by using v-for
, but the code in App.vue
is basically the same:
Example
SlotComp.vue
:
<template>
<slot
v-for="x in foods"
:key="x"
:foodName="x"
></slot>
</template>
<script>
export default {
data() {
return {
foods: ['Apple','Pizza','Rice','Fish','Cake']
}
}
}
</script>
App.vue
:
<slot-comp v-slot="food">
<h2>{{ food.foodName }}</h2>
</slot-comp>
Scoped Slot with an Array of Objects
A scoped slot can send data from an array of objects by using v-for
:
Example
SlotComp.vue
:
<template>
<slot
v-for="x in foods"
:key="x.name"
:foodName="x.name"
:foodDesc="x.desc"
:foodUrl="x.url"
></slot>
</template>
<script>
export default {
data() {
return {
foods: [
{ name: 'Apple', desc: 'Apples are a type of fruit that grow on trees.', url: 'img_apple.svg' },
{ name: 'Pizza', desc: 'Pizza has a bread base with tomato sauce, cheese, and toppings on top.', url: 'img_pizza.svg' },
{ name: 'Rice', desc: 'Rice is a type of grain that people like to eat.', url: 'img_rice.svg' },
{ name: 'Fish', desc: 'Fish is an animal that lives in water.', url: 'img_fish.svg' },
{ name: 'Cake', desc: 'Cake is something sweet that tastes good but is not considered healthy.', url: 'img_cake.svg' }
]
}
}
}
</script>
App.vue
:
<slot-comp v-slot="food">
<hr>
<h2>{{ food.foodName }}<img :src=food.foodUrl></h2>
<p>{{ food.foodDesc }}</p>
</slot-comp>
Static Data from a Scoped Slot
A scoped slot can also send static data, that is data that does not belong to the data property of the Vue instance.
When sending static data we do not use v-bind
.
In the example below we send one static text, and one text bound dynamically to the data instance so that we can see the difference.
Example
SlotComp.vue
:
<template>
<slot
staticText="This text is static"
:dynamicText="text"
></slot>
</template>
<script>
export default {
data() {
return {
text: 'This text is from the data property'
}
}
}
</script>
App.vue
:
<slot-comp v-slot="texts">
<h2>{{ texts.staticText }}</h2>
<p>{{ texts.dynamicText }}</p>
</slot-comp>
Named Scoped Slots
Scoped slots can be named.
To use named scoped slots we need to name the slots inside the component with the 'name' attribute.
And to receive data from a named slot we need to refer to that name in the parent where we use the component, with the v-slot
directive, or shorthand #
.
Example
In this example the component is created one time referring to the slot "leftSlot", and one time referring to the slot "rightSlot".
SlotComp.vue
:
<template>
<slot
name="leftSlot"
:text="leftText"
></slot>
<slot
name="rightSlot"
:text="rightText"
></slot>
</template>
<script>
export default {
data() {
return {
leftText: 'This text belongs to the LEFT slot.',
rightText: 'This text belongs to the RIGHT slot.'
}
}
}
</script>
App.vue
:
<slot-comp #leftSlot="leftProps">
<div>{{ leftProps.text }}</div>
</slot-comp>
<slot-comp #rightSlot="rightProps">
<div>{{ rightProps.text }}</div>
</slot-comp>
Alternatively, we can create the component one time, with two different "template"
tags, each "template"
tag referring to a different slot.
Example
In this example the component is created only one time, but with two "template"
tags, each referring to a different slot.
SlotComp.vue
is exactly the same as in the previous example.
App.vue
:
<slot-comp>
<template #leftSlot="leftProps">
<div>{{ leftProps.text }}</div>
</template>
<template #rightSlot="rightProps">
<div>{{ rightProps.text }}</div>
</template>
</slot-comp>
Dynamic Components can be used to flip through pages within your page, like tabs in your browser, with the use of the 'is' attribute.
The Component Tag and The 'is' Attribute
To make a dynamic component we use the <component>
tag to represent the active component. The 'is' attribute is tied to a value with v-bind
, and we change that value to the name of the component we want to have active.
ExampleGet your own Vue Server
In this example we have a <component>
tag that acts as a placeholder for either the comp-one
component or the comp-two
component. The 'is' attribute is set on the <component>
tag and listens to the computed value 'activeComp' that holds either 'comp-one' or 'comp-two' as value. And we have a button that toggles a data property between 'true' and 'false' to make the computed value switch between the active components.
App.vue
:
<template>
<h1>Dynamic Components</h1>
<p>App.vue switches between which component to show.</p>
<button @click="toggleValue = !toggleValue">
Switch component
</button>
<component :is="activeComp"></component>
</template>
<script>
export default {
data() {
return {
toggleValue: true
}
},
computed: {
activeComp() {
if(this.toggleValue) {
return 'comp-one'
}
else {
return 'comp-two'
}
}
}
}
</script>
<KeepAlive>
Run the example below. You will notice that changes you make in one component is forgotten when you switch back to it. That is because the component is unmounted and mounted again, reloading the component.
Example
This example is the same as the previous example except the components are different. In comp-one
you can choose between 'Apple' and 'Cake', and in comp-two
you can write a message. Your inputs will be gone when you return to a component.
To keep the state, your previous inputs, when returning to a component we use the <KeepAlive>
tag around the <component>
tag.
Example
The components now remember the user inputs.
App.vue
:
<template>
<h1>Dynamic Components</h1>
<p>App.vue switches between which component to show.</p>
<button @click="toggleValue = !toggleValue">
Switch component
</button>
<KeepAlive>
<component :is="activeComp"></component>
</KeepAlive>
</template>
The 'include' and 'exclude' Attributes
All components inside the <KeepAlive>
tag will be kept alive by default.
But we can also define only some components to be kept alive by using 'include' or 'exclude' attributes on the <KeepAlive>
tag.
If we use the 'include' or 'exclude' attributes on the <KeepAlive>
tag we also need to give the components names with the 'name' option:
CompOne.vue
:
<script>
export default {
name: 'CompOne',
data() {
return {
imgSrc: 'img_question.svg'
}
}
}
</script>
Example
With <KeepAlive include="CompOne">
, only the 'CompOne' component will remember its state, the previous inputs.
App.vue
:
<template>
<h1>Dynamic Components</h1>
<p>App.vue switches between which component to show.</p>
<button @click="toggleValue = !toggleValue">
Switch component
</button>
<KeepAlive include="CompOne">
<component :is="activeComp"></component>
</KeepAlive>
</template>
We can also use 'exclude' to choose which components to keep alive or not.
Example
With <KeepAlive exclude="CompOne">
, only the 'CompTwo' component will remember its state.
App.vue
:
<template>
<h1>Dynamic Components</h1>
<p>App.vue switches between which component to show.</p>
<button @click="toggleValue = !toggleValue">
Switch component
</button>
<KeepAlive exclude="CompOne">
<component :is="activeComp"></component>
</KeepAlive>
</template>
Both 'include' and 'exclude' can be used with multiple components by using comma separation.
To show this we will add one more component so that we get three components in total.
Example
With <KeepAlive include="CompOne, CompThree">
, both the 'CompOne' and the 'CompThree' components will remember their state.
App.vue
:
<template>
<h1>Dynamic Components</h1>
<button @click="compNbr++">
Next component
</button>
<KeepAlive include="CompOne,CompThree">
<component :is="activeComp"></component>
</KeepAlive>
</template>
The 'max' Attribute
We can use 'max' as an attribute to the <KeepAlive>
tag to limit the number of components the browser needs to remember the state of.
Example
With <KeepAlive :max="2">
, the browser will only remember the user input of the last two visited components.
App.vue
:
<template>
<h1>Dynamic Components</h1>
<label><input type="radio" name="rbgComp" v-model="compName" :value="'comp-one'"> One</label>
<label><input type="radio" name="rbgComp" v-model="compName" :value="'comp-two'"> Two</label>
<label><input type="radio" name="rbgComp" v-model="compName" :value="'comp-three'"> Three</label>
<KeepAlive :max="2">
<component :is="activeComp"></component>
</KeepAlive>
</template>
The Vue <Teleport>
tag is used to move content to a different place in the DOM structure.
<Teleport> and The 'to' Attribute
To move some content to somewhere else in the DOM structure we use the Vue <Teleport>
tag around the content and the 'to' attribute to define where to move it.
<Teleport to="body">
<p>Hello!</p>
</Teleport>
The 'to' attribute value is given as CSS notation, so if we want to send some content to the body tag like in the code above we simply write <Teleport to="body">
.
We can see that the content is moved to the body tag by inspecting the page after it has loaded.
ExampleGet your own Vue Server
CompOne.vue
:
<template>
<div>
<h2>Component</h2>
<p>This is the inside of the component.</p>
<Teleport to="body">
<div id="redDiv">Hello!</div>
</Teleport>
</div>
</template>
If we right-click our page and choose 'Inspect', we can see that the red <div>
element is moved out of the component and to the end of the <body>
tag.
We could also for example have a tag with an id <div id="receivingDiv">
and teleport some content to that <div>
by using <Teleport to="#receivingDiv">
around the content we want to teleport/move.
Script and Style of Teleported Elements
Even though some content is moved out of a component with the <Teleport>
tag, relevant code inside the component in the <script>
and <style>
tags still works for the moved content.
Example
Relevant code from the <style>
and <script>
tags still works for the teleported <div>
tag even though it is no longer inside the component after compilation.
CompOne.vue
:
<template>
<div>
<h2>Component</h2>
<p>This is the inside of the component.</p>
<Teleport to="body">
<div
id="redDiv"
@click="toggleVal = !toggleVal"
:style="{ backgroundColor: bgColor }"
>
Hello!<br>
Click me!
</div>
</Teleport>
</div>
</template>
<script>
export default {
data() {
return {
toggleVal: true
}
},
computed: {
bgColor() {
if (this.toggleVal) {
return 'lightpink'
}
else {
return 'lightgreen'
}
}
}
}
</script>
<style scoped>
#redDiv {
margin: 10px;
padding: 10px;
display: inline-block;
}
#redDiv:hover {
cursor: pointer;
}
</style>
The HTTP request is a part of the communication between a client and a server.
The client sends an HTTP request to the server, which handles the request and returns an HTTP response.
HTTP
HTTP stands for Hyper Text Transfer Protocol.
Our browser makes HTTP requests all the time in the background when we browse the Internet. When we access an Internet page, our browser (the client) sends several HTTP requests to make the server send us the page we want with all the relevant files and data as HTTP responses.
The most common kinds of HTTP requests are POST
, GET
, PUT
, PATCH
, and DELETE
. Learn more about the different kinds of HTTP requests on our HTTP Request Methods page.
Learn more about what HTTP is on our What is HTTP page.
The 'fetch' Method
To get data from a server in Vue we can use the JavaScript fetch()
method.
When we use the fetch()
method in this tutorial we will not specify the HTTP request method, and that means that the default request method GET
is what is used in the background.
The fetch()
method expects a URL address as an argument so that it knows where to get the data from.
Here is a simple example that uses the fetch()
method to send an HTTP GET
request, and receive data as an HTTP response. The data requested in this case is the text inside the local file file.txt
:
ExampleGet your own Vue Server
App.vue
:
<template>
<div>
<button @click="fetchData">Fetch Data</button>
<p v-if="data">{{ data }}</p>
</div>
</template>
<script>
export default {
data() {
return {
data: null,
};
},
methods: {
fetchData() {
const response = fetch("file.txt");
this.data = response;
}
}
};
</script>
In the example above, we only get "[object Promise]" as a result, but that is not what we want.
We get this result because fetch()
is a promised-based method that returns a promise object. The first return the fetch()
method gives is therefore just an object which means that the HTTP request has been sent. This is the "pending" state.
When the fetch()
method actually gets the data we want, the promise is fulfilled.
To wait for the response to be fulfilled, with the data we want, we need to use the await
operator in front of the fetch()
method:
const response = await fetch("file.txt");
When the await
operator is used inside a method, the method is required to be declared with the async
operator:
async fetchData() {
const response = await fetch("file.txt");
this.data = response;
}
The async
operator tells the browser that the method is asynchronous, which means that it waits for something, and the browser can continue to do other tasks while it waits for the method to complete.
Now what we get is a "Response", and no longer just a "Promise", which means we are one step closer to get the actual text inside the file.txt
file:
Example
App.vue
:
<template>
<div>
<button @click="fetchData">Fetch Data</button>
<p v-if="data">{{ data }}</p>
</div>
</template>
<script>
export default {
data() {
return {
data: null,
};
},
methods: {
async fetchData() {
const response = await fetch("file.txt");
this.data = response;
}
}
};
</script>
To get the text inside the file.txt
file we need to use the text()
method on the response. Because the text()
method is a promise based method, we need to use the await
operator in front of it.
Finally! We now have what we need to get the text from inside the file.txt
file with the fetch()
method:
Example
App.vue
:
<template>
<div>
<button @click="fetchData">Fetch Data</button>
<p v-if="data">{{ data }}</p>
</div>
</template>
<script>
export default {
data() {
return {
data: null,
};
},
methods: {
async fetchData() {
const response = await fetch("file.txt");
this.data = await response.text();
}
}
};
</script>
Fetch Data from a JSON File
In the previous example we fetched text from a .txt
file. But there are many ways to store data, and now we will see how we can fetch information from a .json
file.
JSON
is a common file format that is easy to work with because data is stored as text so that it is easy to read for humans, and the JSON
format is also widely supported by programming languages, so that we can, for example, specify what data to extract from a .json
file.
To read data from a .json
file, the only change we need to do to the example above is to fetch a .json
file, and use the json()
method instead of the text()
method on the response.
The json()
method reads the response from the HTTP request and returns a JavaScript object.
We use the <pre>
tag here to show the JSON
formatted text because it preserves spaces and line breaks so that it is easier to read.
Example
App.vue
:
<template>
<div>
<button @click="fetchData">Fetch Data</button>
<pre v-if="data">{{ data }}</pre>
</div>
</template>
<script>
export default {
data() {
return {
data: null,
};
},
methods: {
async fetchData() {
const response = await fetch("bigLandMammals.json");
this.data = await response.json();
}
}
};
</script>
Because the result of the json()
method is a JavaScript object, we can modify the example above to show a random animal from the bigLandMammals.json
file:
Example
App.vue
:
<template>
<p>Try clicking the button more than once to see new animals picked randomly.</p>
<button @click="fetchData">Fetch Data</button>
<div v-if="randomMammal">
<h2>{{ randomMammal.name }}</h2>
<p>Max weight: {{ randomMammal.maxWeight }} kg</p>
</div>
</template>
<script>
export default {
data() {
return {
randomMammal: null
};
},
methods: {
async fetchData() {
const response = await fetch("bigLandMammals.json");
const data = await response.json();
const randIndex = Math.floor(Math.random()*data.results.length);
this.randomMammal = data.results[randIndex];
}
}
};
</script>
Data from an API
API stands for Application Programming Interface. You can learn more about API here.
There are a lot of interesting free APIs we can connect with and use, to get weather data, stock exchange data, etc.
The response we get when we call an API with an HTTP request can contain all kinds of data, but often contains data in the JSON
format.
Example
A button can be clicked to get a random user from the random-data-api.com API.
App.vue
:
<template>
<h1>Example</h1>
<p>Click the button to fetch data with an HTTP request.</p>
<p>Each click generates an object with a random user from <a href="https://random-data-api.com/" target="_blank">https://random-data-api.com/</a>.</p>
<p>The robot avatars are lovingly delivered by <a href="http://Robohash.org" target="_blank">RoboHash</a>.</p>
<button @click="fetchData">Fetch data</button>
<pre v-if="data">{{ data }}</pre>
</template>
<script>
export default {
data() {
return {
data: null,
};
},
methods: {
async fetchData() {
const response = await fetch("https://random-data-api.com/api/v2/users");
this.data = await response.json();
}
}
};
</script>
We can modify our previous example a little bit to include the random user in a more user friendly way:
Example
We show the random user name in a <pre>
tag, along with the job title and image when the button is clicked.
App.vue
:
<template>
<h1>Example</h1>
<p>Click the button to fetch data with an HTTP request.</p>
<p>Each click generates an object with a random user from <a href="https://random-data-api.com/" target="_blank">https://random-data-api.com/</a>.</p>
<p>The robot avatars are lovingly delivered by <a href="http://Robohash.org" target="_blank">RoboHash</a>.</p>
<button @click="fetchData">Fetch data</button>
<div v-if="data" id="dataDiv">
<img :src="data.avatar" alt="avatar">
<pre>{{ data.first_name + " " + data.last_name }}</pre>
<p>"{{ data.employment.title }}"</p>
</div>
</template>
<script>
export default {
data() {
return {
data: null,
};
},
methods: {
async fetchData() {
const response = await fetch("https://random-data-api.com/api/v2/users");
this.data = await response.json();
},
}
};
</script>
<style>
#dataDiv {
width: 240px;
background-color: aquamarine;
border: solid black 1px;
margin-top: 10px;
padding: 10px;
}
#dataDiv > img {
width: 100%;
}
pre {
font-size: larger;
font-weight: bold;
}
</style>
HTTP Request in Vue with The 'axios' Library
The 'axios' JavaScript library also allows us to make HTTP requests.
To create and run the example on your own machine you first need to install the 'axios' library using the terminal in your project folder, like this:
npm install axiosThis is how we can use the 'axios' library in Vue to fetch a random user:
Example
Only small changes are made to the previous example to do the HTTP request with the 'axios' library instead.
App.vue
:
<template>
<h1>Example</h1>
<p>Click the button to fetch data with an HTTP request.</p>
<p>Each click generates an object with a random user from <a href="https://random-data-api.com/" target="_blank">https://random-data-api.com/</a>.</p>
<p>The robot avatars are lovingly delivered by <a href="http://Robohash.org" target="_blank">RoboHash</a>.</p>
<button @click="fetchData">Fetch data</button>
<div v-if="data" id="dataDiv">
<img :src="data.data.avatar" alt="avatar">
<pre>{{ data.data.first_name + " " + data.data.last_name }}</pre>
<p>"{{ data.data.employment.title }}"</p>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
data: null,
};
},
methods: {
async fetchData() {
this.data = await axios.get("https://random-data-api.com/api/v2/users");
}
}
};
</script>
<style>
#dataDiv {
width: 240px;
background-color: aquamarine;
border: solid black 1px;
margin-top: 10px;
padding: 10px;
}
#dataDiv > img {
width: 100%;
}
pre {
font-size: larger;
font-weight: bold;
}
</style>
Vue Template Refs are used to refer to specific DOM elements.
When the ref
attribute is set on an HTML tag, the resulting DOM element is added to the $refs
object.
We can use the ref
attribute and the $refs
object in Vue as an alternative to methods in plain JavaScript like getElementById() or querySelector().
The 'ref' Attribute and The '$refs' Object
HTML tags with the ref
attribute will be added to the $refs
object and can be reached later from inside the <script>
tag.
ExampleGet your own Vue Server
The text inside a <p>
element is changed.
App.vue
:
<template>
<h1>Example</h1>
<p>Click the button to put "Hello!" as the text in the green p element.</p>
<button @click="changeVal">Change Text</button>
<p ref="pEl">This is the initial text</p>
</template>
<script>
export default {
methods: {
changeVal() {
this.$refs.pEl.innerHTML = "Hello!";
}
}
}
</script>
Below is another example where the $refs
object is used to copy the value of one tag into another tag.
Example
The text from the first <p>
tag is copied into the second <p>
tag.
App.vue
:
<template>
<h1>Example</h1>
<p ref="p1">Click the button to copy this text into the paragraph below.</p>
<button @click="transferText">Transfer text</button>
<p ref="p2">...</p>
</template>
<script>
export default {
methods: {
transferText() {
this.$refs.p2.innerHTML = this.$refs.p1.innerHTML;
}
}
};
</script>
Get The Input Value from '$refs'
We can go further into an HTML element added to the $refs
object to access any property we want.
Example
A <p>
element gets the same content as what's being written in the input field.
App.vue
:
<template>
<h1>Example</h1>
<p>Start writing inside the input element, and the text will be copied into the last paragraph by the use of the '$refs' object.</p>
<input ref="inputEl" @input="getRefs" placeholder="Write something..">
<p ref="pEl"></p>
</template>
<script>
export default {
methods: {
getRefs() {
this.$refs.pEl.innerHTML = this.$refs.inputEl.value;
}
}
};
</script>
'ref' with v-for
HTML elements created with v-for
, with the ref
attribute, will be added to the $refs
object as an array.
Example
The button reveals the the third list element stored as an array element inside the $refs
object.
App.vue
:
<template>
<h1>Example</h1>
<p>Click the button to reveal the 3rd list element stored as an array element in the $refs object.</p>
<button @click="getValue">Get the 3rd list element</button><br>
<ul>
<li v-for="x in liTexts" ref="liEl">{{ x }}</li>
</ul>
<pre>{{ thirdEl }}</pre>
</template>
<script>
export default {
data() {
return {
thirdEl: ' ',
liTexts: ['Apple','Banana','Kiwi','Tomato','Lichi']
}
},
methods: {
getValue() {
this.thirdEl = this.$refs.liEl[2].innerHTML;
console.log("this.$refs.liEl = ",this.$refs.liEl);
}
}
};
</script>
<style>
pre {
background-color: lightgreen;
display: inline-block;
}
</style>
0 Comments