Dynamic Layouts in a Vue Project

Ericko wicaksono
Ericko wicaksono

dynamic-layouts

There are some way to dynamic layouts a Vue js project. in this article, I will share my experience when making layouts for a Vue JS project. this tutorial required an installed Vue project. If you don't know yet how to install the Vue Js project refer to this page for Creating Vue Js project.

After that, we will go to learn with the example case. The case is we have a 3 different page (home, admin dashboard, and admin setting ). There is 2 layout, the first is the default layout for the home page. The second one is the admin layout for admin dashboard and admin setting page with the sidebar difference.

Conditional Render Component

the most simple approach to make dynamic layout is using a conditional v-if in the component. That we are going to do is add the conditional in our App.vue file. Usually, we can show conditionally the header or footer of a page. for example:

1<template>
2   <div class="App">
3+    <nav v-if="visibleNav" class="App__nav">
4       <router-link to="/">Home</router-link> |
5       <router-link to="/about">About</router-link>
6     </nav>
7     <router-view/>
8+    <footer v-if="visibleFooter">
9       &copy; This is Footer
10     </footer>
11   </div>
12 </template>
13 
14 <script>
15	import Layout from '@/components/Layout'
16	export default {
17	  name: 'App',
18	  components: {
19	    Layout
20	  },
21	  created () { console.log('created: layout conditional') },
22	  data () => ({ visibleNav: true, visibleFooter: true })
23	  destoyed () { console.log('destroyed: layout conditional') }
24	}
25 </script>

that was so simple, but this approach has a disadvantage.Your application might have so much condition in a single file. So, that will make difficulties in maintainability and readability of a code.

Static File with Vue Js Component Slots

Second approach is using one of Vue Js feature component slots. If you aren't familiar yet with slot you can assess it in the official Vue Js Slot documentation. Slot have so much functional like allowing you to compose another component . For example:

layouts/LayoutDefault.vue

1<template>
2   <div>
3     <nav>
4       <router-link to="/">Home</router-link> |
5       <router-link to="/about">About</router-link>
6     </nav>
7     
8     <slot></slot>
9     
10     <footer>
11       this is footer
12     </footer>
13   </div>
14 </template>
15
16<script>
17export default {
18  name: 'Layout',
19  components: {
20    Layout
21  },
22  created () { console.log('created: layout static') },
23  destoyed () { console.log('destroyed: layout static') }
24}
25</script>

layouts/LayoutAdmin.vue

1<template>
2   <div class="row">
3     <div class="sidebar">
4	     <sidebar />
5     </div>
6     <div class="column">
7       <nav>
8         <router-link to="/">Home</router-link> |
9         <router-link to="/about">About</router-link>
10       </nav>
11     
12       <slot></slot>
13     
14       <footer>
15         this is footer
16       </footer>
17     </div>
18   </div>
19 </template>
20
21<script>
22export default {
23  name: 'LayoutAdmin',
24  components: {
25    Layout
26  },
27  created () { console.log('created: layout admin static') },
28  destoyed () { console.log('destroyed: layout admin static') }
29}
30</script>

The LayoutAdmin.vue file are containt sidebar, header, body, and footer section in the page and the LayoutDefault.vue file did not containt sidebar. So only have header, body, and footer section.

Home.vue or About.Vue

1<template>
2   <div>
3     <Layout>
4	   <div>This is Body</div>
5     </Layout>
6   </div>
7 </template>
8 
9 <script>
10	import Layout from '@/components/Layout'
11	export default {
12	  name: 'Home',
13	  components: {
14	    Layout
15	  }
16    }
17 </script>

in the code above, The Layout.vue file was imported in the Home.vue file to use the layout. That was the same if we want to use same layout with Layout.vue, we must import it and register it to components. So, we can use the template component.

Statically dynamic layout with this approach will make the better mantainability and readability. But, we must import the Layout.vue file when want to use it in a page. So this will make the bundle bigger cause repeating the import in the file and the file will re-render when change to another page.

Dynamic Layout Component Wrapper

This approach are using the Vue Js dynamic component feature. This feature are make easily change or choose the component that we want to render with some of condition with is special attribute. For example:

1<component v-bind:is="currentComponent"></component>

In the example above, currentComponent are contain the name of a registered component, or a component’s options object. So in our case we can use the implementation like this example.

App.vue

1<template>
2  <component :is="layout">
3    <router-view :layout.sync="layout"/>
4  </component>
5</template>
6
7<script>
8export default {
9  name: 'App',
10  data() {
11    return {
12      layout: 'div',
13    };
14  },
15};
16</script>

Dynamic component approach are implement on App.vue file. In this file we implement is dynamic component and also .sync modifier. that example modifier in the code above will help the component to watch the layout data when updated. if you want to know more about the .sync feature you can follow this link

Home.vue

1<template>
2  <div class="Home">
3    <h1>Home</h1>
4  </div>
5</template>
6
7<script>
8import LayoutDefault from '../layouts/LayoutDefault.vue';
9
10export default {
11  name: 'Home',
12  created() {
13    this.$emit('update:layout', LayoutDefault);
14  },
15};
16</script>

After declare the Layout that you want to render, you can use $emit and call it in the created page, when component and data is not yet loaded. To tell the parent, there is a change or update in the layout data with the LayoutDefault.vue file component value in the second param. So, the parent will updated the value of new layout data and also change the layout because we use is dynamic component.

Maybe we are not metion what's the different with the slot approach?. The difference is in the App.vue file component will change if the layout data are update with difference data. If same data are updated the layout data, that will not re-render the layouts.

Dynamic Layout with Routes

The last approach are change the layout with routes. This approach difference with the others. The last we are adding the layout file in the file page that need to change, but this approach are required to import the layout and use in the file router/index.js or Vue Router. There are some method that can be used to import the layout in the router file. That is approach with router meta, and router component with childern. In this tutorial i will explain the using dynamicly component layout router with childern route. The first is make the layout default file in the folder layouts/LayoutDefault.vue.

layouts/LayoutDefault.vue

1<template>
2  <div id="default">
3    <header-component />
4    <div class="body">
5      <router-view/>
6    </div>
7    <footer-component />
8  </div>
9</template>
10
11<script>
12const HeaderComponent = () => import('@/components/base/Header');
13const FooterComponent = () => import('@/components/base/Footer');
14
15export default {
16  name: 'LayoutDetault',
17  components: {
18    HeaderComponent,
19    FooterComponent,
20  },
21};
22</script>
23

Layouts/LayoutAdmin.vue

1<template>
2   <div class="row">
3     <div class="sidebar">
4	     <sidebar />
5     </div>
6     <div class="column">
7       <nav>
8         <router-link to="/">Home</router-link> |
9         <router-link to="/about">About</router-link>
10       </nav>
11     
12       <slot></slot>
13     
14       <footer>
15         this is footer
16       </footer>
17     </div>
18   </div>
19 </template>
20
21<script>
22export default {
23  name: 'LayoutAdmin',
24  components: {
25    Layout
26  },
27  created () { console.log('created: layout admin static') },
28  destoyed () { console.log('destroyed: layout admin static') }
29}
30</script>

In the file we need to declare component that will be showing in the page. For example in the page are only header, sidebar, body, and footer. Header and footer are staticly import in the file. But the body is a <router-view /> tag that will show the dynamic of component under that route. The next step is setting the router index file

router/index.js

1import Vue from 'vue';
2import VueRouter from 'vue-router';
3
4const LayoutDefault = () => import('@/layouts/LayoutDefault');
5const LayoutAdmin = () => import('@/layouts/LayoutAdmin');
6
7const HomePage = () => import('@/pages/HomePage');
8const AdminDashboardPage= () => import('@/pages/AdminDashboardPage');
9const AdminSettingPage= () => import('@/pages/AdminSettingPage');
10
11Vue.use(VueRouter);
12
13const routes = [
14{
15  path: '/',
16  name: 'Default',
17  component: LayoutDefault,
18  children: [{
19    path: '',
20    name: 'Home',
21    component: HomePage,
22  }],
23},
24{
25  path: '/admin',
26  name: 'Admin',
27  component: LayoutAdmin,
28  children: [{
29    path: '',
30    name: 'AdminDashboard',
31    component: AdminDashboardPage,
32  },
33  {
34	path: '/setting',
35    name: 'AdminSetting',
36    component: AdminSettingPage,
37  }],
38}];
39
40const router = new VueRouter({
41  mode: 'history',
42  base: process.env.BASE_URL,
43  routes,
44});
45
46export default router;

In the code above first we need import the layout and file component, The Layout name is LayoutDefault.vue and the file component is Home, About, and Admin Page. Lets see the routes with / (slash) path this route are have a childern with name Home and will be showing the HomePage.vue component with LayoutDefault.vue file layout.

In the other, we have path /admin this is the parent route, and under this root will using the LayoutAdmin.vue file layout. So, this route have 2 childern component. when we are in the /admin route, the page will show layout admin with admin dashboard childern component. Also, if we are in the /admin/setting, the page will showing layout admin with admin setting page. This Approach are using the benefit of router system that using parent and childern approach. So, we can setting the parent layout of a childern route easily.

Conclusion

Every approach are have benefit in their own way. for example, first conditional approach are good if our layout are simple and didn't have much condition. So that will not impact to maintability or readability of the code. The slot approach will fit if we are using it to component that want to compose to another component. for example like a dynamic card, notify modal, or the other. The third approach good if yout want to setting the layout in the page of you want. But in my opinion, the best approach is using the router approach if necessary. Because this make the code maintainabily and readability are easier. Also, this implementation is better if compare with the other approach in dynamic layouting.

Thanks for reading this long article, and stay healty. :D