Blade Components for your Layout
In the latest blog post 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:
<!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:
@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":
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:
<!-- /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:
<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:
<!-- /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:
<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:
<!-- /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:
<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:
<!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:
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
:
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:
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
- TheIlluminate\View\ComponentAttributeBag
instance that holds all of our component attributes -
slot
- AIlluminate\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.
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:
<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.