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 SubscribeGoogle for "vue lifecycle hooks" and click into The Vue Instance page. About half way down, you'll find a spot that talks about "Instance Lifecycle Hooks".
If you look back at catalog.vue
, "lifecycle hooks" is referring to things like the mounted()
function, which is called when the component is added to the page.
If you scroll down a bit on the docs, they have a huge diagram that you can really nerd out on: this shows all the different things that happen between a Vue instance being instantiated, mounted onto the page, rendered, updated and removed from the page if you programmatically remove a component.
Feel free to study this - it's fun stuff. But I'll highlight the three most important hooks: created
, mounted
and destroyed
, which is called after your component is completely removed.
We used mounted
earlier to start our Ajax call. That means that our Vue instance was created, mounted into the DOM, and then the function was called. It turns out that a better place to load data is actually created
.
Let's try this: change mounted
to created
and then I'll refresh to be safe. That works just fine.
... lines 1 - 23 | |
export default { | |
... lines 25 - 35 | |
async created() { | |
... lines 37 - 39 | |
}, | |
}; | |
</script> |
The created()
function is called as soon as the Vue instance for our component is instantiated. That lets us start our Ajax call as early as possible. By the time it's mounted onto the page, the products
data may or may not yet be available, probably they aren't. But it doesn't really matter. And we can see this when we refresh: the products are missing for a moment.
The point is: created
is the best place to do data setup like this. And mounted
is the correct hook if you need to do something that will manipulate the DOM.
Now even though this is loading a tiny bit faster, it's still not instant. And so, to give our users confidence that our server isn't on vacation, we need a loading message.
To help us have a consistent loading message whenever we need one, let's create a shiny new Loading
component. Inside components/
, add a new loading.vue
file. Give this a <template>
with an <h4>
that says: - wait for it - Loading...
. That's good writing.
Let's also give this a class: :class="$style.component"
.
<template> | |
<h4 :class="$style.component"> | |
Loading... | |
</h4> | |
</template> | |
... lines 6 - 19 |
Before we add that style, create the <script>
tag with export default
and the options object. This only needs a name
key set to Loading
.
... lines 1 - 6 | |
<script> | |
export default { | |
name: 'Loading', | |
}; | |
</script> | |
... lines 12 - 19 |
Now let's add the <style>
tag with lang="scss"
and module
. Add just one class: .component
.
... lines 1 - 12 | |
<style lang="scss" module> | |
.component { | |
... lines 15 - 16 | |
} | |
</style> |
If you look at the assets/
directory, it has an images/
directory with a loading.gif
. In the .component
class, we're going to set this as the background image. We can do that with background: url()
and then the relative path to the file from here: ../../images/loading.gif
. We could also add a Webpack alias for images/
if we want. Finish this with no-repeat left center
and add a little padding to get the positioning just right.
... lines 1 - 13 | |
.component { | |
background: url('../../images/loading.gif') no-repeat left center; | |
padding: 0 0 4px 50px; | |
} | |
... lines 18 - 19 |
Say hello to our super fancy loading component!
Over in index.vue
, time to put it to work! Start by adding some markup to hold the loading message.
<template> | |
<div class="row"> | |
<div class="col-12"> | |
<div class="mt-4"> | |
... line 5 | |
</div> | |
</div> | |
... lines 8 - 14 | |
</div> | |
</template> | |
... lines 17 - 36 |
Next, import it with import Loading from '@/components/loading'
and add Loading
to components
: the order doesn't matter.
... lines 1 - 17 | |
<script> | |
import Loading from '@/components/loading'; | |
... lines 20 - 34 | |
</script> |
Finally, celebrate in the template with: <Loading />
.
<template> | |
... lines 2 - 3 | |
<div class="mt-4"> | |
<loading v-show="products.length === 0" /> | |
</div> | |
... lines 7 - 15 | |
</template> | |
... lines 17 - 36 |
We're not conditionally hiding and showing that yet but... there it is! Not bad!
Ok: we only want to show the Loading
component when the products Ajax call hasn't finished. The two different ways to conditionally hide or show something are v-show
and v-if
. In this case, especially because we're eventually going to be loading the product list multiple times when we have a search bar, let's use v-show
so we can hide & show it quickly. Add v-show=""
. And, let's see: the easiest way to know if the products are still loading is to check if products.length === 0
.
<template> | |
... lines 2 - 4 | |
<loading v-show="products.length === 0" /> | |
... lines 6 - 15 | |
</template> | |
... lines 17 - 36 |
That's not a perfect solution - we'll see why later - but it's good enough for now. And when we reload... that's nice!
We can also add a v-show=""
on the product-card
element with products.length > 0
. It's not really needed since this won't even loop if there are no products, but it balances things.
We now have dynamic products and a loading animation while the Ajax call is finishing. I'm super happy about that! But our categories are not dynamic yet. Wah, wah. Let's fix that next. But after we do, we'll explore a faster way to load them.
// 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
}
}
So now the real questions is how can we get those symfonycasts pens? :)