Passing props to root instances in 2.0

Hi there,

In Vue 1.x, I used props to pass data to Vue when initially loading the page (dynamically generated by SilverStripe). For example:

<div id="vue-app" passedthroughvar="relevant data from backend"></div>
new Vue({
  el: '#vue-app',
  props: ['passedthroughvar'],
  ready() {
    // this.passedthroughvar can now be used for later requests to the backend and such
  },
});

In Vue 2.0, this is no longer available. The migration guide suggests use of propsData, but this does not appear to cover my use case as the data is set in the JS, rather than in the HTML.

Any suggestions on how I can recreate this functionality? While I would love to use SSR with Vue templates, for small widgets on an existing site this is not possible.

You’re looking for propsData

Hi posva,

Possibly - it didn’t appear to be the solution based on the API documentation, but perhaps you could provide a snippet to show how it would work in this situation?

Can’t you simply use the data as a window variable?

Figure it out yet?

The way I’ve worked around this so far is to turn the root (in your case #vue-app) into a component.

<div class="app-entrypoint">
    <vue-app passedthroughvar="...."></vue-app>
</div>

new Vue({ el: '.app-entrypoint', components: .... })

Hi @Cheddam

There is a tricky way and unofficial to do it, on the mounted hook and after the next tick, you can have access to:

vm._vnode.data.attrs.passedthroughvar

And you want also to v-bind your prop to let Vue.js interprets it as JavaScript.

Here my example: https://jsfiddle.net/Atinux/kmfwcmta/

But this might change in future versions of Vue.js, so a good way to do it is like @simshaun said, by using a component for your root entry point (a functional component is enough):

Vue.component('app', {
  functional: true,
  props: ['passedthroughvar'],
  render: function (createElement, context) {
    // You have access to context.props.passedthroughvar here
    return createElement('div', context.data, context.children);
  }
})

I let you check the example updated for using a functional component: https://jsfiddle.net/Atinux/p0jarqro/

I hope it solves your query :slight_smile:

Hi guys,

Thanks for your suggestions! I hadn’t considered Simshaun’s approach of simply putting the component in the HTML - this will suit my purposes! Atinux, I hadn’t seen functional components before; your example was very useful as well.

The final product: https://jsfiddle.net/p0jarqro/2/

Cheers for all your help - consider this case closed.

Just thought I’d throw my solution in. I think the idea of a component is an interesting way of doing this, but it honestly feels wrong to be. Components are reusable bits of code that can be replicated either on the same page via a v-for or on other pages.

So my solution is instead to leave it as a root instance and use the following code instead:

HTML

<div my-data="hey"></div>

Javascript

new Vue({
    mounted: function () {
        this.myData = this.$el.attributes.myData.value;
    }
});

I hope this helps anyone not wanting to use the component method!

9 Likes

Same issue for me, and I solved thanks to you guys. New to vue 2, I struggled to put together your clues, so here is a full code (using vue-cli webpack as starting point).

index.html

<!DOCTYPE html>
<html>
  <body>
    <entry-component myprop="propValue"></entry-component>
  </body>
</html>

main.js

import Vue from 'vue'
import App from './App'

new Vue({
  el: 'entry-component',
  render(h) {
    return h(App, {
      props: {
        myprop: this.$el.attributes.myprop.value
      }
    })
  }
})

App.vue

(...)
export default {
  props: ['myprop']
}
(...)

So ‘myprop’ from root component is available in App component

9 Likes

In case your app declares a template, than you need to detect the target element’s attributes before the app gets mounted, as once mounted, the attributes will be those of the template element.

So my working solution is: https://jsfiddle.net/4Lq943sc/1/

HTML:

<div id="my-target-element" data-var-name="var-value"></div>

JS:

new Vue({
    el: '#my-target-element',
    template: '<div>Just an example with {{varName}}</div>',
    data: {varName: null},
    beforeMount: function () {
        this.varName = this.$el.attributes['data-var-name'].value;
    },
});

PS: I’ve used data-something attribute naming to be valid names even for older browsers.

9 Likes

Thanks, was looking for this for a long time! I’m trying to pass an integer, but when I use this code, it will become a string. When I change data-var-name to :data-var-name, nothing is passed at al. Any suggestions?

1 Like

This looks like the most elegant solution of the many provided. Thanks!

It surprises me that passing in props from markup generated by the server outside of Vue is such a non-standard thing if Vue is meant to be useful on existing sites and not as a SPA, and if we want to think of vue as components outside of itself. it would make sense to me that the attributes on any new Vue instance are automatically available as props.

I guess you could loop over this.$el.attributes yourself, but odd that it doesn’t come easy…

5 Likes

This method works perfectly. Thanks!

Building upon your answer, if you’re using Babel and have the object-rest-spread plugin (which will soon be supported as standard ES2018), you can use Spread to quickly init all data-* attributes as Vue props:

HTML:

<div id="my-target-element"
	data-my-prop="prop-value"
	data-other-prop="other-prop-value"
></div>

App.vue:

<template>
    <div>Just an example with {{myProp}} and {{otherProp}}</div>
</template>
<script>
    export default {
        name: 'App',
        props: ['myProp', 'otherProp']
    };
</script>

index.js:

import App from './app.vue';

const root_element = document.getElementById('my-target-element');

const AppRoot = Vue.extend(App);
new AppRoot({
    el: root_element,
    propsData: { ...root_element.dataset }
});
4 Likes

That’s exactly what I thought!

I’ve worked with riot before digging into vue and in riot it’s much more comfortable: you just write custom attributes on the tag you’re mounting and they’re automatically available at opts within the tag/component.

Would be really great to have something similar in vue without the need to do all of it yourself. Otherwise it’s not fun to use vue when you only use it for specific parts of the page and don’t build a SPA.

The new Vue CLI has a webpack build option that builds your single file components as chunks and gives you an entry point that’ll just load each’s javascript asynchronously when it hits it in the DOM. So all you do is <my-component my-prop=“”> and it all kinda works.

It’s not supported in IE bc it’s webcomponenty, but there must be a way to do it in IE still and I think that’ll exist already or soon in the ecosystem

Hi @Hyzual,
I can’t get your example working. Can you please help me out?

Thanks

https://codesandbox.io/s/82mxv1mw9j

The trick was to use Vue.extend() and make a new App(), instead of new Vue()

1 Like

This worked for me

new Vue({
render: h => h(App,
 { props:
  { myProp: "the data" }
  })
}).$mount("#app");
1 Like

So I also found a solution that is pretty easy to use, but I’m not sure if it violates some of vue’s rules since we will have the root component and template component bound to the same DOM element.

this is similar to @simshaun’s solution, but with the wrapper element being more hidden

ex: https://codepen.io/duprasa/pen/mvZQrB?editors=1010

code:

<component id="component" prop-a ="Value A"></component>
let el = document.getElementById("component")
let component_instance = (new Vue({el: el, components: {component: component_options}})).$children[0];