From cem
Guides using custom elements in Angular apps with CUSTOM_ELEMENTS_SCHEMA for standalone components and NgModules. Detects Angular version and generates integration code.
npx claudepluginhub bennypowers/cem --plugin cemThis skill uses the workspace's default tool permissions.
Generate framework-specific guidance for using custom elements in Angular, including required schema configuration.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Automates semantic versioning and release workflow for Claude Code plugins: bumps versions in package.json, marketplace.json, plugin.json; verifies builds; creates git tags, GitHub releases, changelogs.
Generate framework-specific guidance for using custom elements in Angular, including required schema configuration.
Search the project for Angular version and patterns:
package.json for @angular/core versionCUSTOM_ELEMENTS_SCHEMA usageRead 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:
ControlValueAccessor for Angular forms// 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.
// 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.
@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;
}
}
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>
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;
}
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.
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).
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.
Present the integration with:
[property] vs [attr.attribute]CustomEvent typingslot attributeCUSTOM_ELEMENTS_SCHEMA: Without it, Angular throws template errors for unknown elements[property] binding, not attr.: Angular's property binding sets properties directly, which custom elements handle correctlyCustomEvent: Angular's template type system doesn't know about CustomEvent.detailng-content with slot: Angular content projection and web component slots are separate systemsngModel and reactive forms won't work with custom elementsCUSTOM_ELEMENTS_SCHEMA in tests: Test modules/components need the schema too