Skip to content
Advertisement

Adding HTML element to a vue template

I’m using a chart component (Chartist) that requires a HTML element to use as a parent when rendering the SVG.

The elements that the chart can use is generated during a v-for loop, which means that they are not added to the DOM at the time of the chart rendering.

The code looks something like this (in the vue):

<div v-for="chart in charts">
  <h1>{{chart.Title}}</h1>
  <div class="ct-chart" :id="'chart-' + chart.name"></div>
  {{generateChart('#chart-' + chart.name, chart.Data}}
  <div>
    <span v-for="l in legend" :class="chart.ClassName">{{l.DisplayName}}</span>
</div>

In the component:

generateChart(chartName: string, data: IChartData) {
    /* More code here */

    //doesn't get added (can't find html node in dom)
    new Chartist.Line(chartName, data, options);

    // this worked
    //var element = document.createElement("DIV");
    //element.className = "ct-chart";
    //document.body.appendChild(element);
    //new Chartist.Line(element, data, options);

}

Which results in that Chartist fails on querySelectorAll.

If I on the other hand generate an element using document.createElement and attach it to the html body the generation works fine.

Summary:

I want to render a chart which requires a DOM element for the rendering. But since Vue renders everything in it’s virtual DOM, there is no element available when Vue renders the view (including the chart).

Question:

How can I tell vue to add a pre-created HTML element? Or how can I defer the chartist generation until vue have added everything to the real DOM?

Workaround:

I’m using a timeout (setTimeout) to defer the chart rendering so that Vue can complete before I invoke the chart rendering function.

Advertisement

Answer

First and foremost, don’t use function in a template like this. Template is converted into render function and that function is executed whenever something changes – in this case that means you will be creating new chartist objects every time the template is rendered ….which is something you don’t want.

What you want is to render DOM nodes (without any charts) first and use mounted hook to initialize each chart after the DOM is ready:

mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}

If you need DOM elements references, you can use refs

Also be aware that most libs which create DOM element dynamically (like Chartist) almost always need some cleanup code to avoid memory leaks

Or just use some Vue wrapper, for example vue-chartist

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement