How to add Vue lifecycle listener dynamically
You might ask, can it be simpler?
From Vue.js Component Hooks as Events, this is the syntax you are looking for
this.$once('hook:beforeDestroy', () => {
I'm not sure how exactly you intend to make it dynamic, but here is an adaption of your logOnDestroy()
method in Vue CLI's default HelloWorld app,
Demo
Vue.component('helloworld', {
template: '<h1>{{ msg }}</h1>',
name: 'helloworld',
props: { msg: String },
mounted() {
this.logOnDestroy('Goodbye HelloWorld')
},
methods: {
logOnDestroy(txt) {
this.$once('hook:beforeDestroy', () => {
console.log(txt)
})
}
}
});
new Vue({
el: '#app',
data: {
showHello: true
},
mounted() {
setTimeout(() => {
this.showHello = false
}, 3000)
}
});
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://unpkg.com/vue"></script>
<div id="app">
<img alt="Vue logo" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/277px-Vue.js_Logo_2.svg.png" style="height: 50px;">
<helloworld v-if="showHello" msg="Welcome to Your Vue.js App"/>
</div>
Jan 2019 - A warning about the answer to this question - I found a subtle problem when using this code to add dynamic listeners.
this.$options.beforeDestroy.push(() => {
console.log(txt)
});
It works ok when there is no static beforeDestroy
defined. In this case the handlers array is a direct property of $options
.
But if you define a static beforeDestroy
hook on the component, the handlers array is property of $options.__proto__
, which means multiple instances of the component inherit dynamic handlers of previous instances (effectively, the above code modifies the template used to create successive instances).
How much of a practical problem this is, I'm not sure. It looks bad because the handler array gets bigger as you navigate the app (e.g switching pages adds a new function each time).
A safer way of adding dynamic handlers is to use this injectHook
code, which is used by Vue for hot module reload (you can find it in index.js
of a running Vue app). Note, I am using Vue CLI 3.
function injectHook(options, name, hook) {
var existing = options[name]
options[name] = existing
? Array.isArray(existing) ? existing.concat(hook) : [existing, hook]
: [hook]
}
...
injectHook(this.$options, 'beforeDestroy', myHandler)
What happens here is a new array is created on the instance which contains all the handlers from __proto__
, plus the new one. The old array still exists (unmodified), and the new array is destroyed along with the instance, so there is no build-up of handlers in __proto__
handler array.
An array of handlers for each life-cycle event is stored in the this.$options
object. You can add a handler by pushing to the corresponding array (you'll need to create the array first if there are no handlers set already):
new Vue({
el: '#app',
created() {
if (!this.$options.mounted) {
this.$options.mounted = [];
}
this.$options.mounted.push(() => {
console.log('mounted')
});
this.$options.mounted.push(() => {
console.log('also mounted')
});
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<div></div>
</div>
So in your case:
methods: {
logOnDestroy(txt) {
if (!this.$options.beforeDestroy) {
this.$options.beforeDestroy = [];
}
this.$options.beforeDestroy.push(() => {
console.log(txt)
});
}
}