Skip to content

Angular

A custom element built with elements-kit is just class extends HTMLElement registered via customElements.define. Angular renders it fine at runtime, but you need to tell the Angular compiler to allow unknown elements and annotate element references with the class to get typed access in TypeScript.

// shared element β€” built once, used anywhere
import { defineElement } from "elements-kit/custom-elements";
import { reactive } from "elements-kit/signals";
export class XCounter extends HTMLElement {
@reactive() count = 0;
}
defineElement("x-counter", XCounter);

Allow custom elements in the template compiler

By default Angular raises an error for unknown element tags. Add CUSTOM_ELEMENTS_SCHEMA to the schemas array of the component (standalone) or NgModule that uses the element:

import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
@Component({
selector: "app-root",
template: `<x-counter [count]="5"></x-counter>`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppComponent {}

CUSTOM_ELEMENTS_SCHEMA suppresses the unknown-element error for any tag that contains a hyphen. You only need it on the module or standalone component that contains the custom element β€” not globally.

Source the prop shape from the class

Three type helpers exported from elements-kit/custom-elements read the raw platform surface off the class. All three take the constructor (typeof XCounter); PropertiesOf also accepts an instance type.

HelperShapeUse when
PropertiesOf<typeof XCounter>Public instance fields, all optional β€” the HTMLElement surface is dropped; Slot-typed fields accept a NodeTyping property assignments β€” prop bindings, wrappers, helper functions
AttributesOf<typeof XCounter>Every key of static [ATTRIBUTES], valued string | nullTyping the raw HTML attribute surface β€” setAttribute, server-rendered markup
EventsOf<typeof XCounter>The static events map, verbatim β€” event name to event typeTyping addEventListener, framework event bindings, HTMLElementEventMap augmentations

Type element references

Angular doesn’t autocomplete custom element props in templates β€” CUSTOM_ELEMENTS_SCHEMA is a runtime-only allowlist. To get typed access to an instance, annotate @ViewChild or document.querySelector with the class directly:

import { Component, ViewChild, ElementRef, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { XCounter } from "./x-counter";
@Component({
selector: "app-root",
template: `<x-counter #counter [count]="5"></x-counter>`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppComponent {
// ref is typed as XCounter β€” access reactive fields directly
@ViewChild("counter") counter!: ElementRef<XCounter>;
increment() {
this.counter.nativeElement.count++;
}
}

PropertiesOf<typeof XCounter> is useful as a type annotation when you need to describe the settable surface of the element β€” for example, when building a wrapper component or writing a helper function:

import type { PropertiesOf } from "elements-kit/custom-elements";
import { XCounter } from "./x-counter";
function applyProps(el: XCounter, props: PropertiesOf<typeof XCounter>) {
Object.assign(el, props);
}

Typed document.querySelector and createElement

Independent of any framework, augmenting the global HTMLElementTagNameMap gives you typed lookups in plain DOM code:

declare global {
interface HTMLElementTagNameMap {
"x-counter": XCounter;
}
}
const el = document.querySelector("x-counter"); // XCounter | null

Worth adding to the same file as the element class.


See also