Search code examples
jqueryvue.jsrendering

Is there any function in Vue to check if the DOM inside child component is updated


I had this issue wherein, I wasn't able to select a DOM element inside the child component. As documented I was using the updated method but inside the root component.

And it works a bit weird,

Vue.component('newdiv', {
  template: `
    <div class="msg-wrap">
      <p v-html='this.$parent.message' class="msg-p"></p>
      <p v-html='this.$parent.message1' class="msg-p"></p>
    </div>
`,
  updated: function() {
    this.$nextTick(function() {
      $('.blue').css('color', 'blue');
    });
  }
});

new Vue({
  el: '#app',
  data: {
    message: 'Hello this is para 1',
    message1: 'Hello this is para 2',
  },
  methods: {
    ajaxCallEx() {
      // think ajax call has happened  and returns an html
      this.message = "<p> Hiiii This is a p1 <span class='red'> make me red </span> </p>";
      this.message1 = "<p> Hiiii This is a p2 <span class='blue'> make me blue </span> </p>";
    }
  },
  updated: function() {
    this.$nextTick(function() {
      // you will see that this dosen't work as the element is not present 
      $('.red').css('color', 'red');

    });
  }
})
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/jquery"></script>

<div id="app">

  <newdiv></newdiv>
  <button @click="ajaxCallEx">Clik Me</button>
</div>

Here on the button click the blue para turns blue while the red remmain as it is( as it is not present in the DOM yet).

So my first assumption was the update method of the parent component is called first and then the rendering of child element happens. Which holds true for this case. But just to prove me right i did try the following,

Vue.component('newdiv', {
  template: `
  <div class="msg-wrap">
    <p v-html='this.$parent.message' class="msg-p"></p>
    <p v-html='this.$parent.message1' class="msg-p"></p>
  </div>
`,
  updated: function() {
    this.$nextTick(function() {
      $('.blue').css('color', 'blue');
    });
  }
});

new Vue({
  el: '#app',
  data: {
    message: 'Hello this is para 1',
    message1: 'Hello this is para 2',
    message3: 'Hello this is root para'
  },
  methods: {
    ajaxCallEx() {
      // think ajax call has happened  and returns an html
      this.message = "<p> Hiiii This is a p1 <span class='red'> make me red </span> </p>";
      this.message1 = "<p> Hiiii This is a p2 <span class='blue'> make me blue </span> </p>";
      this.message3 = "<p> Hiiii This is a root para <span class='green'> make me green</span> </p>";
    }
  },
  updated: function() {
    this.$nextTick(function() {
      // you will see that this dosen't work as the element is not present 
      $('.red').css('color', 'red');

      // this will work and it will only color the p directly within the root. 
      $('.green').css('color', 'green');
    });
  }
})
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/jquery"></script>

<div id="app">
  <p v-html='this.message3'></p>
  <newdiv></newdiv>
  <button @click="ajaxCallEx">Clik Me</button>
</div>

As per my understanding from the last experiment, on click of the button, I expected green to turn green, blue to turn blue while red should not be working. but in this case, all the element where detected and colors were painted properly.

I want to know the working of Updated, Why in the first case it is not able to get element in the child component and why in the second case it is able to get the element. Is there a hook or event that lets root know that all its child has been rendered ?


Solution

  • What you are doing here is quite odd. You shouldn't be reaching for the message from the parent but passing it as a prop:

    Vue.component('newdiv', {
      template: `
            <div class="msg-wrap">
              <p v-html='message' class="msg-p"></p>
              <p v-html='message1' class="msg-p"></p>
            </div>
          `,
      props: ['message', 'message1']
    });
    
    new Vue({
      el: '#app',
      methods:{
        ajaxCallEx(){
          this.message = "<p> Hiiii This is a p1 <span class='red'> make me red </span> </p>";
          this.message1 = "<p> Hiiii This is a p2 <span class='blue'> make me blue </span> </p>";
        }
      },
      data:{
        message: 'Hello this is para 1',
        message1: 'Hello this is para 2'
      }
    })
    

    Here's the JSFiddle: https://jsfiddle.net/craig_h_411/e6nvb63m/

    Obviously, that doesn't style anything yet, to do that you just need to add the classes to your file, rather than injecting them.

    .red{
      color:red
    }
    
    .blue{
      color:blue
    }
    

    See: https://jsfiddle.net/craig_h_411/60o3ypbf/

    If for some reason you do need to apply the styles at runtime, you can use a watcher to watch the props and apply the colors (which needs to be done in a $nextTick), here's the entire thing:

    Vue.component('newdiv', {
      template: `
            <div class="msg-wrap">
              <p v-html='message' class="msg-p"></p>
              <p v-html='message1' class="msg-p"></p>
            </div>
          `,
      props: ['message', 'message1'],
      methods: {
        applyColor(className, color) {
            let els = document.getElementsByClassName(className)
            Array.prototype.forEach.call(els, el => {
              el.style.color = color
            })
          },
          applyClassStyles() {
            this.applyColor('red', 'red')
            this.applyColor('blue', 'blue')
          }
      },
      watch: {
        message() {
            this.$nextTick(() => {
              this.applyClassStyles();
            });
    
          },
          message1() {
            this.$nextTick(() => {
              this.applyClassStyles();
            });
          }
      },
    
    });
    
    new Vue({
      el: '#app',
      methods: {
        ajaxCallEx() {
          this.message = "<p> Hiiii This is a p1 <span class='red'> make me red </span> </p>";
          this.message1 = "<p> Hiiii This is a p2 <span class='blue'> make me blue </span> </p>";
        }
      },
      data: {
        message: 'Hello this is para 1',
        message1: 'Hello this is para 2'
      }
    })
    

    I'm just using Vanilla JavaScript but you can stick to jQuery for the same effect, and here's the JSFiddle: https://jsfiddle.net/craig_h_411/zdfxodyg/