If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe added a title
prop to legend to make it dynamic: whenever we use this component, we can pass it different text. It's like an argument to the legend
component.
But in this situation, I want to go one step further: I want to pretend that the legend text on this page needs to change while our app is running - like the shipping time magically starts to decrease if the user is on the site for awhile... and we really want them to buy.
Whenever we need a value to change while the app is running, that value needs to be stored as data
. In products.vue
, we already have a legend
data from earlier, but we're temporarily not using it. So what we really want to do is this: pass the legend
data as the title
prop, instead of the hardcoded text.
Easy enough! We know that anything in data
and props
is available in our template. So, for the title
attribute, or technically prop
, say title="{{ legend }}"
<template> | |
... lines 2 - 47 | |
<div class="row"> | |
<legend-component title="{{ legend }}" /> | |
</div> | |
... lines 51 - 53 | |
</template> | |
... lines 55 - 85 |
As soon as we do that, Webpack is mad! Go check out the terminal. Yikes, it doesn't like this syntax at all - it can't even build our assets:
interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of
id="{{ val }}"
, use:id="val"
.
That's... a pretty awesome error message.
Basically, the {{ }}
syntax that we've grown to know and love... can't be used inside an attribute. If you need a dynamic value in an attribute, you need to prefix the attribute with v-bind:
. And then, inside the attribute, just use the variable name.
... lines 1 - 47 | |
<div class="row"> | |
<legend-component v-bind:title="legend" /> | |
</div> | |
... lines 51 - 85 |
Now it builds successfully. Before we talk more about this, let's try it! Refresh and... it works! Over on the Vue dev tools, the Products
component has a legend
data... and it looks like the Legend
component is receiving that as its title
prop.
The cool thing is that if we modify the legend
data - "Will ship slowly!" - the text updates! The modified data is passed to Legend
as the title
prop, and Vue re-renders it. So while we will never change a prop directly, if we change a data... and that data is passed to a prop, the prop will update to reflect that.
Back at our editor, let's talk more about this v-bind
thing. There will actually be several of these v-
things in Vue: they're used whenever Vue needs to do something special, including if statements and for loops. v-bind
is probably the most important one. Very simply: if you want an attribute to be set to a dynamic value, you must prefix the attribute with v-bind
.
As soon as you do that, the attribute is no longer just text, like pb-2
up here. We're now writing JavaScript inside the attribute.
I'll prove it: add +
open quote and say
this is really JavaScript.
... lines 1 - 47 | |
<div class="row"> | |
<legend-component v-bind:title="legend + ' this is really JavaScript!'" /> | |
</div> | |
... lines 51 - 85 |
And... when we try this... yea! That text shows up! It's a mixture of our data and that string.
So... that's really it! v-bind
is meant to "bind" an attribute to some dynamic value, often a data or prop. But honestly, I don't even think of it like that. I just think: if I want to use JavaScript inside of an attribute, I need to use v-bind
.
We're going to use this all the time: we're constantly going to be setting attributes to dynamic values. Vue understand this. And so, they've provided us with a nice shortcut. Instead of v-bind:title
, just say :title
.
... lines 1 - 47 | |
<div class="row"> | |
<legend-component :title="legend" /> | |
</div> | |
... lines 51 - 85 |
That means the exact same thing: it's still really v-bind
behind the scenes.
I like this way more. In my mind, an attribute without a colon
is literally set to that string. Prefixing the attribute with :
transforms it into JavaScript. Simple.
Anyways, when we try it now it... of course, works brilliantly.
While we're talking about the legend
component, I want to make one small tweak to the props
option. Remember: to allow a title
prop to be passed to us, we needed to first define the prop here.
If you hover over this, ESLint is mad:
prop
title
should define at least its type.
In addition to just saying:
please allow a prop called
title
to be passed to me
We can also tell Vue what type we expect this prop to be and whether or not it's required.
Change props
to an object where title
is a key. Set this to another object with two items: type
set to String and required
set to true
. Valid types are things like String, Number, Boolean, Array, Object and a few others - all with upper case names.
... lines 1 - 6 | |
<script> | |
export default { | |
... line 9 | |
props: { | |
title: { | |
type: String, | |
required: true, | |
}, | |
}, | |
}; | |
</script> |
Oh, and ESLint is still mad because I messed up my indentation! Thanks!
This won't change how our app works: it just helps to document our code and Vue will give us some nice validation. Back in products.vue
, temporarily "forget" to pass the title
prop.
... lines 1 - 47 | |
<div class="row"> | |
<legend-component :title="legend" /> | |
</div> | |
... lines 51 - 85 |
When we reload... error!
Missing required props: "title".
Remember: props are basically arguments you pass to a component. But since they're super dynamic, this is the way that we, sort of, type-hint each prop and mark it as required or not. There's also a default
option you can pass for optional props.
Next: let's make our styles more hipster - and less likely to cause accidental side effects - by using modular CSS.
Hey lexhartman!
Excellent question! There are 2 things to know.
1) In theory, it should be this simple:
<img src="../../images/loading.gif" />
Where you literally just use the relative path to the image file. So in this example, my loading.gif is "up" 2 directories, then inside an images/ directory (relative to the component where I wrote this code).
2) But... there's a pretty good chance that when you try this, you will end up with src="[object Module]"
. This is due to some internal options in Encore... it's confusing - here is some info on it https://github.com/vuejs/vue-loader/issues/1612
The best way to fix this is to make it dynamic:
<img :src="loadingImgPath" />
Then in your component:
import loadingImgPath from '../../images/loading.gif';
export default {
// ...
computed: {
loadingImgPath() {
return loadingImgPath;
}
}
}
That's it! A bit of an annoying extra step, due to how file-loader and the Vue compiler aren't playing nicely together, but it should get you what you need :).
Cheers!
The first solution indeed ended up with src="[object Module]"
. After making it dynamic it worked. So thanks!
// package.json
{
"devDependencies": {
"@symfony/webpack-encore": "^0.30.0", // 0.30.2
"axios": "^0.19.2", // 0.19.2
"bootstrap": "^4.4.1", // 4.5.0
"core-js": "^3.0.0", // 3.6.5
"eslint": "^6.7.2", // 6.8.0
"eslint-config-airbnb-base": "^14.0.0", // 14.1.0
"eslint-plugin-import": "^2.19.1", // 2.20.2
"eslint-plugin-vue": "^6.0.1", // 6.2.2
"regenerator-runtime": "^0.13.2", // 0.13.5
"sass": "^1.29.0", // 1.29.0
"sass-loader": "^8.0.0", // 8.0.2
"vue": "^2.6.11", // 2.6.11
"vue-loader": "^15.9.1", // 15.9.2
"vue-template-compiler": "^2.6.11", // 2.6.11
"webpack-notifier": "^1.6.0" // 1.8.0
}
}
Nice tutorial again! How can i add a static image to a template in vue? I tried a few options, one is :src="'/static/img/clear.gif'". But no succes.