0

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 }
tony19
  • 125,647
  • 18
  • 229
  • 307
Fanna1119
  • 1,878
  • 4
  • 24
  • 30

2 Answers2

1

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

tony19
  • 125,647
  • 18
  • 229
  • 307
0

Just to clarify: You're trying to pass a template ref to your function but it always turns out as null when you execute the logic, right?

You could simply use a onMounted(() => {}) hook in your composable.js file or you could implement templateRef (which you already tried to include from the looks of it) and until(https://vueuse.org/shared/until/#usage).

So instead of const target = ref(null) you'll do const target = templateRef('target', null) and pass that to your composable.js.

There you'll watch until the ref is truthy. So before your actual logic you'll do this:

await until(unrefElement(target)).toBeTruthy() Afterwards the ref should provide an actual element (use unrefElement to get the element of the templateRef) and you can start applying your actual logic to it.

Braks
  • 561
  • 2
  • 9
  • I will give this a try too. thanks :D however as the solution said, the reason i am only seeing updates once was because i was not looking for the dom to mutate – Fanna1119 Jul 31 '21 at 18:18