Angular - library for using Angular Components as Custom Elements
npx @tessl/cli install tessl/npm-angular--elements@20.2.0Angular Elements provides Angular's custom element API that enables developers to package Angular components as custom elements (Web Components). It serves as a bridge between Angular's component interface and change detection functionality to the built-in DOM API through the createCustomElement() function.
npm install @angular/elements@angular/core, rxjs (^6.5.3 || ^7.4.0)import { createCustomElement, NgElementConfig } from "@angular/elements";
import { Type } from "@angular/core";For CommonJS:
const { createCustomElement } = require("@angular/elements");import { createCustomElement, NgElementConfig } from "@angular/elements";
import { Component, Injector, Input, Output, EventEmitter } from "@angular/core";
// Define an Angular component
@Component({
selector: 'my-widget',
template: `
<div>
<h3>{{title}}</h3>
<button (click)="handleClick()">Click me</button>
</div>
`
})
export class MyWidgetComponent {
@Input() title: string = '';
@Output() clicked = new EventEmitter<string>();
handleClick() {
this.clicked.emit('Widget clicked!');
}
}
// Create custom element
const MyWidgetElement = createCustomElement(MyWidgetComponent, {
injector: injector
});
// Register with browser
customElements.define('my-widget', MyWidgetElement);Angular Elements is built around several key components:
createCustomElement() function that transforms Angular components into custom element classesNgElementStrategy interface for different component lifecycle management approachesCreates a custom element class based on an Angular component that can be registered with the browser's CustomElementRegistry.
import { Type } from "@angular/core";
/**
* Creates a custom element class based on an Angular component.
* @param component - The Angular component to transform
* @param config - Configuration providing initialization information
* @returns The custom-element construction class
*/
function createCustomElement<P>(
component: Type<any>,
config: NgElementConfig
): NgElementConstructor<P>;Abstract base class that implements the functionality needed for a custom element.
import { Subscription } from "rxjs";
/**
* Abstract base class for custom elements created from Angular components.
* Extends HTMLElement and provides Angular-specific functionality.
*/
abstract class NgElement extends HTMLElement {
/** The strategy that controls how a component is transformed in a custom element */
protected abstract ngElementStrategy: NgElementStrategy;
/** A subscription to change, connect, and disconnect events in the custom element */
protected ngElementEventsSubscription: Subscription | null;
/**
* Handler that responds to changes in observed attributes.
* @param attrName - The name of the attribute that has changed
* @param oldValue - The previous value of the attribute
* @param newValue - The new value of the attribute
* @param namespace - The namespace in which the attribute is defined
*/
abstract attributeChangedCallback(
attrName: string,
oldValue: string | null,
newValue: string,
namespace?: string
): void;
/**
* Handler that responds to the insertion of the custom element in the DOM.
*/
abstract connectedCallback(): void;
/**
* Handler that responds to the deletion of the custom element from the DOM.
*/
abstract disconnectedCallback(): void;
}The strategy pattern implementation for managing component lifecycle and change detection.
import { Observable } from "rxjs";
/**
* Underlying strategy used by NgElement to create/destroy components and react to input changes.
*/
interface NgElementStrategy {
/** Observable stream of events emitted by the strategy */
events: Observable<NgElementStrategyEvent>;
/**
* Connect the strategy to an HTML element.
* @param element - The HTML element to connect to
*/
connect(element: HTMLElement): void;
/** Disconnect the strategy and clean up resources */
disconnect(): void;
/**
* Get the current value of an input property.
* @param propName - The name of the property
* @returns The current property value
*/
getInputValue(propName: string): any;
/**
* Set the value of an input property.
* @param propName - The name of the property
* @param value - The new value to set
* @param transform - Optional transformation function for the value
*/
setInputValue(propName: string, value: string, transform?: (value: any) => any): void;
}
/**
* Factory for creating new strategies for each NgElement instance.
*/
interface NgElementStrategyFactory {
/**
* Creates a new strategy instance.
* @param injector - The injector to use for the strategy
* @returns A new NgElementStrategy instance
*/
create(injector: Injector): NgElementStrategy;
}
/**
* Interface for events emitted through the NgElementStrategy.
*/
interface NgElementStrategyEvent {
/** The name of the event */
name: string;
/** The event payload */
value: any;
}import { Version } from "@angular/core";
/**
* Version constant for the @angular/elements package.
*/
const VERSION: Version;import { Injector } from "@angular/core";
/**
* Prototype for a class constructor based on an Angular component for custom element registration.
*/
interface NgElementConstructor<P> {
/** Array of observed attribute names for the custom element, derived from component inputs */
readonly observedAttributes: string[];
/**
* Constructor that initializes a new custom element instance.
* @param injector - Optional injector to override the configured injector
* @returns A new NgElement instance with component properties
*/
new (injector?: Injector): NgElement & WithProperties<P>;
}
/**
* Configuration that initializes an NgElementConstructor with dependencies and strategy.
*/
interface NgElementConfig {
/** The injector to use for retrieving the component's factory */
injector: Injector;
/** An optional custom strategy factory to use instead of the default */
strategyFactory?: NgElementStrategyFactory;
}
/**
* Additional type information that can be added to the NgElement class,
* for properties that are added based on the inputs and methods of the underlying component.
*/
type WithProperties<P> = {
[property in keyof P]: P[property];
};import { createCustomElement } from "@angular/elements";
import { Component, Input, Injector } from "@angular/core";
@Component({
selector: 'greeting-card',
template: '<h2>Hello {{name}}!</h2>'
})
export class GreetingCardComponent {
@Input() name: string = 'World';
}
// Convert to custom element
const GreetingCardElement = createCustomElement(GreetingCardComponent, {
injector: injector
});
// Register with browser
customElements.define('greeting-card', GreetingCardElement);
// Use in HTML
// <greeting-card name="Angular"></greeting-card>import { createCustomElement } from "@angular/elements";
import { Component, Input, Output, EventEmitter, Injector } from "@angular/core";
@Component({
selector: 'counter-widget',
template: `
<div>
<p>Count: {{count}}</p>
<button (click)="increment()">+</button>
<button (click)="decrement()">-</button>
</div>
`
})
export class CounterWidgetComponent {
@Input() count: number = 0;
@Output() countChange = new EventEmitter<number>();
increment() {
this.count++;
this.countChange.emit(this.count);
}
decrement() {
this.count--;
this.countChange.emit(this.count);
}
}
// Convert and register
const CounterWidgetElement = createCustomElement(CounterWidgetComponent, {
injector: injector
});
customElements.define('counter-widget', CounterWidgetElement);
// Listen for events in vanilla JavaScript
document.querySelector('counter-widget').addEventListener('countChange', (event) => {
console.log('New count:', event.detail);
});import { createCustomElement, NgElementStrategyFactory } from "@angular/elements";
import { Injector } from "@angular/core";
class CustomStrategyFactory implements NgElementStrategyFactory {
create(injector: Injector): NgElementStrategy {
// Return custom strategy implementation
return new MyCustomStrategy(injector);
}
}
const CustomElement = createCustomElement(MyComponent, {
injector: injector,
strategyFactory: new CustomStrategyFactory()
});The Angular Elements package integrates with Angular's standard error handling mechanisms. Component lifecycle errors, change detection errors, and initialization errors will be handled according to Angular's error handling strategy configured in your application.
Common error scenarios: