Vuejs unable to access dom element after mounted() even with this.nextTick. Using chartjs

Viewed 911

This is my child element

<template lang="html">
  <div class="row">
    <div class="col-lg-8 col-md-8 col-sm-12 col-xs-12">
      <bar-chart :v-if="this.barChartReadyToBeRendered" :chart-data='null' :height="340"></bar-chart>
    </div>
    <div class="flex-col-docs col-lg-3">
      <div class="column" style="height: 150px">
        <div class="col">
            <q-select dark stack-label="Show Targets" class="select-notification"
              v-model="selectTargetNotification"
              :options="this.getTargetChangeOptions"
            />
        </div>
        <div class="col">
            <q-select dark stack-label="Agency" class="select-notification"
              v-model="selectOrgNotification"
              :options="this.getOrganisationOptions"
            />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import BarChart from '../../components/BarChart'

export default {
  components: {
    BarChart
  },

.
.
/* Other code */

mounted () {
    console.log('OUTSIDE MOUNTED')
    this.$nextTick(() => {
      console.log(this.$el)
      let ctx = document.getElementById('bar-chart')
      console.log('WWWWWWWWWWWWWWWWWW')
      console.log(ctx)
      console.log(this.$el)
      this.createChart('bar-chart')
    })
  }

</script>

The bar chart chartjs is

<script>
import { Bar, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins

export default {
  extends: Bar,
  mixins: [reactiveProp],
  props: ['options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
}
</script>

<style>
</style>

In my parent element, the template is

<template>
  <q-page padding class="row justify-center">
    <div style="width: 80vw; max-width: 100vw;">
      <div class="flex-row-docs">
        <div class="doc-container">
          <q-list no-border>
            <div class="row justify-start">
              <div class="col-6">
                <target-changes-agency></target-changes-agency>
              </div>
            </div>
            <div class="q-mb-md q-mt-md q-headline">Full coverage</div>
            <span v-if="!isNewsByIdLoaded" class="row justify-center">
              <q-spinner-mat :size="36" style="color: #027be3ff; text-align: justify; margin: 2rem;" />
            </span>
            <div class="row">
              <article-cluster :isNewsByIdLoaded="isNewsByIdLoaded"></article-cluster>
            </div>
          </q-list>
        </div>
      </div>
    </div>
  </q-page>
</template>

I am expecting to console.log(ctx) and console.log(this.$el), however the output of those 2 is null and <!-- --> respectively.

I thought mounted and this.$nextTick() will allow me to have access to the DOM. What am i missing here? please help thank you

1 Answers

Why are you assuming that document.getElementById('bar-chart') would return any element? There is no element with that ID being created. What you're rather looking for is document.getElementsByTagName('bar-chart'), but that will also yield no result, because Vue does not internally create Web Components, but inserts the component's root element in place instead. So, what you can do is give your bar-chart component an id attribute, which will be passed to the root element automatically.

The next issue is that your bar-chart component is only visible when the condition in v-if is truthy. That's probably not the case when the component is first being loaded. In this working minimal example, I simply set v-if="false".

const { Bar, mixins } = VueChartJs
const { reactiveProp } = mixins

const BarChart = Vue.component('bar-chart', {
  extends: Bar,
  mixins: [reactiveProp],
  props: ['options'],
  mounted () {
    //this.renderChart(this.chartData, this.options) 
    this.$nextTick(() => {
        console.log('mounted bar-chart component:');
        console.log(this.$el)
    });
  }
});

Vue.component('example-component', {
  template: `<div><bar-chart v-if="false" id="barchart" chart-data="null" height="340"></bar-chart></div>`,
  components: [BarChart],
  mounted () {
      this.$nextTick(() => {
        console.log('mounted child component:');
        let ctx = document.getElementById('barchart')
        console.log(ctx)
        console.log(this.$el)
      })
    }
});

// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
  el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-chartjs@3.5.0/dist/vue-chartjs.min.js"></script>
<div id="app">
  <example-component></example-component>
</div>

(The stack snippet console actually hides the <!-- -->, but you can see it in this codepen. Vue automatically inserts this empty HTML comment as a placeholder for a component that is not currently being displayed.)

The output is actually expected, as the bar-chart component is not being rendered, therefore this.$el (referring to the child component, not the bar-chart component) is empty.

Now here ist the same snippet with v-if="true" on the bar-chart component:

const { Bar, mixins } = VueChartJs
const { reactiveProp } = mixins

const BarChart = Vue.component('bar-chart', {
  extends: Bar,
  mixins: [reactiveProp],
  props: ['options'],
  mounted () {
    //this.renderChart(this.chartData, this.options) 
    this.$nextTick(() => {
        console.log('mounted bar-chart component:');
        console.log(this.$el)
    });
  }
});

Vue.component('example-component', {
  template: `<div><bar-chart v-if="true" id="barchart" chart-data="null" height="340"></bar-chart></div>`,
  components: [BarChart],
  mounted () {
      this.$nextTick(() => {
        console.log('mounted child component:');
        let ctx = document.getElementById('barchart')
        console.log(ctx)
        console.log(this.$el)
      })
    }
});

// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
  el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-chartjs@3.5.0/dist/vue-chartjs.min.js"></script>
<div id="app">
  <example-component></example-component>
</div>

As you can see, the logs now return the correct elements, also in the mounted() hook of the bar-chart component.

Of course, you shouldn't use the id attribute in your component if you ever plan to have multiple instances of this component, because it would result in multiple elements having the same ID, which is invalid HTML and might lead to unexpected interferences. So, this was only for demonstration purposes in this minimal example. In your real code, you could use Vue's ref attribute instead, which you can then refer to via this.$refs inside the parent component.

There are two other issues in your code:

  1. You don't need the colon in front of v-if, because it automatically binds to the expression given as its value.
  2. You don't need this. in your expressions, you're in the components context automatically and can simply use the properties' names directly.
Related