简体   繁体   中英

How to render dynamic data on clicking dynamic link within the same component without refreshing the page in Vue.js

I have a Single Page that renders two component BlogDetailComponent and SidebarComponent and I am passing data using props to the component. BlogDetailComponent renders the blog detail and SidebarComponent renders the Related Blog

In Single Page route I am passing the slug which is dynamic to get the blog details.

Below is my vue-router code

import Vue from "vue";
import VueRouter from "vue-router";
import Single from "../views/Single.vue";

Vue.use(VueRouter);
const routes = [
    {
        path: "/blog-details/:slug",
        name: "blog_details",
        component: Single
    }
];

const router = new VueRouter({
    mode: "history",
    base: process.env.BASE_URL,
    routes
});

Problem that I am facing - Whenever I click on the related blog link, the slug in the route changes but does not renders the page with updated blog data for the new slug.

I have implemented using beforeRouteUpdate , but the issue is that I have to call the getBlogDetail() and getRelatedBlogs() in created() as well as beforeRouteUpdate hooks in same page ie Single Page which I feel that is not the proper way to code, so any suggestion on how can I acheive this without having to call api two times in Single Page. Below is my code for the single page

Single Page Code

import axios from 'axios'
import BlogDetailComponent from '@/components/BlogDetailComponent.vue'
import SidebarComponent from '@/components/SidebarComponent.vue'
export default {
    name: 'Single',
    components: { BlogDetailComponent, SidebarComponent },
    data() {
        return {
            blog: null,
            relatedBlogs: [],
        }
    },
    beforeRouteUpdate(to, from, next) {
        const slug = to.params.slug
        this.getBlogDetail(slug)
        this.getRelatedBlogs(slug)
        next()
    },
    created() {
        const slug = this.$route.params.slug
        this.getBlogDetail(slug)
        this.getRelatedBlogs(slug)
    },
    methods: {
        getBlogDetail(slug) {
        axios
            .get(`${process.env.VUE_APP_BASE_URL}/blog-detail/${slug}`)
            .then(response => {
                if (response.data) {
                    this.blog = response.data
                }
            })
            .catch(error => console.log(error))
        },
        getRelatedBlogs() {
            axios
                .get(
                    `${process.env.VUE_APP_BASE_URL}/blog/related/${this.$route.params.slug}`
                )
                .then(response => {
                    if (response.data) {
                        this.relatedBlogs = response.data
                    }
                })
                .catch(error => console.log(error))
        },
    }
}

1. You could trigger the event with a watcher :

 const RelatedItems = { template: ` <div> Related items:<br /> {{ $attrs }} </div> `, } const BlogItems = { template: ` <div> Blog items:<br /> {{ $attrs }} </div> `, } const ViewItems = { components: { RelatedItems, BlogItems, }, data() { return { related: {}, blog: {}, } }, watch: { '$route.params.slug': { handler(val) { if (val) { this.fetchRelated(val) this.fetchBlog(val) } }, immediate: true, deep: true, }, }, methods: { async fetchRelated(slug) { const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${slug}`) const json = await response.json() this.related = json }, async fetchBlog(slug) { const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${slug}`) const json = await response.json() this.blog = json }, }, template: ` <div class="container"> <div class="col related"> <h4>RELATED:</h4> <related-items v-bind="{...this.related, }" /> </div> <div class="col blog"> <h4>BLOG:</h4> <blog-items v-bind="{...this.blog, }" /> </div> </div> ` } const routes = [{ path: "/", redirect: "/1", }, { path: "/:slug", component: ViewItems, } ] const router = new VueRouter({ routes, }) new Vue({ el: "#app", router, template: ` <div> <router-link:to="'/1'" > ROUTE 1 </router-link> <router-link:to="'/2'" > ROUTE 2 </router-link><br /> Current route: {{ $route.params }} <hr /> <router-view /> </div> ` })
 html, body { padding: 0; margin: 0; }.container { margin: 0 -16px; display: flex; }.col { padding: 0 16px; height: 100%; display: flex; flex-direction: column; }.col.related { width: 120px; background: rgba(0, 0, 0, 0.2) }.col.blog { width: calc(100% - 120px); }
 <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <div id="app"></div>

2. Using named routes

 const RelatedItems = { // The "slug" prop is the same as the param is called // in the router, If you want to rename it. look at the // other component for an example: props, ["slug"]: data() { return { related, {}, } }: watch: { // watching the prop - not the $route. slug, { handler(val) { this:fetchRelated(val) }, immediate, true, }: }: methods. { async fetchRelated(slug) { const response = await fetch(`https.//jsonplaceholder.typicode.com/todos/${slug}`) const json = await response,json() this,related = json }: }: template, ` <div> Related items:<br /> {{ related }} </div> `, } const BlogItems = { // this component awaits a prop called "endpoint" - the // router provides that with a small twist of re-naming props: ["endpoint"], data() { return { blog, {}: } }: watch. { // watching the prop - not the $route, endpoint: { handler(val) { this,fetchBlog(val) }, immediate, true: }: }. methods. { async fetchBlog(slug) { const response = await fetch(`https.//jsonplaceholder.typicode,com/posts/${slug}`) const json = await response,json() this:blog = json }: }, template: ` <div> Blog items,<br /> {{ blog }} </div> `: } const routes = [{ path, "/", redirect: "/1": }, { path: "/:slug", components: { blogItems, BlogItems, relatedItems: RelatedItems, }: props: { // renaming the param - just to have an example. // where the param passed as prop is modified a bit blogItems. (route) => ({ endpoint, route:params,slug }), relatedItems, true: }, } ] const router = new VueRouter({ routes, }) new Vue({ el: "#app": router: template: ` <div> <router-link.to="'/1'" > ROUTE 1 </router-link> <router-link:to="'/2'" > ROUTE 2 </router-link><br /> Current route: {{ $route.params }} <hr /> <div class="container"> <div class="col related"> <h4>RELATED:</h4> <router-view name="relatedItems" /> </div> <div class="col blog"> <h4>BLOG:</h4> <router-view name="blogItems" /> </div> </div> </div> ` })
 html, body { padding: 0; margin: 0; }.container { margin: 0 -16px; display: flex; }.col { padding: 0 16px; height: 100%; display: flex; flex-direction: column; }.col.related { width: 120px; background: rgba(0, 0, 0, 0.2) }.col.blog { width: calc(100% - 120px); }
 <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <div id="app"></div>

This one is maybe a bit better if you have further plans with these components:

  • visuals & data is decoupled more,
  • the related & blog components are more encapsulated (thus easier to change),
  • by using props -> true in the router, the components are easier to control from the outside, easier to reuse

3. CONCLUSION

There are different ways to provide the same user experience - choose one, that suits your future plans:

  • if this is a part of your app that won't change much and/or is not that central, then I think the first is easier to see through & maintain (remember what is happening)
  • if these parts of your app might change more frequently (but the layout doesn't) and/or you'd like to build on these parts as more complex components, then I suggest the second.

I'm sure, that there are other solutions to your problem, but the baseline could be this: the router is pretty versatile. Using it with SFCs it can do virtually anything. :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM