Search code examples
vue.jsnotificationscomponentsvue-componentsnackbar

How to use a component multiple times while passing different data to it?


I'm trying to create a snackbar component for showing simple notifications. It can be used at many places in the entire application as well as on a single page as well. I've created a component as child component and imported it in the parent component where i want to use it. In this parent component many times this child can be used. How should i implement in a way that each time this component is called it gets its appropriate data(Ex. for error color=red text="error", for success color="green" message="success).

Any suggestions on how to implement it?

parent.vue----------------------------

<snackbar
      :snackbar="snackbar"
      :color="color"
      :text="message"
      v-on:requestClose="close"
    />


data() {
    return {
      snackbar: false,
      color: "orange",
      timeout: 3000,
      message: "calling from employee compoenent"
    };
  },
  methods: {
    hello() {
      console.log("button clicked!!!");
      this.snackbar = true;
    },
    close() {
      this.snackbar = false;
    },


child.vue-----------------------------------------------

<template>
  <v-snackbar v-model="snackbar" right top :timeout="timeout" :color="color"
    >{{ text }}
    <v-btn dark text @click.native="$emit('requestClose')">Close</v-btn>
  </v-snackbar>
</template>

<script>
export default {
  name: "snackbar",
  data() {
    return {
      timeout: 3000
    };
  },
  props: ["snackbar", "text", "color"],

};
</script>

<style></style>


Solution

  • Recommended would be to create a custom wrapper Vue plugin

    plugins/snackbar/index.js

    import snackbar from './snackbar.vue'
    
    export default {
      install (Vue) {
        // INSTALL
        if (this.installed) return
        this.installed = true
    
        // RENDER
        const root = new Vue({ render: h => h(snackbar) })
        root.$mount(document.body.appendChild(document.createElement('div')))
    
        // APIs
        let apis = Vue.prototype['$snackbar'] = {
          show: ({ text="Foo", color="blue" }) => root.$emit('show', { text, color }), // SHOW
          hide: () => root.$emit('hide') // HIDE
        }
    
        Vue.prototype['$snackbar'] = apis
        Vue.snackbar = apis
      }
    }
    

    plugins/snackbar/snackbar.vue

    <template>
      <v-snackbar right top v-model="show" :timeout="timeout" :color="color">
        {{ text }}
        <v-btn dark text @click.native="this.show = false">Close</v-btn>
      </v-snackbar>
    </template>
    
    <script>
    export default {
      name: "snackbar",
    
      data() {
        return {
          show,
          timeout: 3000,
          text: "",
          color: ""
        };
      },
    
      mounted () {
       // LISTENING :: SHOW
       this.$root.$on('show', ({ text, color }) => {      
        this.text = text
        this.color = color
        this.show = true
       })
    
       // LISTENING :: HIDE
       this.$root.$on('hide', () => this.show = false)
      }
    };
    </script>
    

    // main.js

    import Snackbar from './plugins/snackbar/index.js'
    Vue.use(Snackbar)
    

    To show / hide it in any component

    this.$snackbar.show({ text: "Foo bar", color: "red" }) // OR
    Vue.snackbar.show({ text: "Foo bar", color: "red" })
    

    As per use case, you can keep updating your plugin with more params / APIs.


    Alternative: By using an event bus

    event-bus/bus.js

    // Create an event bus
    import Vue from 'vue'
    export default new Vue()
    

    app.vue

    <template>
     // Render the component in app.vue
     <v-snackbar 
      right top 
      v-model="snackbar.show" 
      :timeout="snackbar.timeout" 
      :color="snackbar.color"
     >
      {{ snackbar.text }}
      <v-btn 
       dark text 
       @click.native="this.snackbar.show = false"
      >
       Close
      </v-btn>
     </v-snackbar>
    </template>
    
    <script>
    import bus from './event-bus/bus.js'
    
    export default {
     data () {
      return {
       snackbar: {
        show: false,
        text: '',
        color: '',
        timeout: 3000
       }
      }
     },
    
     mounted () {
      // LISTEN TO SHOW
      bus.$on('show', ({ text, color }) => {
       this.snackbar.text = 'foo'
       this.snackbar.color = 'red'
       this.snackbar.show = true
      })
    
      // LISTEN TO HIDE
      bus.$on('hide', () => this.snackbar.show = false)
     }
    }
    </script>
    

    To show / hide snackbar from any component

    import bus from './event-bus/bus.js
    
    export default {
     mounted () {
      bus.emit('show', { text: 'Foo bar baz', color: 'orange' }) // TO SHOW
      // bus.emit('hide') // TO HIDE
     }
    }
    

    Another way: By using Vuex

    Render the <v-snackbar> in app.vue as done in an alternative approach & use Vuex state / getters to pass the value to the props of v-snackbar.