Cross-component Communication Patterns in AlpineJs
Tags: javascript, html, webdev, tutorial
Published: 2021-03-23 19:44:24

One of the most frequent requirements when writing AlpineJs components is the communication between them. There are various strategies for how to tackle this problem. This article describes the four most common patterns that help pass information between different Alpinejs components. 

As a simple example of the presenting patterns, we will create a [snackbar](https://material.io/components/snackbars) component from the material design guidelines. Snackbars are concise, informative messages about some event or the output of a process. 

Let's create the snackbar HTML structure and a button that initiates the message. 

```html
<body>
    <h1>Material Snackbar Example using Alpine.Js</h1>
    <button class="button">Show Snackbar</button>
    <h2>Click on the above button to see snackbar message</h2>
    <div class="alpine-snackbar-wrapper">
        <div class="alpine-snackbar-content">Sample Text</div>
    </div>
</body>
```

Next, we add the styles to mimic the material designs.

```css
 body {
        font-family: 'Roboto', sans-serif;
}

.alpine-snackbar-wrapper {
    min-width: 344px;
    max-width: 672px;
    min-height: 48px;
    background-color: #2196F3;
    color: #fff;
    text-align: center;
    margin: auto 8px;
    display: flex;
    align-items: center;
    padding: 0;
    border-radius: 4px;
    position: fixed;
    right: 1%;
    z-index: 1;
    bottom: 30px;
    box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .2), 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12);
}

.alpine-snackbar-content {
    flex-grow: 1;
    font-size: 0.875em;
    font-weight: 400;
    padding: 14px 16px;
}

.button {
    border: none;
    padding: 14px 16px;
    border-radius: 4px;
    font-size: 1em;
    background-color: #2196F3;
    color: white;
    cursor: pointer;
}
```

The end result is:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvvrpnm7nuhp090de749.png)

The main goal is to click the button and display the snackbar for a specific time with a custom message. Let's start describing the strategies to achieve it using the AlpineJs framework. 

## Custom Javascript Event

One apparent approach is to use the javascript standard of [dispatching and consuming javascript events](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events) on the `window` object.  

This approach's main advantage is the framework-agnostic aspect that may communicate with your AlpineJs components from everywhere in your app. Following the custom events documentation, when the button is clicked, a **CustomEvent** is created and then dispatched carrying the custom message.

You may check out the source and the result:

{% codepen https://codepen.io/wittyprogramming/pen/poNoNKa %}

The HTML button code is:

```html
<button onclick="showAlpineSnackbar()" class="button">Show Snackbar</button>
```

Then, we write the `showAlpineSnackbar()` javascript function in which the **CustomEvent** is created and dispatched:

```javascript
function showAlpineSnackbar() {
    let event = new CustomEvent("alpine-snackbar-showMessage", {
        detail: {
            message: "Hello from outside of the component"
        }
    });
    window.dispatchEvent(event);
}
```

We created a **CustomEvent** object that defined the event name that we want to dispatch (`alpine-snackbar-showMessage`) and the information that the event carries (custom message). 

The next step involves the creation of the AlpineJs snackbar component.

```javascript
function alpineSnackbar() {
    return {
        show: false,
        message: null,
        init() {
            window.addEventListener("alpine-snackbar-showMessage", (event) => {
                this.showMessage(event.detail.message);
            });
        },
        showMessage(msg) {
            this.message = msg;
            this.show = true;
            window.setTimeout(() => {
                this.show = false;
                this.message = null;
            }, 2000);
        }
    }
}
```

 We defined a component scope with two reactive variables:
 
- `show` that sets the snackbar visibility and
- `message` that defines the displaying custom message

and two functions:

- `init()` and
- `showMessage(msg)`

In the `init()` function executed during the component initialization stage, we create the listener for the custom event. When the event fires, we call the `showMessage` function with the custom message as an argument, taken from the `detail` object.

The `showMessage` function initially sets the custom message and displays the snackbar by setting the `show` variable as `true`. Then, we place a timeout function that runs after two seconds and resets the snackbar's state and hides it. 

We chose to extract data and behavior into a function, which gives us breathing room for coding. The final step involves wiring up our component to the HTML: 

```html
<div x-data="alpineSnackbar()" x-init="init()" x-show.transition="show" class="alpine-snackbar-wrapper">
        <div class="alpine-snackbar-content" x-text="message"></div>
    </div>
```

## The `dispatch` magic attribute

Another similar approach to custom events is by using the AlpineJs native `$dispatch` magic attribute. A magic attribute in AlpineJs is a user-defined attribute that implements a helpful operation. There many magic helpers [native in AlpineJs](https://github.com/alpinejs/alpine#magic-properties) or by [importing additional packages](https://github.com/alpine-collective/alpine-magic-helpers). One of the native magic attributes is the `$dispatch`, a shortcut for creating a javascript custom event internally and fire up with a  **dispatchEvent**.

The full snackbar example using the `$dispatch` magic helper is:

{% codepen https://codepen.io/wittyprogramming/pen/YzpzgBQ %}

Therefore, by using the `$dispatch` magic attribute, we drop the function that creates the **CustomEvent**:

```html
<button x-data @click="$dispatch('alpine-snackbar-show-message', {
        message: 'Hello from outside of the component'
    })" class="button">Show Snackbar</button>
```

Please note that we utilized the `x-data` to create an Alpinejs component to use the magic attribute. The  `$dispatch` syntax is straightforward. The first argument corresponds to the **CustomEvent** name, and the second is the payload that attaches to the event. 

The snackbar AlpineJs component that consumes the `$dispatch` event is transformed: 

```html
 <div x-data="alpineSnackbar()" @alpine-snackbar-show-message.window="showMessage($event.detail.message)"
    x-show.transition="show" class="alpine-snackbar-wrapper">
    <div class="alpine-snackbar-content" x-text="message"></div>
</div>
```

The previously written `init()` function, executed during the initialization of the AlpineJs component on `x-init` attribute, is replaced with the equivalent [x-on](https://github.com/alpinejs/alpine#x-on) attribute (or its shorthand syntax `@`). This syntax attaches an event listener to the element it is declared. Therefore, we used the **CustomEvent** name to catch the event and execute the `showMessage(msg)` function with the corresponding custom message accessed by the `$event` object. 

You should consider the way that event propagates when you are using the `$dispatch`. If you need to capture events from elements not under the same node, you need to use the `.window` modifier. This modifier installs the listener on the global window object instead of the DOM node on which it is declared. 

## The `component` magic helper

Another approach to cross-component communication is using the `$component` from [AlpineJs Magic Helpers](https://github.com/alpine-collective/alpine-magic-helpers), a collection of magic properties and helper functions. The installation is simple enough, add the following `script` tag **before** the AlpineJs tag.

```html
<script src="https://cdn.jsdelivr.net/gh/alpine-collective/alpine-magic-helpers@0.5.x/dist/component.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js"></script>
```

The `$component` gives access to other AlpineJs components. To achieve this, you provide a unique id for the component you want to access using the `x-id` attribute.

```html
<div x-id="alpine-snack-bar"
     x-data="{ show : false, message: null }"
     x-show="show"
     class="alpine-snackbar-wrapper">
<div class="alpine-snackbar-content"
     x-text="message"></div>
</div>
```

Initially, we gave our snackbar component an id (`alpine-snack-bar`) and defined the two variables that we need. The `show` and `message` in `x-data` control the snackbar component's visibility and content, respectively.

Then, in the button we write a function inside the `buttonShowSnackbar` component that display the snackbar with the appropriate message and hides after two seconds. The HTML code is:

```html
<button x-data="buttonShowSnackbar()"
        @click="showAlpineSnackbar('alpine-snack-bar')"
        class="button">Show
        Snackbar</button>
```

The `buttonShowSnackbar` component:

```javascript
function buttonShowSnackbar() {
    return {
        showAlpineSnackbar(id) {
            this.$component(id).message = "Hello from another component using the $component";
            this.$component(id).show = true;
            setTimeout(() => {
                this.$component(id).show = false;
                this.$component(id).message = null;
            }, 2000)
        }
    }
}
```

We need to define the `showAlpineSnackbar` function inside an AlpineJs component to access the `$component` magic helper.

You can check the full example:

{% codepen https://codepen.io/wittyprogramming/pen/ZEBENdj %}

## Having  a global state by using the `spruce` library

Finally, another cross-communication pattern between AlpineJs components is by using a global state. For AlpineJs, there is [spruce](https://github.com/ryangjchandler/spruce), an excellent global state management library from [Ryan Chandler](https://ryangjchandler.co.uk).

The installation is more or less the same. Add the `spruce` library `script` tag **before** the AlpineJs tag.

```html
<script src="https://cdn.jsdelivr.net/npm/@ryangjchandler/spruce@2.x.x/dist/spruce.umd.js"
        defer></script>
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js"
        defer></script>
```

The approach is very similar to the `$component` pattern. The only difference is that we manipulate the global state instead of a specific component state. Initially, we initialize our global state:

```javascript
Spruce.store("snackbar", {
    message: null,
    show: false
});
```

We create a store called **snackbar** with two state variables, the **message** to be shown and the **show** that controls the snackbar visibility.

The HTML part of the `buttonShowAlpineSnackbar()` component is exactly the same as before: 
```html
<button x-data="buttonShowAlpineSnackbar()" @click="showMessage('A message from spruce')" class="button">Show Snackbar</button>
```

The difference is that the `showMessage(msg)` function manipulates the **snackbar** store state:

```javascript
function buttonShowAlpineSnackbar() {
    return {
        showMessage(msg) {
            this.$store.snackbar.message = msg;
            this.$store.snackbar.show = true;
            window.setTimeout(() => {
                this.$store.snackbar.show = false;
                this.$store.snackbar.message = null;
            }, 2000);
        }
    }
}
```

The snackbar component is similar to the previous example, except that the **snackbar** store state controls the global state's visibility and message content. 

```html
<div x-data x-show.transition="$store.snackbar.show" class="alpine-snackbar-wrapper">
  <div class="alpine-snackbar-content" x-text="$store.snackbar.message"></div>
</div>
```

You can check the full example:

{% codepen https://codepen.io/wittyprogramming/pen/zYoxKMw %}

## Conclusions

I presented four different approaches for cross-component communication. Although you can achieve the same outcome with every one of the above patterns, I think each one is better under a specific requirement. For example, the custom event javascript pattern is best suited for passing information between AlpineJs components and vanilla javascript code (or maybe another framework). Otherwise, the `$dispatch` magic attribute is more appropriate as it reduces the `CustomEvent`'s boilerplate code. When you want to manipulate a specific component, the `$component` magic helper is most suitable as it gives you direct access to it. Finally, the `spruce` library is better suitable for apps that need global state.