Jonathon
Jonathon

Reputation: 16333

Vue.js pass data from slot into component instance

I'm trying to build a re-usable component to deal with submitting forms via AJAX using Vue.js. Ideally, what I want to do is have a generic component that can be used in place of a HTML form element, in that may contain an unknown set of form elements such as input, select, textarea and so on.

I have got the following code for my component which is named ajax-form:

<template>
    <form class="form" :action="action" :method="method" v-on:submit.prevent="ajaxSubmit">
        <slot></slot>
    </form>
</template>

<script>
module.exports = {
    props: {
        action: {
            required: true,
            type: String
        },
        method: {
            default: 'post',
            required: false,
            type: String
        }
    },
    data() {
        return {
            formData: {}
        }
    },
    methods: {
        ajaxSubmit() {
            // Do ajax
        }
    }
}
</script>

And in my HTML, I would have something like the following:

<ajax-form action="http://example.com/do/something">
    <input name="first_name" type="text">
    <textarea name="about_you"></textarea>
</ajax-form>

What I would ideally like to happen is have all of the form elements that are placed inside my component using it's slot to be mapped to the data.formData property in my Vue component instance. So in this case, the data property would look like:

data: {
    formData: {
        first_name: '',
        about: ''
    }
}

If I was to add another field to the component in my HTML, I would expect that to also be mapped to the Vue instance's data property.

Is there a way I can achieve this? Is there a way that I can tell Vue, when putting my form elements into the component via the slot, that I want this element to be mapped to something in the component's data?

I have tried adding v-model and v-bind on each form element to see if it that would somehow pass the data into the component's data:

<ajax-form action="http://example.com/do/something">
    <input name="first_name" type="text" v-model="formData.first_name">
</ajax-form>

However, Vue complains that reactive data properties must be declared before they're used in the template:

[Vue warn]: Property or method "formData" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.

Upvotes: 7

Views: 23617

Answers (4)

I Want Answers
I Want Answers

Reputation: 441

While @blockhead's answer is correct, it's important to note that it doesn't matter if the slot inserted into ajax-form is a single file component with own template tag. You will still have to wrap that component in an outer template tag in order to intercept the slot properties, and then pass them as properties into your actual target component

<modal-two modal_id="notif-modal">

    <template #default="{id_for_children}">

        <notification-alert :parent_modal_name="id_for_children" />
    </template>
</modal-two>

I didn't see this anywhere else

Upvotes: 0

Hazem Sabry
Hazem Sabry

Reputation: 25

You can pass the data from parent to child as a parameter as follow:

In the Parent:

<child-component ref="child">
      <h1 slot="heading">Test passing data to child</h1>
      <form slot="content">
          <input type="text" v-model="name" placeholder="Type your name" class="half">
          <input type="text" v-model="email" placeholder="Type your email" class="half">
          <div class="actions">
              <button data-role="submit" @click.prevent="callAddRep($event)">Add</button>
          </div>
      </form>
</child-component>
data() {
    return {
        name: '',
        email: ''
    }
},
methods: {
    callAddRep: function(e) {

      let name = this.name,
          email = this.email

      this.$refs.child.addRep(e, {
          name,
          email
      })
  }

In the Child:

methods: {
      addRep: function(e, data) {

          //Data passed successfully
          console.log(data)

      }
  }

Upvotes: 2

blockhead
blockhead

Reputation: 9705

You can do this with scopedSlots.

The api looks like this:

<ajax-form action="http://example.com/do/something">
   <template scope="{formData}">    
   <input name="first_name" type="text" v-model="formData.first_name">
   </template>
</ajax-form>

And in the ajax-form component:

<form class="form" :action="action" :method="method" v-on:submit.prevent="ajaxSubmit">
   <slot :formData="formData">
</form>

Upvotes: 35

Goodbytes
Goodbytes

Reputation: 694

You can't do what you're asking as the slots scope is limited to the parent scope.

You can define formData on the global Vue instance, so it is now accessible from the parent scope.

const app = new Vue({
    el: '#app',
    data: {
        formData : {}
    }
});

Now you can pass it to the form as a prop:

<ajax-form action="#" :form-data="formData>
    <input name="first_name" type="text" v-model="formData.first_name">
</ajax-form>

Upvotes: 4

Related Questions