Subscribe on changes!

Merge multiple ref assignments in templates

avatar
Jul 15th 2023

What problem does this feature solve?

When authoring headless libraries, composables or container components, it is often encountered that you pass in certain attributes to your user that must be bound to an element that the user renders himself.

For example, you can have an const { attributes } = useAriaTitle() composable, which returns the aria attributes for headings and the user can then create his own component and bind them to the element. That composable would encapsulate the logic and the user is responsible for wiring it up.

If we take this a step further in the context of Modals, Popovers etc, the logic behind the composable can become substantial and sometimes requires access to the rendered dom element ( for example, if you want to detect a click outside of something, you need to know the boundaries ).

Refs are an ideal candidate for that. You can add a ref property to the attributes object that you return to the user and when he binds them, you have access to the dom element, when mounted, without requiring additional assistance on his part.

This would work great, however, Vue only allows one ref to be bound to an element in templates. So, if the user wants to add his own local ref to the same element, only one of these will work based on the ordering of attributes.

This is demonstrated here

It would be great if the compiler and Vue core allow binding multiple refs on the same element and they become merged in an array and have all of them invoked at runtime.

I will assume some hints in advance, so:

Require a ref to be passed to the composable, like this

<script>
const title = ref()
const { attributes } = useAriaTitle(title)
</script>
<template>
  <h3 v-bind="attributes" ref="title" />
</template>

While this works, it would require the user to manage these manually. This pattern also breaks down in complex scenarios where the composable is called in the root component, but requires access to a ref which is located in a child component ( because the user might want to use compound components instead of a single monolithic one )

What does the proposed API look like?

I don't think this requires a public interface of some sort ( it might even be possible with render functions / JSX ), but it would be great if the attributes could be merged in a similar fashion

<script>
const regularRef = ref()
function. functionRef(element) {}
</script>

<template>
  <h3 :ref="[regularRef, functionRef]">My title</h3>
</template

So, using this syntax, the DOM element will be assigned to regularRef and the functionRef will get called with the element.

This does not require a change in how refs function, its more of an ability to handle an array of options that it already knows how to handle.

This way, the mergeProps handler can merge them similar to how it merges event listeners.

avatar
Jul 19th 2023

Use ref and Custom Directives.

avatar
Jul 19th 2023

Use ref and Custom Directives.

Can you elaborate on that?

Are you suggesting that I create a custom directive and add that to the attributes object, so that it gets bound to the element?

As far as I'm aware the withDirectives helper works only inside render functions, is there a way to add a directive through the v-bind syntax?

I'm looking for an unobtrusive way to accomplish this, ie if the developer does <h3 v-bind="titleAttrs"></h3> and later he decides to add his own ref to the element, I would like that to work by default for him, instead of having to use custom directives to achieve a functionality that should be available to him by default

avatar
Jul 19th 2023
<script lang="ts" setup>
import { ref } from 'vue';

const regularRef = ref();

const vFunc = { // v-func
  mounted(el) {
    // ...
  }.
};
</script>

<template>
  <h3 ref="regularRef" v-func>My title</h3>
</template
avatar
Jul 19th 2023
<script lang="ts" setup>
import { ref } from 'vue';

const regularRef = ref();

const vFunc = { // v-func
  mounted(el) {
    // ...
  }.
};
</script>

<template>
  <h3 ref="regularRef" v-func>My title</h3>
</template

Thank you for that example. Unfortunately, this doesn't quite satisfy the initial requirements of not having to do anything extra besides binding the attributes object to the element and adds additional properties.

It would have been cool if it was possible to add directives through the v-bind="attributes" syntax, where the directive could have been an implementation detail.

I would still prefer the manual binding as opposed to this, because at least its clear in what it does, but it would have been cool if this was handled by the framework