[Vuejs]-Cannot access $el of Vue component except on $mounted

2👍

I can try to explain what is happening, though I’m somewhat unclear why you’re doing what you’re doing.

Let’s start here:

var ocharcomp = new ccharcomp({
  propsData: {
    char: ch
  }
});
vm.charcomponents.push(ocharcomp);

This is creating new instances of the ccharcomp component, which is effectively just CharComponent. Each of these is then being added to the array.

None of these Vue instance is being mounted. They never get rendered. They are just created and pushed onto an array. They won’t have an $el and the mounted hook will never be called.

Then in your template you have this:

<charcomponent 
  v-for = "c in charcomponents"
  v-bind:char="c.char"
></charcomponent>

This loops over that same array and creates a new CharComponent instance for each entry. The value of char is copied across from each component in the array to the corresponding component created in the template.

The components created within the template will be rendered, mounted and will have an $el. It is this that you are seeing in your logging from the mounted hook.

Then we have this:

showchars() {
  for (var c of this.charcomponents) {
    c.showinfo("it's different later");      
  }
}

This is looping over the original array of components, the ones that were never mounted. These don’t have an $el, they never have. The components created by the template still exist and still have an $el but they are not in the array.

I can’t make a concrete suggestion for how to fix your problem as I don’t really understand why you are creating the child components in such a strange way. The more normal pattern would be:

  1. Have the array ['a','b','c'] in the data for the relevant parent component.
  2. Loop over that array in the template to create the children.
  3. Use ref in the template and $refs in the parent to access the child components, then use $el to grab the element for each child.

However, if you just want to apply styling then grabbing the element like this is generally discouraged. Instead you should use :class or :style bindings within the template to let Vue apply the styling for you. You may need to introduce extra data properties to hold the underlying state so that the template can determine which styles to apply where.

1👍

The components you are instantiating via this method (declarative):

<charcomponent 
  v-for = "c in charcomponents"
  v-bind:char="c.char"
></charcomponent>

Are different from the ones that you are creating here (programmatic):

var ccharcomp = Vue.extend(CharComponent);

for (var ch of ['a','b','c']) {
    var ocharcomp = new ccharcomp({
    propsData: {
        char: ch
    }
  });
  vm.charcomponents.push(ocharcomp);
}

The initial series of alerts were triggered by the mounted hook from for the first set of components (declarative). Definitely, the $el object is present here because of the markup is already part of the DOM and that it also used the local declaration that you have in your Vue instance

 components: {
    charcomponent: CharComponent 
   },

For the second set of components, which was stored in array named charcomponents, does not have any mounted elements. Take note you when you are using Vue.extend, only a subclass of the Vue instance is created and therefore new objects from instanciated must be mounted via the $mount method.

showchars() {
      for (var c of this.charcomponents) {
        c.showinfo("it's different later");      
      }
    }

Effectively, accessing the $el element for the second batch of components, it is not yet defined.

I create a simple implementation here in JSFiddle to show the different implementations.

0👍

The very informative answers from @skirtle and @Jose Mari Ponce helped me to see why what I had wasn’t working. I intend to look again at class/style binding in the template as a possibly better way of accomplishing what this particular app needs, but I wanted to follow this through too as it seems looping through a known list of components and accessing $el will also be needed, since app logic needs to determine the values the template is bound to. I also think it’s something with much wider applicability beyond this one app.

It turned out to be simply a matter of replacing my HTML v-for with a JS appendChild, plus mounting the dynamically created component on the new child element:

HTML

<div id="components">
</div>

JS

var ccharcomp = Vue.extend(CharComponent);
var container = document.getElementById("components");

for (var ch of ['a','b','c']) {
    var p = document.createElement("p");
    container.appendChild(p);
    var ocharcomp = new ccharcomp({
        propsData: {
            char: ch
        }
    });
    vm.charcomponents.push(ocharcomp.$mount(p));
}

JSFiddle

Leave a comment