Aurelia event won't fire on first page load - aurelia

I'm using Aurelia's EventAggregator to publish and subscribe to events in my app. Some of my custom elements take a while to load, so I've used a loading event to tell my main app.js to add a spinner to the page during loading.
This works fine once the app has loaded and I start switching between routes, however, on first page load the event doesn't seem to fire - or at least, it isn't picked up by the subscribe method.
Here's basically what my app.js does:
attached () {
this.mainLoadingSubscription = this.eventAggregator.subscribe('main:loading', isLoading => {
// If main is loading
if (isLoading) {
document.documentElement.classList.add('main-loading');
}
// Stopped loading
else {
document.documentElement.classList.remove('main-loading');
}
});
}
And here's what my custom elements do:
constructor () {
this.eventAggregator.publish('main:loading', true);
}
attached () {
this.doSomeAsyncAction.then(() => {
this.eventAggregator.publish('main:loading', false);
});
}
This causes the first page load to not show a spinner and instead the page looks kind of broken.
Btw, I am aware of the fact that you can return a Promise from the element's attached method but I can't do this because of this other problem

Set up your subscriptions in your viewModel's constructor or activate callback
In the above example, you set up subscriptions in the viewModel's attached() callback. Unfortunately, this will not be called until all child custom element's attached() callbacks are called, which is long after any one custom element's constructor() function is called.
Try this:
app.js
#inject(EventAggregator)
export class AppViewModel {
constructor(eventAggregator) {
this.mainLoadingSubscription = eventAggregator.subscribe('main:loading', isLoading => {
// do your thing
}
}
}
If the viewModel is a route that can be navigated to, then handle this in the activate() callback with appropriate teardown in the deactivate() callback.
#inject(EventAggregator)
export class AppViewModel {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
}
activate() {
this.mainLoadingSubscription = this.eventAggregator.subscribe('main:loading', isLoading => {
// do your thing
}
}
deactivate() {
this.mainLoadingSubscription.dispose();
}
}

Related

Decorating Lifecycle Hooks in vue.js

Does anyone know a good way to decorate lifecycle hooks from within the vue component? I am adding a function in the mounted in my component that gets called from the app.vue when the screen size changes. When I navigate away from the page I want to remove this function. Currently I have to do it in the destroy method on the component. If I was able to pass in the destroyed method on the component when I am creating the listener and decorate it with the logic to destroy the listener I would not have to worry about someone adding the listener on a component and not adding the removal of the listener in the destroy method.
public AddUpdateIsMobileFunction(fn: (screenType: any) => void, destroyHook: any): any {
this.updateIsMobileFunctions.push(fn);
const currentFunctionPlacementInArray: number = this.updateIsMobileFunctions.length;
const destroySelf: () => void = () => {
this.updateIsMobileFunctions.splice(currentFunctionPlacementInArray, 1);
}
const originalDestroyHook: any = _.deepClone(destroyHook)
destroyHook = () => {
originalDestroyHook();
destroySelf();
}
return destroySelf;
}
In the component
private mounted(): void {
this.destroyIsMobileFunction = this.$Global.AddUpdateIsMobileFunction((screenType: any) => {
this.isMobile = screenType.isMobile;
console.log("test is mobile fn")
}, pass in the destroyed function here )
}
remove the function below
private beforeDestroy(): void {
console.log('in the destroy function in the vue component')
this.destroyIsMobileFunction();
}

Vue.js component not loading/rendering data when called via URL or F5

I have a Vue.js SPA with some pages that display data from a backend. When I navigate the pages via the navbar, everything works fine, components and data are loaded.
When I'm on the page, e.g. localhost:8080/#/mypage and press F5, the data doesn't get loaded / rendered. Same goes for when I directly navigate to the page via the address bar.
The data gets loaded in this function:
async beforeMount() {
await this.initializeData();
}
I've tried to call the method in every lifecycle hook, i.e. created, beforeCreated, mounted etc...
In the mounted lifecycle hook I'm setting a boolean property to true, so that the table is only rendered when the component is loaded (done with v-if).
mounted() {
this.componentLoaded = true;
}
Not sure if this is important, but I've tried it with or without and it doesn't work.
I would really appreciate it if somebody knew whats happening here.
EDIT:
this.applications is a prop and contains multiple applications which contain instances. I want to add some variables from the backend to each application.
console.log(1) gets printed
console.log(2) does not
initializeData: function () {
let warn = 0;
console.log("1");
this.applications.forEach(async application => {
const instance = application.instances[0];
console.log("2");
let myData = null;
try {
const response = await instance.axios.get('url/myData');
myData = response.data;
} catch (err) {
}
let tmpCount = 0;
let tmpFulfilled = 0;
myData.forEach(ba => {
if(!ba.fulfilled){
warn++;
application.baAllFulfilled = false;
}else {
tmpFulfilled++;
}
tmpCount++;
})
console.log("3");
// Assign values
this.baTotalWarnings = warn;
application.baAnzahl = tmpCount;
application.baFulfilled = tmpFulfilled;
this.componentLoaded = true;
}
Try removing the async and await keywords from your beforeMount, and remove this.componentLoaded from mounted. Set it instead in the then block (or after await) in your initializeData method. I'm not sure Vue supports the async keyword in its lifecycle methods.
Something like this:
beforeMount() {
this.initializeData(); // start processing the method
}
methods: {
initializeData() {
callToBackend().then(() => {
this.componentLoaded = true // backend call ready, can now show the table
})
}
}

Nuxt Js Event Fires Twice

I am using Nuxt js SSR for an app that am build, I installed Vue Event plugin but when i emit an event it runs twice at the listener. Created hook runs twice too.
Modules am using:
Axios, Auth, Toast
Child Component
methods: {
initPlaylist(songs) {
console.log(songs)
}
},
mounted () {
this.$events.$on('playAll', data => {
this.initPlaylist(data.songs) //runs twice
})
}
Parent Component
method () {
playAll (songs) {
this.$events.$emit('playAll', songs)
}
}
How can i resolve this issues guys? I need your help.
Maybe you have to call that parent's method on client side only.
you can write code like this to prevent emit on server side:
methods: {
playAll(songs) {
if (process.server) return
this.$events.$emit('playAll', songs)
}
}
or do not call playAll method on server side. (eg: created, mounted...)
You need to off that event first before.
this.$events.$off("playAll");
this.$events.$on('playAll', data => {
this.initPlaylist(data.songs) //runs twice
})

Destroyed component called 'updated' hook

Version
Vue#2.5.16
Vuex#3.0.1
VueRouter#3.0.1
Code
First my code looked like this
export default {
//...
updated () {
//TODO
}
destroyed () {
this.unregisterModule(module.name)
}
}
But when the app get other route, this component will call the updated once and cause some problems.
Now I use the _isDestroyed state property to resolve this:
updated () {
if (!this._isDestroyed) {
//TODO
}
}
Question
How to understand this logic of hooks?

Run method before route

I have a login modal that I activate by setting .is-active to it. For this, I have a method like this:
methods: {
toggleModal: function (event) {
this.isActive = !this.isActive
}
}
that I run onclick. Depending on the boolean value of isActive, my modal gets the class .is-active.
Thing is, in my modal I have a button that takes the user to a new view which means it's rendering a new component, with this code:
<router-link class="control" #click="toggleModal()" to="/register">
As you can see, it's routing to /register. Before doing this, I need to run toggleModal() so that the modal gets closed. Right now it's routing without running the method which means that the new view has my modal overlay which is... not optimal.
Is there any good way to do this in Vue? Could I maybe create a method, that first calls toggleModal(), and then routes from the method?
Thanks.
I would define a method that calls toggleModal first, then navigates. Like so:
methods: {
navigateAway () {
this.isActive = !this.isActive
this.$router.push('/register')
}
}
You don't need the event argument unless you intend on capturing more data from the event or event target. You could also wrap the router push in a setTimeout if you so desire, for perhaps cleaner looking view changes.
methods: {
navigateAway () {
let vm = this
vm.isActive = !vm.isActive
setTimeout(function () {
vm.$router.push('/register')
}, 50)
}
}
Of course, there are hooks that you can use from vue-router that make this easy. Example (assuming you're using single file components and Vue.js 2.x):
export default {
data () {
return {
isActive: false
}
},
beforeRouteLeave (to, from, next) {
this.isActive = false // I assume that you would not want to leave it open upon navigating away
next()
}
}
Link to vue router hooks: https://router.vuejs.org/en/advanced/navigation-guards.html