The CustomElement resource is a fundamental concept in Aurelia 2, providing the core functionality for creating encapsulated and reusable components. This comprehensive guide covers all aspects of the CustomElement API, including methods, decorators, and configuration options.
Retrieves the Aurelia controller associated with a DOM node. The controller provides access to the element's view model, lifecycle methods, and other properties.
Method Signatures
Parameters
node: Node - The DOM node for which to retrieve the controller
opts?: object - Optional configuration object with the following properties:
optional?: boolean - If true, returns null instead of throwing when no controller is found
searchParents?: boolean - If true, searches parent nodes (including containerless elements) for a controller
name?: string - If provided, only returns controllers for custom elements with this specific name
Examples
CustomElement.define
Registers a class as a custom element in Aurelia. This method can be called directly or is used internally by the @customElement decorator.
Method Signatures
Parameters
nameOrDef: string | PartialCustomElementDefinition - Either the element name or a complete definition object
Type?: Constructable - The class containing the element's logic (optional when using definition object)
Examples
CustomElement.getDefinition
Retrieves the CustomElementDefinition for a custom element class, providing access to all metadata about the element.
Method Signature
Parameters
Type: Constructable - The custom element class
Return Value
Returns a CustomElementDefinition object containing all metadata about the custom element.
Example
CustomElement.find
Searches for a custom element definition by name within a specific container's registry.
Method Signature
Parameters
container: IContainer - The dependency injection container to search in
name: string - The name of the custom element to find
Return Value
Returns the CustomElementDefinition if found, or null if no element with the specified name is registered.
Example
CustomElement.isType
Checks whether a given value is a custom element type (class decorated with @customElement or defined via CustomElement.define).
Method Signature
Parameters
value: any - The value to check
Return Value
Returns true if the value is a custom element type, false otherwise.
Example
Metadata Methods
CustomElement.annotate
Attaches metadata to a custom element class. This is typically used internally by decorators and the framework.
Method Signature
Parameters
Type: Constructable - The custom element class to annotate
prop: string - The property key for the annotation
value: any - The value to associate with the property
Example
CustomElement.getAnnotation
Retrieves metadata that was previously attached to a custom element class.
Method Signature
Parameters
Type: Constructable - The custom element class
prop: string - The property key to retrieve
Return Value
Returns the annotation value, or undefined if not found.
Example
Utility Methods
CustomElement.generateName
Generates a unique name for a custom element, useful for anonymous or dynamically created elements.
Method Signature
Return Value
A string representing a unique name (typically in the format unnamed-{number}).
Example
CustomElement.generateType
Dynamically generates a CustomElementType with a given name and prototype properties.
Method Signature
Parameters
name: string - The name for the generated type
proto?: object - An optional object containing properties and methods to add to the prototype
Return Value
A CustomElementType that can be used with CustomElement.define.
Example
CustomElement.createInjectable
Creates an InjectableToken for dependency injection scenarios.
Method Signature
Return Value
An InterfaceSymbol that can be used as a dependency injection token.
Example
CustomElement.keyFrom
Generates the registry key used internally to store and retrieve custom element definitions.
Method Signature
Parameters
name: string - The custom element name
Return Value
A string representing the internal registry key.
Example
Decorators
@customElement Decorator
The primary decorator for marking a class as a custom element.
Syntax
Examples
@useShadowDOM Decorator
Enables Shadow DOM for the custom element.
Syntax
Example
@containerless Decorator
Renders the custom element without its element container.
Syntax
Example
@capture Decorator
Enables capturing of all attributes and bindings that are not explicitly defined as bindables or template controllers.
Syntax
Example
@processContent Decorator
Defines a hook that processes the element's content before compilation.
Syntax
Example
Definition Objects
PartialCustomElementDefinition
An object that describes a custom element's configuration. All properties are optional.
Properties
Example
CustomElementDefinition
The complete, resolved definition of a custom element (read-only).
Key Properties
Programmatic Resource Aliases
PartialCustomElementDefinition.aliases is only one way to expose alternative names. For reusable libraries or bridge packages you often need to add aliases outside of the definition itself. The runtime provides two helpers to make that ergonomic.
alias(...aliases) decorator
Apply the decorator directly to any custom element, custom attribute, value converter, or binding behavior to append aliases to the resource metadata.
The decorator merges with aliases declared via the definition object, so you can sprinkle default aliases in a base class and extend them in derived implementations without clobbering earlier metadata.
registerAliases(...)
When you need to attach aliases to an existing resource (for example, to keep backwards compatibility after a rename), call registerAliases during app startup.
The resource argument identifies which registry to update. Pass CustomElement, CustomAttribute, ValueConverter, or BindingBehavior depending on the resource you are aliasing. Because aliases are registered against the supplied container you can scope them to individual feature modules or make them global by running the task in your root configuration.
Best Practices
1. Use Decorators Over Direct API Calls
2. Type Your Controllers
3. Handle Errors Gracefully
4. Leverage Definition Objects for Complex Elements
// Get controller for the current node
CustomElement.for<T>(node: Node): ICustomElementController<T>
// Get controller with optional flag (returns null if not found)
CustomElement.for<T>(node: Node, opts: { optional: true }): ICustomElementController<T> | null
// Search parent nodes for a controller
CustomElement.for<T>(node: Node, opts: { searchParents: true }): ICustomElementController<T>
// Get controller for a named custom element
CustomElement.for<T>(node: Node, opts: { name: string }): ICustomElementController<T> | undefined
// Get controller for a named custom element, searching parents
CustomElement.for<T>(node: Node, opts: { name: string; searchParents: true }): ICustomElementController<T> | undefined
import { CustomElement, ILogger } from 'aurelia';
// Basic usage - get controller for current node
const myElement = document.querySelector('.my-custom-element');
try {
const controller = CustomElement.for(myElement);
// You can inject ILogger in your classes for proper logging
this.logger?.info('View model:', controller.viewModel);
this.logger?.info('Element state:', controller.state);
} catch (error) {
this.logger?.error('The provided node does not host a custom element.', error);
}
// Safe retrieval without throwing errors
const optionalController = CustomElement.for(myElement, { optional: true });
if (optionalController) {
// Controller found and available for use
optionalController.viewModel.someMethod();
} else {
// No controller found, handle gracefully
this.logger?.info('Node is not a custom element');
}
// Search parent hierarchy for any custom element controller
const someInnerElement = document.querySelector('.some-inner-element');
const parentController = CustomElement.for(someInnerElement, { searchParents: true });
// parentController is the closest controller up the DOM tree
// Get controller for a specific named custom element
const namedController = CustomElement.for(myElement, { name: 'my-custom-element' });
if (namedController) {
// Found a controller for the specific element type
} else {
// The node is not hosting the named custom element type
}
// Search parents for a specific named custom element
const namedParentController = CustomElement.for(someInnerElement, {
name: 'my-custom-element',
searchParents: true
});
// Access view model properties and methods
const controller = CustomElement.for(myElement);
const viewModel = controller.viewModel;
viewModel.myProperty = 'new value';
viewModel.myMethod();
// Access lifecycle state
this.logger?.info('Current state:', controller.state);
this.logger?.info('Is activated:', controller.isActive);
// Define with name and class
CustomElement.define<T>(name: string, Type: Constructable<T>): CustomElementType<T>
// Define with definition object and class
CustomElement.define<T>(def: PartialCustomElementDefinition, Type: Constructable<T>): CustomElementType<T>
// Define with definition object only (generates type)
CustomElement.define<T>(def: PartialCustomElementDefinition): CustomElementType<T>
import { CustomElement } from 'aurelia';
// Basic definition with name and class
class MyCustomElement {
public message = 'Hello, World!';
public greet() {
alert(this.message);
}
}
CustomElement.define('my-custom-element', MyCustomElement);
// Definition with complete configuration object
const definition = {
name: 'advanced-element',
template: '<h1>${title}</h1><div class="content"><au-slot></au-slot></div>',
bindables: ['title', 'size'],
shadowOptions: { mode: 'open' },
containerless: false,
capture: true,
dependencies: []
};
class AdvancedElement {
public title = '';
public size = 'medium';
}
CustomElement.define(definition, AdvancedElement);
// Definition without explicit type (generates anonymous class)
const simpleDefinition = {
name: 'simple-element',
template: '<p>${text}</p>',
bindables: ['text']
};
const SimpleElementType = CustomElement.define(simpleDefinition);
// Note: Using @customElement decorator is preferred over calling define directly
@customElement('my-element')
class MyElement {
// This is equivalent to calling CustomElement.define('my-element', MyElement)
}
import { CustomElement, IContainer, ILogger } from 'aurelia';
// In a custom service or component
class MyService {
constructor(private container: IContainer, private logger: ILogger) {}
public checkElementExists(elementName: string): boolean {
const definition = CustomElement.find(this.container, elementName);
return definition !== null;
}
public getElementTemplate(elementName: string): string | null {
const definition = CustomElement.find(this.container, elementName);
return definition?.template as string || null;
}
}
// Usage in template compiler or dynamic composition
class SomeComponent {
constructor(private logger: ILogger) {}
public checkDynamicElement(container: IContainer) {
const definition = CustomElement.find(container, 'my-dynamic-element');
if (definition) {
// Element is registered and available for use
this.logger.info('Found element:', definition.name);
} else {
// Element not found in current container
this.logger.warn('Element not registered');
}
}
}
CustomElement.isType<T>(value: T): value is CustomElementType<T>
import { CustomElement, customElement, ILogger } from 'aurelia';
@customElement('my-element')
class MyElement {}
class RegularClass {}
// Service class that performs type checking
class TypeCheckingService {
constructor(private logger: ILogger) {}
public demonstrateTypeChecking() {
// Type checking
this.logger.info('MyElement is custom element type:', CustomElement.isType(MyElement)); // true
this.logger.info('RegularClass is custom element type:', CustomElement.isType(RegularClass)); // false
this.logger.info('String is custom element type:', CustomElement.isType('string')); // false
this.logger.info('Number is custom element type:', CustomElement.isType(42)); // false
}
// Usage in dynamic scenarios
public processComponent(component: unknown) {
if (CustomElement.isType(component)) {
// Safe to use as custom element
const definition = CustomElement.getDefinition(component);
this.logger.info('Processing element:', definition.name);
} else {
this.logger.info('Not a custom element type');
}
}
}
import { CustomElement } from 'aurelia';
class MyElement {}
// Manually annotate the class (decorators do this automatically)
CustomElement.annotate(MyElement, 'template', '${message}');
CustomElement.annotate(MyElement, 'bindables', ['message']);
CustomElement.annotate(MyElement, 'containerless', true);
import { CustomElement } from 'aurelia';
// Generate unique names for dynamic elements
const uniqueName1 = CustomElement.generateName(); // 'unnamed-1'
const uniqueName2 = CustomElement.generateName(); // 'unnamed-2'
// Use with dynamic element creation
class DynamicElement {
public data = '';
}
const DynamicElementType = CustomElement.define(uniqueName1, DynamicElement);
import { customElement, useShadowDOM } from 'aurelia';
@customElement('shadow-element')
@useShadowDOM({ mode: 'open' })
class ShadowElement {
// This element will render in Shadow DOM
}
// Or with default mode (open)
@customElement('shadow-element-simple')
@useShadowDOM()
class ShadowElementSimple {}
import { customElement, containerless } from 'aurelia';
@customElement('invisible-wrapper')
@containerless()
class InvisibleWrapper {
// This element won't create its own DOM node
// Only its content will be rendered
}
// Usage: <invisible-wrapper>content</invisible-wrapper>
// Renders: content (without the wrapper element)
import { customElement, capture } from 'aurelia';
@customElement('flexible-element')
@capture() // Capture all unrecognized attributes
class FlexibleElement {
// Any attribute not defined as bindable will be captured
}
@customElement('filtered-element')
@capture((attrName) => attrName.startsWith('data-'))
class FilteredElement {
// Only capture attributes that start with 'data-'
}
// Use optional flag when controller might not exist
const controller = CustomElement.for(element, { optional: true });
if (controller) {
// Safe to use
} else {
// Handle missing controller
}