Laravel 8: Simple, Yet Powerful Alert Component
Using Laravel in a side project recently, the introduction of Blade Components has been a literal gift for me. Creating reusable components is something we all want in our work process. Thankfully, as of Laravel 7+, you can do just that with a simple artisan command.
Let’s make a basic “Alert” component, then build on it to make it powerful.
Step 1: Create the component
php artisan make:component Alert
Here, Laravel will create two new files:
- App\View\Components\Alert.php
- resources\views\components\alert.blade.php
You have a PHP component that collects data, and a view that delivers data. Simple!
Let’s look at the newly generated PHP file:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Alert extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|string
*/
public function render()
{
return view('components.alert');
}
}
Let’s clean it up a bit, reducing redundancy and introducing semantics:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\Contracts\View\View;
class Alert extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
}
/**
* Get the view / contents that represent the component.
*
* @return View
*/
public function render() : View
{
return view('components.alert');
}
}
We’ve cleaned up some redundancy and properly typed our render()
method with View
— Semantics are important after all. Not necessary, but it’s considered good practice to strictly declare your return types.
Step 2: Create Some Properties
An alert usually has two distinctive properties:
- Level: i.e., Info, Success, Error, Warning
- Message: i.e., “Successfully registered!”
With that in mind, let’s create those properties in our PHP file. Inside the class, update it with two new public
properties:
$level
and $message
— both of which can now be accessed within the corresponding blade template.
class Alert extends Component
{
/**
* The priority of the alert, i.e., "info", or "warning"
*
* @var string
*/
public $level;
/**
* The message or an array of messages to present to the user
*
* @var mixed
*/
public $message;
/**
* Create a new component instance.
*
* @param string $level
* @param mixed $message
*/
public function __construct(string $level, $message)
{
$this->level = $level;
$this->message = $message;
}
/**
* Get the view / contents that represent the component.
*
* @return View
*/
public function render() : View
{
return view('components.alert');
}
}
You may have noticed that the second parameter $message
in our constructor is “mixed”. Error messages may not only contain a string, but an array of strings for multiple messages. This is, by definition, a mixed property.
Step 3: Building a dynamic component template
The proponent of features that come out of the box with Laravel’s Component system are something to be reckoned with. First, let’s build a simple template. In our alert.blade.php
file, add:
<div class="c-alert c-alert--{{ $level }}" role="alert">
<p class="c-alert__message">{{ $message }}</p>
</div>
Notice what is happening here?
$level
and $message
are both accessible properties from the PHP component without passing them in to the second parameter of view()
from within the render()
method (similar to how you would pass data from within a controller, i.e., view('page.page-show', ['page' => $page])
) — cool, right?!
Now, anywhere in your blade templates, (presumably your default layout template), you can put the following:
<x-alert level="info" message="Please try again." />
This would render the following HTML:
<div class="c-alert c-alert--info" role="alert">
<p class="c-alert__message">Please try again.</p>
</div>
You can now easily implement the <x-alert />
component anywhere in your blade templates.
Step 4: Making our component dynamic
Now we have created a simple Laravel component, we need to address a couple of issues within our template and make it a bit more dynamic.
The issues are:
- No check if
message
orlevel
is empty. This will throw a BindingResolutionException error if the either declaration is empty. - There’s no way to add additional class selectors to our existing selectors of
c-alert c-alert--{{ $level }}
if we wanted to. - There’s no way of adding additional HTML within our parent HTML element.
We can, fortunately, address all these issues thanks to some smart interpolation and handy variables.
Addressing each issue all at once
To solve each of the above, we can make some simple changes to our PHP and Blade template.
Back in our PHP file, let’s update the constructor:
public function __construct(string $level = 'info', $message = [])
{
$this->level = $level;
$this->message = $message;
}
And in the component blade template:
<div {{ $attributes->merge([ 'class' => 'c-alert c-alert--' . $level ]) }} role="alert">
@if (is_array($message))
{{-- Loop through an array of messages --}}
<ul class="c-alert__message">
@foreach($message as $item)
<li>{{ $item }}</li>
@endforeach
</ul>
@else
{{-- Display string message --}}
<p class="c-alert__message">{{ $message }}</p>
@endif
{{-- Allow additional HTML --}}
{{ $slot }}
</div>
So now, we can do the following:
<x-alert />
And the $level
variable will resolve to a class name of c-alert--info
but we will now have an empty message. This now:
- Eliminates the BindingResolutionException problem when the properties are blank by using default values within the
__construct()
method $attributes->merge()
allows us to add new class declarations whilst persisting the existing declarations- The
@if
checks are used to determine an array or string$message
value {{ $slot }}
allows us inject custom HTML directly in to the template.
Here’s an example:
<x-alert message="Sign up now!" class="mb-4" id="MyAlert">
<a href="#" class="c-btn c-btn--secondary">Register</a>
</x-alert>
Will render:
<div id="MyAlert" role="alert" class="c-alert c-alert--info mb-4">
<p class="c-alert__message">Sign up now</p>
<a href="#" class="c-btn c-btn--secondary">Register</a>
</div>
Notice the extra mb-4
class, additional element properties and injected HTML?
Step 5: PHP variables as properties
Now we have a dynamic component, how do we pass PHP variables to it? Take for example, we have a session flash that returns an $alert
object with two properties — an array: message
and a string: level— you would declare it like so:
@if (session()->has('alert'))
<x-alert :message="$alert->message" :level="$alert->level" />
@endif
Notice the semicolons? This might be familiar to you is you use Vue. In fact, it’s the same principle: If you need to interpolate a PHP variable, you use :propertyName
instead of just propertyName
— cool, isn’t it?! 😉
Here’s a quick example you can drop right in:
@php
$alert = (object)[];
$alert->message = ['Hello', 'World'];
$alert->level = 'warning';
@endphp<x-alert :message="$alert->message" :level="$alert->level" />
Will render:
<div role="alert" class="c-alert c-alert--warning">
<ul class="c-alert__message">
<li>Hello</li>
<li>World</li>
</ul>
</div>
Bonus: Give it some style 😎
We’ll use SASS (SCSS) for styling up our component.
Create a new SCSS file and import it in to your base stylesheet. I use BEM methodology, and name my files consistently using namespaces, i.e.,
scss/components/_component.alert.scss
— Component
scss/styles.scss
— Base
Then you import it to your Base stylesheet using:
@import "components/component.alert";
Here’s the full declaration:
/*-----------------------------------------------------------------
* Alert
*-----------------------------------------------------------------
*
*-----------------------------------------------------------------
*
* A way of highlighting informative information by defining color
* codes, i.e., `danger` or `info` to differentiate them.
*
*-----------------------------------------------------------------
*
* [1] Remove margin from the content to keep spacing consistent.
* [2] Modifier declaration for informative messages.
* [3] Modifier declaration for successful messages.
* [4] Modifier declaration for critical messages.
* [5] Modifier declaration for warning messages.
* [6] Modifier declaration to align the message text center.
* [7] Support for placing icons inline with the message.
*
*---------------------------------------------------------------*/
//-----------------------------------------------------------------
// Defaults
// ----------------------------------------------------------------
$alert-namespace : 'c-' !default;
$alert-spacing : 24px !default;
$alert-spacing : $alert-spacing / 2 !default;
$alert--success : #3dee06 !default;
$alert--information : #0F35FF !default;
$alert--danger : #ef1e08 !default;
$alert--warning : #ffc01f !default;
//-----------------------------------------------------------------
// Declaration
// ----------------------------------------------------------------
.#{$alert-namespace}alert {
border : 1px solid lighten($alert--information, 10%);
background-color : $alert--information;
color : lighten($alert--information, 40%);
padding : $alert-spacing / 2;
/* [1] */
> p,
&__message {
display : inline-block;
margin : 0;
}
/* [2] */
&--info {
background-color : $alert--information;
color : lighten($alert--information, 40%);
border-color : lighten($alert--information, 10%);
}
/* [3] */
&--success {
background-color : $alert--success;
color : lighten($alert--success, 40%);
border-color : lighten($alert--success, 10%);
}
/* [4] */
&--danger {
background-color : $alert--danger;
color : lighten($alert--danger, 40%);
border-color : lighten($alert--danger, 10%);
}
/* [5] */
&--warning {
background-color : $alert--warning;
color : lighten($alert--warning, 40%);
border-color : lighten($alert--warning, 10%);
}
/* [6] */
&--center {
text-align: center;
}
/* [7] */
&__icon {
padding-right: $alert-spacing / 3;
}
}
You can now fully customize your component by overriding the !default
values above your @import
statement, for example:
$alert-namespace : 'devjmd-';
$alert-spacing : 12px;@import "components/component.alert";
This would now generate:
.devjmd-alert {
padding: 6px;
...
}
.devjmd-alert--info {
...
}
… and so forth.
Final Words
There’s so much more to Laravel’s component layer. In fact, this very guide is based on the documentation version, except with a bit more control over the type of data you want to show.
I highly recommend reading their official documentation on Blade Components: https://laravel.com/docs/8.x/blade#components — it’s worth the investment if you’re managing a large project.
On another side note, you can use these components alongside Vue.js, but be warned, this could get complicated in the long run. Make sure you’re using Blade Components wisely, and only where necessary where duplication becomes obvious. If you’re worried about Server-side Rendering, then Blade Components are absolutely the way to go as they’re rendered on the back-end anyway. Maybe this is a valid reason to use the two in conjunction.
The way to use Blade Components along side Vue, is by excluding the x-
tag from Vue’s tag collection — that I will leave up to you to figure out! 😉
Enjoy! 😁