Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Loading Component

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Google 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.

The Important Lifecycle Hooks

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.

mounted vs created

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

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.

Creating the Loading Component

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".

<h4 :class="$style.component">
... 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
export default {
name: 'Loading',
... 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

Referencing an Image in CSS

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!

Using the Component

Over in index.vue, time to put it to work! Start by adding some markup to hold the loading message.

<div class="row">
<div class="col-12">
<div class="mt-4">
... line 5
... lines 8 - 14
... 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
import Loading from '@/components/loading';
... lines 20 - 34

Finally, celebrate in the template with: <Loading />.

... lines 2 - 3
<div class="mt-4">
<loading v-show="products.length === 0" />
... lines 7 - 15
... lines 17 - 36

We're not conditionally hiding and showing that yet but... there it is! Not bad!

Hiding / Showing the Loading Animation

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.

... lines 2 - 4
<loading v-show="products.length === 0" />
... lines 6 - 15
... 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.

Leave a comment!

Login or Register to join the conversation
Peter-K Avatar
Peter-K Avatar Peter-K | posted 3 years ago

So now the real questions is how can we get those symfonycasts pens? :)


Ha! I might just be able to convince the team to start producing and selling those worldwide!! :D

Cat in space

"Houston: no signs of life"
Start the conversation!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// 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