We can reduce import fatigue and move a bit faster in writing higher level components by having the common components registered globally.
TL;DR
- put all of your common components in a folder
- import them into an
index.js
- export them from that
index.js
in anArray
- import that array and loop over them and register them with
Vue
in your main entry point.
Example
A concrete example always resonates with me rather than abstract notions. Therefore, let’s look at how we’ve applied this technique for a project we recently shipped for the TN Department of Education:
Here are some utility components that we use throughout the app:
PrettyDate.vue
The <pretty-date>
component formats a date in a common format using date-fns
allowing us to centralize the format as well as how it is formatted into a single component. We were originally
using moment.js but since we don't need localization, we could shed a lot of package
weight and use date-fns
.
// components/PrettyDate.vue
<template>
<span v-if="date" class="pretty-date" :title="date">{{ formattedDate }}</span>
</template>
<script>
import { format } from 'date-fns';
export default {
name: 'pretty-date',
props: ['date'],
computed: {
formattedDate() {
return format(this.date, 'MMM dd');
}
}
}
</script>
SuccessMessage.vue
The <success-message>
is not a top-level global component but is used by one
in order to render user messages.
// components/SuccessMessage.vue
<template>
<div class="system-message alert alert-success" role="alert">
<button @click.stop="$emit('clearMessage')" class="close">×</button>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
name: 'success-message',
props: ['message']
}
</script>
ErrorMessage.vue
Like <success-message>
, <error-message>
is used to display messages to the user
but to display errors instead of success messages. Both components are used directly
by <system-messages>
.
// components/ErrorMessage.vue
<template>
<div class="system-message alert alert-danger" role="alert">
<button @click.stop="$emit('clearMessage')" class="close">×</button>
<ul><li v-for="(message, index) in error.messages" :key="index">{{ message }}</li></ul>
</div>
</template>
<script>
export default {
name: 'error-message',
props: ['error']
}
</script>
SystemMessages.vue
The <system-messages>
component reads errors and messages directly from the store and renders
out either <success-message>
or <error-message>
as appropriate.
// components/SystemMessages.vue
<template>
<div class="system-messages sticky-top">
<error-message
v-for="(error, index) in errors"
:key="index"
:error="error"
@clearMessage="onClearError(index)">
</error-message>
<success-message
v-for="(message, index) in messages"
:key="index"
:message="message"
@clearMessage="onClearMessage(index)">
</success-message>
</div>
</template>
<script>
import { CLEAR_ERROR, CLEAR_MESSAGE } from '../../constants';
import ErrorMessage from './ErrorMessage.vue';
import SuccessMessage from './SuccessMessage.vue';
export default {
name: 'system-messages',
components: {
ErrorMessage,
SuccessMessage
},
computed: {
errors() {
return this.$store.state.errors;
},
messages() {
return this.$store.state.messages;
}
},
methods: {
onClearError(index) {
this.$store.dispatch(CLEAR_ERROR, index);
},
onClearMessage(index) {
this.$store.dispatch(CLEAR_MESSAGE, index);
}
}
}
</script>
LoadingIndicator.vue
The <loading-indicator>
is a simple CSS-based loading indicator. It's all display
but it's nice to wrap it as a single entity rather that copying that HTML/template
everywhere.
// components/LoadingIndicator.vue
<template>
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</template>
<script>
export default {
name: 'loading-indicator'
}
</script>
index.js
In this index.js
we import the top level ones and export them as an Array
:
// components/index.js
import PrettyDate from './PrettyDate.vue';
import SystemMessages from './SystemMessages.vue';
import LoadingIndicator from './LoadingIndicator.vue';
export default [
PrettyDate,
SystemMessages,
LoadingIndicator
];
main.js
Now in our entry point we register the components before loading Vue:
import Vue from 'vue';
import commonComponents from './components';
commonComponents.forEach(component => {
Vue.component(component.name, component);
});
// now load Vue
Conclusion
We have found this to be a very useful pattern and as we begin to copy and paste certain components from project to project, we are realizing that some of these are candidates to ship as their own pacakge.
Do you have any Vue projects you'd like some help with? Send us a message and let us know. We'd love to help!