Watch child properties from parent component in vue 3
Asked Answered
O

2

6

I'm wondering how I can observe child properties from the parent component in Vue 3 using the composition api (I'm working with the experimental script setup).

<template>//Child.vue
  <button 
    @click="count++" 
    v-text="'count: ' + count" 
  />
</template>

<script setup>
import { ref } from 'vue'

let count = ref(1)
</script>
<template>//Parent.vue
  <p>parent: {{ count }}</p> //update me with a watcher
  <Child ref="childComponent" />
</template>


<script setup>
import Child from './Child.vue'
import { onMounted, ref, watch } from 'vue'

const childComponent = ref(null)
let count = ref(0)

onMounted(() => {
  watch(childComponent.count.value, (newVal, oldVal) => {
    console.log(newVal, oldVal);
    count.value = newVal
  })
}) 
</script>

I want to understand how I can watch changes in the child component from the parent component. My not working solution is inspired by the Vue.js 2 Solution asked here. So I don't want to emit the count.value but just watch for changes.

Thank you!

Officialism answered 21/2, 2021 at 14:48 Comment(2)
Why don't you want to emit the event?Uniocular
Because I want to understand how to observe changed data across components. The counting is only just an example. It's not an exotic case, but unfortunately I'm not getting anywhere, so that' s why I'm asking.Officialism
G
3

The Bindings inside of <script setup> are "closed by default" as you can see here.

However you can explicitly expose certain refs. For that you use useContext().expose({ ref1,ref2,ref3 })

So simply add this to Child.vue:

import { useContext } from 'vue'

useContext().expose({ count })

and then change the Watcher in Parent.vue to:

watch(() => childComponent.value.count, (newVal, oldVal) => {
    console.log(newVal, oldVal);
    count.value = newVal
  })

And it works!

Gamez answered 21/2, 2021 at 16:7 Comment(6)
Both of your suggestions have no effect. childComponent.value.count is undefined. I know that it is solved via emit and props. As I said I am interested in the example.Officialism
Have you changed your Code in Child.vue? from <script setup> import { ref } from 'vue' let count = ref(1) </script> to <script> import { ref } from 'vue'; export default { setup(){ const count = ref(1) return { count } } } </script> ? It works for me.Gamez
Hey Tom, no sorry. I want to work with the script setup method. I can try it out with it if you like. Maybe this will bring me further to apply the basics to the experimental version.Officialism
It seems like what you are trying to do is not yet possible with the script setup method. Take a look at this topic (closed by default) in the RFC github.com/vuejs/rfcs/blob/script-setup-2/active-rfcs/… . It's intended that the bindings are not exposed in the parent component. Note: "How to explicitly expose imperative public interface will be finalized in github.com/vuejs/rfcs/pull/210."Gamez
I have edited my response, there is already a way to expose refs with the <script setup> syntax!Gamez
Works fine, thank you. I hope you will use the script setup as well (when it finally gets approved)Officialism
F
2

I've answered the Vue 2 Solution and it works perfectly fine with Vue 3 if you don't use script setup or explicitly expose properties.

Here is the working code.

Child.vue

<template>
  <button @click="count++">Increase</button>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    return {
      count: ref(0),
    };
  },
};
</script>

Parent.vue

<template>
  <div id="app">
    <Child ref="childComponent" />
  </div>
</template>

<script>
import { ref, onMounted, watch } from 'vue';
import Child from './components/Child.vue';

export default {
  components: {
    Child,
  },
  setup() {
    const childComponent = ref(null);

    onMounted(() => {
      watch(
        () => childComponent.value.count,
        (newVal) => {
          console.log({ newVal }) // runs when count changes
        }
      );
    });

    return { childComponent };
  },
};
</script>

See it live on StackBlitz


Please keep reading

In the Vue 2 Solution I have described that we should use the mounted hook in order to be able to watch child properties. In Vue 3 however, that's no longer an issue/limitation since the watcher has additional options like flush: 'post' which ensures that the element has been rendered.

Make sure to read the Docs: Watching Template Refs


When using script setup, the public instance of the component it's not exposed and thus, the Vue 2 solutions will not work. In order to make it work you need to explicitly expose properties:

With script setup

import { ref } from 'vue' const a = 1 const b = ref(2) defineExpose({ a, b })

With Options API

export default {
  expose: ['publicData', 'publicMethod'],
  data() {
    return {
      publicData: 'foo',
      privateData: 'bar'
    }
  },
  methods: {
    publicMethod() {
      /* ... */
    },
    privateMethod() {
      /* ... */
    }
  }
}

Note: If you define expose in Options API then only those properties will be exposed. The rest will not be accessible from template refs or $parent chains.

Foresheet answered 13/12, 2021 at 11:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.