5

i need to to focus ref with name test1 a set some value which is placed in compontend slot (from outside). Is it possible to do it somehow? I tried to get from $refs or $slots, but failed.

App.vue

    <template>
      <div id="app">
        <HelloWorld>
          <input type="text" ref="test1" />
        </HelloWorld>
      </div>
    </template>
    
    ```
    <script>
    import HelloWorld from './components/HelloWorld.vue';
    
    export default {
      name: 'App',
      components: {
        HelloWorld,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>

Component.vue

    <template>
      <slot></slot>
      <hr />
      <input type="text" ref="test2" />
    </template>
    
    <script>
    export default {
      name: 'HelloWorld',
      mounted() {
        this.$refs.test2.value = 'test 2 value';
        // how get ref 'test1' ?
      },
    };
    </script>
Moritz Ringler
  • 9,772
  • 9
  • 21
  • 34
Mr. RJ
  • 224
  • 5
  • 14
  • It seems slightly counterintuitive, but I think this question and my recent question on vNode.componentInstance are related and once we have an answer for one, we'll have an answer for both. In both cases, we are trying to get details of a component passed in through a slot. https://stackoverflow.com/questions/72399153/vue3-equivalent-of-vnode-computedinstance – kingcoyote May 27 '22 at 16:42

4 Answers4

4

You can simply pass a ref through an event or a prop. Here's a solution using your example.

   <!-- App.vue -->
   <div id="app">
    <HelloWorld :test="inputEl">
      <template v-slot:input>
        <input type="text" ref="test1" />
      </template>
    </HelloWorld>
   </div>

    <script>
        export default {
          name: 'App',
          mounted() {
            this.inputEl = this.$refs.test1;
          },
          data() {
            return {
              inputEl: {}
            }
          }
        };
    </script>
   
   <!-- HelloWorld.vue -->
   <template>
     <slot></slot>
     <hr />
     <input type="text" ref="test2" />
   </template>
   
   <script>
    export default {
      name: 'HelloWorld',
      mounted() {
        this.$refs.test2.value = 'test 2 value';
        
        // Use the prop. Or watch it until it has been updated.
        this.props.test.value = "test 1 value";
      },
    };
   </script>
Prince Owen
  • 1,225
  • 12
  • 20
  • Hi, thanks for your comment, but doesn´t work. Live demo: https://stackblitz.com/edit/vue-tgtjxr?file=src%2FApp.vue,src%2Fcomponents%2FHelloWorld.vue Can you use $refs variable directly in template? It seems not, however I get undefined in the child component – Mr. RJ Jul 22 '22 at 13:04
  • You're right. The `$refs` does not seem to work all the time when used in the template. You can still pass the component directly in your code. Here's a working version of your demo: https://stackblitz.com/edit/vue-frxfyq?file=src/App.vue – Prince Owen Jul 22 '22 at 15:47
  • Hi Prince Owen, thx, works great. I dont know if it is a preferrable approach, i discover maybe better -> https://stackoverflow.com/a/73081327/1519236 But i saved your approchach too, thx! – Mr. RJ Jul 25 '22 at 14:23
1

In Vue3, you can use function refs and scoped slot.

example

// Parent
const slotRef = ref()
const setSlotRef = (el) => {
  slotRef.value = el;
}

<template>
  <slot name="child" :set-ref="setSlotRef">
</template>

// Child
<template #child="{ setRef }">
   <button :ref="(el) => setRef(el)">
     button
   </button>
</template>

Sunghyun Cho
  • 689
  • 5
  • 8
  • Can you prepare demo pls? I cannot reproduce code that works – Mr. RJ Jul 22 '22 at 13:10
  • Can you try this example? link - https://sfc.vuejs.org/#eNqtUs1OwzAMfhUrHLZJrBUcSzeBeAOQOOXSde7WkSZRko5D1XfHbtqOnhASN/9+/vzZnXixNrm2KDKR+9LVNoDH0Nq91HVjjQvQgcMKeqicaWBFpas59a5M+GgxpqRIUg4wmhRSS10a7QmOYm+EsGOc9WYOY+DqmFmj2sBuD53UMDUk10IR9g5QPUndS52nkSBRIydgY1URkDyAfGQyOOROSbgrz7U67qToeCAP66WIVTLIkB/aEIyGjKhR0UQjlrI3FcfyAezWnMZu5kMuB5akBjkiq0VK3Iso4LYpbHLxRpP6w+pyTHgpsigGxwZBMzLOIVifpamvSlb54hPjTilZiWt1qBtM0DfbgzNfHh0B8xmANtY9jZxu87dL//sVp06jX1VdfnLbrYlTRmGizGm9AKCBv3wA78FyWYfeQ/XwCMGAsaihgCNeUZHtKGaUj8eJDTmPAV00SPeP9xWQ0Vrb+BK3BaVIY8f4M88l86eScZP5VX4sMagQx82/snyF/ht/+DNb – Sunghyun Cho Sep 05 '22 at 10:52
  • Hi, thanks for the demo. I rewrote it in the Options API. It looks similar to my solution (https://stackoverflow.com/a/73081327/1519236), except that I set it from the top (parent to child), you from the bottom (from child to parent via scoped slot) I don't know which is better, but it's good to know about both. And the Composition API is not needed, i think. Thanks a lot. – Mr. RJ Sep 05 '22 at 18:35
  • It's not a good solution, you can't write `:ref="(el) => setRef(el)"` for everything where you are using this slot, and it can be hundreds of components. – Vedmant Nov 06 '22 at 18:28
0

I have it!

<template>
  <div id="app">
    <HelloWorld name="child" :b="setRefOnA">
      <input type="text" ref="a" value="default value of this input" />
    </HelloWorld>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue';

export default {
  name: 'App',
  components: {
    HelloWorld,
  },
  methods: {
    setRefOnA(el) {
      return this.$refs.a;
    },
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>


<!-- HelloWorld.vue -->
<template>
  <div>
    <slot> ... </slot>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: ['b'],
  mounted() {
    console.log(this.b().focus());
  },
};
</script>

Link: https://stackblitz.com/edit/vue-jynies?file=src/App.vue

Mr. RJ
  • 224
  • 5
  • 14
0

You define <input type="text" ref="test1" /> in App.vue so you need to execute this.$refs.test1.focus() in the App.vue too. You can listen and use parent component's mounted event using vue:mounted.

App.js

<template>
  <div id="app">
    <HelloWorld @vue:mounted="wrapperMounted">
      <input type="text" ref="test1" />
    </HelloWorld>
  </div>
</template>


<script>
import HelloWorld from './components/HelloWorld.vue';

export default {
  name: 'App',
  components: {
    HelloWorld,
  },
  methods: {
    wrapperMounted() {
      this.$nextTick(() => this.$refs.test1.focus());
    },
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Component.vue

<template>
  <slot></slot>
  <hr />
  <input type="text" ref="test2" />
</template>

<script>
export default {
  name: 'HelloWorld',
  mounted() {
    this.$refs.test2.value = 'test 2 value';
    // how get ref 'test1' ?
  },
};
</script>