LogoLogo
HomeDiscourseBlogDiscord
  • Introduction
  • Introduction
    • Quick start
    • Aurelia for new developers
    • Hello world
      • Creating your first app
      • Your first component - part 1: the view model
      • Your first component - part 2: the view
      • Running our app
      • Next steps
  • Templates
    • Template Syntax
      • Attribute binding
      • Event binding
      • Text interpolation
      • Template promises
      • Template references
      • Template variables
      • Globals
    • Custom attributes
    • Value converters (pipes)
    • Binding behaviors
    • Form Inputs
    • CSS classes and styling
    • Conditional Rendering
    • List Rendering
    • Lambda Expressions
    • Local templates (inline templates)
    • SVG
  • Components
    • Component basics
    • Component lifecycles
    • Bindable properties
    • Styling components
    • Slotted content
    • Scope and context
    • CustomElement API
    • Template compilation
      • processContent
      • Extending templating syntax
      • Modifying template parsing with AttributePattern
      • Extending binding language
      • Using the template compiler
      • Attribute mapping
  • Getting to know Aurelia
    • Routing
      • @aurelia/router
        • Getting Started
        • Creating Routes
        • Routing Lifecycle
        • Viewports
        • Navigating
        • Route hooks
        • Router animation
        • Route Events
        • Router Tutorial
        • Router Recipes
      • @aurelia/router-lite
        • Getting started
        • Router configuration
        • Configuring routes
        • Viewports
        • Navigating
        • Lifecycle hooks
        • Router hooks
        • Router events
        • Navigation model
        • Current route
        • Transition plan
    • App configuration and startup
    • Enhance
    • Template controllers
    • Understanding synchronous binding
    • Dynamic composition
    • Portalling elements
    • Observation
      • Observing property changes with @observable
      • Effect observation
      • HTML observation
      • Using observerLocator
    • Watching data
    • Dependency injection (DI)
    • App Tasks
    • Task Queue
    • Event Aggregator
  • Developer Guides
    • Animation
    • Testing
      • Overview
      • Testing attributes
      • Testing components
      • Testing value converters
      • Working with the fluent API
      • Stubs, mocks & spies
    • Logging
    • Building plugins
    • Web Components
    • UI virtualization
    • Errors
      • Kernel Errors
      • Template Compiler Errors
      • Dialog Errors
      • Runtime HTML Errors
    • Bundlers
    • Recipes
      • Apollo GraphQL integration
      • Auth0 integration
      • Containerizing Aurelia apps with Docker
      • Cordova/Phonegap integration
      • CSS-in-JS with Emotion
      • DOM style injection
      • Firebase integration
      • Markdown integration
      • Multi root
      • Progress Web Apps (PWA's)
      • Securing an app
      • SignalR integration
      • Strongly-typed templates
      • TailwindCSS integration
      • WebSockets Integration
      • Web Workers Integration
    • Playground
      • Binding & Templating
      • Custom Attributes
        • Binding to Element Size
      • Integration
        • Microsoft FAST
        • Ionic
    • Migrating to Aurelia 2
      • For plugin authors
      • Side-by-side comparison
    • Cheat Sheet
  • Aurelia Packages
    • Validation
      • Validation Tutorial
      • Plugin Configuration
      • Defining & Customizing Rules
      • Architecture
      • Tagging Rules
      • Model Based Validation
      • Validation Controller
      • Validate Binding Behavior
      • Displaying Errors
      • I18n Internationalization
      • Migration Guide & Breaking Changes
    • i18n Internationalization
    • Fetch Client
      • Overview
      • Setup and Configuration
      • Response types
      • Working with forms
      • Intercepting responses & requests
      • Advanced
    • Event Aggregator
    • State
    • Store
      • Configuration and Setup
      • Middleware
    • Dialog
  • Tutorials
    • Building a ChatGPT inspired app
    • Building a realtime cryptocurrency price tracker
    • Building a todo application
    • Building a weather application
    • Building a widget-based dashboard
    • React inside Aurelia
    • Svelte inside Aurelia
    • Synthetic view
    • Vue inside Aurelia
  • Community Contribution
    • Joining the community
    • Code of conduct
    • Contributor guide
    • Building and testing aurelia
    • Writing documentation
    • Translating documentation
Powered by GitBook
On this page
  • Introduction
  • Installing The Plugin
  • Basic Setup
  • API Reference
  • Parameters
  • How it works
  • Important Notes
  • Examples
  • 1. Basic Web Component
  • 2. Web Component with Bindable Properties
  • 3. Web Component with Shadow DOM
  • 4. Web Component with Lifecycle and Host Injection
  • 5. Web Component with Object Definition
  • 6. Extending Built-in Elements
  • 7. Web Component with Advanced Features
  • Error Handling and Validation
  • Invalid Element Names
  • Containerless Components
  • Usage Outside Aurelia Applications
  • Vanilla JavaScript
  • React Integration
  • Angular Integration
  • Best Practices

Was this helpful?

Export as PDF
  1. Developer Guides

Web Components

The basics of the web-component plugin for Aurelia.

Introduction

Web Components are part of an ever-evolving web specification that aims to allow developers to create native self-contained components without the need for additional libraries or transpilation steps. This guide will teach you how to use Aurelia to create Web Components that can be used in any framework or vanilla JavaScript application.

Installing The Plugin

To use the web components functionality, you need to install the @aurelia/web-components package:

npm install @aurelia/web-components

The package provides the IWcElementRegistry interface which allows you to define web-component custom elements by calling the define method.

Basic Setup

To use web components in your Aurelia application, import the IWcElementRegistry interface from @aurelia/web-components and register your web components:

import { Aurelia, AppTask } from 'aurelia';
import { IWcElementRegistry } from '@aurelia/web-components';

Aurelia
  .register(
    AppTask.creating(IWcElementRegistry, registry => {
      // Define your web components here
      registry.define('my-element', class MyElement {
        static template = '<p>Hello from Web Component!</p>';
      });
    })
  )
  .app(class App {})
  .start();

API Reference

The IWcElementRegistry.define method has the following signatures:

interface IWcElementRegistry {
  define(name: string, def: Constructable, options?: ElementDefinitionOptions): Constructable<HTMLElement>;
  define(name: string, def: Omit<PartialCustomElementDefinition, 'name'>, options?: ElementDefinitionOptions): Constructable<HTMLElement>;
}

Parameters

  • name: The custom element name (must contain a hyphen - as per Web Components specification)

  • def: Either a class constructor or an object with element definition properties

  • options: Optional configuration for extending built-in elements

How it works

  • Each web component custom element is backed by an Aurelia view model, like a normal Aurelia component.

  • For each define call, a corresponding native custom element class is created and registered with the browser's customElements registry.

  • Each bindable property on the backing Aurelia view model is converted to a reactive attribute (via observedAttributes) and reactive property on the custom element.

  • The web component uses standard Web Components lifecycle callbacks (connectedCallback, disconnectedCallback, attributeChangedCallback, adoptedCallback).

  • Regular custom elements: Used as <my-element></my-element> in HTML.

  • Extended built-in elements: Used as <button is="my-button"></button> in HTML with the is attribute.

Important Notes

  • Web component custom elements work independently of Aurelia components. The same class can be both a web component and an Aurelia component, though this should be avoided to prevent double rendering.

  • containerless mode is not supported. Use extend-built-in functionality instead if you want to avoid wrapper elements.

  • Defined web components continue working even after the owning Aurelia application has stopped.

  • template and bindables information is retrieved and compiled only once per define call. Changes after this call have no effect.

  • Slot: [au-slot] is not supported when upgrading existing elements. Standard <slot> elements work as normal web components.

Examples

For simplicity, all examples below define elements at application start, but they can be defined at any time after the container is available.

1. Basic Web Component

import { Aurelia, AppTask } from 'aurelia';
import { IWcElementRegistry } from '@aurelia/web-components';

Aurelia
  .register(
    AppTask.creating(IWcElementRegistry, registry => {
      registry.define('hello-world', class HelloWorld {
        static template = '<h1>Hello, Web Components!</h1>';
      });
    })
  )
  .app(class App {})
  .start();

// Usage in HTML: <hello-world></hello-world>

2. Web Component with Bindable Properties

import { Aurelia, AppTask } from 'aurelia';
import { IWcElementRegistry } from '@aurelia/web-components';

Aurelia
  .register(
    AppTask.creating(IWcElementRegistry, registry => {
      registry.define('user-greeting', class UserGreeting {
        static template = '<p>Hello, ${name}! You are ${age} years old.</p>';
        static bindables = ['name', 'age'];

        name: string = 'World';
        age: number = 0;
      });
    })
  )
  .app(class App {})
  .start();

// Usage in HTML:
// <user-greeting name="John" age="25"></user-greeting>
// Or programmatically:
// const element = document.createElement('user-greeting');
// element.name = 'Jane';
// element.age = 30;

3. Web Component with Shadow DOM

import { Aurelia, AppTask } from 'aurelia';
import { IWcElementRegistry } from '@aurelia/web-components';

Aurelia
  .register(
    AppTask.creating(IWcElementRegistry, registry => {
      registry.define('shadow-element', class ShadowElement {
        static template = `
          <style>
            p { color: blue; font-weight: bold; }
          </style>
          <p>This is styled within Shadow DOM</p>
        `;
        static shadowOptions = { mode: 'open' };
      });
    })
  )
  .app(class App {})
  .start();

4. Web Component with Lifecycle and Host Injection

import { Aurelia, AppTask } from 'aurelia';
import { IWcElementRegistry } from '@aurelia/web-components';
import { INode } from 'aurelia';

Aurelia
  .register(
    AppTask.creating(IWcElementRegistry, registry => {
      registry.define('tick-clock', class TickClock {
        static template = '${message}';
        static inject = [INode];

        private time: number;
        private intervalId: number;
        message: string = '';

        constructor(private host: HTMLElement) {
          this.time = Date.now();
        }

        attaching() {
          this.intervalId = setInterval(() => {
            this.message = `${Math.floor((Date.now() - this.time) / 1000)} seconds passed.`;
          }, 1000);
        }

        detaching() {
          clearInterval(this.intervalId);
        }
      });
    })
  )
  .app(class App {})
  .start();

5. Web Component with Object Definition

import { Aurelia, AppTask } from 'aurelia';
import { IWcElementRegistry } from '@aurelia/web-components';

Aurelia
  .register(
    AppTask.creating(IWcElementRegistry, registry => {
      registry.define('simple-card', {
        template: `
          <div class="card">
            <h2>\${title}</h2>
            <p>\${content}</p>
          </div>
        `,
        bindables: ['title', 'content'],
        shadowOptions: { mode: 'open' }
      });
    })
  )
  .app(class App {})
  .start();

6. Extending Built-in Elements

When extending built-in elements, you use the { extends: 'element-name' } option and reference them in HTML using the is attribute:

import { Aurelia, AppTask } from 'aurelia';
import { IWcElementRegistry } from '@aurelia/web-components';

Aurelia
  .register(
    AppTask.creating(IWcElementRegistry, registry => {
      // Extend a button element
      registry.define('enhanced-button', class EnhancedButton {
        static template = '<span>🚀</span> <slot></slot>';
        static bindables = ['variant'];

        variant: string = 'primary';
      }, { extends: 'button' });

      // Extend a paragraph element
      registry.define('rich-paragraph', class RichParagraph {
        static template = '<strong>${title}</strong>: ${content}';
        static bindables = ['title', 'content'];

        title: string = '';
        content: string = '';
      }, { extends: 'p' });
    })
  )
  .app(class App {})
  .start();

// Usage in HTML:
// <button is="enhanced-button" variant="secondary">Click Me</button>
// <p is="rich-paragraph" title="Note" content="This is enhanced content"></p>

7. Web Component with Advanced Features

import { Aurelia, AppTask } from 'aurelia';
import { IWcElementRegistry } from '@aurelia/web-components';
import { INode, ILogger } from 'aurelia';

Aurelia
  .register(
    AppTask.creating(IWcElementRegistry, registry => {
      registry.define('data-display', class DataDisplay {
        static template = `
          <div class="loading" if.bind="loading">Loading...</div>
          <div class="content" else>
            <h3>\${title}</h3>
            <div repeat.for="item of items">
              <p>\${item.name}: \${item.value}</p>
            </div>
          </div>
        `;
        static bindables = ['url', 'title'];
        static inject = [INode, ILogger];

        url: string = '';
        title: string = 'Data';
        loading: boolean = false;
        items: Array<{name: string, value: string}> = [];

        constructor(
          private host: HTMLElement,
          private logger: ILogger
        ) {}

        async urlChanged(newUrl: string) {
          if (!newUrl) return;

          this.loading = true;
          try {
            const response = await fetch(newUrl);
            const data = await response.json();
            this.items = data.items || [];
            this.logger.info(`Loaded ${this.items.length} items`);
          } catch (error) {
            this.logger.error('Failed to load data', error);
            this.items = [];
          } finally {
            this.loading = false;
          }
        }
      });
    })
  )
  .app(class App {})
  .start();

Error Handling and Validation

The web components implementation includes built-in validation:

Invalid Element Names

// This will throw an error because element names must contain a hyphen
registry.define('myelement', class MyElement {}); // ❌ Error!

// This works
registry.define('my-element', class MyElement {}); // ✅ Correct!

Containerless Components

// This will throw an error because containerless is not supported
registry.define('my-element', class MyElement {
  static containerless = true; // ❌ Error!
});

// Use extend-built-in instead if you need to avoid wrapper elements
registry.define('enhanced-span', class MyElement {
  static template = '<span>Content</span>';
}, { extends: 'span' }); // ✅ Alternative approach

// Usage in HTML: <span is="enhanced-span">Content</span>

Usage Outside Aurelia Applications

Web components defined with Aurelia can be used in any context:

Vanilla JavaScript

<!DOCTYPE html>
<html>
<head>
  <script src="./aurelia-web-components-bundle.js"></script>
</head>
<body>
  <!-- Use regular web components -->
  <user-greeting name="John" age="25"></user-greeting>

  <!-- Use extended built-in elements -->
  <button is="enhanced-button" variant="primary">Click Me</button>

  <script>
    // Create regular web components programmatically
    const greeting = document.createElement('user-greeting');
    greeting.name = 'Jane';
    greeting.age = 30;
    document.body.appendChild(greeting);

    // Create extended built-in elements programmatically
    const enhancedBtn = document.createElement('button', { is: 'enhanced-button' });
    enhancedBtn.variant = 'secondary';
    enhancedBtn.textContent = 'Dynamic Button';
    document.body.appendChild(enhancedBtn);
  </script>
</body>
</html>

React Integration

import React from 'react';

function App() {
  return (
    <div>
      <h1>React App with Aurelia Web Components</h1>
      <user-greeting name="React User" age="25" />
      {/* For extended built-in elements: */}
      <button is="enhanced-button" variant="primary">Enhanced Button</button>
    </div>
  );
}

Angular Integration

// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }
<!-- app.component.html -->
<user-greeting name="Angular User" [attr.age]="userAge"></user-greeting>
<!-- For extended built-in elements: -->
<button is="enhanced-button" variant="primary">Enhanced Button</button>

Best Practices

  1. Element Naming: Always use kebab-case with at least one hyphen for element names.

  2. Property Binding: Define bindable properties explicitly using the bindables array for reactive updates.

  3. Shadow DOM: Use Shadow DOM for style encapsulation when your component has its own styles.

  4. Lifecycle Management: Implement attaching and detaching lifecycle methods for setup and cleanup.

  5. Error Handling: Always handle errors gracefully, especially in async operations.

  6. Performance: Remember that web components are created for each instance, so avoid heavy operations in constructors.

  7. Dependencies: Keep dependencies minimal since web components should be self-contained.

  8. Extended Built-ins: When extending built-in elements, remember to use the is attribute in HTML (<button is="my-button">) rather than creating new element names.

This enhanced documentation provides a complete guide to creating and using Aurelia-powered Web Components with accurate examples and proper error handling.

PreviousBuilding pluginsNextUI virtualization

Last updated 4 days ago

Was this helpful?