In this post I will explain how to generate a webpage using dynamic Vue.js components. This could be the foundation of a CMS based on Vue.
The finished project can be found here
Project Setup
First of all install the vue-cliif you havent already. To do so open a console and type
npm install -g @vue/cli
Afterwards create a new project by entering
vue create widget-app
You will be prompted to pick a preset. For this project just use default.
When the installation finished, go into the new project directory and run the Vue app.
cd widget-app
npm run serve
The app should now be available at http://localhost:8080/
First of all, to keep it simple, remove everything in the template of src/components/HelloWorld.vue
exept the <h1>.
<template>
<h1>{{ msg }}</h1>
</template>
Then remove everything in src/App.vue
template inside of app div. Then add ref="main" to the div.
<template>
<div id="app" ref="main">
</div>
</template>
Dynamic Component creation
First of all we want to create the HelloWorld component dynamically. Therefor update the code in the <script>
block inside of your App.vue
import Vue from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
HelloWorld
},
methods: {
init: function() {
const ComponentClass = Vue.extend( HelloWorld );
const instance = new ComponentClass({
propsData: { msg: 'This is a dynamic headline' }
});
instance.$mount();
this.$refs['main'].appendChild(instance.$el);
}
},
mounted() {
this.init();
}
Here we added import Vue from 'vue'
in the first line. Then we addedmethods
and mounted()
.
mounted gets called when the app has mounted and then proceeds to call the function init
inside of our methods.
Because components do not export a class, we need to pass the component to Vue.extend
to be able to create an instance of the component with the new
keyword.
When creating the instance, we can provide the properties with propsData
To mount the instance, we have to call instance.$mount();
. As a last step, we are then able to attach the instance of the component to the DOM. Therefor we call this.$refs['main']
to get the <div>
where we want to insert our component. Then we call appendChild
with instance.$el
where $el
is used to get the native DOM element reference.
If you refresh localhost:8080, the dynamic headline should now be visible. Now let's make it possible to choose the component based on a string.
Replace const ComponentClass = Vue.extend( HelloWorld );
with this two lines:
const component = this.$options.components['HelloWorld'];
const ComponentClass = Vue.extend(component)
The code should still work the same, but now we are able to use variables to choose the component we want to generate.
Next let's extract our method for the dynamic creation of components into a separate function.
init: function() {
this.createComponent('HelloWorld', { msg: 'This is a dynamic headline' }, 'main');
},
createComponent: function(componentName, propsData, domRef) {
const component = this.$options.components[componentName];
const ComponentClass = Vue.extend(component);
const instance = new ComponentClass({
propsData // shorthand for propsData: propsData
});
instance.$mount();
this.$refs[domRef].appendChild(instance.$el);
}
As you can see, we now have a function called createComponent
, which receives the name of the component to render, the properties of the component and the DOM reference, where the component will be inserted.
Now I'll add an object array after our imports, which defines how widgets are initialized. In a real application this would probably be data, which is fetched from a database.
const widgets = [
{
'component': 'HelloWorld',
'dom': 'main',
'props': { msg: 'This is a dynamic headline'}
},
{
'component': 'HelloWorld',
'dom': 'main',
'props': { msg: 'This is a second headline'}
}
]
Afterwards we can iterate through this array in our init function to render all the Widgets:
init: function() {
for (const widget of widgets) {
this.createComponent(widget.component, widget.props, widget.dom);
}
}
You should now be able to see both headlines defined in our widgets array.
For the last step i'll change the data a little bit to make the widgets reusable.
const page = {
'main': [0, 0, 1]
}
const widgets = [
{
'id': 0,
'component': 'HelloWorld',
'props': { msg: 'This is a dynamic headline' }
},
{
'id': 1,
'component': 'HelloWorld',
'props': { msg: 'This is a second headline' }
}
]
and update the init function to loop through the page object as well as the widgetIds inside of each DOM reference inside of the page object.
init: function() {
for (let domRef in page) {
for (let widgetId of page[domRef]) {
const widget = widgets.find(widget => widget.id === widgetId);
this.createComponent(widget.component, widget.props, domRef);
}
}
},
As a final result you should see the first widget rendered two times and the second widget one time.
Conclusion
A working example of the code can be found here: https://codesandbox.io/s/01x2xyq7kv
Dynamic rendering of components is pretty straightforward in Vue.js.
This pattern can be easily extended for usage on multiple pages and multiple components. It is possible to dynamically reuse components with different properties and to append them multiple times to different DOM elements.
Cheers,
Vincent