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 add InstanceProps<I> annotations to get typed element references 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

Two helpers exported from elements-kit/jsx-runtime:

HelperShapeUse when
InstanceProps<I>Public instance fields only (drops the HTMLElement surface)You want to type just the user-defined props
ElementProps<C>Full elements-kit JSX surface β€” attrs, fields, events from static events, slots from [SLOTS], childrenYour element declares typed events or slots and you want callers to see them

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 type { InstanceProps } from "elements-kit/jsx-runtime";
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++;
}
}

InstanceProps<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 { InstanceProps } from "elements-kit/jsx-runtime";
import { XCounter } from "./x-counter";
function applyProps(el: XCounter, props: InstanceProps<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