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 }
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.
Instead of the watcher, use a MutationObserver
to observe the changes made to the target node, including the outerHTML
.
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
}
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)
})
})
In an onUnmounted
hook, disconnect the observer as cleanup:
onUnmounted(() => observer?.disconnect())