Chimezie Enyinnaya I'm a self-taught software developer based in Lagos, Nigeria. I enjoy teaching what I have learned and what I'm currently learning so that others can benefit from it.

Intro to Alpine.js for Vue developers

6 min read 1749

Intro to Alpine.js for Vue Developers

Alpine is a relatively new JavaScript framework that borrows concepts from other frameworks such as Vue and React. Compared to these frameworks, Alpine is small and lightweight.

In this guide, we’ll zoom in on Alpine.js and Vue and demonstrate how to duplicate Vue tasks and features in Alpine. But this won’t be a typical X-framework-is-better-than-Y-framework post; both are unique in their own right and the more appropriate framework often depends on the use case.

What is Alpine?

Before we dive in, let’s take a moment to understand the philosophy behind Alpine. As its tagline states, Alpine is “a rugged, minimal framework for composing JavaScript behavior in your markup.” These behaviors are added as attributes using directives.

Alpine syntax is very much inspired by Vue. If you’re coming from a Vue background, you’ll most likely feel at home working with Alpine. Like Vue, Alpine uses a x- prefix on all its directives. Unlike Vue, Alpine doesn’t use a virtual DOM, so we get to work with the actual DOM directly.

Initializing a component

In Alpine, the x-data directive is used to declare the scope and data of a component. It works as a combination of Vue’s el and data properties. It tells the framework to initialize a new component with the defined data object.

// in Vue

<div id="app"></div>

new Vue({
  el: '#app',
  data: {
    posts: []
  }

  // or in the case of a component
  data() {
    return {
      posts: []
    }
  }
})


// in Alpine

<div x-data="{ posts: [] }"></div>

Instead of declaring the data inline, you can extract it into a reusable function.

// in Alpine

<div x-data="posts()"></div>

<script>
  function posts() {
    return {
      posts: []
    }
  }
</script>

Hooking to component initialization stages

Unlike Vue, Alpine doesn’t provide any explicit lifecycle hooks. However, you can achieve two of Vue’s lifecycle hooks — created and mounted — in Alpine using the x-init directive.

The x-init directive is used to execute some JavaScript when a component is initialized. This can range from simple expressions to complex functions. Think of it as created hooks  for Vue.

// in Vue

<script>
  export default {
    name: 'App',
    data() {
      return {
        posts: []
      };
    },
    created() {
      fetch("https://jsonplaceholder.typicode.com/posts")
        .then(response => response.json())
        .then(data => (this.posts = data))
    }
  }
</script>


// in Alpine

<div
  x-data="{ posts: [] }"
  x-init="
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(data => (posts = data));
  "
>
  <!-- `posts` will be array of a posts returned from the API -->
</div>

To achieve Vue’s mounted hook, you can return a callback from x-init, which will be run after Alpine has made its initial updates to the DOM.

// in Vue

<script>
  export default {
    name: 'App',
    data() {
      return {
        posts: []
      };
    },
    mounted() {
      fetch("https://jsonplaceholder.typicode.com/posts")
        .then(response => response.json())
        .then(data => (this.posts = data))
    }
  }
</script>


// in Alpine

<div
  x-data="{ posts: [] }"
  x-init="() => {
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(data => (posts = data));
  }
  "
></div>

Interpolation

Unlike Vue, Alpine doesn’t use a mustache-like syntax for interpolation. Instead, it uses the x-text and x-html directives for interpolation, which work the same way as their Vue counterparts.

// in Vue

<template>
  <div>
    <h3>{{ post.title }}</h3>

    <h3 v-text="post.title"></h3>

    <h3 v-html="post.title"></h3>
  </div>
</template>

<script>
  export default {
    name: 'App',
    data() {
      return {
        post: {
          title: "My first post"
        }
      }
    }
  }
</script>


// in Alpine

<div x-data="{ post: { title: 'My first post' } }">
  <h3 x-text="post.title"></h3>
</div>

<div x-data="{ post: { title: 'My first post' } }">
  <h3 x-html="post.title"></h3>
</div>

Event handling

Just like Vue, Alpine makes handling mouse and keyboard events seamless by providing a x-on directive. This directive attaches an event listener to an element and executes some JavaScript when the event is triggered.



// in Vue

<template>
  <div>
    <button @click="deletePost(post)">Delete</button>
  </div>
</template>
<script>
export default {
  name: 'App',
  data() {
    return {
      post: {
        title: "This is my first post"
      }
    };
  },
  methods: {
    deletePost(post) {
      confirm(`Want to delete: ${this.post.title}?`)
    }
  }
};
</script>


// in Alpine

// inline
<div x-data="{ post: { title: 'My first post' } }">
  <button x-on:click="confirm(`Want to delete: ${post.title}?`)">
    Delete
  </button>
</div>

// dedicated function
<div x-data="{ post: { title: 'My first post' } }">
  <button x-on:click="deletePost(post)">
    Delete
  </button>
</div>

<script>
  function deletePost(post) {
    confirm(`Want to delete: ${post.title}?`)
  }
</script>

If you’d rather use the shorthand form (@), the above would be rewritten as:

// in Alpine

<div x-data="{ post: { title: 'My first post' } }">
  <button @click="confirm(`Want to delete: ${post.title}?`)">
    Delete
  </button>
</div>

Just like in Vue, you can access the DOM event using the $event property.

Note: The $event property is only available in DOM expressions. When using a function, you’ll have to pass as an argument to the function: <button @click="deletePost(post, $event)"></button>.

Alpine also provides a handful of event modifiers, including keydown, away, prevent, passive, stop, debounce, self, once, window, etc.

// in Alpine

<a href="http://example.com" @click.prevent>Example.com</a>

Two-way data binding

When working with forms, Alpine provides two-way data binding, which keeps the value of an input in sync with the component data.

// in Vue

<template>
  <div>
    <h3>{{ title }}</h3>

    <input type="text" v-model="title">
  </div>
</template>

<script>
  export default {
    name: 'App',
    data() {
      return {
        title: ''
      }
    }
  }
</script>


// in Alpine

<div x-data="{ title: '' }">
  <h3 x-text="title"></h3>

  <input type="text" x-model="title" />
</div>

Iterating over arrays

To iterate over an array, you can use the x-for directive, which works like Vue’s v-for.

// in Vue

<ul>
  <li v-for="post in posts" :key="post.id">
    {{ post.title }}
  </li>
</ul>


// in Alpine

<ul>
  <template x-for="post in posts" :key="post.id">
    <li x-text="post.title"></li>
  </template>
</ul>

Unlike, however, v-for, x-for has some some caveats:

  • x-for has to be on a template tag instead of a regular DOM element.
  • The <template> tag must have a single element root inside of it.

Binding attributes

Alpine supports attributes binding using the x-bind directive. The x-bind directive enables you to bind an HTML attribute to a JavaScript expression. The directive works the same way as v-bind.

// in Vue

<template>
  <div>
    <h3>{{ post.title }}</h3>

    <img
      v-bind:src="post.featuredImage"
      v-bind:alt="post.title"
      v-bind:class="{ hidden: isOnMobile }"
    >

    <button v-bind:disabled="post.isPublished">Publish</button>
  </div>
</template>

<script>
  export default {
    name: 'App',
    data() {
      return {
        post: {
          title: "My first post",
          featuredImage: "https://dummyimage.com/600x400",
          isPublished: false
        },
        isOnMobile: true
      }
    }
  }
</script>

<style>
  .hidden {
    display: none
  }
</style>


// in Alpine

// within the head tag
<style>
  .hidden {
    display: none;
  }
</style>

<div
  x-data="{
    post: {
      title: 'My first post',
      featuredImage: 'https://dummyimage.com/600x400',
      isPublished: false
    },
    isOnMobile: true,
  }"
>
  <h3 x-text="post.title"></h3>

  <img
    x-bind:src="post.featuredImage"
    x-bind:alt="post.title"
    x-bind:class="{ hidden: isOnMobile }"
  />

  <button x-bind:disabled="post.isPublished">Publish</button>
</div>

As with events handling, you can also use the shorthand form (:).

// in Alpine

<img
  :src="post.featuredImage"
  :alt="post.title"
  :class="{ hidden: isOnMobile }"
/>

<button :disabled="post.isPublished">Publish</button>

Toggling visibilities

Alpine provides two directives for toggling visibilities — x-if and x-show — which work just like their Vue counterparts.

// in Vue

<template>
  <div>
    <button @click="show = !show" v-text="show ? 'Close' : 'Open'"></button>

    <a href="#" v-if="isAdmin">Edit Post</a>
  </div>
</template>

<script>
  data() {
    return {
      show: false,
      isAdmin: false
    };
  }
</script>


// in Alpine

<div x-data="{ show: false, isAdmin: false  }">
  <button @click="show = !show" x-text="show ? 'Close' : 'Open'"></button>

  <template x-if="isAdmin">
    <a href="#">Edit Post</a>
  </template>
</div>

Like x-for, x-if has some caveats:

  • x-if has to be on a template tag instead of a regular DOM element.
  • The <template> tag must have a single element root inside of it.

Watching

Alpine also support watching for when a component’s property changes through a method called $watch().

// in Vue

<template>
  <div>
    <p>
      <label>Dollar:</label>
      <input type="text" v-model="dollar">
    </p>
    <p>
      <label>Naira:</label>
      <input type="text" v-model="naira">
    </p>
  </div>
</template>

<script>
  export default {
    name: 'App',
    data() {
      return {
        dollar: 0,
        naira: 0
      };
    },
    watch: {
      dollar(value) {
        this.naira = value * 390
      },
      naira(value) {
        this.dollar = value / 390
      }
    }
  }
</script>


// in Alpine

<div x-data="watcher()" x-init="initWatcher">
  <p>
    <label>Dollar: </label>
    <input type="text" x-model="dollar" />
  </p>
  <p>
    <label>Naira: </label>
    <input type="text" x-model="naira" />
  </p>
</div>
<script>
  function watcher() {
    return {
      dollar: 0,
      naira: 0,
      initWatcher() {
        this.$watch("dollar", value => {
          this.naira = value * 390;
        });
        this.$watch("naira", value => {
          this.dollar = value / 390;
        });
      }
    };
  }
</script>

The $watch() accepts two arguments: the property to watch and a callback that will be run whenever the property changes. The callback accepts the new value of the property.

Conclusion

As you can see, Alpine is similar to Vue in myriad ways. In some cases, in fact, you can take Vue component and simply swap the v- prefixes with x- to create an Alpine component.

To learn more about Alpine, check out the GitHub repo.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Chimezie Enyinnaya I'm a self-taught software developer based in Lagos, Nigeria. I enjoy teaching what I have learned and what I'm currently learning so that others can benefit from it.

One Reply to “Intro to Alpine.js for Vue developers”

Leave a Reply