인터페이스는 현지화되어 있으며, 원작자 Skill 콘텐츠는 원래 언어로 유지됩니다. 일부 안내 페이지는 영어로 제공됩니다.
Skills 둘러보기
개발 및 엔지니어링 · SKILL.md

angular

angular

Angular 21.x core patterns and APIs — Signals, Standalone components, Zoneless change detection, SSR/Hydration, Dependency Injection, Component composition, Signal-based state, Testing. Load when writing Angular code for API reference, testing patterns, or SSR configuration.

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: angular description: "Angular 21.x core patterns and APIs — Signals, Standalone components, Zoneless change detection, SSR/Hydration, Dependency Injection, Component composition, Signal-based state, Testing. Load when writing Angular code for API reference, testing patterns, or SSR configuration." allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, mcp__context7__resolve-library-id, mcp__context7__query-docs, mcp__angular-cli__get_best_practices, mcp__angular-cli__search_documentation metadata: triggers: Angular signals, Angular standalone, Angular zoneless, Angular SSR, Angular hydration, Angular DI, Angular testing, Angular state management, Angular 21 related-skills: angular-spa, angular-best-practices, angular-ui-patterns domain: frontend role: specialist scope: reference output-format: code last-reviewed: "2026-03-15"

Iron Law

READ angular-spa skill for TailwindCSS 4.x, daisyUI 5.5.5, and workspace conventions BEFORE implementing. This skill is API reference only — no design token or styling patterns here.

When to Use This Skill

  • Building new Angular applications (v20+)
  • Implementing Signals-based reactive patterns
  • Creating Standalone Components and migrating from NgModules
  • Configuring Zoneless Angular applications
  • Implementing SSR, prerendering, and hydration
  • Optimizing Angular performance
  • Adopting modern Angular patterns and best practices

Do Not Use This Skill When

  • Migrating from AngularJS (1.x) — use angular-migration skill
  • Working with legacy Angular apps that cannot upgrade
  • General TypeScript issues — use typescript-expert skill

Version context: Angular 20 (Signals/Zoneless stable), Angular 21 (Signals-first default, Signal Forms available — current), Angular 22 (Signal Forms further enhancements).


Angular 21 Zoneless Note

Angular 21 is zoneless by default. Do NOT add provideZonelessChangeDetection() — it is implicit and adding it causes warnings.

  • Angular 20: provideZonelessChangeDetection() required explicitly
  • Angular 21+: Zoneless is the default. No provider needed. No zone.js import.

This workspace uses Angular 21+. The patterns below show v20-style explicit providers for reference — in Angular 21 omit those providers.


1. Signals: The New Reactive Primitive

Signals are Angular's fine-grained reactivity system, replacing zone.js-based change detection.

Core signal / computed / effect

import { signal, computed, effect } from "@angular/core";

const count = signal(0);
count.set(5);
count.update((v) => v + 1);

const doubled = computed(() => count() * 2);

effect(() => {
  console.log(`Count changed to: ${count()}`);
});

Signal inputs, outputs, and model

import { Component, input, output, model } from "@angular/core";

@Component({
  selector: "app-user-card",
  standalone: true,
  template: `
    <div class="card">
      <h3>{{ name() }}</h3>
      <span>{{ role() }}</span>
      <button (click)="select.emit(id())">Select</button>
    </div>
  `,
})
export class UserCardComponent {
  id = input.required<string>();
  name = input.required<string>();
  role = input<string>("User");
  select = output<string>();
  isSelected = model(false);
}
// <app-user-card [id]="'123'" [name]="'John'" [(isSelected)]="selected" />

Signal queries (viewChild, viewChildren, contentChild): See references/signals-core.md


1b. linkedSignal — Derived Writable Signals

linkedSignal creates a writable signal whose default resets when its source changes.

UseTool
Derived read-only valuecomputed()
Writable value that resets on source changelinkedSignal()
Local state with no dependencysignal()
import { signal, linkedSignal } from '@angular/core';

const items = signal(['a', 'b', 'c']);
const selectedItem = linkedSignal(() => items()[0]);

selectedItem.set('b');      // 'b'
items.set(['x', 'y', 'z']); // selectedItem resets to 'x'

Full examples (advanced + pagination): references/signals-core.md


1c. resource() — Reactive Async Data Fetching

resource() is Angular's built-in reactive primitive for async data.

ScenarioUse
Component-local async data tied to signalsresource()
Service-level shared HTTP callsHttpClient + inject()
Complex async pipelines (retry, cancel, merge)HttpClient + RxJS
One-time data loadsEither
import { httpResource } from '@angular/common/http';

// HTTP GET shorthand — refetches when userId() changes
userResource = httpResource<User>(() => `/api/users/${this.userId()}`);

Full API (resource() component, abort signal, status signals): references/signals-core.md


1d. afterNextRender / afterRender — DOM Lifecycle Hooks

In zoneless Angular 21, afterNextRender and afterRender replace NgZone lifecycle hacks for DOM-dependent initialization. Use these instead of ngAfterViewInit for code that requires a real DOM (chart init, third-party widget, scroll position).

import { Component, afterNextRender, afterRender, ElementRef, viewChild } from '@angular/core';

@Component({ selector: 'app-chart', template: '<canvas #canvas></canvas>' })
export class ChartComponent {
  canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');

  constructor() {
    // Runs once after the first render — DOM is guaranteed to exist
    afterNextRender(() => {
      initChart(this.canvas().nativeElement);
    });

    // Runs after every render — use sparingly (performance cost)
    afterRender(() => {
      updateScrollPosition();
    });
  }
}

Rule: Prefer afterNextRender over afterRender — it runs once. Both run in the browser only (SSR-safe).


1e. @let — Template Variable Declaration (Angular 18+)

@let declares a local template variable — replaces the *ngIf as alias hack and verbose ng-template patterns.

<!-- Declare a local alias for a long expression -->
@let user = currentUser();
@let greeting = 'Hello, ' + user.name + '!';

<h1>{{ greeting }}</h1>
<p>{{ user.email }}</p>

<!-- Useful with async resources -->
@let data = userResource.value();
@if (data) {
  <app-user-profile [user]="data" />
}

Rule: Use @let to avoid repeating computed signal calls in templates. Do not use it as a substitute for computed() — @let re-evaluates on every render pass.


1f. Testing: Vitest (Stable in Angular 21)

Angular 21 ships with Vitest as the stable test runner (replaces Karma). New projects default to Vitest. Migrate existing Karma setups.

// vitest.config.ts (Angular CLI generates this)
import { defineConfig } from 'vitest/config';
import angular from '@analogjs/vite-plugin-angular';

export default defineConfig({
  plugins: [angular()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['src/test-setup.ts'],
  },
});
// Component test with zoneless TestBed
import { TestBed } from '@angular/core/testing';
import { provideExperimentalZonelessChangeDetection } from '@angular/core';

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [provideExperimentalZonelessChangeDetection()],
    imports: [MyComponent],
  });
});

Run: ng test (uses Vitest by default in Angular 21 workspaces). See reference/testing-vitest.md for full patterns.


2. Standalone Components

Standalone components are self-contained and don't require NgModule declarations.

Creating Standalone Components

import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { RouterLink } from "@angular/router";

@Component({
  // standalone: true is the default in Angular v20+ — omit it
  selector: "app-header",
  imports: [RouterLink], // CommonModule is a compat shim — don't import it
  template: `
    <header>
      <a routerLink="/">Home</a>
      <a routerLink="/about">About</a>
    </header>
  `,
})
export class HeaderComponent {}

Bootstrapping Without NgModule

// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { provideRouter } from "@angular/router";
import { provideHttpClient } from "@angular/common/http";
import { AppComponent } from "./app/app.component";
import { routes } from "./app/app.routes";

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes), provideHttpClient()],
});

Lazy Loading Standalone Components

export const routes: Routes = [
  {
    path: "dashboard",
    loadComponent: () =>
      import("./dashboard/dashboard.component").then((m) => m.DashboardComponent),
  },
  {
    path: "admin",
    loadChildren: () =>
      import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
  },
];

3. Zoneless Angular

Angular 21 reminder: Zoneless is the default. The provideZonelessChangeDetection() call below is v20-style — omit it in Angular 21 projects.

// main.ts (Angular 20 style — NOT needed in Angular 21+)
bootstrapApplication(AppComponent, {
  providers: [provideZonelessChangeDetection()],
});

Zoneless Component Pattern

import { Component, signal, ChangeDetectionStrategy } from "@angular/core";

@Component({
  selector: "app-counter",
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div>Count: {{ count() }}</div>
    <button (click)="increment()">+</button>
  `,
})
export class CounterComponent {
  count = signal(0);
  increment() { this.count.update((v) => v + 1); }
}

Benefits: no zone.js patches, cleaner stack traces, ~15KB bundle savings, better Web Component interop.


4. Server-Side Rendering & Hydration

See references/ssr-hydration.md for SSR setup, hydration configuration, incremental hydration patterns, TransferState, and common SSR troubleshooting.


5. Modern Routing Patterns

See references/api-reference.md for full routing examples (functional guards, resolvers).

Key patterns:

  • CanActivateFn with inject() for functional guards
  • ResolveFn<T> for route-level data pre-fetching
  • toSignal(route.data.pipe(...)) to consume resolved data

6. Dependency Injection

See references/api-reference.md for full DI examples (inject(), InjectionToken).

Key patterns:

  • inject() function (no constructor required)
  • InjectionToken<T> for typed configuration values

7. Component Composition

See references/api-reference.md for full composition examples (ng-content slots, hostDirectives).

Key patterns:

  • <ng-content select="[attr]"> for named slots
  • hostDirectives for behavior composition without inheritance

8. Signal-Based State Management

See references/api-reference.md for full state service and component store examples.

Key patterns:

  • Private signal() + public computed() for encapsulated state
  • @Injectable() (no providedIn: 'root') for scoped component stores

9. Forms with Signals

See references/api-reference.md for full reactive forms and signal form patterns.

Key patterns:

  • Signal Forms (preferred for Angular 21+) — see signal-forms.md reference below
  • FormBuilder + Validators for reactive forms (legacy, still supported)
  • Signal-based validation via computed() for derived validation state

Angular 21+: Prefer Signal Forms over reactive forms for new apps. See references/signal-forms.md.


10. Performance Optimization

Change Detection Strategies

Always use ChangeDetectionStrategy.OnPush. Triggers re-check only when: input signal/reference changes, event handler runs, async pipe emits, or signal value changes.

Defer Blocks for Lazy Loading

@defer (on viewport) {
  <app-heavy-chart />
} @placeholder {
  <div class="skeleton" />
} @loading (minimum 200ms) {
  <app-spinner />
} @error {
  <p>Failed to load chart</p>
}

NgOptimizedImage

import { NgOptimizedImage } from '@angular/common';

@Component({
  imports: [NgOptimizedImage],
  template: `
    <img ngSrc="hero.jpg" width="800" height="600" priority />
    <img ngSrc="thumbnail.jpg" width="200" height="150" loading="lazy" placeholder="blur" />
  `
})

11. Testing Modern Angular

See references/api-reference.md for full testing examples (signal components, setInput).

Key patterns:

  • Import standalone components directly in TestBed.configureTestingModule({ imports: [...] })
  • Use componentRef.setInput('name', value) to set signal inputs in tests
  • Call fixture.detectChanges() after signal mutations to trigger DOM update

Key Decision Tables

See references/api-reference.md for full Signals vs RxJS table and Pattern Do/Don't summary.

Use CaseUse SignalsUse RxJS
Local component stateYesNo — overkill
HTTP requestsNoYes — HttpClient Observable
Complex async flowsNoYes — switchMap, mergeMap

Common Troubleshooting

See references/api-reference.md for the full troubleshooting table.

IssueSolution
Signal not updating UIEnsure OnPush + call signal as function count()
provideZonelessChangeDetection warning in v21Remove call — zoneless is the default in v21+
SSR fetch failsUse TransferState or withFetch()

Related Skills

  • angular-spa — workspace skill with TailwindCSS 4.x, daisyUI 5.5.5, conventions
  • angular-best-practices — impact-prioritized rules (CRITICAL to LOW-MEDIUM)
  • angular-ui-patterns — loading, error, empty state patterns
저장한 Skills