I need to destroy and re-create the root application component. This is my template:
<div id="app">
{{ num }}
</div>
And this is my code:
if (app) {
app.$destroy();
} else {
app = new Vue(Object.assign({
el: '#app',
}, { data: { num: Math.random() } }))
}
This code runs on button click, but it doesn't clean the template and refresh the new data I'm passing. Any idea why?
I'm pretty sure that your attempt so solve a problem in a way that it should not be solve, or that you use vue in the wrong way.
But to you problem, when you execute this code:
if (app) {
app.$destroy();
}
Then the documentation state:
Completely destroy a vm. Clean up its connections with other existing vms, unbind all its directives, turn off all event listeners.
So they don't say that the element to wich the vue app is attached to has to or will change. If you want to clean up the DOM then you need to delete the content manually.
After app.$destroy() the app variable will still hold the destroyed vue instance (app wont be magically unset). So another execution of the code will not trigger the else block.
You also have to set app to something that evaluates to falsy.
if (app) {
app.$destroy();
app = undefined;
}
Now app is undefined so at the next time the if statment is evaluated, the else block would be evaluated and a new instance is created.
Your specific use case is not specified in the question, but I assume you want to update something that is not being updated automatically.
To update your component you can use
this.$forceUpdate()
to force a reload of the component.
Related
I have a component, lets call component 1. A method in component1 makes an axios.post request and the server returns a bunch of data. When data is loaded, a new button appears. When this button is clicked, it will be navigated to another route with another component, let call this component2. Now some of the loaded data from component1 needs to transferred to component2 and should be opened in new tab. Below is the code:
<script>
import axios from 'axios';
export default {
name: "CheckStandard",
data() {
return {
standard: '',
time: {},
programs: {},
example: {},
}
},
methods: {
checkData(){
let std= {
std: this.standard,
}
axios.post('http://localhost:3000/postdata', std)
.then(res => {
if (res.status === 200) {
if (res.data === 0) {
this.invalidID = "This Standard does not exist"
}
else {
let data = res.data
this.time = res.data["Starttime"];
this.programs = res.data["program"]
this.example = res.data["example"]
}
}).catch(error => {
console.log(error);
this.error = error.response
})
},
}
goToPictures(){
let route = this.$router.resolve({
name:'ProgramCheckList',
params: {
programs: this.programs,
time: this.time,
example: this.example
}
})
window.open(route.href,'_blank')
},
}
}
</script>
The function goToPictures is the function that is invoked after clicking the button. Now in this function goToPictures I have defined the route to navigate to another tab. But the problem the data in the params which it should carry is lost. I tried with $router.push ofcourse it works but it is not to open in new tab. Below is the code for the same:
goToPictures(){
this.$router.resolve({
name:'ProgramCheckList',
params: {
programs: this.programs,
time: this.time,
example: this.example
}
})
},
}
Since I am new to vue, I have tried my best to look for an answer for this, even I have came across some posts in several forums mentioning, it is may be not be possible even, instead advised to use vuex. But I still wanted to post it, maybe we have a solution now or any other idea. Thanks
The problem you're seeing stems from the fact that, when you open a new window, Vue is basically going to re-render your components as if you hit refresh. Your Component 2 has props that it can only inherit from another component, and as such, it has no possible way of knowing what the props it needs to use are.
To illustrate in simple terms what's happening:
The user navigates to Component 1. They click the button, which makes the GET request. You now have some data that you can pass onto Component 2 as props.
In a regular environment, the user would simply click on the link leading to Component 2, and the props would be passed on normally, and everything would work as intended.
The problem in your situation is that Component 2 depends on Component 1 for its data. By navigating directly to the Component 2 route (in this situation, opening a new window is functionally identical to a user copy/pasting the url into the adress bar), Vue never has the chance of interacting with Component 1, and never gets told where to get the props it needs to populate Component 2.
Overcoming the issue
There's a few things you can do here to overcome this issue. The most obvious one is to simply open Component 2 as you would normally, without opening a new window, but keep in mind that even if you do this, should a user copy/paste the URL where Component 2 is, they'll run into the exact same issue.
To properly deal with the issue, you have to specify a way for Component 2 to grab the data it needs. Since the data is already fetched, it makes sense to do this in the created() or mounted() hooks, though if you wanted to you could also deal with this in Vue Router's beforeRouteEnter() hook.
Vuex
While you don't necessarily need a state management tool like Vuex, it's probably the simplest way for your needs. When you grab the data from Component 1, store it and access it from the Component 2 mounted() hook. Easy-peasy.
localStorage()
Alternatively, depending on how the data is being served, you could use localStorage(). Since you're opening a new window, sessionStorage() won't work. Do note that localStorage() can only hold strings and nothing else, and isn't necessarily available in every browser.
You can store the data to a global variable and use that in the newly opened window. Asked here Can I pass a JavaScript variable to another browser window?
Provided the windows are from the same security domain, and you have a reference to the other window, yes.
Javascript's open() method returns a reference to the window created (or existing window if it reuses an existing one). Each window created in such a way gets a property applied to it "window.opener" pointing to the window which created it.
Either can then use the DOM (security depending) to access properties of the other one, or its documents,frames etc.
Another example from same thread:
var thisIsAnObject = {foo:'bar'};
var w = window.open("http://example.com");
w.myVariable = thisIsAnObject;
//or this from the new window
var myVariable = window.opener.thisIsAnObject;
I'm trying to sync a variable between components using the .sync command in Vue. It works without problems when I put components in the page using the tag (for example: <my-component></my-component>). When I bind it in a <router-view>, it works only one-way. Is it normal?
When I click on the link inside the component, it does not change the value of test in the root Vue object. When I change it in the root object, the component inherits the value correctly.
Click here for an example
You are trying to update the prop directly, the correcto way to update it is to emit an update. Remember :test.sync is a shorthand to v-bind:test and v-on:update:test
On your Foo component:
editTest: function() {
this.$emit('update:test', false) // instead of this.test = false;
}
Fiddle: https://jsfiddle.net/hansfelix50/u7k5qpwz/
I have two components that conditionally render with v-if:
<Element v-if="this.mode === 'mode'"/>
<OtherElement v-if="this.mode !== 'mode'"/>
I have load-in animations for both components that I have under mounted(), that I only want to run the first time they are loaded. But with mounted, each time the component is recreated when this.mode changes, the animations trigger again. How can I avoid this?
You could wrap your components within a keep-alive element ..
<keep-alive>
<Element v-if="this.mode === 'mode'"/>
<OtherElement v-else />
</keep-alive>
If created() doesn't do the job, you should try to do a simple check in the parent element if this.mode was switched on and off before, save the result as a variable and pass that to the mounted hook and only run the animation if the mode wasn't switched before.
Using v-if, re-renders components every time the this.mode changes. Vue stores them in virtual DOM, but re-renders them if you use v-if.
If you have access to code for these components, consider using prop for v-show and watching it, in optional combination with emitso you can communicate between parent and child components, and set flag if you need it in child and in parent component if child component loads animation initially, to avoid loading it all over again.
This would be one of the child components:
<template>
<div v-show="mode === 'themode'">
</div>
</template>
<script>
export default {
props: {
mode: {
type: Boolean,
required: true,
twoWay: true
},
},
data() {
return {
animationLoaded : false,
}
},
mounted(){
},
watch: {
'mode' : function(){
if(this.mode === 'mode' && this.animationLoaded === false){
//load animation and set flag to true, to avoid loading it again.
this.animationLoaded = true;
this.$root.$emit('component-name:animation-loaded');
}
}
},
...
And putting child in parent component:
<child-component :mode.sync="mode"></child-component>
I had a similar problem, I only wanted something in my mounted to run on page load, but when I navigated away (using vue router) and back again, the mounted would run each time, re-running that code. Solution: I put a window.addEventListener('load', () => {}) in the created function, which runs that code instead.
The created function runs early on in the vue bootstrapping process, whereas the window.load event is the last event in the page load queue. As vue runs as a single page app, the window.load event will only fire once, and only after all the javascript (including vue) has got itself up and running (in a sense). I can route around my environment back and forth knowing that the initial build scripts would only be run when the page is actually re-loaded.
I have a feeling there is a more vue-tiful way of doing this, or I am otherwise missing something that vue-terans would chastise me for, but at this point vue has slapped me on the wrist far too many times for me to bother trying anymore. Hacky workarounds it is!
I'm looking for advice in architecting my Vue app. There's a map with stuff you can click on, and a sidepanel that shows information about what you clicked on. The side panel is wrapped in a new Vue(...) (I'm not sure what to call that - a Vue object?) Currently I attach every Vue object to window so I do stuff like (simplified):
map.on('click', e => window.sidepanel.thingName = e.feature.thingName);
Now, the sidepanel code and the map code are in different modules that otherwise have little reason to communicate.
My approach seems to work ok, but I just wonder what some better patterns would be, other than using globals.
new Vue() => called a vue instance (aka vm)
I think what you are doing is reasonable if you are working inside of constraints. There are a few alternatives
Create an event bus (which is itself a vue instance) that you can use to manage the shared events. Benefits here are that you don't have to reach into the components as deeply, but you also add complexity. https://alligator.io/vuejs/global-event-bus/
Have you considered rending this as a page with the 2 vue instances as components inside of a parent? That would allow you to have the parent take care of the state and pass it down to the components. I think the main benefit of this approach is that it will be more simple to add additional functionality.
Both of these, you would end up doing something like this in the map
map.on('click', e => emit('mapClick', e.feature));
Then in your component listen for the mapClick either on the event bus if you go route 1, or in the parent container component if you go route 2
Hope that helps, good luck!
Example of Parent, the sidepanel would emit
sidepanel = Vue.component('sidepanel')
map = Vue.component('map')
parentComponent = Vue.component('parent', {
components: { map, sidepanel },
template: `
<div>
<map #mapClick="handleMapClick" :dataAsProp="someData"></map>
<sidepanel #userClicked="handleUserClick" :dataAsProp="someData"/>
</div>
`,
data() {
return { someData: [] }
},
methods: {
handleMapClick(event, data) {
// handle your update here, save data, etc
},
handleUserClick(event, data) {
// handle your update here, save data, etc
}
}
})
I am fairly new to vue and trying to figure out the best way to structure my event bus. I have a main layout view (Main.vue) inside of which I have a router view that I am passing a child's emitted info to like so:
<template>
<layout>
<router-view :updatedlist="mainupdate" />
</layout>
</template>
import { EventBus } from './event-bus.js'
export default {
data () {
return {
mainupdate: []
}
},
mounted () {
EventBus.$on('listupdated', item => {
this.mainupdate = item
console.log(item)
})
}
}
The structure looks like: Main.vue contains Hello.vue which calls axios data that it feeds to child components Barchart.vue, Piechart.vue, and Datatable.vue
The axios call in Hello.vue populates a data property called list. I then check if updatedlist (passed as router prop from Datatable.vue to Main.vue when something changes) is empty, and if so set it to the value of list
I know that the event is being emitted and received by Main.vue because the console.log(item) shows the data. But my child components are not getting updated, even though they are using updatedlist as their data source. (If I reload the page, they will be updated btw, but why aren't they reactive?)
UPDATE: I thought I would cut out the top level parent Main.vue and just put my EventBus$on inside Hello.vue instead and then just change list when it was received. But this does two things, neither of which are great:
On every page refresh, my console logs add one more log to the list and output all of them. So for example if I have made 3 changes, there will be three logs, 4 there will be 4, etc. The list grows until I restart the app.
My child components are STILL not updated :(
UPDATE 2: UGH. I think I see why they aren't updating, the problem is with the reactivity in my chartsjs component, so I will have to resolve that there (Barchart.vue, Piechart.vue). A simple component I built myself to just read the total length DOES get updated, so that works. This still leaves the mystery of the massive number of duplicate console logs though, any ideas?
It sounds like you may have the answer to the original question? To answer your last update you likely have duplicate logs because you do not appear to be removing event handlers. When you add an event handler to a bus like this:
mounted () {
EventBus.$on('listupdated', item => {
this.mainupdate = item
console.log(item)
})
}
}
You need to remove it yourself. Otherwise you are just adding a new handler every time the component is mounted.
I suggest you move the actual handler into a method:
methods: {
onListUpdated(item){
this.mainupdate = item
console.log(item)
}
}
Move code to add the handler to created:
created() {
EventBus.$on('listupdated', this.onListUpdate)
}
}
And add a beforeDestroy handler:
beforeDestroy(){
EventBus.$off("listupdated", this.onListUpdate)
}
And do that in every component that you are adding event handlers to EventBus.
I' not aware if you require to stick to the current structure. For me the following works perfectly fine.
// whereever you bootstrap your vue.js
// e.g.
window.Vue = require('vue');
// add a central event bus
Vue.prototype.$bus = new Vue();
Now you can simply access the event bus in every component by using
this.$bus.$on( ... )
As said I'm not aware of your full event bus code but this should actually work fine