Where to put VueJS components common across the application - vue.js

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'
],

Related

Best way to add components dynamically to a Vue app

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"
}
}

What is the purpose of 'main.js' and 'App.vue' in a Vue.js application?

I don't understand the exact purpose of each file.
Suppose I want to add authentication code. Where should I place it, in main.js or App.vue?
I believe you might be missing on some of the basics behind the structure of Vue.js and where and/or how to put in functionality like authentication. It might be worth going through their introduction again to solidify your knowledge.
To answer more directly, when you run a Vue.js application you need to have a basic HTML page (like index.html) as an entry point and the initialisation for your Vue.js application loaded in a <script> in that page.
When you write a Vue.js application you can choose to do it in pure JavaScript, in TypeScript or in the .vue component format which combines the HTML, CSS and JavaScript you need to define components.
The vue format is not run directly. It has to be transpiled into plain JavaScript by the Vue-CLI/builder and packed using a packager like Webpack first and then loaded by your entry point. Luckily, the Vue.js CLI handles nearly all of this process so you can get on with building.
File App.vue
This is typically the root of your application defined in Vue.js Component file format. It's usually something that defines the template for your page:
<template>
<div id="app">
<SideBar />
<router-view v-if="loaded" />
</div>
</template>
<script>
import SideBar from "./pages/SideBar";
export default {
components: { SideBar },
computed: {
loaded() {
return this.$store.state.loadState == "loaded";
}
}
};
</script>
File main.js
This is usually the JavaScript file that will initialise this root component into a element on your page. It is also responsible for setting up plugins and third-party components you may want to use in your app:
import Vue from "vue";
import { store } from "./store/store";
import router from "./router";
import App from "./App.vue";
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
File index.html
The index page provides your entry point in HTML providing an element for Vue.js to load into and imports file main.js to initialise your application.
<!-- The HTML element that hosts the App.vue component -->
<div id="app"></div>
<!-- Built files will be auto injected -->
<script type="text/javascript" src="main.js"></script>
On another note, a decent place to put your authentication logic is in the router where you can add navigation guards to restrict access to pages based on the current authentication state and send your users to a login page:
// GOOD
router.beforeEach((to, from, next) => {
if (!isAuthenticated) next('/login')
else next()
})
I don't think you specifically need an index.html in your project. Provided your main.js has an import that references you main Vue page, e.g.:
import App from "./App.vue";
and then renders it.
new Vue({
...
render: (h) => h(App),
...
The .vue file is a special Vue.js CLI project feature allowing you to write Vue.js apps or Vue.js components in more convenient way. You write your app/component in the .vue file and Vue.js CLI transforms it into code that works in a browser.
The main.js in Vue.js CLI project is starting the instance of the app. The index.html file in Vue.js CLI project is handled automatically (it's located in the 'public' folder).
The right place to start with Vue.js CLI is: Instant Prototyping

Can you access Vue component lifecycle hooks externally?

I am using a component library within my web app and I'd like to attach functionality to one of the provided components.
So let's say I have a .vue file
<template>
<div>
... some stuff
<LibraryComponent />
</div>
</template>
<script>
import LibraryComponent from 'library'
export default {
components: {
LibraryComponent
}
}
</script>
I would love to be able to reach into the LibraryComponent and attach a method to the mounted hook from the parent. I figure I can update the code of the component itself in node_modules but that seems like a bad solution if the package gets updated.
Vue’s lifecycle hooks emit their own custom events.
Take a look at this article:
Vue.js Component Hooks as Events

Can i import single file component using Vue and Vue Router CDN?

I'm currently using Vue and Vue Router CDN. I want to import a single file component (user.html) to my index.html with Vue router. But when I click on "Go to user" the data didn't display. I read a few guides about Vue router but they used NPM or CIL instead of Vue CDN.
Index.html
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- use router-link component for navigation. -->
<!-- specify the link by passing the `to` prop. -->
<!-- `<router-link>` will be rendered as an `<a>` tag by default -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
<router-link to='/User.html'>Go to User</router-link>
</p>
<!-- route outlet -->
<!-- component matched by the route will render here -->
<router-view></router-view>
</div>
<script>
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const User = { template: '#test'}
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar },
{
path:'/User.html', component: User
}
]
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
</script>
User.html
<template id = "test">
fsjdfjdfldskjflkd
</template>
I think there's been a misunderstanding. As far as I can tell you're trying to load User.html in the way you would load a .vue file, but that doesn't work unless you're using Webpack to build your project since the .vue filetype is something that the vue-loader project is responsible for parsing.
Vue Router CDN means you're not building with Webpack, so loading a component like you're doing is impossible. You can configure Vue Router to serve a User.html file, but that file can't be a template component since in CDN mode Vue Router has no idea what files are on your server, nor can it simply retreive them.
So you need to do one of the following three options:
Option 1: Start using Webpack for your project
This is what I recommend you doing. You'll find that your project will perform better and will be easier to develop using it.
Option 2: Use the proper template syntax for CDN
This is for example:
var mytemplate = `<div>
<h1>This is my template</h1>
</div>`
Vue.component('mycomp1', {
template: mytemplate
});
Vue.component('mycomp2', {
template: `
<div>
Hello, {{ name }}!
</div>
`,
props: ['name'],
});
You can't load other files as templates when you're not building using Webpack. Webpack puts them into your single page application for you, Vue Router has no idea what's inside User.html nor can Vue use it as a template. Vue Router can be told to redirect to a fully functional User.html website page, but not to just use it as a template.
Option 3: Use Ajax requests to fetch the template file
This is something I STRONGLY URGE YOU NOT TO DO, but for the sake of completeness, you can use the CDN version if you fetch the contents of User.html file using an Ajax request and create a component from that.
I really, really recommend that you stop using the CDN version and instead go for a Webpack based solution, give in to the dark side! Or make it even simpler and use Nuxt.js instead since it's easier for beginners to use.

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.