Search code examples
javascriptvue.jsvuejs3vue-composition-apicomposable

How to update template ref after mounted vue


I am trying run a function be it computed or watch or watcheffect after the template has mounted. the watcheffect only executes once, computed of course runs before mounted. I have tried flush: 'post' in watcheffect and flush in watch, I am rather stuck. looking at the docs it should work as expected: https://v3.vuejs.org/guide/composition-api-template-refs.html#watching-template-refs

Therefore, watchers that use template refs should be defined with the flush: 'post' option. This will run the effect after the DOM has been updated and ensure that the template ref stays in sync with the DOM and references the correct element.

app.vue

<template>
  <div ref="target">
    <h1>my title</h1>
    <p>Tenetur libero aliquam at distinctio.</p>
    <h1>hello</h1>
    <p class="fuckyeah yolo">quia nam voluptatem illum ratione ipsum.</p>
    <img src="img.jpg" />
    " title="hello" alt />
    <h2>hello</h2>
    <ol>
      <li>hello inital</li>
      <li v-for="i in inc">hello</li>
    </ol>
  </div>
  <div>
    <button @click="inc++">inc</button>
  </div>
  <pre>
  <code>
    {{ toJson }}
  </code>
</pre>
</template>

<script>
import { ref } from '@vue/reactivity'
import { templateRef } from '@vueuse/core'
import { useParser } from './markcomposable.js'
import { onMounted, computed, watchEffect } from '@vue/runtime-core';

export default {
  setup() {

    const inc = ref(0);

    const target = ref(null);


    const { toJson } = useParser(target);


    return {
      inc, target, toJson
    }
  }
}
</script>

//composable.js

import { parse, validate } from "fast-xml-parser"
import { ref, reactive, watchEffect, toRef, nextTick } from 'vue'

const useParser = (target) => {


    const toJson = ref(null);


    const jsonOptions = reactive({
        //defaults
        attributeNamePrefix: "",
        ignoreAttributes: false,
        textNodeName: "text",
        arrayMode: true
    })

    const dumpJson = (target, options) =>
        validate(target.outerHTML) ? parse(target.outerHTML, options) : isValid.value;


    watchEffect(() => {
        if (target.value) {
            toJson.value = dumpJson(target.value, jsonOptions)
            console.log(toJson.value)
        }
    }, {
        flush: 'post',
    })

    return {
        target,
        toJson,
    }

}

export { useParser }

Solution

  • If I understand correctly, you're trying to observe the outerHTML of the template ref, and you're expecting the template-ref watcher to be invoked whenever you insert nodes (via the button callback), but it's only ever invoked once.

    This happens because the watcher effectively only watches the template ref and not its properties. The watcher would only be invoked when the template ref is initialized with the component/element reference. Template refs cannot be reassigned, so the watcher would not be invoked again. Moreover, the template ref's properties are not reactive, so the watcher would not be invoked if the target node's HTML changed.

    Solution

    Instead of the watcher, use a MutationObserver to observe the changes made to the target node, including the outerHTML.

    1. Create a function that uses a MutationObserver to invoke a callback:

      const observeMutations = (targetNode, callback) => {
        const config = { attributes: true, childList: true, subtree: true }
        const observer = new MutationObserver(callback)
        observer.observe(targetNode, config)
        return observer
      }
      
    2. In an onMounted hook, use that function to observe the template ref in target.value, passing a callback that sets toJson.value:

      let observer = null
      
      onMounted(() => {
        toJson.value = dumpJson(target.value.outerHTML, jsonOptions)
      
        observer = observeMutations(target.value, () => {
          toJson.value = dumpJson(target.value.outerHTML, jsonOptions)
        })
      })
      
    3. In an onUnmounted hook, disconnect the observer as cleanup:

      onUnmounted(() => observer?.disconnect())
      

    demo