Harness-engineering angular-component-pattern

Angular Component Pattern

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/angular-component-pattern" ~/.claude/skills/intense-visions-harness-engineering-angular-component-pattern-be55f7 && rm -rf "$T"
manifest: agents/skills/codex/angular-component-pattern/SKILL.md
source content

Angular Component Pattern

Author Angular components with correct inputs/outputs, change detection strategy, and lifecycle hooks

When to Use

  • Creating a new Angular component and deciding on its API surface (inputs, outputs, queries)
  • Choosing between
    Default
    and
    OnPush
    change detection strategies
  • Wiring parent-child communication without a shared service
  • Using lifecycle hooks (
    ngOnInit
    ,
    ngOnDestroy
    ,
    ngAfterViewInit
    ) correctly
  • Querying child elements with
    @ViewChild
    or
    @ContentChild

Instructions

  1. Declare the component with
    @Component
    and always set
    selector
    ,
    template
    /
    templateUrl
    , and
    changeDetection
    .
  2. Prefer
    changeDetection: ChangeDetectionStrategy.OnPush
    for all new components — it prevents unnecessary re-renders and forces explicit data flow.
  3. Declare inputs with the
    input()
    signal function (Angular 17+) or
    @Input()
    decorator. Prefer signal inputs for new code.
  4. Declare outputs with
    output()
    (Angular 17+) or
    @EventEmitter
    with
    @Output()
    . Signal outputs compose with effects naturally.
  5. Keep component logic in the class; keep templates declarative. Move complex expressions into computed properties or pipes.
  6. Implement
    OnDestroy
    (or use
    DestroyRef
    ) to clean up subscriptions, timers, and manual DOM listeners.
  7. Use
    @ViewChild
    to access child component or DOM element references after
    ngAfterViewInit
    . Never access them in
    ngOnInit
    — they are not yet rendered.
  8. Scope CSS with
    ViewEncapsulation.Emulated
    (the default) unless you have a specific reason to pierce the shadow DOM.
import {
  ChangeDetectionStrategy,
  Component,
  input,
  output,
  computed,
  OnDestroy,
} from '@angular/core';

export interface Product {
  id: string;
  name: string;
  price: number;
}

@Component({
  selector: 'app-product-card',
  template: `
    <div class="card">
      <h2>{{ product().name }}</h2>
      <p>{{ formattedPrice() }}</p>
      <button (click)="onAdd()">Add to cart</button>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductCardComponent implements OnDestroy {
  // Signal input — reactive by default
  product = input.required<Product>();

  // Derived value — recalculates only when product() changes
  formattedPrice = computed(() =>
    new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
      this.product().price
    )
  );

  // Typed output
  addToCart = output<Product>();

  onAdd(): void {
    this.addToCart.emit(this.product());
  }

  ngOnDestroy(): void {
    // Clean up any manual subscriptions here
  }
}

Details

Change detection in depth: Angular's default change detection walks the entire component tree on every browser event.

OnPush
restricts checks to components whose input references have changed, an async pipe resolves, or a signal emits. This makes rendering O(changed subtree) instead of O(entire tree). Adopt it globally by setting it as the default in
angular.json
under
schematics
.

Signal inputs vs

@Input()
: Signal inputs (Angular 17.1+) return a
Signal<T>
rather than a plain value. This means the input participates in the reactivity graph —
computed()
and
effect()
can read it without subscribing to a subject or change detection cycle. Prefer
input()
for new components;
@Input()
still works and is required for libraries targeting older Angular versions.

Lifecycle hook order:

  1. ngOnChanges
    — fires before
    ngOnInit
    and on every
    @Input()
    change
  2. ngOnInit
    — fires once after first
    ngOnChanges
    ; safe to read inputs but NOT view children
  3. ngAfterViewInit
    — fires after the component's view and child views are initialized; safe to use
    @ViewChild
  4. ngOnDestroy
    — fires just before the component is removed; clean up subscriptions and effects

ViewChild timing pitfall: Accessing a

@ViewChild
in
ngOnInit
returns
undefined
because the view hasn't been created yet. Move that logic to
ngAfterViewInit
.

Output naming: Angular convention for outputs is camelCase event names without the

on
prefix in the class, but HTML consumers use
(addToCart)
. Avoid naming outputs with the
on
prefix in the property name — it creates
(onAddToCart)
which reads redundantly in templates.

Host bindings: Use

host: { '[class.active]': 'isActive()' }
in the decorator instead of
@HostBinding
— it is more declarative and compatible with the component metadata compiler.

Source

https://angular.dev/guide/components

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.