vue-runtime-template-compiler
vue-runtime-template-compiler copied to clipboard
v-model not binding
Here's an example of the issue: https://codesandbox.io/s/kind-mclaren-tss6z?file=/src/App.vue
A component that uses v-model doesn't emit the events back through to the parent. I'm sorry to say I don't know enough JavaScript to fix this and make a PR myself, but I did find this which might be helpful:
https://github.com/vuejs/vue/issues/7042
I tried to implement this on a fork of this project, but couldn't get it to work the way I hoped it would.
I run into the same issue, while using this. Everything worked, until I wanted to move from the compiled template something directly into the component, where the compiled template was used in. And then, all of the variables modified from within the compiled template would become non-reactive in relation to the component itself.
It is like if template compiler is making it's own copies of everything the component contains and they are no longer "connected" to the methods and values of the component used in. Speak: They are working only within the compiled template, not the whole component, as they should.
This is quite a big issue, making it unsuitable to add any other parts inside the component.
I tried to modify the data into computed properties instead, to see if this makes any difference and may cause the original data to update. Unfortunately, the same issue exists here too. Nothing helps.
Examining this
shows, that it is pointing to two different objects, as such, accessing or updating this.sameVariable
in the component is not the same as accessing this.sameVariable
inside the compiled template.
Correction: Part of what I wrote is not fully correct. I had set the "parent" property and used a computed method to return this
, because it wouldn't work with data
. Thing is, it matters, how a function is defined, as this
is different or may not exist - why it wouldn't work previously and I used a computed method.
If someone uses the parent prop and has data: () => {
, then change it to data: function() {
and part of what I wrote above will work just fine.
The v-model
however seems not related to this, and still won't work, unfortunately. Even if the parent property is set (I tried it on pgrantmartello's example too).
Partial solution So I have quite experimented around on this issue, while still learning some deeper aspects of Vuejs and deepening myself into some advanced topics. I figured something out.
I first modified your example to pass variables as props to the child component used as template, because it felt easier to test around and actually experiment also with vuejs newer .sync
option and if this somehow solves the issue. But it feels that it may work similarly with using v-model
.
Here's what I figured out:
The vue-runtime-template-compiler
does receive an update event, when the value gets modified in the child component, but from there on, nothing happens. I tried to work with $listeners
and similar ways, but couldn't manage with it to forward the events to the grand-parent. Then I tried to use watchers. And here comes the interesting part. A regular watcher seems to not work, but adding below snippet inside src/components/RuntimeTemplateCompiler.vue
of this plugin and adding ref="child"
on the component
tag, will receive the change update from the child and forward it to the grand-parent, so that it updates too and all involved parts be in sync. Notice, that using .sync
on the prop is still required, for this to work.
mounted() {
this.$watch(
"$refs.child.compilerModel",
(new_value, old_value) => (this.parent.compilerModel = new_value)
);
},
One issue with this is, that the update occurs it on the variable name that has been passed to the prop. Maybe using v-model
instead of a prop may be different, but since I would rather use myself props to be able to pass several data, this testing approach was for me more relevant and I haven't tried to confirm it with v-model
too, as I already spent too much time with this.
Notice too, how it requires to watch for the names used in the parent, because of this. So, I'm not sure, what approach would make sense to add it to the plugin, otherwise I would have tried to submit a patch already. But, having this info, may help someone more advanced to create a working solution. Maybe, adding watchers like this inside mounted()
(remember, regular watchers won't work) for each and every name inside data
could make this work always.
For what I need it, it was enough to create my copy of the plugin and have a object as prop, which contains all the data I need to pass to the component and sync back again.
Hehe, I finally did it, to make it work for any variable names (even though I said above I wouldn't).
These are the changes required to make it work (still only tested with props though, but since v-model
and props are shorthands for v-bind
, this may work for both).
Modify the template of src/components/RuntimeTemplateCompiler.vue
:
<template>
<component ref="child" :is="compiled" v-if="compiled"/>
</template>
And add the following to the export of src/components/RuntimeTemplateCompiler.vue
:
mounted() {
Object.keys(this.parentComponent.$data).forEach((key) => {
try {
if (this.parentComponent.$data[key] !== this.parentComponent) // avoid "Too much recursion" error, when using parent
this.$watch("$refs.child." + key,
(new_value) => (this.parentComponent.$data[key] = new_value),
{deep: true}
);
// catch other cases which may not work this way (usually because of deep-check)
} catch {
console.warn('Cannot watch: ' + '$refs.child.' + key)
}
});
},
With this, all values will be modified in the parent too. Remember, to use the .sync
option in your prop, ie. (based on the codebase example at the beginning):
<runtime-template-compiler :template="`<custom-input :myprop.sync='compilerModel'/>`" :parent="parentComponent"/>
I may submit later a pull request for this...