Using Quasar Ajax Bar within every component

Onur Köse
4 min readMay 10, 2018

--

In case you’ve missed, there is Quasar Framework. The team behind has done an amazing job.

I’m trying to learn it just after the Vue.js and Quasar made me more ambitious about how the client-side will evolve in this era.

Loading components and displaying what’s going on to user is a bit burden in any SPA and Vue.js is not an exemption. But Quasar has a lot of components to overcome this flow. You have Ajax Bar, Loading, Inner Loading, Progress Bar and Spinner under the hood.

It was little bit tricky for me to find out how to use Ajax Bar component. It was simple at first, but gets complicated if you want second or third level routes.

The basic installation is the same as the other Quasar components. Add QAjaxBar to your quasar.conf.js file and <q-ajax-bar /> to your App.vue, below or above of the <router-view />, it really doesn’t matter.

But what matters is starts just after this point. You can simply leave it like this and the fancy red line works with any Ajax request automatically. But what if you have a component that has it’s own children components? And who doesn’t has?

The good thing of Ajax Bar is, you can manually trigger it with start and stop functions. Since the component registered at the very root level, triggering from children requires a bit of labor.

Imagine you have your App.vue like this;

<template>
<div id="q-app">
<q-ajax-bar />
<router-view />
</div>
</template>

First thing to do is clearing that default 1000ms delay from the Ajax Bar props (it requires Int, so “0” will not work if you put directly to delay attribute);

<template>
<div id="q-app">
<q-ajax-bar :delay="delay" />
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
data: () => {
return {
delay: 0
}
},
methods: {},
mounted () {}
}
</script>

There should be event listeners over the router-view, so we can call start/stop functions from children components;

<template>
<div id="q-app">
<q-ajax-bar ref="bar" size="2px" :delay="delay" />
<router-view v-on:startAjaxBar="onStartAjaxBar" v-on:stopAjaxBar="onStopAjaxBar" />
</div>
</template>
<script>
export default {
name: 'App',
data: () => {
return {
delay: 0
}
},
methods: {
onStartAjaxBar () {
console.log('started')
this.$refs.bar.start()
},
onStopAjaxBar () {
console.log('stopped')
this.$refs.bar.stop()
}
},

mounted () {}
}
</script>

Note that we now also have the ref attribute on <q-ajax-bar />(Checkout Vue.js docs for further reading). It basically attaches vDOM elements to Vue instance, which is collected into this.$refs object so we can use it inside our methods. The bad part of the refs object, it’s bound the component that creates the element. So you can’t get refs object from children routes. That’s why we listen our events from the router-view element. The good part is, you know, all that jQuery history of yours doesn’t remind you the good old days?

Well, now it’s time to trigger our events from a child route. Imagine our router file has some auth pages config like this, routes.js;

export default [
{
path: '/',
component: () => import('layouts/default'),
children: [
{ path: '', component: () => import('pages/index') }
]
},

{
path: '/auth',
component: () => import('layouts/auth'),
children: [
{ path: 'login', component: () => import('pages/auth/login') },
{ path: 'register', component: () => import('pages/auth/register') },
{ path: 'lost-password', component: () => import('pages/auth/lost-password') }
]
},

{
path: '*',
component: () => import('pages/404')
}
]

We have a default layout for all the content, and auth layout for the auth pages only. That means auth layout has it’s own children. This is the auth.vue;

<template>
<q-layout>
<q-tabs color="primary" align="justify">
<q-route-tab name="login-content" default slot="title" icon="mdi-login-variant" label="Login" to="/auth/login" exact />
<q-route-tab name="register-content" slot="title" icon="mdi-account-plus" label="Register" to="/auth/register" exact />
<q-route-tab name="lost-password-content" slot="title" icon="mdi-textbox-password" label="Lost Password" to="/auth/lost-password" exact />
<router-view />
</q-tabs>
</q-layout>
</template>
<script>
export default {
name: 'Auth Layout',
data () {
return {}
},
methods: {},
mounted () {
let self = this
this.$router.beforeEach((to, from, next) => {
self.$emit('startAjaxBar')
next()
})
this.$emit('stopAjaxBar')
}

}
</script>

Auth layout is a basic Tabs view with it’s own router-view element. You can see the mounted function has and immediate stopAjaxBar call so it stops whatever Ajax Bar occuring during initial document loading. Before that there’s our navigation guard that triggers App.vue’s events to start Ajax Bar. We now only need a stop trigger, login.vue;

<template>
<q-tab-pane name="login-content">
<h3>Login Screen</h3>

<div style="max-width: 90vw;">
<p class="caption">
This is where the login form should placed
</p>
</div>
</q-tab-pane>
</template>

<script>
export default {
name: 'Login Component',
mounted () {
this.$emit('stopAjaxBar')
}

}
</script>

Here you can see how the stop is triggered. But wait, this is a child component of auth layout, not App.vue. It means this $emit here, will not reach his grandfather. We should listen this from his father, auth.vue;

<template>
<q-layout>
<q-tabs color="primary" align="justify">
<q-route-tab name="login-content" default slot="title" icon="mdi-login-variant" label="Login" to="/auth/login" exact />
<q-route-tab name="register-content" slot="title" icon="mdi-account-plus" label="Register" to="/auth/register" exact />
<q-route-tab name="lost-password-content" slot="title" icon="mdi-textbox-password" label="Lost Password" to="/auth/lost-password" exact />
<router-view v-on:startAjaxBar="onStartAjaxBar" v-on:stopAjaxBar="onStopAjaxBar" />
</q-tabs>
</q-layout>
</template>

<script>
export default {
name: 'Auth Layout',
data () {
return {}
},
methods: {
onStartAjaxBar () {
console.log('started sent')
this.$emit('startAjaxBar')
},
onStopAjaxBar () {
console.log('stopped sent')
this.$emit('stopAjaxBar')
}

},
mounted () {
let self = this
this.$router.beforeEach((to, from, next) => {
self.$emit('startAjaxBar')
next()
})
this.$emit('stopAjaxBar')
}
}
</scrip>

So auth.vue transfers both start and stop events from his children to his parent component and listens those events from it’s own router-view element.

There might be better implementations out there in the wilderness, but it does work for the moment, and it’s all that matters for now.

Please shake some hands if you find these notes useful to yourself.

Edit: Here are the gist of those codes above.

--

--