Ari
Ari

Reputation: 6169

Remove dynamically created Vue Components

I followed the solution to create dynamic form elements here: Dynamically adding different components in Vue

Works great, my next conundrum is now I want to remove form elements if the user adds too many.

The way it works is the user creates a "set" which is defined as a group of inputs. So each set could be for a different person, or place, etc.

Here is my JSfiddle https://jsfiddle.net/61x784uv/

Html

<div id="component-pii-input" v-for="field in fields" v-bind:is="field.type" :key="field.id">
</div>
<button id='button-add-pii-component' v-on:click="addFormElement('pii-entry-field')">Add Set</button>

</div>

Javascript

Vue.component('pii-entry-field', {
  data: function () {
    return {
fields: [],
    count: 0,
    }
  },
     methods: {
      addFormElement: function(type) {
      this.fields.push({
        'type': type,
        id: this.count++
      });
    },
     },
  template: ` <div class='pii-field'><div>
     <component v-for="field in fields" v-bind:is="field.type":key="field.id"></component>
             </div>

            <button id='button-add-pii-input' v-on:click="addFormElement('pii-input-field')">Add Input</button>
            <hr>
            </div>`,
})

Vue.component('pii-input-field', {
  data: function () {
    return {

    }
  },

  template: ` <div>
            <select>
                <option disabled>Select Classification</option>
                <option>Name</option>
                <option>Address</option>
                <option>Email</option>
                <option>Phone</option>
                <option>Medical</option>
                <option>Financial</option>
            </select>

        <div>
            <input type="text" placeholder="Input">
        </div>
        <button v-on:click="removeFormElement">Remove Input</button></span>
        </div>`,
})

var app = new Vue({
  el: '#app',
  data: {
    fields: [],
    count: 0,
  },
    methods: {
      addFormElement: function(type) {
      this.fields.push({
        'type': type,
        id: this.count++
      });
    },

    }
});

Upvotes: 1

Views: 11321

Answers (2)

Bergur
Bergur

Reputation: 4057

Here is a working fiddle: https://jsfiddle.net/e12hbLcr/

Walkthrough:

  1. You need to give the component some kind of id to know what component should be removed. You can do this with props.

    <component v-for="field in fields" v-bind:is="field.type" :id="field.id" :key="field.id">

  2. Now create the function in the child-component and edit the button so it sends the id.

    <button v-on:click="removeFormElement(id)">Remove Input&lt/button>

Remember in Vue that props go down (parent -> child) and events up (child-parent). So now we need to tell the parent that this button was clicked and an id was sent.

removeFormElement(id) {
  console.log('sending message up to remove id', id)
  this.$emit('remove', id)      
}
  1. Tell the parent component to listen to that event and attach a function to that event.

    <component v-for="field in fields" v-bind:is="field.type" :id="field.id" @remove="removeFormElement" :key="field.id">

Note that the @ is same as v-on:

  1. Finally remove that item from the fields array.
removeFormElement(id) {
    console.log('removing form element', id)        
    const index = this.fields.findIndex(f => f.id === id)
    this.fields.splice(index,1)                
}

Upvotes: 6

Yom T.
Yom T.

Reputation: 9180

You should probably move these remove buttons into a <slot> of the component so you could pass in the scoped id.

But if you can't, you could $emit removal event on the $parent of the individual components, passing the id of the item to remove.

Vue.component('pii-entry-field', {
  data() {
    return {
      fields: [],
      count: 0,
    }
  },

  mounted() {
    this.$on('removeFormElement', this.removeFormElement);
  },

  methods: {
    addFormElement(type) {
      this.fields.push({
        'type': type,
        id: this.count++
      });
    },

    removeFormElement(id) {
      const index = this.fields.findIndex(f => f.id === id);

      this.fields.splice(index, 1);
    }
  },

  template: `
    <div class='pii-field'>
      <component v-for="field in fields" v-bind:is="field.type" :key="field.id"></component>

      <button id='button-add-pii-input' v-on:click="addFormElement('pii-input-field')">Add Input</button>
      <hr>
    </div>`,
})

Vue.component('pii-input-field', {
  data() {
    return {

    }
  },

  methods: {
    removeFormElement() {
      const id = this.$vnode.key;
      this.$parent.$emit('removeFormElement', id);
    }
  },

  template: `
    <div>
      <select>
        <option disabled>Select Classification</option>
        <option>Name</option>
        <option>Address</option>
        <option>Email</option>
        <option>Phone</option>
        <option>Medical</option>
        <option>Financial</option>
      </select>

      <div>
        <input type="text" placeholder="Input" />
      </div>
      <button v-on:click="removeFormElement">Remove Input</button>
    </div>`,
})

var app = new Vue({
  el: '#app',

  data() {
    return {
      fields: [],
      count: 0,
    }
  },

  methods: {
    addFormElement(type) {
      this.fields.push({
        'type': type,
        id: this.count++
      });
    },

  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <div id="component-pii-input" v-for="field in fields" v-bind:is="field.type" :key="field.id">

  </div>

  <button id="button-add-pii-component" v-on:click="addFormElement('pii-entry-field')" class="uk-button uk-button-primary uk-width-1-1 uk-margin-small">Add Set</button>
</div>

Upvotes: 1

Related Questions