or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-angular--elements

Angular - library for using Angular Components as Custom Elements

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@angular/elements@20.2.x

To install, run

npx @tessl/cli install tessl/npm-angular--elements@20.2.0

index.mddocs/

Angular Elements

Angular 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.

Package Information

  • Package Name: @angular/elements
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install @angular/elements
  • Peer Dependencies: @angular/core, rxjs (^6.5.3 || ^7.4.0)

Core Imports

import { createCustomElement, NgElementConfig } from "@angular/elements";
import { Type } from "@angular/core";

For CommonJS:

const { createCustomElement } = require("@angular/elements");

Basic Usage

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);

Architecture

Angular Elements is built around several key components:

  • Custom Element Factory: createCustomElement() function that transforms Angular components into custom element classes
  • Strategy Pattern: NgElementStrategy interface for different component lifecycle management approaches
  • Change Detection Integration: Automatic integration with Angular's change detection system
  • Attribute/Property Mapping: Automatic conversion between HTML attributes and Angular component inputs
  • Event Propagation: Mapping of Angular component outputs to DOM custom events

Capabilities

Custom Element Creation

Creates 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>;

Custom Element Base Class

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;
}

Element Strategy System

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;
}

Version Information

import { Version } from "@angular/core";

/**
 * Version constant for the @angular/elements package.
 */
const VERSION: Version;

Types

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];
};

Usage Examples

Basic Component Conversion

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>

Component with Events

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);
});

Custom Strategy Factory

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()
});

Error Handling

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:

  • Component factory resolution failures
  • Injector configuration issues
  • Custom element registration conflicts
  • Change detection errors during property updates