angular
Show how to use custom elements in Angular, including required CUSTOM_ELEMENTS_SCHEMA configuration
Install / Use
npx skills add bennypowers/cemInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
Development & EngineeringSupported Platforms
Tags
Skill content
View source on GitHubname: angular description: Show how to use custom elements in Angular, including required CUSTOM_ELEMENTS_SCHEMA configuration. Use when the user asks to "use in Angular", "Angular custom elements", "custom elements in Angular", "CUSTOM_ELEMENTS_SCHEMA", or mentions "Angular module", "standalone component", or "angular integration". tools: Read, Glob, Grep
Custom Elements in Angular
Generate framework-specific guidance for using custom elements in Angular, including required schema configuration.
Workflow
Phase 1: Detect Angular Version and Style
Search the project for Angular version and patterns:
- Check
package.jsonfor@angular/coreversion - Determine if using standalone components (Angular 14+) or NgModules
- Check for existing
CUSTOM_ELEMENTS_SCHEMAusage - Check for TypeScript strict mode settings
Phase 2: Gather Element Data
Read the target element(s):
cem://element/{tagName}
cem://element/{tagName}/attributes
cem://element/{tagName}/events
cem://element/{tagName}/slots
Understand which features need framework-specific handling:
- Properties: Angular's property binding works well with custom elements
- Events: Angular's event binding works with custom events
- Two-way binding: Needs manual setup
- Forms: Custom elements need
ControlValueAccessorfor Angular forms
Phase 3: Required Configuration
Standalone Components (Angular 14+, recommended)
// my.component.ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
selector: 'app-my-page',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<my-element variant="primary" (my-event)="handleEvent($event)">
Content
</my-element>
`,
})
export class MyPageComponent {
handleEvent(e: Event) {
console.log((e as CustomEvent).detail);
}
}
Each standalone component that uses custom elements needs CUSTOM_ELEMENTS_SCHEMA in its schemas array.
NgModule-based (Angular < 14 or legacy projects)
// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
declarations: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// ...
})
export class AppModule {}
The schema applies to all components declared in that module. For larger apps, add it only to modules that use custom elements.
Phase 4: Generate Integration Code
Basic Usage
@Component({
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<my-element
variant="primary"
[disabled]="isDisabled"
(my-event)="onMyEvent($event)"
>
Default slot content
<span slot="header">Header content</span>
</my-element>
`,
})
export class MyComponent {
isDisabled = false;
onMyEvent(event: Event) {
const detail = (event as CustomEvent).detail;
}
}
Property Binding
Angular's [property] syntax sets properties directly on the element, which works correctly with custom elements:
<!-- Attribute (string) -->
<my-element variant="primary"></my-element>
<!-- Property binding (passes JS values) -->
<my-element [complexData]="myObject"></my-element>
<!-- Boolean -->
<my-element [disabled]="isDisabled"></my-element>
Angular maps [camelCase] property bindings to the element's camelCase properties. For kebab-case attributes, use [attr.kebab-case]:
<!-- Property: sets el.complexData -->
<my-element [complexData]="value"></my-element>
<!-- Attribute: sets el.setAttribute('aria-label', value) -->
<my-element [attr.aria-label]="label"></my-element>
Event Handling
Angular's (event) syntax works with custom element events:
<!-- Listen to custom events -->
<my-element (my-event)="handleEvent($event)"></my-element>
<!-- Access detail inline -->
<my-element (change)="value = $any($event).detail.value"></my-element>
For TypeScript, cast the event to the correct type of the event based on the manifest.
handleCustomEvent(event: CustomEvent<{ value: string }>) {
this.value = event.detail.value;
}
// manifest has "type": { "text": "TypedInManifestEvent", "source": ... }
handleTypedInManifestEvent(event: TypedInManifestEvent) {
this.value = event.detail.value;
}
Slots
Use the native HTML slot attribute:
<my-element>
<!-- Default slot -->
<span>Default content</span>
<!-- Named slots -->
<div slot="header">Header content</div>
<div slot="footer">Footer content</div>
</my-element>
Angular content projection (<ng-content>) and web component slots are different mechanisms. Use slot="name" for web component slots.
Angular Forms Integration
Custom elements don't work with Angular's ngModel or reactive forms out of the box. Create a ControlValueAccessor:
import { Directive, ElementRef, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Directive({
selector: 'my-input[formControlName], my-input[ngModel], my-input[formControl]',
standalone: true,
host: {
'(input)': 'onInput($event)',
'(blur)': 'onTouched()',
},
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyInputValueAccessor),
multi: true,
},
],
})
export class MyInputValueAccessor implements ControlValueAccessor {
private onChange: (value: any) => void = () => {};
onTouched: () => void = () => {};
constructor(private el: ElementRef<HTMLElement>) {}
writeValue(value: any): void {
(this.el.nativeElement as any).value = value;
}
registerOnChange(fn: (value: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
(this.el.nativeElement as any).disabled = isDisabled;
}
onInput(event: Event): void {
const value = (event as CustomEvent).detail?.value
?? (event.target as any).value;
this.onChange(value);
}
}
Then import the directive alongside the component:
@Component({
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [ReactiveFormsModule, MyInputValueAccessor],
template: `<my-input [formControl]="nameControl"></my-input>`,
})
export class MyFormComponent {
nameControl = new FormControl('');
}
Be sure to check the element descriptions for indication if it is a Form-Associated Custom Element (FACE).
TypeScript Declarations
Extend the HTMLElementTagNameMap so Angular's template type checker understands the elements:
// custom-elements.d.ts
declare global {
interface HTMLElementTagNameMap {
'my-element': import('my-element-library').MyElement;
}
}
For stricter typing in templates, combine with CUSTOM_ELEMENTS_SCHEMA — Angular won't type-check custom element bindings by default when the schema is present.
Phase 5: Output
Present the integration with:
- Schema configuration appropriate to standalone vs NgModule style
- Usage examples for each element
- Property binding showing
[property]vs[attr.attribute] - Event handling with proper
CustomEventtyping - Slot usage with native
slotattribute - Forms integration if the element is form-associated (has value/input patterns)
Guidelines
- Always include
CUSTOM_ELEMENTS_SCHEMA: Without it, Angular throws template errors for unknown elements - Use
[property]binding, notattr.: Angular's property binding sets properties directly, which custom elements handle correctly - Cast events to
CustomEvent: Angular's template type system doesn't know aboutCustomEvent.detail - Don't confuse
ng-contentwithslot: Angular content projection and web component slots are separate systems - ControlValueAccessor is required for forms: Without it,
ngModeland reactive forms won't work with custom elements - Check for
CUSTOM_ELEMENTS_SCHEMAin tests: Test modules/components need the schema too
Related Skills
career-ops
72.3kOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)
ai-job-search
43.5kThe job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it.
claude-howto
41.6kA visual, example-driven guide to Claude Code — from basic concepts to advanced agents, with copy-paste templates that bring immediate value.
guizang-ppt-skill
26.7kAI-agent Skill for generating polished HTML slide decks: editorial magazine and Swiss layouts, image prompts, social covers, and a WebGL/low-power presentation runtime.
Security Score
Audited on Sep 10, 2026
