What if, instead, there was a way to access the children passed to an element through the slotted area one by one?
Well, is there a way to do that?
Yes.
Let's start with a simple example: Counting how many children a component has.
Counting a Component's Children
Let's count how many elements and components are being passed to our component:
React
Angular
Vue
React has a built-in helper called Children that will help you access data passed as a child to a component. Using this Children helper, we can use the toArray method to create an array from children that we can then do anything we might otherwise do with a typical array.
This comes in handy when trying to access the length count.
import { Children } from "react";const ParentList = ({ children }) => { const childArr = Children.toArray(children); console.log(childArr); // This is an array of ReactNode - more on that in the next sentence return ( <> <p>There are {childArr.length} number of items in this array</p> <ul>{children}</ul> </> );};const App = () => { return ( <ParentList> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ParentList> );};
An embedded webpage:React Counting Comp Children - StackBlitz
Here, childArr is an array of type ReactNode. A ReactNode is created by React's createElement method.
Remember that JSX transforms to call React's createElement node under-the-hood.
This means that the following JSX:
const el = <div />;
Becomes the following code after compilation:
const el = createElement("div");
There also exists a Children.count method that we could use as an alternative if we wanted.
const ParentList = ({ children }) => { const childrenLength = Children.count(children); return ( <> <p>There are {childrenLength} number of items in this array</p> <ul>{children}</ul> </> );};
An embedded webpage:React Counting Comp Children Util - StackBlitz
To get the count of the children elements within a component in Angular requires some pre-requisite knowledge. Let's go through each step until we find ourselves at the solution.
In our previous example, we used them to access a template variable within a component's template. Well, we can also use them to access tags from a child's template as well, by using contentChild.
contentChild is a way to query the projected content within ng-content from JavaScript.
contentChildren returns an array of the accessed children. You can then access the properties of children inside of the template itself, like what we're doing with children().length.
Unlike React and Angular, Vue's APIs don't allow us to count a child's list items easily. There are a lot of nuances as to why this is the case, but we'll do our best to explain that when we rewrite Vue from scratch in the third book of the series.
Instead, you'll want to pass the list from the parent component to the list display to show the value you intend:
<!-- ParentList --><script setup>const props = defineProps(["list"]);</script><template> <p>There are {{ props.list.length }} number of items in this array</p> <ul> <slot></slot> </ul></template>
<!-- App.vue --><script setup>import ParentList from "./ParentList.vue";const list = [1, 2, 3];</script><template> <ParentList :list="list"> <li v-for="i in list">Item {{ i }}</li> </ParentList></template>
An embedded webpage:Vue Counting Component Children - StackBlitz
const ParentList = ({ children }) => { const childArr = Children.toArray(children); console.log(childArr); // This is an array of ReactNode - more on that in the next sentence return ( <> <p>There are {childArr.length} number of items in this array</p> <ul> {children.map((child) => { return <li>{child}</li>; })} </ul> </> );};const App = () => { return ( <ParentList> <span style={{ color: "red" }}>Red</span> <span style={{ color: "green" }}>Green</span> <span style={{ color: "blue" }}>Blue</span> </ParentList> );};
An embedded webpage:React Children in Loop - StackBlitz
Since Angular's contentChildren gives us an HTMLElement reference when using our template variables on HTMLElements, we're not able to wrap those elements easily.
While Vue can't render children in a list, it has many more capabilities to showcase. Read on, dear reader.
Here, we can see that whenever a random number is added to the list, our list item counter still increments properly.
Passing Values to Projected Content
While counting the number of items in a list is novel, it's not a very practical use of accessing projected content in JavaScript.
Instead, let's see if there's a way that we can pass values to our projected content. For example, let's try to change the background color of each li item if the index is even or odd.
React
Angular
Vue
By now, we should be familiar with the children property in React. Now get ready to forget everything you know about it:
const AddTwo = ({ children }) => { return 2 + children;};// This will display "7"const App = () => { return <AddTwo children={5} />;};
An embedded webpage:React Numerical Child - StackBlitz
Let's take our code from the start of this chapter and refactor it so that we don't have to have our v-for inside of the App.vue. Instead, let's move it into ParentList.vue and pass properties to the <slot> element.
<!-- ParentList.vue --><script setup>const props = defineProps(["list"]);</script><template> <p>There are {{ props.list.length }} number of items in this array</p> <ul id="parentList"> <slot v-for="(item, i) in props.list" :item="item" :i="i" :backgroundColor="i % 2 ? 'grey' : ''" ></slot> </ul></template>
<!-- App.vue --><script setup>import { ref } from "vue";import ParentList from "./ParentList.vue";const list = ref([1, 2, 3]);function addOne() { const randomNum = Math.floor(Math.random() * 100); list.value.push(randomNum);}</script><template> <ParentList :list="list"> <!-- Think of this as "template is receiving an object we'll call props" from "ParentList" --> <template v-slot="props"> <li :style="'background-color:' + props.backgroundColor"> {{ props.i }} {{ props.item }} </li> </template> </ParentList> <button @click="addOne()">Add</button></template>
An embedded webpage:Vue Pass Val to Projected Content - StackBlitz
This v-slot is similar to how you might pass properties to a component, but instead, we're passing data directly to a template to be rendered by v-slot.
You can object destructure the v-slot usage to gain access to the property names without having to repeat props each time: