Embracing the love between Livewire and Alpine

Embracing the love between Livewire and Alpine


Since the creation of Livewire, my applications have taken on more functionality using less code all without the need to write much Javascript.

It is totally possible to create a fully fledged application using Livewire without Javascript, however there is no reason to not include Javascript in your Livewire applications just because you don't know how to get them to play nice together.

Luckily Alpine.js was created by Caleb Porzio, who also created Livewire, and over time both frameworks have been molded to include niceties to help you have them interact with each other.

This article will be ever evolving as I learn new ways to achieve all of these goals, but these are the tools & methodologies I currently employ to interact & sync data between Livewire and Javascript.

Note: These are in no particular order, but does assume a working knowledge of both Livewire and Alpine (and Javascript in general).

What do they all have in common?

These are methodologies I usually employ on most of my components (Not every component gets all depending on its needs):

Working with data

These are methodologies I usually employ to move or sync data between Livewire and Alpine:

Extracting to blade components to take advantage of $attributes

One of the best things you can do when working with all of these technologies is to extract them out to bite sized pieces called components.

Laravel has components built in, that give you added functionality out of the box just by putting them in a specific folder.

I'll be working with anonymous components in this example.

As an example, we are going to create a datepicker component by creating views/components/input/datepicker.blade.php.

By default, any blade view in views/components gets registerd as a blade component in Laravel.

You can access blade components using the x- syntax, and you can access nested blade components using dot notation. So to register our component in the DOM we would use:

<x-input.datepicker />

Our component is auto-closing because we do not have the need for a slot in this case, as well as our component will take no arguments. You can read all about this in the Laravel Blade Component documentation.

So what goes in the component markup and how do we utilize the $attributes property?

The $attributes property will hold any key-value pairs we add to our component from the DOM so that we can utilize them inside our component.

One trick I like to do is single out the wire:model I'm going to use and let the rest of the attributes apply to the main element:

<div
    wire:ignore
    x-data="{value: @entangle($attributes->whereStartsWith('wire:model')->first())}"
>
    <input
        {{ $attributes->whereDoesntStartWith('wire:model') }}    
        type="text"
        ...
    />
</div>
<x-input.datepicker
    wire:model="published_at"
/>

Note that for wire:model I'm using whereStartsWith, so that it will also include wire:model.defer or any other modifiers.

These are the methods you can use to place your attributes from the outside where they need to go:

merge: Merge the value of the attribute with defaults
filter: Filter by the vale and key and return true if you want the attribute in the bag
whereStartsWith: Retrieve all attributes whose keys begin with a given string
whereDoesntStartWith: Exclude all attributes whose keys begin with a given string
has: Check if an attribute is present on the component
get: Retrieve a specific attribute's value

Using @push to add scripts to a main stack from within a component

If I need my component to push scripts to the main view, I use a stack defined in the layout I'm extending:

After the Livewire or Alpine component's main element:

@push('styles')
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" />
@endpush

@push('scripts')
    <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
@endpush

In my layout in the appropriate areas:

@stack('styles')
@stack('scripts')

Using @once to add a script call once if the component is going to be used multiple times on one page.

If I'm extracting out a blade component that may be used multiple times on one page (i.e. a datepicker, wysiwyg, file upload) I use the @once directive to make sure that the contents of that directive are only included once per page load regardless of how many times the component is included:

@push('styles')
    @once
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" />
    @endonce
@endpush

@push('scripts')
    @once
        <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
    @endonce
@endpush

Using wire:ignore to avoid Livewire updating certain areas of the DOM

One of the biggest issues with working with Javascript components inside Livewire components is that they will render correctly on the first page load because the browser is interpreting the Javascript on the page, but if Livewire does a DOM-diff on that component when it is refreshing part of the UI then you will lose the Javascript functionality.

You can test this easily by loading a WYSIWYG editor and then changing a part of the components state. You will see the UI refresh, and the WYSIWYG editor turn into a normal HTML5 textarea.

Obviously this can be rectified in many ways which will be explained below, but generally the component is wrapped with a wire:ignore, which will tell Livewire to ignore this portion of the page when DOM-diffing. You then have to figure out how to sync your data with the component based on factors such as custom Javascript events, native Javascript events, Alpine property changes, etc.

<div wire:ignore>
    <trix-editor></trix-editor>
</div>

Using @entangle to sync data between Livewire and Alpine

The best tool available to use for syncing data between Livewire and Alpine is going to be @entangle which watches the values and change them on both sides.

@entangle is a directive that amounts to:

window.Livewire.find('COMPONENT_ID').entangle('COMPONENT_PROPERTY')

So if the value changes from your Javascript, it's updated on the actual Livewire component as well as the other way around.

<div 
    x-data="{value: @entangle($attributes->whereStartsWith('wire:model')->first())}"
>
    <input type="text" x-model.debounce.500ms="value" />
</div>

In the above example when the component is loaded, the Alpine property value will be set to the Livewire property that was set to wire:model. The x-model on the input will then mirror that value, and 500ms after it has been changed it will be sync'd back to the Alpine propery which will trigger @entangle to update the Livewire property that was set to wire:model.

Using $wire to manipulate Livewire from within an Alpine component

As per the Livewire documentation:

From any Alpine component inside a Livewire component, you can access a magic $wire object to access and manipulate the Livewire component.

If for some reason you can't use @entangle, you can always trigger call a method on the Livewire component from Alpine:

@props(['initial' => ''])

<div
    x-data="{ trix: '{{ $initial }}' }"
    @trix-blur="$wire.updateContent($refs.trix.value)"
>
    <div wire:ignore>
        <trix-editor
            x-ref="trix"
            x-model.debounce.1000ms="trix"
        ></trix-editor>
    </div>
</div>

The above example is a Trix editor, when the user blurs away from the element, Alpine will catch the event fired by the Trix Javascript and use $wire to call a method on the Livewire component to update the content.

Using @this to access the Livewire component in Javascript

Sometimes I may be using an old jQuery plugin, and it's just easier to do things outside of an Alpine component right in the pages Javascript stack, in this case Livewire gives us the @this blade directive. @this is pretty much the same thing as $wire is inside an Alpine component, just global to the component (I think).

@this compiles to window.Livewire.find('COMPONENT_ID')

$('.select2').on('change', function () {
    @this.syncData($(this).select2("val"));
});

In the above arbitrary example we listen for a jQuery select2 plugin to update and pass the current values to the component's syncData method.

Using $watch to listen for an Alpine property to change

You can use $watch in Alpine to perform an action when an Alpine property changes. The uses are only limited to your imagination.

<div
    x-data="{ trix: '{{ $value }}' }"
    x-init="$watch('trix', function (value) {
        $refs.trix.editor.loadHTML(value);
        var length = $refs.trix.editor.getDocument().toString().length;
        $refs.trix.editor.setSelectedRange(length - 1);
    })"
>

Using the Trix editor example from above, we can watch when the value of the editor changes. When Livewire reloads the DOM, the cursor gets placed at the beginning of the content again (which is annoying when auto-saving or syncing), by watching the value change we can place the cursor back at the end of the content so the user can continue from where they left off.

Using Javascript events to listen and modify data to or from a component.

Events are another resource available to you that have unlimited uses. Some examples would be:

You can listen for events that the component is listening to:

<script>
Livewire.on('postAdded', postId => {
    alert('A post was added with the id of: ' + postId);
})
</script>

You can fire a browser event from the backend component:

$this->dispatchBrowserEvent('name-updated', ['newName' => $value]);

You can then catch that event from the component's Javascript:

<script>
window.addEventListener('name-updated', event => {
    alert('Name updated to: ' + event.detail.newName);
})
</script>

You can catch the event on an Alpine component:

<div x-data="{ open: false }" @name-updated.window="open = false">
    <!-- Modal with a Livewire name update form -->
</div>

These examples are straight from the Livewire event documentation.

You can also dispatch events right from Alpine to do with as you please:

<div @custom-event="console.log($event.detail.foo)">
    <button @click="$dispatch('custom-event', { foo: 'bar' })">
    <!-- When clicked, will console.log "bar" -->
</div>

Using x-model to sync properties in Alpine

It's good to use the x-model directive in Alpine to add two-way data binding to an element. Combining this with $watch or other directives gives you all the tools you need to get you data where it needs to be.

From an example above:

<div 
    x-data="{value: ''}"
>
    <input type="text" x-model.debounce.500ms="value" />
</div>

We can see that the value property of the Alpine component will be updated 500ms after the user finishes typing in the text field.


There is a good possibility that I have some of these concepts wrong, or that I'm using them wrong as I am constantly learning and sharing my thoughts.

There is also the possibility that I missed many other ways of achieving the same goals listed in this article.

Please let me know if I have something wrong or missed something, and I will add or modify it.

As always I hope you enjoyed this article, and I hope you learned something.

Anthony Rappa

By Anthony Rappa

Hello! I'm a full stack developer from Long Island, New York. Working mainly with Laravel, Tailwind, Livewire, and Alpine.js (TALL Stack). I share everything I know about these tools and more, as well as any useful resources I find from the community. You can find me on GitHub and LinkedIn.