How do I create a component?
Generate component
npm run generate component greeting # target directory: src/app/components/greeting
Modify component
// greeting.component.ts
import { Component, ComponentConfig } from '../../../core/index.js';
interface GreetingConfig {
name?: string;
}
const templateFn = (config?: GreetingConfig) => `
<div>
<span data-bind="name">${config?.name}</span>
<button data-event="click:_sayHi">Say hi</button>
</div>
`;
export class GreetingComponent extends Component<HTMLDivElement, GreetingConfig> {
constructor(mountTarget: HTMLElement, config: GreetingConfig) {
super({
templateFn,
mountTarget,
config,
normalizeKeys: ['name'],
defaults: { name: 'friend' },
});
}
private _sayHi(): void {
console.log('hi', this.config.name);
}
}
Render component
// in a parent component
registerChildren(): ComponentConfig[] {
return [
{
selector: 'my-greeting-selector',
factory: (el) => new GreetingComponent(el, { name: 'Ada' })
}
]
}
// calling render directly
const greeting = new GreetingComponent(el, { name: 'Ada' });
greeting.render();
Constructing a component mounts its root element immediately, but does not render it — you must either register it in a parent via child registration or call render() yourself.
Constructor options
interface ComponentOptions<K = any> {
templateFn?: (config?: K) => string; // default: () => ''
mountTarget?: string | HTMLElement; // default: '#app'
tagName?: keyof HTMLElementTagNameMap; // default: 'div'
props?: ComponentProps; // default: {}
config?: K;
defaults?: Partial<K>;
normalizeKeys?: (keyof K)[];
trustLevel?: 'strict' | 'normal' | 'trusted'; // default: 'normal'
}
mountTargetas a CSS selector string appends the component into that element; passing an actualHTMLElementreplaces it instead. A selector that matches nothing leaves the component unmounted, silently.templateFnalways receives the normalized config —defaultsmerged underconfig, then every key listed innormalizeKeyscoerced to''if it's stillundefined/null. See How do I pass data to a component? for the full normalization rules.trustLevelcontrols HTML sanitization of the rendered template — see How do I control HTML sanitization?.
Render lifecycle
render() runs the same pipeline on every call (initial and any subsequent re-render):
onBeforeRender()
element.innerHTML = sanitize(generateTemplate())
compose() → mounts data-component/data-catalog children (see registerChildren)
hydrate() → parses data-i18n / data-pipe, then removes empty data-optional elements
autoBindEvents() → rebinds all data-event listeners
onAfterRender()
Calling render() again while a render is already in progress (e.g. from inside onBeforeRender) is a safe no-op — it just returns the current element instead of recursing.
destroy() unbinds all data-event listeners, unsubscribes every event-bus subscription made via this.listen(...), destroys all tracked children, then removes the element from the DOM. See What lifecycle hooks are available? for every overridable hook and their exact firing order.