-
to maintain a clear separation between HTML and JavaScript logic. This separation keeps presentation distinct from logic as much as possible. With Alpine.js, we now get efficient two-way data bindings and reactivity using even less code.
-
to have some kind of central registry of HTML templates and Javascript classes and use it to register components.
The registry is just 2 global Javascript objects, templates
and components
.
How the registry is delivered to the browser depends on bundling or application, for example:
- bundle everything into a single file by converting HTML files to strings: see
examples/build.js
for a simpleesbuild
plugin - keep HTML templates in JSON files to load separately via fetch on demand
- maintain HTML files on the server to load individually on demand similar to
htmx
-
Components: These are the primary building blocks of the UI. They can be either standalone HTML templates or HTML backed by JavaScript logic, capable of nesting other components.
-
Main Component: Displays the current main page within the
#app-main
HTML element. It manages the navigation and saves the view to the browser history usingapp.savePath
. -
History Navigation: Supports browser back/forward navigation by rendering the appropriate component on window
popstate
events, achieved usingapp.restorePath
. -
Direct Deep-Linking: For direct access, server-side routes must redirect to the main app HTML page, with the base path set as ‘/app/’ by default.
npm install @vseryakov/alpinejs-app
” dir=”auto”>
<script src="https://unpkg.com/alpinejs-app@1.x.x/dist/app.min.js">script> <script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer>script>
Here’s a simple hello world example.
Live demo is available at demo.
This is the index component
This is the component
Param:
Reason:
” dir=”auto”>
<head> <script src="bundle.js">script> head> <body> <div id="app-main">div> body> <template id="index"> <h5>This is the index componenth5> <button x-render="'hello/hi?reason=World'">Say Hellobutton> template> <template id="hello"> <h5>This is the <span x-text=$name>span> componenth5> Param: <span x-text="params.param1">span><br> Reason: <span x-text="params.reason">span><br> <div x-template="template">div> <button @click="toggle">Togglebutton> <button x-render="'index'">Backbutton> template>
import '../dist/app.js' import './hello' import './dropdown' import "./dropdown.html" import "./example.html" app.debug = 1 app.start();
app.components.hello = class extends app.AlpineComponent { template = "" toggle() { this.template = !this.template ? "example" : ""; } }
Explanation:
- The script defines a template and a component,
app.start
callsrestorePath
when the page is ready, defaulting to renderindex
since the static path doesn???t match. (running locally with file:// origin will not replace history) - The body includes a placeholder for the main app.
- The
index
template is the default starting page, with a button to display thehello
component with parameters. - Clicking ‘Say Hello’ switches the display to the
hello
component viax-render
directive. - The
hello
component is a class extendingapp.AlpineComponent
with a toggle function. - A
x-template
directive remains empty until thetemplate
variable is populated by clicking the ‘Show’ button, which triggers the component’s toggle method to render theexample
template in the contained div. - The
example
template is defined in theexamples/example.html
file and bundled into bundle.js.
Nothing much, all the work is done by Alpinejs actually.
Binds to click events to display components. Can set components via a syntax supporting names, paths, or URLs with parameters through parsePath
.
Special options include:
$target
– to define a specific container for rendering.$history
– to explicitly manage browser history.
” dir=”auto”>
<a x-render="'hello/hi?reason=World'">Say Helloa> <button x-render="'index?$target=#div'">Showbutton>
Render a template or component inside the container from the expression which must return a template name or nothing to clear the container.
This can be an alternative to the x-if
Alpine directive especially with multiple top elements because x-if only support one top element.
” dir=”auto”>
<div x-template="template">div> <div x-template="show ? 'index' : ''">div>
Reduce data scope depth for the given element, it basically cuts off data inheritance at the requested depth.
Useful for sub-components not to interfere with parent’s properties. In most cases declaring local properties would work but
limiting scope for children might as well be useful.
$app
object is an alias to the global app object to be called directly in the Alpine.js directives.
Render Magic” dir=”auto”>
<a @click="$app.render('page/1/2?$target=#section')">Render Magica> <a @click="$app.render({ name: 'page', params: { $target: '#section' }})">Render Magica>
While Alpinejs has several ways how to reuse the data this app makes it more unified, this is opinionated of course.
Here is the life-cycle of a component:
-
on creation a component calls
onCreate
method if exists. it can be created in derived class to implement custom initialization logic and create properties.
At this time the context is already initialized, theparams
property is set with parameters passed in the render call and event handler forcomponent:event
is registered on the app. -
The
component:create
event is broadcasted with an object { name, element, params, component } -
when a component is removed from the DOM
onDelete
method is called to cleanup resources like event handlers, timers… -
app event
component:event
is sent to all live components, this can be used to broadcast important events happening and
each component will decide what to do with it. This is uses app event emitter instead of DOM events to keep it separate and not overload
browser with app specific messages.
Add a new button to the index template:
<button x-render="'hello2'">Say Hello2button>
Introduce another component:
}
onDelete() {
clearInterval(this._timer)
}
onToggle(data) {
console.log(“received toggle event:”, data)
}
toggle() {
super.toggle();
app.emit(app.event, “toggle”, this.template)
}
}” dir=”auto”>
app.templates.hello2 = "#hello" app.components.hello2 = class extends app.components.hello { onCreate() { this.params.reason = "Hello2 World" this._timer = setInterval(() => { this.params.param1 = Date() }, 1000); } onDelete() { clearInterval(this._timer) } onToggle(data) { console.log("received toggle event:", data) } toggle() { super.toggle(); app.emit(app.event, "toggle", this.template) } }
Key additions include:
- A
hello2
template referencing existinghello
for shared markup. - A new
hello2
component extending fromhello
, showcasing class inheritance.
The hello2
component utilizes lifecycle methods:
onCreate
sets up initialization like overriding reasons and running a timer.onDelete
manages cleanup by stopping timers.toggle
method reuses the toggling but adds broadcasting changes via events.
For complete interaction, access live demo at the index.html.
Component classes are registered as Custom Elements with app-
prefix,
using the example above hello component can be placed inside HTML as
.
See also how the dropdown component is implemented.
The examples/ folder contains more components to play around and a bundle.sh script to show a simple way of bundling components together.
An example to show very simple way to bundle .html and .js files into a single file.
It comes with pre-created bundle but to rebuild:
- run
npm run demo
- it will generate examples/bundle.js file that includes all HTML and Javascript code
- load it in the browser:
open examples/index.html
The examples/build.js
script is an esbuild
plugin that bundles templates from .html files to be used by the app.
Running node build.js
in the examples folder will