Using Laravel Blade components in your layout
Published: 2021-04-14 09:49:24

# Blade Components for your Layout

In the [latest blog post](/blog/using-laravel-view-components) I showed you how Laravels Blade view components work and how you can make use of them.
In this part of the series, I want to focus on how we can use these components throughout our application - starting with the very basic: your layout.

In a typical Laravel application, you might have a file called `layouts/app.blade.php` that all of your other views extend from.

Such a view could look like this:

```html
<!DOCTYPE html>
<html>
    <head>
	    @yield('scripts')
    </head>
    <body>
	    @yield('content')
    </body>
</html>
```

And in your actual pages, you will `extend` that view and provide your content within the `content` section. Here's an example `welcome.blade.php` file:

```html
@extends('layouts.app')

@section('scripts')
	<!-- Some JS and styles -->
@endsection

@section('content')
  <div>My Page content is here</div>
@endsection
```

So far so good, so let's see how we can make use of our newly learned Blade components to refactor this.

First of all, we are going to create a new component called "Layout":

```sh
php artisan make:component Layout
```

Next, we can take all of our existing layout view code and place it inside of our new layout component view:

```html
<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
    <head>
	    @yield('scripts')
    </head>
    <body>
	    @yield('content')
    </body>
</html>
```

To make this work, the only thing we need to do in our `welcome.blade.php` file is, instead of extending our layout - we can just wrap all of the content inside of our new layout component, like this:

```html
<x-layout>
	@section('scripts')
		<!-- Some JS and styles -->
	@endsection
	
	@section('content')
	  <div>My Page content is here</div>
	@endsection
</x-layout>
```

This is already pretty good, but as we have seen in the previous post, Laravel provides a `$slot` variable that automatically holds all of the content that was placed inside of the blade component tags. So lets go and change our layout to make use of the `$slot` variable, instead of yielding a content section:

```html
<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
    <head>
	    @yield('scripts')
    </head>
    <body>
		  {{ $slot }}
    </body>
</html>
```

Instead of using the `@section` directive, we can now simply pass the data as is:

```html
<x-layout>
	@section('scripts')
		<!-- Some JS and styles -->
	@endsection
	
  <div>My Page content is here</div>
</x-layout>
```

Alright - so we got rid of the content section and wrapped everything into our layout component. That's already pretty cool and I think this improves the readability of our view component. Because of the indentation it is immediately clear which layout is being used. This `scripts` section is still bothering me though. So let's see how we can get rid of this as well - and there are multiple options.

## Named Slots

The first option that we have is to make use of a "named slot". We already discussed the `$slot` variable, that will automatically be populated with everything within the component HTML, but we can also manually specify a slot name. This can be done using the `x-slot` tag - which itself is sort of a pre-defined Blade component that comes out of the box with Laravel.

By passing a `name` property to our `x-slot`, we can make the data available within a variable with the same name as our `name` attribute.

Let's modify our layout component to make use of a new `$scripts` variable:

```html
<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
    <head>
	    {{ $scripts }}
    </head>
    <body>
		  {{ $slot }}
    </body>
</html>
```

To pass this variable to our view, we can now pass it into a `scripts` slot:

```html
<x-layout>
  <x-slot name="scripts">
		<!-- Some JS and styles -->
  </x-slot>
	
  <div>My Page content is here</div>
</x-layout>
```

Refresh the page, and you can see that everything works as expected. With this, we introduced an error though, as our `$scripts` variable is now mandatory within our layout - so if a  view would not provide a `scripts` slot, you would get an error "Undefined variable: scripts".

While this works with a named slot, we no longer have the ability to append to the scripts section from within multiple of our views/components - because we don't have a real "section" anymore.

So what if we want to get rid of the `@section` directives and use a component for this instead? First of, lets get our `@yield` directive back into our layout component:

```html
<!DOCTYPE html>
<html>
    <head>
	    @yield('scripts')
    </head>
    <body>
		  {{ $slot }}
    </body>
</html>
```

Alright - now lets see how we can make use of this section, without using a directive.

## Renderless Blade components

When you create a custom Blade component, the class has a `render` method, that by default returns a string containing the name of the view for the given component:

```php
namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    public function render()
    {
        return view('components.alert');
    }
}
```

There's another way of returning a view though, which is by returning a callable, that is then going to return the actual view. In this case, our component only holds the data/state that was provided for the component, but it doesn't actually return a view - which is great for our section use case.

So lets create a new view component called `Section`:

```sh
php artisan make:component Section
```

Now instead of returning a view, we are actually going to return a function - that will return the `@section` directive for us:

```php
namespace App\View\Components;

use Illuminate\View\Component;

class Section extends Component
{
    public function render()
    {
        return function ($componentData) {
            return <<<BLADE
                @section("{$componentData['attributes']->get('name')}")
                    {$componentData['slot']}
                @endsection
            BLADE;
        };
    }
}
```

Alright - so lets break this render method down line by line.

The `render` method returns a callable with one parameter - an array holding the component data.
This array has the following data and keys available:

* `componentName` - The name for the component, in our case "section"
* `attributes` - The `Illuminate\View\ComponentAttributeBag` instance that holds all of our component attributes
* `slot` - A `Illuminate\Support\HtmlString` object with the string that was passed to the default slot

Inside of this callable, we are returning a string - this string is escaped using PHPs [Heredoc Syntax](https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc).

Inside of this string, we can now return any Blade code that we want - so in our case, we are opening a `@section` directive, with the `name` attribute that was provided in the `<x-section` tag. Inside of that `@section` directive, we pass the slot of our component (and cast it to a string, as it's a HtmlString object).
And last but not least, we close the section directive.

To make use of this renderless component, we can now rewrite our page view like this:

```html
<x-layout>
  <x-section name="scripts">
	 <!-- Some JS and styles -->
  </x-section>
	
  <div>My Page content is here</div>
</x-layout>
```

Yay - we did it! We got rid of our ugly directive and have successfully refactored it to make use of a custom blade component. 

Is this better than using the directives? You'll have to decide for yourself - but I hope that this inspired you to take a look at renderless Blade components, as well as how you can use components for your traditional layout extensions.