component

Generate an Angular component with signals, OnPush, and host-based styling following Momentum CMS conventions. Use when creating new UI components in any library.

SKILL.md

GitHub

Generate Angular Component

Create an Angular component following Momentum CMS conventions.

Arguments

  • First argument: Library path (e.g., "admin", "ui", "admin/components")
  • Second argument: Component name in kebab-case (e.g., "data-table", "field-renderer")

File Naming Convention

Momentum CMS uses short file names without the .component infix. This is a deliberate project convention — do NOT follow the Angular CLI default of .component.ts.

File Name it NOT
Component <name>.ts <name>.component.ts
Template <name>.html <name>.component.html
Tests <name>.spec.ts <name>.component.spec.ts

For example, a component called data-table produces:

  • data-table.ts
  • data-table.html (only if template is complex)
  • data-table.spec.ts

Steps

  1. Create component files in libs/<library>/src/lib/<component>/:

    • <component>.ts — the component class (NOT <component>.component.ts)
    • <component>.html — template, only if complex (NOT <component>.component.html)
    • <component>.spec.ts — tests (NOT <component>.component.spec.ts)
  2. Use this template for the component:

import { ChangeDetectionStrategy, Component, computed, input, output, signal, inject } from '@angular/core';

@Component({
  selector: 'mcms-<component-name>',
  host: { class: 'block' },
  template: `
    <!-- Template here -->
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class <PascalName>Component {
  // Signal inputs (use proper types, never `any`)
  readonly data = input.required<DataType>();

  // Optional inputs with defaults
  readonly disabled = input(false);

  // Signal outputs
  readonly valueChange = output<ValueType>();

  // Internal state
  private readonly _loading = signal(false);

  // Computed values
  readonly loading = this._loading.asReadonly();
  readonly hasData = computed(() => !!this.data());

  // Injected services
  private readonly http = inject(HttpClient);
}
  1. Export from library's index.ts:
export { <PascalName>Component } from './lib/<component>/<component>';

Note: the import path ends with /<component> (the short filename), NOT /<component>.component.

Key Conventions

  • NO standalone: true — default in Angular 21, redundant (ESLint enforced)
  • NO any types — use proper interfaces (ESLint enforced)
  • NO CommonModule import — unnecessary in Angular 21
  • NO .component.ts suffix — use plain .ts (project convention, see File Naming above)
  • Always set changeDetection: ChangeDetectionStrategy.OnPush — never use Default
  • Use input() and input.required() for inputs — never use @Input() decorator
  • Use output() for outputs — never use @Output() decorator
  • Use signal() for internal state, computed() for derived values
  • Use inject() for dependency injection — never use constructor injection
  • Use @for/@if/@switch control flow — never use *ngFor/*ngIf/[ngSwitch]
  • Prefix selectors with mcms-

UI Component Patterns

No Wrapping Divs

Angular components create a host element. Style the host directly — do NOT add wrapper divs:

@Component({
  selector: 'mcms-button',
  host: { class: 'inline-flex items-center gap-2' },
  template: `<ng-content />`,
})

NOT:

// BAD: unnecessary wrapper div
template: `<div class="inline-flex items-center gap-2"><ng-content /></div>`,

Class Configuration

Accept a class input for Tailwind customization via hostClasses() computed:

@Component({
    selector: 'button[mcms-button]',
    host: { '[class]': 'hostClasses()' },
    template: `<ng-content />`,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class McmsButtonComponent {
    readonly variant = input<'primary' | 'secondary'>('primary');
    readonly class = input('');

    readonly hostClasses = computed(
        () => `${baseClasses} ${variantClasses[this.variant()]} ${this.class()}`,
    );
}

Theme Service

Use McmsThemeService for dark mode support:

import { McmsThemeService } from '@momentumcms/admin';

@Component({...})
export class MyComponent {
  private readonly theme = inject(McmsThemeService);

  toggleDarkMode(): void {
    this.theme.toggleTheme();
  }

  readonly isDark = this.theme.isDark;           // computed signal
  readonly currentTheme = this.theme.theme;      // 'light' | 'dark' | 'system'
}

App Tailwind Setup

Apps using the admin library must:

  1. tailwind.config.js — Use the admin preset and include library sources:
const adminPreset = require('../../libs/admin/tailwind.preset');

module.exports = {
    presets: [adminPreset],
    content: [
        join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
        join(__dirname, '../../libs/admin/src/**/*.{ts,html}'),
        ...createGlobPatternsForDependencies(__dirname),
    ],
};
  1. styles.css — Include CSS variables inline (Angular's esbuild can't resolve library CSS imports):
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
    :root {
        --mcms-background: 0 0% 100%;
        --mcms-foreground: 222 47% 11%;
        /* ... copy from libs/admin/src/styles/theme.css */
    }
    .dark {
        /* dark mode variables */
    }
}