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
// 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
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
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);
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
// 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>
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
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: '<template><h1>${title}</h1><div class="content"><au-slot></au-slot></div></template>',
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: '<template><p>${text}</p></template>',
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)
}
CustomElement.getDefinition
Retrieves the CustomElementDefinition for a custom element class, providing access to all metadata about the element.
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
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
Checks whether a given value is a custom element type (class decorated with @customElement or defined via CustomElement.define).
Method Signature
CustomElement.isType<T>(value: T): value is CustomElementType<T>
Parameters
value: any - The value to check
Return Value
Returns true if the value is a custom element type, false otherwise.
Example
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');
}
}
}
Metadata Methods
CustomElement.annotate
Attaches metadata to a custom element class. This is typically used internally by decorators and the framework.
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
import { CustomElement } from 'aurelia';
class MyElement {}
// Manually annotate the class (decorators do this automatically)
CustomElement.annotate(MyElement, 'template', '<template>${message}</template>');
CustomElement.annotate(MyElement, 'bindables', ['message']);
CustomElement.annotate(MyElement, 'containerless', true);
CustomElement.getAnnotation
Retrieves metadata that was previously attached to a custom element class.
Generates a unique name for a custom element, useful for anonymous or dynamically created elements.
Method Signature
CustomElement.generateName(): string
Return Value
A string representing a unique name (typically in the format unnamed-{number}).
Example
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);
CustomElement.generateType
Dynamically generates a CustomElementType with a given name and prototype properties.
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 {}
@containerless Decorator
Renders the custom element without its element container.
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)
@capture Decorator
Enables capturing of all attributes and bindings that are not explicitly defined as bindables or template controllers.
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-'
}
@processContent Decorator
Defines a hook that processes the element's content before compilation.
// Use optional flag when controller might not exist
const controller = CustomElement.for(element, { optional: true });
if (controller) {
// Safe to use
} else {
// Handle missing controller
}
4. Leverage Definition Objects for Complex Elements