Vue.js detach styles from template
This works for my specific situation where I allow the users to store a string of CSS and then I need to render it on specific pages - ei: preview page.
The context here is css
is saved as string in database, fetched and rendered within an Vue component.
# html
<html>
<head>
<style id="app_style"></style>
</head>
<body>
<div id="app"></div>
</body>
</html>
# app.vue
data() {
return {
dynamic_css: ''
}
},
created() {
// fetch css from database, set as `this.dynamic_css`
},
watch {
dynamic_css: function(newValue) {
document.getElementById('app_style').innerHTML = newValue
}
}
The Issue
In Vue 2 the root instance is treated more like a component than it was in Vue 1.
This means when you bind the Vue instance to #app it digests everything in #app as a vue template. This means tags are invalid and they'll be removed from the template. This is just the way things work in Vue 2.
Recreation
I recreated the issue in a codepen here
https://codepen.io/Fusty/pen/gqXavm?editors=1010
The <style>
tag nested within the tag Vue is bound to. It should style the background red and the text color green. However, we see only a flash of this (depending on how fast your browser fires up Vue) and eventually vue removes these style tags as it digest #app as a template and then updates the DOM with what it thinks should be there (without <style>
tags).
Better Recreation
Thanks to user @joestrouth1#6053 on the Vue-Land discord, we also have this fork of my recreation of the issue.
https://codepen.io/joestrouth1/pen/WPXrbg?editors=1011
Check out the console. It reads . . .
"[Vue warn]: Error compiling template:
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as <style>, as they will not be parsed.
1 | <div>
2 | <style>
| ^^^^^^^
... etc ...
Complaining about the style tags in a template.
This zeroes in on the actual issue. It is good to note this doesn't occur in Vue 1. Probably because it treats the root instance more uniquely than components, but I am not 100% sure on this topic.
Solution (Hack, not best practice or especially recommended)
The <style>
tags are still in the DOM during the created
lifecycle hook for the Vue instance and they are removed by the time the mounted
lifecycle hook fires. Let's just query for all of the style tags within the #app element, save them, and then append them back to the #app element after Vue has digested the template.
Adding the following to your root Vue instance will take any <style>
tags within whatever element your Vue instance is bound to (via el: 'someSelector'
) and append them (possibly relocating them) to the element your Vue instance is bound to.
created: function() {
this.styleTagNodeList = document.querySelector(this.$options.el).querySelectorAll('style');
},
mounted: function() {
for(var i = 0; i < this.styleTagNodeList.length; ++i)
this.$el.appendChild(this.styleTagNodeList[i]);
}
NOTE: This is definitely a hack which likely has unintended consequences I have not run into yet and cannot specifically disclaim. USE AT YOUR OWN RISK.