[Vuejs]-Adding v-model to v-dialog breaks the element in array

0👍

We don’t need to create a modal component for each item in a for-loop.

Because when we create modals in for-loop then all modals’ components have been created separately but they all are binding with the same v-model, so the last v-model will always be active and pass only the last item’s data.

This problem can also be fixed by passing the array index or creating a separate v-model using an object but The good practice is to keep the modal component outside of for loop and use it accordingly by passing data of each selected item as a prop.

That means you can do it like this-
(I used dummy data to create the UI)

PARENT COMPONENT-

<template>
    <div>
        <!-- To open dialog on click on any item -->
        <Dialog
            v-if="dialog && selectedApp"
            v-model="dialog"
            :app="selectedApp"
            @closeDialog="closeDialog"
        />

        <!-- Loop on items -->
        <v-col v-for="app in apps" :key="app._id" cols="12">
            <v-card class="pa-2 " outlined height="230px">
                <v-dialog v-model="dialog">
                    <template v-slot:activator="{ on: dialog, attrs }">
                        <v-tooltip right color="#363636" max-width="250px">
                            <template v-slot:activator="{ on: tooltip }">
                                <v-img
                                    :src="app.src"
                                    class="white--text align-end"
                                    gradient="to bottom, rgba(0,0,0,.5), rgba(0,0,0,.5)"
                                    height="85%"
                                    v-bind="attrs"
                                    v-on="{ ...tooltip, ...dialog }"
                                    @click="selectedApp = app"
                                >
                                    <v-card-title v-text="app.title"></v-card-title>
                                </v-img>
                            </template>
                            <span v-if="app.vpnRequired">VPN needed to run!</span>
                            <v-divider class="my-1" v-if="app.vpnRequired" dark></v-divider>
                            <span>{{ app.description }}</span>
                        </v-tooltip>
                        <!-- shows the link of the clicked app (for example the second of 5) in the array-->
                        <h1>{{ app.link }}</h1>
                    </template>
                    <!-- shows the link of the last ( 5 of 5) app in the array. Why is this link different then the app.link from 2 lines up?-->
                    <h1>{{ app.link }}</h1>
                </v-dialog>
                <v-card-actions mb-2>
                    <v-spacer></v-spacer>
                    <v-tooltip top color="#363636">
                        <template v-slot:activator="{ on, attrs }" v-if="app.quickActions">
                            <v-btn @click="DeleteAppFromQuickActions(app)" icon>
                                <v-icon v-bind="attrs" v-on="on">
                                    mdi-minus-box-outline
                                </v-icon>
                            </v-btn>
                        </template>
                        <template v-slot:activator="{ on, attrs }" v-else>
                            <v-btn @click="AddAppToQuickActions(app)" icon>
                                <v-icon v-bind="attrs" v-on="on">
                                    mdi-plus-box-outline
                                </v-icon>
                            </v-btn>
                        </template>
                        <span v-if="app.quickActions">Delete from QuickActions</span>
                        <span v-else>Add to QuickActions</span>
                    </v-tooltip>
                    <v-spacer></v-spacer>
                </v-card-actions>
            </v-card>
        </v-col>
    </div>
</template>

<script>
import Dialog from "/var/www/html/weresearchfrontend/src/Dialog.vue";

export default {
    name: "ParentComponent",

    data() {
        return {
            dialog: false,
            selectedApp: null,
            apps: [
                {
                    title: "A",
                    vpnRequired: true,
                    src: "https://source.unsplash.com/user/c_v_r/1900x800",
                    description: "Hello Google",
                    link: "https://google.com",
                    quickActions: true,
                },
                {
                    title: "B",
                    vpnRequired: true,
                    src: "https://source.unsplash.com/user/c_v_r/100x100",
                    description: "Hello Github",
                    link: "https://github.com",
                    quickActions: true,
                },
            ],
        };
    },

    components: {
        Dialog,
    },

    methods: {
        closeDialog: function() {
            this.dialog = false;
            // reset selected app also
            this.selectedApp = app;
        },
    },
};
</script>

CHILD COMPONENT (DIALOG COMPONENT)-

<template>
    <v-dialog :value="value">
        <v-card>
            <v-container fluid>
                <v-row>
                    <v-col fixed style="background:#363636;" class="d-flex" cols="12">
                        <span class="white--text font-weight-bold"
                            >{{ app.title }} > {{ app.description }} > {{ app.link }}</span
                        >
                        <v-spacer></v-spacer>
                        <v-btn plain color="grey" :href="app.link" target="_blank">
                            <v-icon>mdi-open-in-new</v-icon>
                        </v-btn>
                        <v-btn @click="$emit('closeDialog')" plain color="grey">
                            <v-icon>mdi-close-box-outline</v-icon>
                        </v-btn>
                    </v-col>
                    <v-col cols="12">
                        <iframe :src="app.link" width="100%" height="800px" frameborder="0">
                        </iframe>
                    </v-col>
                </v-row>
            </v-container>
        </v-card>
    </v-dialog>
</template>

<script>
export default {
    name: "Dialog",

    props: ["value", "app"],
};
</script>

Leave a comment