1

As my single file component is getting bigger, I attempted to refactor it a bit. Turns out I have a couple of dialogs that make the .vue file huge (800 lines+). So in order to keep the component clean, I am trying to turn the dialogs into dedicated child components (mydialog). I am using vuetify (familiarity with vuetify is not required).

I have a button in the parent component which enables the dialog. I am sending the dialog prop to the child component which then gets attached to the v-model of the dialog. The problem is, it only works the first time. And that's because as I click the close button, it changes the dialog's value to false and it remains false forever because the child component has lost communication with the parent. Under such circumstances, how can I fix this?

Here is the snippet:

Vue.component('mydialog', {
  props:{
    dialog: {
      type: Boolean,
      default: false
    }
  },

  template: `
<div class="text-xs-center">
    <v-dialog
      v-model="dialog"
      width="500"
    >

      <v-card>
        <v-card-title
          class="headline grey lighten-2"
          primary-title
        >
          A Dialog
        </v-card-title>

        <v-card-text>
          Lorem ipsum dolor sit amet,
        </v-card-text>

        <v-divider></v-divider>

        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn
            color="primary"
            flat
            @click="dialog = false"
          >
            Close
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </div>
`,
  data: function() {
    return {
      // dialog: false,
    }
  }

});

new Vue({
  el: '#app',
  data: {
    dialog: false
  }

});
<!doctype html>
<html>

<head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.2.0/vuetify.min.css" />
</head>

<body>
  <div id="app">
    <v-app id="inspire">
      <v-btn color="red lighten-2" dark @click="dialog=true">Click Me</v-btn>
      <mydialog :dialog="dialog"></mydialog>
    </v-app>

  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.2.0/vuetify.min.js"></script>
</body>

</html>
Tanmay
  • 3,009
  • 9
  • 53
  • 83
  • Looks like a duplicate of [this one](https://stackoverflow.com/questions/48035310/open-a-vuetify-dialog-from-a-component-template-in-vuejs). – raina77ow Aug 31 '18 at 19:12

1 Answers1

2

Your component communication was a bit off: basically your dialog did not inform the app component that it was closed. The following two changes enable the upstream communication between modal and app:

In app template:

  <mydialog :dialog.sync="dialog"></mydialog>

In mydialog controller:

data: function() {
    return {
      dialog$: false,
    };
  },
  methods: {
    onClose() {
      this.dialog$ = false;
      this.$emit('update:dialog', this.dialog$);
    },
  },
  watch: {
    dialog: {
      immediate: true,
      handler() {
        this.dialog$ = this.dialog;
      },
    },
  }

The first change makes the app component listen to updates on the dialog props of mydialog.

In mydialog I added a data property dialog$ which reflects the dialog props (because props are considered constant and should not be changed). A watcher takes care that downstream updates on dialog update dialog$. The onClose method updates dialog$ when the dialog was closed and emits an update to subscribers (specifically app).

Vue.component('mydialog', {
  props:{
    dialog: {
      type: Boolean,
      default: false
    }
  },

  template: `
<div class="text-xs-center">
    <v-dialog
      v-model="dialog$"
      width="500"
    >

      <v-card>
        <v-card-title
          class="headline grey lighten-2"
          primary-title
        >
          A Dialog
        </v-card-title>

        <v-card-text>
          Lorem ipsum dolor sit amet,
        </v-card-text>

        <v-divider></v-divider>

        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn
            color="primary"
            flat
            @click="onClose"
          >
            Close
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </div>
`,
  data: function() {
    return {
      dialog$: false,
    };
  },
  methods: {
    onClose() {
      this.dialog$ = false;
      this.$emit('update:dialog', this.dialog$);
    },
  },
  watch: {
    dialog: {
      immediate: true,
      handler() {
        this.dialog$ = this.dialog;
      },
    },
  }
});

new Vue({
  el: '#app',
  data: {
    dialog: false
  },
  template: `
    <v-app id="inspire">
      <v-btn color="red lighten-2" dark @click="dialog=true">Click Me</v-btn>
      <mydialog :dialog.sync="dialog"></mydialog>
    </v-app>
  `,

});
<!doctype html>
<html>

<head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.2.0/vuetify.min.css" />
</head>

<body>
  <div id="app"></div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.2.0/vuetify.min.js"></script>
</body>

</html>

As @raina77ow has mentioned, you can also solve this kind of issue with an event bus but it's not necessary in this case.

FK82
  • 4,907
  • 4
  • 29
  • 42