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.mdsource 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
andDefault
change detection strategiesOnPush - Wiring parent-child communication without a shared service
- Using lifecycle hooks (
,ngOnInit
,ngOnDestroy
) correctlyngAfterViewInit - Querying child elements with
or@ViewChild@ContentChild
Instructions
- Declare the component with
and always set@Component
,selector
/template
, andtemplateUrl
.changeDetection - Prefer
for all new components — it prevents unnecessary re-renders and forces explicit data flow.changeDetection: ChangeDetectionStrategy.OnPush - Declare inputs with the
signal function (Angular 17+) orinput()
decorator. Prefer signal inputs for new code.@Input() - Declare outputs with
(Angular 17+) oroutput()
with@EventEmitter
. Signal outputs compose with effects naturally.@Output() - Keep component logic in the class; keep templates declarative. Move complex expressions into computed properties or pipes.
- Implement
(or useOnDestroy
) to clean up subscriptions, timers, and manual DOM listeners.DestroyRef - Use
to access child component or DOM element references after@ViewChild
. Never access them inngAfterViewInit
— they are not yet rendered.ngOnInit - Scope CSS with
(the default) unless you have a specific reason to pierce the shadow DOM.ViewEncapsulation.Emulated
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
: Signal inputs (Angular 17.1+) return a @Input()
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:
— fires beforengOnChanges
and on everyngOnInit
change@Input()
— fires once after firstngOnInit
; safe to read inputs but NOT view childrenngOnChanges
— fires after the component's view and child views are initialized; safe to usengAfterViewInit@ViewChild
— fires just before the component is removed; clean up subscriptions and effectsngOnDestroy
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
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- 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.