Photo by Kari Shea on Unsplash

We can reduce import fatigue and move a bit faster in writing higher level components by having the common components registered globally.

TL;DR

  1. put all of your common components in a folder
  2. import them into an index.js
  3. export them from that index.js in an Array
  4. 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">&times;</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">&times;</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!