Playing Next Lesson In
seconds

EdgeJS Components #2.4

Main Slot, Named Slots, and Slot Scopes

In This Lesson

We'll learn all about the main slot, how to define and use named slots, as well as passing state information from within our components to our slots using slot scopes.

Created by
@tomgobich
Published

So lastly, before we actually move on to creating our components, let's cover slots. Slots allow us to insert specific HTML content into designated areas within our components.

The Main Slot

First, let's understand the main slot, which we've already touched upon in our app layout. Anything enclosed between the component's start and end tags is rendered as the default slot.

@layout.app({ title: request.params().name }) {{-- πŸ‘ˆ start tag --}}

  Our main slot content {{-- πŸ‘ˆ main slot --}}

@end {{-- πŸ‘ˆ end tag --}}
Copied!

The main slot is accessible in the component by encompassing await $slots.main() with three curly braces. Remember, three curly braces allow us to plop raw HTML within EdgeJS.

<div class="max-w-4xl mx-auto my-8">
  <header class="bg-gray-100 p-6 -mx-6 mb-6 rounded-md">
    @if (title != 'Home')
      <a href="/" class="text-xs">Home</a>
    @endif
    <h1 class="text-2xl font-bold">{{ titleCase(title) }}</h1>
  </header>

  <main {{ $props.serializeExcept(['title']) }}>
    {{{ await $slots.main() }}} {{-- πŸ‘ˆ renders our main slot content --}}
  </main>
</div>
Copied!
  • resources
  • views
  • components
  • layout
  • app.edge

The main slot is always going to be provided by default, so we don't need to worry about this particular method being undefined; we're always safe to go ahead and call it.

Named Slots

In addition to that main slot, we can incorporate named slots within our component as well. Named slots work very similarly to the main slot, the main difference being they’re named differently and require us to specify what their content is when we use our component.

Let’s say, within the head section of our app layout, we want to be able to define a different title per page within our application. Currently our head section looks like the below.

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <title>Adocasts' EdgeJS Components Series</title>

  @entryPointStyles('app')
  @entryPointScripts('app')
</head>
Copied!
  • resources
  • components
  • layout
  • app.edge

We can add in a named slot, called title, which will then give us the option to overwrite the default title we have above.

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  @if ($slots.title)
    {{{ await $slots.title() }}}
  @else
    <title>Adocasts' EdgeJS Components Series</title>
  @endif

  @entryPointStyles('app')
  @entryPointScripts('app')
</head>
Copied!
  • resources
  • components
  • layout
  • app.edge

Now unlike the main slot, name slots aren't always going to exist by default. So, in this case, our title slot is not going to be defined unless we explicitly provide the slot within our component usage.

We can provide content to the β€œtitle” named slot, by calling the @slot tag within our component usage, passing it the name of the slot, and providing the content as it’s children.

@layout.app({ title: request.params().name })
  {{-- starts our named "title" slot --}}
  @slot('title')
    This will now be our title
  @endslot
  
  {{-- starts our main slot --}}
  Our main slot content

@end
Copied!

Optional Versus Required Named Slots

Above, since we’ve wrapped our β€œtitle” named slot within an if check, which is checking the existence of $slots.title, we’re making this slot optional. Meaning, if we don’t provide content to this slot within our component usage, everything will work just fine.

However, if we remove the if check to leave just the below. We’ll now get an error if we don’t provide content to this β€œtitle” slot.

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  {{{ await $slots.title() }}}

  @entryPointStyles('app')
  @entryPointScripts('app')
</head>
Copied!
  • resources
  • components
  • layout
  • app.edge

Meaning, we’ve essentially changed this slot from being optional to required, since our app no longer works properly without it.

Slot Scope

In addition to slots, similarly to Vue, you can pass slot scope data to access component state within the slot content. All you have to do is pass the data in as the first argument to the slot method, like below.

{{{ await $slots.title({ title }) }}}
Copied!

For example, we’re still passing the param name into our component as title, so we can pass that along as the slot scope in both our named and main slots.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  @if ($slots.title)
    {{{ await $slots.title({ title }) }}}
    {{{ await $slots.title() }}}
  @else
    <title>{{ title ? titleCase(title) + ' | ' : '' }} Adocasts' EdgeJS Components Series</title>
  @endif

  @entryPointStyles('app')
  @entryPointScripts('app')
</head>
<body>

  <div class="max-w-4xl mx-auto my-8">
    <header class="bg-gray-100 p-6 -mx-6 mb-6 rounded-md">
      @if (title != 'Home')
        <a href="/" class="text-xs">Home</a>
      @endif
      <h1 class="text-2xl font-bold">{{ titleCase(title) }}</h1>
    </header>

    <main {{ $props.serializeExcept(['title']) }}>
      {{{ await $slots.main({ title }) }}}
      {{{ await $slots.main() }}}
    </main>
  </div>
  
</body>
</html>
Copied!
  • resources
  • components
  • layout
  • app.edge

For our named slot, this will be passed along as the second parameter of the slot tag.

@layout.app({ title: request.params().name })
  @slot('title', scope)
    {{ scope.title }}
  @slot('title')
    This will now be our title
  @endslot
  
  Our main slot content
@end
Copied!

For our main slot, in order to access the slot scope, we’ll need to wrap it within the @slot tag and use it the same as a named slot.

@layout.app({ title: request.params().name })
  @slot('title', scope)
    {{ scope.title }}
  @endslot
  
  @slot('main', scope)
    {{ scope.title }}
  @endslot
  Our main slot content
@end
Copied!

So, in the case that we have above for both our named and main slots, our title prop is doing a full round trip.

  1. First, it’s passed as a prop into layout component

  2. Then, passed from our layout component back to our page as a slot scope

  3. Lastly, we’re rendering it as slot content inside of our component from our page.

In summary, slots within EdgeJS Components enable you to define flexible content areas, both for main content and for named slots, and you can pass scope data to access component state within those slots. Which allows you to make the actual content of your components fluid and extendable.

Join the Discussion 0 comments

Create a free account to join in on the discussion
robot comment bubble

Be the first to comment!