13

Question: Is there any way to update the props of a manually mounted vue component/instance that is created like this? I'm passing in an object called item as the component's data prop.

let ComponentClass = Vue.extend(MyComponent);

let instance = new ComponentClass({
    propsData: { data: item }
});

// mount it
instance.$mount();

Why

I have a non vue 3rd party library that renders content on a timeline (vis.js). Because the rest of my app is written in vue I'm attempting to use vue components for the content on the timeline itself.

I've managed to render components on the timeline by creating and mounting them manually in vis.js's template function like so.

template: function(item, element, data) {

    // create a class from the component that was passed in with the item
    let ComponentClass = Vue.extend(item.component);

    // create a new vue instance from that component and pass the item in as a prop
    let instance = new ComponentClass({
        propsData: { data: item },
        parent: vm
    });

    // mount it
    instance.$mount();

    return instance.$el;
}

item.component is a vue component that accepts a data prop.

I am able to create and mount the vue component this way, however when item changes I need to update the data prop on the component.

Craig Harshbarger
  • 1,863
  • 3
  • 18
  • 29
  • 1
    I suspect you're going to need to write a `render` function for the parent that causes your dynamic new component to display. That is how you properly hook up props. `propsData` is unchangeable and intended for testing creation. – Roy J Dec 14 '18 at 22:37
  • @RoyJ so there is no way to add dynamic props to a manually mounted instance? Even using something different than `propsData`? I don't have the option of mounting this component in a render function because its being passed into a 3rd party library (that is not vue). – Craig Harshbarger Dec 17 '18 at 15:46
  • If `item` is an object whose members' values are changing, you could set the `data` function to return `item` and probably get what you want. – Roy J Dec 17 '18 at 17:44
  • @RoyJ Can you provide an example? – Craig Harshbarger Dec 17 '18 at 21:36

4 Answers4

4

If you define an object outside of Vue and then use it in the data for a Vue instance, it will be made reactive. In the example below, I use dataObj that way. Although I follow the convention of using a data function, it returns a pre-defined object (and would work exactly the same way if I'd used data: dataObj).

After I mount the instance, I update dataObj.data, and you can see that the component updates to reflect the new value.

const ComponentClass = Vue.extend({
  template: '<div>Hi {{data}}</div>'
});

const dataObj = {
  data: 'something'
}

let instance = new ComponentClass({
  data() {
    return dataObj;
  }
});

// mount it
instance.$mount();
document.getElementById('target').appendChild(instance.$el);

setTimeout(() => {
  dataObj.data = 'another thing';
}, 1500);
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="target">
</div>
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • This might be what I'm looking for. So basically the answer is to use data rather than props? So you're modifying the components data directly which okay? – Craig Harshbarger Dec 18 '18 at 16:51
  • 1
    That's basically it. There really isn't a rule or convention for this situation. Vue isn't managing the component, so you have to. – Roy J Dec 18 '18 at 18:08
  • but what if I want to recalc a value based on a non-reactive property (say which depends on a `ref`)? – YakovL Dec 19 '19 at 11:09
  • @YakovL You would have to create something that reacts to the change. – Roy J Dec 20 '19 at 18:37
  • @RoyJ not sure what do you mean by that.. well, I've ended up with changing a calculated property to data and recalcing on mount (https://stackoverflow.com/q/59392865/3995261) – YakovL Dec 20 '19 at 21:17
0

In Vue 2.2+, you can use $props. In fact, I have the exact same use case as yours, with a Vue project, vis-timeline, and items with manually mounted components.

In my case, assigning something to $props.data triggers watchers and the whole reactivity machine.

EDIT: And, as I should have noticed earlier, it is NOT what you should do. There is an error log in the console, telling that prop shouldn't be mutated directly like this. So, I'll try to find another solution.

tony19
  • 125,647
  • 18
  • 229
  • 307
Adrien Clerc
  • 2,636
  • 1
  • 21
  • 26
0

This has changed in Vue 3, but it's still possible when using the new Application API.

You can achieve this by passing a reactive object to App.provide():

<!-- Counter.vue -->

<script setup>
import { inject } from "vue";

const state = inject("state");
</script>

<template>
  <div>Count: {{ state.count }}</div>
</template>
// script.js

import Counter from "./Counter.vue";

let counter;
let counterState;

function create() {
  counterState = reactive({ count: 0 });

  counter = createApp(Counter);
  counter.provide("state", counterState);
  counter.mount("#my-element");
}

function increment() {
  // This will cause the component to update
  counterState.count++;
}
Joshua Wade
  • 4,755
  • 2
  • 24
  • 44
-1

Here's how I'm able to pass and update props programmatically:

const ComponentClass = Vue.extend({
  template: '<div>Hi {{data}}</div>',
  props: {
    data: String
  }
});

const propsObj = {
  data: 'something'
}

const instance = new ComponentClass()

const props = Vue.observable({
      ...instance._props,
      ...propsObj
})

instance._props = props

// mount it
instance.$mount();
document.getElementById('target').appendChild(instance.$el);

setTimeout(() => {
  props.data = 'another thing';  // or instance.data = ...
}, 1500);
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="target">
</div>
AndyDeveloper
  • 2,790
  • 1
  • 21
  • 23
  • Thank you for your answer. Seems logical, though I think if I attempted this again I wouldn't use props and would use either the instance data or a vuex store to. – Craig Harshbarger Sep 02 '20 at 01:50