Jon Taylor
Jon Taylor

Reputation: 7905

Dynamically add properties to Vue component

I am loading data from the database which drives what type of component I display

An AJAX call goes off and returns me some data (this can be restructured if needed)

{
  component_type: 'list-item',
  props: {
    name: 'test',
    value: 'some value'
  }
}

This is accessible on my parent object a variable called component

Within the template of my parent object I have the following

<component :is="component.component_type" ></component>

This works fine and loads the component as expected.

Next I want to add the properties from my data object into this tag too

<component :is="component.component_type" {{ component.props }} ></component>

This doesn't work and rejects writing a tag with {{ in it. I presume this is an error thrown by the browser rather than Vue, although I'm unsure.

For reference I want the output to actually look like:

<component :is="component.component_type" name='test' value='some value' ></component>

How can I go about passing in these properties? Ideally I'd like these to be tied to data / props of the parent as I'm showing so that they can easily be changed in database and the UI will change accordingly.

At worst I will generate it all on server side, but I'd rather do it via ajax as I'm currently trying to do.

Upvotes: 13

Views: 16351

Answers (3)

Mr_Chimp
Mr_Chimp

Reputation: 6907

In case anyone is wondering how to do this using Vue 2 you can just pass an object to v-bind:

<template>
    <component :is="componentType" v-bind="props"></component>
</template>

<script>
    export default {
        data() {
            return {
                componentType: 'my-component',
                props: {
                    foo: 'foofoo',
                    bar: 'barbar'
                }
            }
        }
    }
</script>

Upvotes: 14

Saurabh
Saurabh

Reputation: 73589

Following this thread, I see two options to do this.

one is to pass a single prop which is an object in itself, and pass all the relevant key values in it which can be used by the child component, something like following:

<component :is="component. component_type"
        :options="component.props"
</component>

Other solution mentioned is to have a directive, where you pass the object and it will set the attributes which are keys in that object to corresponding values, you can see this in work here.

Vue.directive('props', {
        priority: 3000,
    bind() {

        // set the last component child as the current
        let comp = this.vm.$children[this.vm.$children.length - 1];
        let values = null;
        if(this._scope && this._scope.$eval) {
            values = this._scope.$eval(this.expression);
        } else {
            values = this.vm.$eval(this.expression);
        }
        if(typeof values !== 'object' || values instanceof Array) {
            values = { data: values };
        }

        // apply properties to component data
        for(let key in values) {
            if(values.hasOwnProperty(key)) {
                let hkey = this.hyphenate(key);
                let val = values[key];
                if(typeof val === 'string') {
                    comp.$options.el.setAttribute(hkey, values[key]);
                } else {
                    comp.$options.el.setAttribute(':' + hkey, values[key]);
                }
            }
        }

                console.log(comp.$options.el.outerHTML);
        comp._initState();
    },

    /*
     * Hyphenate a camelCase string.
     */
    hyphenate(str) {
        let hyphenateRE = /([a-z\d])([A-Z])/g;
        return str.replace(hyphenateRE, '$1-$2').toLowerCase();
    }
});

and use it like this:

<div class="app">
    <component is="component.component_type" v-props="component.props"></component>
</div>

Upvotes: 5

CodinCat
CodinCat

Reputation: 15914

As far as I know, there is no rest props (spread props) syntax in Vue.js template.

A possible solution is render functions. You have full power of JavaScript when using render functions, so you can do something like this:

render (h) {
  return h('foo', {
    props: {
      ...yourData
    }
  })
}

I created a simple example here: http://codepen.io/CodinCat/pen/bgoVrw?editors=1010

both component type (like foo) and props can be dynamic (but you still need to declare all the possible fields of prop (like a, b and c) in your child components)

There is another solution is JSX.

Use the JSX babel plugin: https://github.com/vuejs/babel-plugin-transform-vue-jsx

then you can use the spread props syntax:

return <foo {...{yourData}}></foo>

Upvotes: 3

Related Questions