5 Vue.js slot tricks to watch out for
Tags: webdev, vue, javascript
Published: 2023-01-06 18:47:55

Vue.js is a powerful library for creating user interfaces for web and mobile platforms. It is part of the so-called big three (also Angular and React) libraries for interactive user interface programming. I think you use slots all the time when using Vue.js. It's so simple and intuitive mechanism that it is a pleasure to use it. Slots are everywhere, in libraries, in components, and in scaffolders. But for the most part, everyone uses them only on one side, on the side of consumption, and when creating their components, they are rarely added. In this article I will show you how easy it is to add slots into your own components and a few cases where they can save a couple of lines of code.

## Preface
First, what is a slot in Vue.js. A slot is a special communication mechanism between components. Yes, you should think about slots
not as a mechanism for inserting content, but as a mechanism for communication between components. So, in all examples, we will have two components `TestParent.vue` and `TestChildren.vue`. I will not use `TypeScript` and `Composition API` to keep all examples as simple as possible.
The simplest example of communication through slots:
```html
<!-- TestParent.vue -->

<template>
    <div>
        <test-children>
            <span>World</span>
        </test-children>
    </div>
</template>

<script>
export default function () {
    const TestChildren = <...>;
    
    return {
        name: `Parent`,
        components: { TestChildren }
    }
}
</script> 

<!-- TestChildren.vue -->

<template>
    <div>
        <span>Hello</span>
        <slot></slot>
    </div>
</template>
```
As a result, we will get the text `Hello World`.

In our example, I used the so-called default slot. You can define more slots, for example let's add another slot to indicate an exclamation point.
```html
<!-- TestParent.vue -->

<template>
    <div>
        <test-children>
            <span>World</span>
            <template v-slot:extra>
                !
            </template			
        </test-children>		
    </div>
</template>
<!--
I omitted the script section because it
hasn't changed from the previous example
-->

<!-- TestChildren.vue -->

<template>
    <div>
        <span>Hello</span>
        <slot></slot>
		<slot name="extra"></slot>
    </div>
</template>
```
The result is `Hello World !`. Here we have added a named slot called `extra` where we added an exclamation mark. You can add as many named slots as you want to your component. You can even create them dynamically.

So, we are ready to move on to cases that can be useful in the daily creation of components.
## Case 1: Consolidation of functionality
Suppose the `TestChildren` component displays text with formatting that removes edge spaces (trim operation). It also has an additional slot for displaying additional text. We add additional text in the `TestParent` component.
```html
<!-- TestParent.vue -->

<template>
    <div>
        <test-children header="Hello">
            <template v-slot:extra>
                {{ parentValue }}
            </template
        </test-children>
    </div>
</template>

<script>
export default function () {
    const TestChildren = <...>;
    
    return {
        name: `Parent`,
        components: { TestChildren },
        data() {
            return {
                parentValue: 'World!'
            }
        }
    }
}
</script> 

<!-- TestChildren.vue -->

<template>
    <div>
        <!-- Using a formatting method -->
        <span>{{ formatValue(header) }}</span>
        <slot name="extra"></slot>
    </div>
</template>

<script>
export default function () {
    return {
        name: `TestChildren`,
        props: {
            header: String
        },
        methods: {
            // we have added a new formatting method
            formatValue(value) { 
                return value.trim()
            }
        }
    }
}
</script> 
```
All goes well until we get a request to also format the `parentValue` in the `TestParent`. The problem here is that the formatting function is in the `TestChildren` component. You can naturally take out the `formatValue` function from the component into a separate js / ts file and import it to both components. But what if I will say that you can do this without separating the function into a separate script file. You can use the functionality of the slots to solve this problem. Let's change it so that we can use this function from the `TestParent` component.
```html
<!-- TestParent.vue -->

<template>
    <div>
        <test-children header="Hello">
            <template v-slot:extra="{ format }"> 
				<!-- call the method to format the value -->
                {{ format(parentValue) }} 
            </template
        </test-children>
    </div>
</template>
<!-- script section stay as in previous example -->

<!-- TestChildren.vue -->

<template>
    <div>
        <span>{{ formatValue(header) }}</span>
        <slot name="extra" :format="formatValue"></slot>
    </div>
</template>

<script>
export default function () {
    return {
        name: `TestChildren`,
        props: {
            header: String
        },
        methods: {
            formatValue(value) {
                return value.trim()
            }
        }
    }
}
</script> 
```
As we see, we got result without using extra files just inside our components. The `TestChildren` component exposes the function for the `TestParent` component only in the slot definition.
## Case 2: Component without UI
This case is an extension of the previous case to the entire component. Let's say we have a small (or maybe vice versa large) component, but each time it is used, a different representation must be used. A simple example is a button, a button can be in the form of a rectangle and as a link, as an icon, etc. How to solve this problem? We can create a component in which we describe the logic of how it should work (methods and properties) and when using an external component, it will decide which view it should use.
```html
<!-- TestParent.vue -->

<template>
    <div>
        <test-children
            title="Say Hello World!"
            @clicked="fire()">
            <template #default="{ context }">
			    <!--
					We use context to create a view for the button.
				-->
                <button
                    @click="context.click($event)">
                    <span>
                        {{ context.title }}
                    </span>
                </button>
            </template>
        </test-children>    
    </div>
</template>

<script>
export default function () {
    const TestChildren = <...>;
    
    return {
        name: `Parent`,
        components: { TestChildren },
        data() {
            return {
            }
        },
        methods: {
            fire() {
                window.alert('Hello World!');
            }
        }
    }
}
</script>

<!-- TestChildren.vue -->

<template>
    <div>
		<!-- We put the whole component in the context variable -->
        <slot :context="this"></slot> 
    </div>
</template>

<script>
export default {
    name: `TestChildren`,
    props: {
        title: {
            type: String,
            default: () => ``
        },
        disable: {
            type: Boolean,
            default: () => false
        }
    },
    emits: [`clicked`],
    methods: {
        click($event) {
            if (this.disable) return;
            this.$emit(`clicked`, $event);
        }
    }
};
</script>
```
The `TestChildren` component contains a set of properties (`title` and `disable`) and a `click` method. This state and behavior is the same in all buttons regardless of how they look. In this way, we can save code by not creating the same type of components that just look different but have the same functionality.
## Case 3: Rollover slots
Let's say we have three components, first `SomeComponent` in which there are several slots (among which there are `header` and `footer`).
It is imported within second `TestChildren` component and it defines the content for all the slots of the `SomeComponent` component, plus some functionality. And we have third `TestParent` component where imported `TestChildren`. At some point in time, there is a need to change the content for slots in different situations, but only for `header` and `footer` everything else remains as is. I have a component (perhaps complex) but I need to somehow definition content for slot from the parent component for the slot of the component nested in different component. Let's take an example, in the `TestParent` component, I should be able to change the content of the `footer` slot in the `SomeComponent` component, which is imported within `TestChildren` component.
```html

<!-- SomeComponent.vue -->

<template>
    <div>
        <slot name="header" :message="'value is '"></slot>
        <slot name="footer" :value="1"></slot>
    </div>
</template>

<!-- TestChildren.vue -->

<template>
    <some-component>
		<!-- Here we rollover the header and footer slots -->
		<template v-slot:header="{ message }">
			<slot name="header" :message="message"></slot> 
		</template>
		<template v-slot:footer="{ value }">
			<slot name="footer" :value="value"></slot>
		</template>

        <!-- definition for other slots and other stuff -->
	</some-component>
</template>

<!-- TestParent.vue -->

<template>
    <div>
        <test-children>
            <template v-slot:header="{ message }">
				<span style="color: red;">{{ message }}</span>
            </template>
            <template v-slot:footer="{ value }">
				<span style="color: green;">{{ value }}</span>
            </template>
        </test-children>
    </div>
</template>
```
As a result, we get `value is 1` but written in different colors. I draw your attention to the fact that in addition to the slots themselves, I pass the `message` and `value` parameters for the slots. This approach allows you to rollover some slots for the parent component from other the component, increasing the customization of the all components.
## Case 4: Data change chain
I am using the same components as in the previous example to illustrate the idea. In the previous example, we just rollover from one slot to another the data of the `header` and `footer` slots, but what if I tell you that we can change them during the rollover.
```html

<!-- SomeComponent.vue -->

<template>
    <div>
        <slot name="header" :message="'value is '"></slot>
        <slot name="footer" :value="1"></slot>
    </div>
</template>

<!-- TestChildren.vue -->

<template>
    <some-component>
		<template v-slot:header="{ message }">
			<!-- transform message to upper case -->
			<slot
				name="header"
				:message="message.toUpperCase()">
			</slot> 
		</template>
		<template v-slot:footer="{ value }">
			<!-- multiply on ten -->
			<slot name="footer" :value="value * 10"></slot>
		</template>

        <!-- definition for other slots and other stuff -->
	</some-component>
</template>

<!-- TestParent.vue -->

<template>
    <div>
        <test-children>
            <template v-slot:header="{ message }">
				<span>{{ message + ":" }}</span>
            </template>
            <template v-slot:footer="{ value }">
				<span>{{ value / 10 }}</span>
            </template>
        </test-children>
    </div>
</template>
```
Result: `VALUE: 1`. Although this example seems to be extremely simple, imagine what can be done based on this idea. For example, destructuring a large object into small ones or vice versa. And note that the scope of such transformations does not go beyond the components, each component is have its own responsibility and knows nothing about data transformations in other components.
## Case 5: Combining slots
Let's say we have some component `SomeComponent` and it has two slots - `header`, `footer`. Sometimes it happens that both of these slots must contain the same content. To solve this problem, we will make a `TestChildren` component which will imported  `SomeComponent` but define only one `postcontent` slot and insert it into the `header` and `footer` slots.
```html
<!-- TestParent.vue -->
<template>
    <div>
        <test-children>
            <template v-slot:postcontent>
				<span>Header and Footer Message</span>
            </template>
        </test-children>
    </div>
</template>

<!-- TestChildren.vue -->

<template>
    <some-component>
		<!-- insert the postcontent slot definition into the header and footer slots -->
		<template v-slot:header>
			<slot name="postcontent"></slot> 
		</template>
		<template v-slot:footer>
			<slot name="postcontent"></slot>
		</template>
	</some-component>
</template>
```
Thus, from the point of view of the `TestParent` component, there is only one slot, which is actually multiplied by several places. Although this case seems highly unlikely, if you take a close look at your components, you will most likely find similar cases. For example, the title text of a blog post is repeated at the beginning of the article, in the header, in some kind of drop-down panel, etc.
## Afterword
I hope you find my tricks for Vue.js slots was interesting and will be useful for someone.