Best way to add components dynamically to a Vue app - vue.js

What is the best way to add components dynamically to your vue app?
Say you have three different components in your app and you want to show each one depending on the value that a data has.
data:() => ({
tab: 1
})
<custom-component-1></custom-component-1> <!-- Show this if tab is 1 -->
<custom-component-2></custom-component-2> <!-- Show this if tab is 2 -->
<custom-component-3></custom-component-3> <!-- Show this if tab is 3 -->
I'm gonna go through all the possible ways of doing this.

Using v-if or v-show
The first and obvious way is to add v-if to your component like this:
<custom-component-1 v-if="tab === 1"></custom-component-1> <!-- Show this if tab is 1 -->
<custom-component-2 v-if="tab === 2"></custom-component-2> <!-- Show this if tab is 2 -->
<custom-component-3 v-if="tab === 3"></custom-component-3> <!-- Show this if tab is 3 -->
You can also use v-show if you want to, it's up to you.
See the difference between v-show and v-if. v-show vs v-if
This probably is the easiest way of doing it but not the most efficient.
once your code starts to get more complicated this code is going to be your hell
Using Vue's dynamic components
The second way of doing this is by using Vue's dynamic components Link to documention
Here is our example again with dynamic components:
computed: {
component: function () {
return `custom-component-${this.tab}`;
}
},
data:() => ({
tab: 1
})
And we just need to pass the name of the components:
<component is="component">
<!-- instead of -->
<custom-component-number></custom-component-number>
<component :is="component"> </component>
<button #click="tab++"></button>
Using the computed and is property we can have infinite components dynamically.
This is a nice clean way of doing it. You take the computation part away from your markup and put it in the script for a cleaner and more efficient code
If you are using this approach make sure to import and initialize the components you want to use in the page or add them in your main.js as global components like this:
import Vue from "vue";
import Component1 from "~/components/component1.vue";
import Component2 from "~/components/component2.vue";
import Component3 from "~/components/component3.vue";
Vue.component("custom-component-1",Component1);
Vue.component("custom-component-2",Component2);
Vue.component("custom-component-3",Component3);
You can also add the components to your page:
import customComponent from "~components/customComponent";
export default {
components : {
customComponent: "custom-component"
}
}

Related

Vue SSR with v-if. How to correctly perform hydration?

I have a problem with Vue SSR.
In my project I have a page called slug, where depending on the data received in asyncData, the appropriate component is mounted. It looks more or less like this:
<div>
<component-a v-if='type === a' />
<component-b v-else-if='type === b' />
<component-c v-else-if='type === c' />
</div>
<script>
export default {
asyncData({ store }) {
store.dispatch('product/fetchAsync')
},
computed () {
type () {
return this.$store.state.type
}
}
}
</script>
However, Vue is not able to perform SSR hydration.
Is there a possibility that this is due to v-if statement?
How to solve this correctly? The only thing I can think of is prefixes and making each component a separate page, without v-if. But the client would like to avoid this.
As some comments say, your html structure at server side (where ssr is happening) don't know if the conditions for rendering an item or other are true.
You can use v-show instead of v-if. The difference is that v-show will render that element but with no data. This way your html won't change and hydration is successful

How to make a Base component that will allow other components to inherit new functionality or code from it in Vue JS

Component 1:-
<template>
<blur :isData="isData">
<!-- logic/implementation of component 1 -->
<div>
</div>
</blur>
<template>
<script>
import blur from "../shared/Blur";
name: "component-1",
components: {
blur,
},
</script>
Just like this component1.vue, I have multiple components which are using blur component. Is it possible that instead of writing and importing blur in every single component, I can make some base class that can transfer the blur functionality in every single component in the folder. Can something like this be achieved in vue ?
With Vue.component you can create globally registered components:
Vue.component('my-component-name', {
// ... options ...
})
Find out more here

Stop full page re-render on route change? (Nuxt.js)

I'm trying to implement custom routing in Nuxt using _.vue. My _.vue page file has two child components (let's call them Category and Product), each of which is displayed when their data is present in the store by using v-if. I'm using middleware for the _.vue component to process custom routing.
My problem is that all my components get re-rendered on each route change, which causes delays and image flickering.
Let me explain what I'm trying to achieve. There's a list of products in a category. When you click on a product, it opens in a modal window with the category still in the background, and also changes the current page URL, hence the routing thing. When you close the product, it should go back to the category in the same state as before. Everything seems to be working as needed, except when I open or close a product, all my components components get re-rendered (including _.vue). I tried naming them, using key(), etc. with no results.
Is there any way to keep current components on route change without rerendering? Or is there a workaround?
<template>
<div>
<Category v-if="current_category" />
<Product v-if="current_product" />
</div>
</template>
<script>
import {mapState} from 'vuex'
import Product from '~/components/product/Product'
import Category from '~/components/category/Category'
export default {
middleware: 'router',
scrollToTop: false,
components: {
Product,
Category,
},
computed: {
...mapState({
current_category: state => state.current_category,
current_product: state => state.current_product,
}),
},
mounted: function() {
console.log('_ component mounted')
},
}
</script>
You should use "key" option in page components. By default value is "route.fullPath", so you see rerendering after changing URL parameters.
https://nuxtjs.org/docs/2.x/features/nuxt-components#the-nuxt-component

Where to put VueJS components common across the application

In my nuxtjs application, I have few features that are used across the application viz Login/Signup dialog, a snackbar to show alerts etc. Since, I want these features on every page and v-app-bar component is already added to all the pages. I have included the components for these features inside v-app-bar component.
<template>
<v-app-bar app id="app-bar">
<LoginJoinDialog />
<AlertSnackbar />
<!-- Code for App bar -->
</v-app-bar>
</template>
But I am not happy with this approach for following reasons
I know these common components does not belong to the v-app-bar component. And just for the sake of DRY and maintenance headaches I have included them. So from design perspective this is not very intuitive.
Secondly, What if in future I have pages that do not have a v-app-bar component. In that case I will be repeating code for these common components anyway. So, the pain of maintaining the code at multiple places will still be there.
Considering the above points, I am looking for a more elegant approach than what I have implemented. If there is a vuejs recommendation on this, even better. What suggestions do you have for component structuring for these common features?
You can acheive what you are looking for using layouts. What you need to do is make layouts folder inside src directory. And then you can create as many
layout components(*.vue files) and use them as you like.
For an example, this is default.vue component inside layouts folder:
<template>
<main>
<!-- Your app bar component -->
<v-app-bar app id="app-bar">
<LoginJoinDialog />
<AlertSnackbar />
<!-- Code for App bar -->
</v-app-bar>
<!-- Page Content (This tag will automatically embed the page content into layouts)-->
<nuxt />
</main>
</template>
<script>
export default {};
</script>
Now, on your pages folder, you can add index.vue file where you can reference the default layout, as a property in this manner: layout: 'default'
The index.vue file should look something like this:
<template>
<!-- page content goes here -->
</template>
<script>
export default {
name: 'HomePage',
layout: 'default',
};
</script>
I also have created an example project in nuxt with layouts.
For a working prototype of the project: Visit this link.
I hope it helps to solve your issue.
You can use global component registration trick by Chris Fritz. You just to need to modify it a bit so it's more fitting for a nuxt.js app. So you can create a base folder under your components folder and keep all these shared components there. Then create a new plugin and change the path to your #/components/base folder and modify the regex so it grabs all the files, no matter what the name is:
globalComponents.js
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
export default () => {
const requireComponent = require.context(
'#/components/base', false, /[\w-]+\.vue$/
)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(
camelCase(fileName.replace(/^\.\/(.*)\.\w+$/, '$1'))
)
Vue.component(componentName, componentConfig.default || componentConfig)
})
}
nuxt.config.js
plugins: [
'~/plugins/globalComponents.js'
],

How to preserve custom component tag names in Vue.js

I'm using Vue's single-file component spec (*.vue) for custom components in my application. Together with rollup and rollup-plugin-vue, I have observed the output in the DOM, for custom components I have written, to be composed of the equivalent html elements.
For example:
component-a.vue
<template>
<span>Hello World</span>
</template>
<script>
export default { name: 'component-a' };
</script>
component-b.vue
<template>
<component-a></component-a>
</template>
<script>
import ComponentA from './path/to/component-a.vue';
export default { name: 'component-b', components: { ComponentA } };
</script>
The above example, if component-a is added to the Vue mount component, will render to a the sum of the two component's template contents in the DOM, which in this case is simply a span element:
<span>Hello World<span>
Is it possible to achieve a rendered output in the DOM like the snippet below, such that custom elements' templates are represented in the DOM by tags which preserve their tag names?
<component-b>
<component-a>
<span>Hello World</span>
</component-a>
</component-b>
Inside your component-a.vue you should be able to achieve that by including some html code within your <template> tag as follow
component-a.vue:
<template>
<customelement>
// Other stuff
</customelement>
</template>
In this way you will be able to call your component-a from anywhere in your app, and to render an element named customelement.
Ideally you should use this "trick" to render standard HTML5 elements, otherwise you might see some error in your vue app console. Let me know how it goes.
Referring to the Vuejs documentation
https://v2.vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats
It should be noted that this limitation does not apply if you are
using string templates from one of the following sources:
String templates (e.g. template: '...')
Single-file (.vue) components
<script type="text/x-template">
If you use Vuejs like the examples above you won't get the result you wanted. So if you render your components in other ways you should get the result you wanted.