Vue 3 - Emit event from child component within a slot

4.9k views Asked by At

I'm looking at upgrading to v3 and was disappointed to see inline-template has been removed. Therefore I'm trying to convert to use a scoped slot instead.

I have defined the following list component:

<template>
    <slot :items="filteredItems" :page="page" :totalPages="totalPages" :onPageChanged="onPageChanged"></slot>
</template>

<script>
    export default {
        props: {
            items: {
                type: Array
            },
            initialPage: {
                type: Number,
                default: 1,
            },
            pageSize: {
                type: Number,
                default: 10
            }
        },
        beforeCreate() {
            this.page = this.initialPage;
        },
        computed: {
            filteredItems() {
                return this.items.slice((this.page - 1) * this.pageSize, this.page * this.pageSize);
            },
            totalPages() {
                return Math.ceil(this.items.length / this.pageSize);
            }
        },
        methods: {
            onPageChanged(page) {
                console.log('Page changed!!!');

                this.page = page;
            }
        }
    };
</script>

Which is called like so:

<list :items="[ { foo: 'A' }, { foo: 'B' }, { foo: 'C' } ]" :page-size="2" #="{ items, page, totalPages, onPageChanged }">
    <ul class="list-group">
        <li class="list-group-item" v-for="item in items">{{ item.foo }}</li>
    </ul>
    <pager :page="page" :total-pages="totalPages" @pageChanged="onPageChanged"></pager>
</list>

Here's the pager component:

<template>
    <ul class="pagination">
        <li class="page-item" v-if="hasPreviousPage"><a href="#" @click="changePage(1)" class="page-link">&laquo;</a></li>
        <li class="page-item" v-if="hasPreviousPage"><a href="#" @click="changePage(page - 1)" class="page-link">&#8249;</a></li>
        <li v-for="page in pages" :class="['page-item', { active: page.isActive }]"><a href="#" @click="changePage(page.name)" class="page-link">{{ page.name }}</a></li>
        <li class="page-item disabled" v-if="page < totalPages - 2"><span class="page-link"> ... </span></li>
        <li class="page-item" v-if="page < totalPages - 2"><a href="#" @click="changePage(totalPages)" class="page-link">{{ totalPages }}</a></li>
        <li class="page-item" v-if="hasNextPage"><a href="#" @click="changePage(page + 1)" class="page-link">&#8250;</a></li>
        <li class="page-item" v-if="hasNextPage"><a href="#" @click="changePage(totalPages)" class="page-link">&raquo;</a></li>
    </ul>
</template>

<script>
    export default {
        props: {
            page: {
                type: Number,
                required: true
            },
            totalPages: {
                type: Number,
                required: true
            }
        },
        computed: {
            hasPreviousPage() {
                return this.page > 1;
            },
            hasNextPage() {
                return this.page < this.totalPages;
            },
            pages() {
                const range = [];

                for (let i = this.page <= 2 ? 1 : this.page - 2; i <= (this.page >= this.totalPages - 2 ? this.totalPages : this.page + 2); i++) {
                    range.push({
                        name: i,
                        isActive: this.page == i
                    });
                }

                return range;
            }
        },
        methods: {
            changePage(page, e = event) {
                e.preventDefault();

                // Trigger the page changed event.
                this.$emit('pageChanged', page);
            }
        }
    };
</script>

However whenever I try to change the page, the changePage method is invoked which emits the pageChanged event, but it doesn't invoke the onPageChanged method within the list component.

I'd appreciate if someone could show me what I'm doing wrong. Thanks

1

There are 1 answers

1
Boussadjra Brahim On BEST ANSWER

The event name should be written in kebab-case format as follows :

this.$emit('page-changed', page);

and use it like @page-changed="onPageChanged