When writing components in Vue you always have to declare the props you want to pass to the component upfront. I’m a big proponent of this because it forces you to declare a clear api, making usage much easier. Recently, somebody asked whether it was possible to pass dynamically declared props to a component, i.e. props that aren’t declared upfront, and while it isn’t natively supported, it is in fact possible.

The Vue Way

The usual way to pass props that you have not declared upfront it to declare a catch all prop that your component can use. A good example of this is if you have a text field component and you don’t want to declare every possible attribute upfront, so you do something like this:

Component Vue Instance

Vue.component('textfield', {
  template: "#textfield",
  props: ['attrs']
});

Component Template

<template id="textfield">
  <div>
    <input type="text" v-bind="attrs" />
  </div>
</template>

Then in your parent you would do:

<textfield :attrs="{placeholder: 'Name'}"></textfield>

Now, we can pass any attribute using an object. One issue here is that it isn’t as clear to see what is getting passed through to the component, especially if you are binding your prop to data on the parent vue instance or you have a long list of attributes.

Declaring Props Dynamically

If you try to add a prop that wasn’t declared upfront vue will ignore it, so we are going to have to grab the attribute passed through manually, and to do that we can use this.$vnode.elm.attributes. So let’s write a mixin that get’s all the attributes added to the component:

var DynamicProps = {
  mounted(){
    this.getProps()
  },
  methods: {
    getProps() {
      let props = this.$vnode.elm.attributes;
      Object.keys(props).forEach(key => {
        this.$set(this.props, props[key].name, props[key].nodeValue)
      });
    }
  },
  data(){
    return {
      props: {}
    }
  }
}

Here we have declared a props object in data and in the mounted hook (when this.$vnode.elm becomes available) we call the getProps() method which loops through each attribute and adds it to our props object. Then all we have to do is use the mixin in our component and bind to the props object in our mixin:

Component Vue Instance

Vue.component('textfield', {
  template: "#textfield",
  mixins: [DynamicProps]
});

Component Template

<template id="textfield">
  <div>
    <input type="text" v-bind="props" />
  </div>
</template>

Now in our parent we can pass any prop as if we declared it up front:

<textfield placeholder="Name"></textfield>

And here’s the final JSFiddle: https://jsfiddle.net/vuetiful/8ekam6tk/

A word of warning though, don’t overuse this technique. You should still make sure you declare your props upfront whenever possible, however, when that isn’t desirable then this is a nice clean option, just make sure you document the behaviour for future reference!

Advertisements