Claude Code for Angular Signals: Fine-Grained Reactivity and Zoneless Apps — Claude Skills 360 Blog
Blog / Frontend / Claude Code for Angular Signals: Fine-Grained Reactivity and Zoneless Apps
Frontend

Claude Code for Angular Signals: Fine-Grained Reactivity and Zoneless Apps

Published: February 1, 2027
Read time: 9 min read
By: Claude Skills 360

Angular Signals give the framework a fine-grained reactivity model — signal() creates a reactive value, computed() derives values lazily, and effect() runs side effects when dependencies change. Components mark only the DOM nodes that depend on changed signals for update, replacing Angular’s full component tree change detection. Zoneless mode (provideZonelessChangeDetection()) removes Zone.js entirely, reducing bundle size and enabling native async/await without patching. input() and output() declare component APIs as signals. resource() loads async data with built-in loading/error states. toSignal() bridges existing RxJS observables to the signals world. Claude Code generates Angular signal-based components, services, route resolvers, interceptors, and the zoneless application configurations for production Angular 19+ applications.

CLAUDE.md for Angular Signals

## Angular Signals Stack
- Version: @angular/core >= 19, standalone components (no NgModules)
- Reactivity: signal(), computed(), effect() — core primitives
- Interop: toSignal(observable$) to consume RxJS; toObservable(sig) for reverse
- Component API: input() / output() replaces @Input / @Output decorators
- Async: resource() for async reads; linkedSignal() for dependent state
- Zoneless: provideZonelessChangeDetection() in app.config.ts — remove zone.js from polyfills
- State: inject(SignalStore) pattern with signalStore() from NgRx Signals
- HTTP: inject(HttpClient) — returns Observable, wrap with toSignal() or rxResource()

Signal-Based Component

// src/app/components/order-list/order-list.component.ts
import {
  Component, inject, signal, computed, effect, ChangeDetectionStrategy
} from "@angular/core"
import { toSignal } from "@angular/core/rxjs-interop"
import { FormsModule } from "@angular/forms"
import { JsonPipe, CurrencyPipe, DatePipe } from "@angular/common"
import { OrderService } from "../../services/order.service"

@Component({
  selector: "app-order-list",
  standalone: true,
  imports: [FormsModule, CurrencyPipe, DatePipe],
  changeDetection: ChangeDetectionStrategy.OnPush,  // Signals-compatible
  template: `
    <div class="order-list">
      <div class="stats">
        <span>{{ filteredOrders().length }} orders</span>
        <span>Total: {{ totalSpent() | currency }}</span>
      </div>

      <div class="filters">
        <input
          type="search"
          [value]="searchQuery()"
          (input)="searchQuery.set($event.target.value)"
          placeholder="Search orders..."
        />
        <select
          [value]="selectedStatus()"
          (change)="selectedStatus.set($event.target.value)"
        >
          <option value="">All statuses</option>
          <option value="pending">Pending</option>
          <option value="shipped">Shipped</option>
        </select>
      </div>

      @if (ordersResource.isLoading()) {
        <p>Loading...</p>
      } @else if (ordersResource.error()) {
        <p class="error">{{ ordersResource.error() }}</p>
      } @else {
        @for (order of filteredOrders(); track order.id) {
          <div class="order-card">
            <span>{{ order.id | slice:0:8 }}</span>
            <span>{{ order.status }}</span>
            <span>{{ order.totalCents / 100 | currency }}</span>
            <span>{{ order.createdAt | date:'short' }}</span>
          </div>
        } @empty {
          <p>No orders found</p>
        }
      }
    </div>
  `,
})
export class OrderListComponent {
  private orderService = inject(OrderService)

  // Mutable state signals
  searchQuery = signal("")
  selectedStatus = signal("")

  // resource(): async data with loading/error states (Angular 19+)
  ordersResource = this.orderService.ordersResource()

  // computed(): derived state — re-computes only when dependencies change
  filteredOrders = computed(() => {
    const orders = this.ordersResource.value() ?? []
    const query = this.searchQuery().toLowerCase()
    const status = this.selectedStatus()

    return orders.filter(order => {
      const matchesQuery = !query || order.id.includes(query)
      const matchesStatus = !status || order.status === status
      return matchesQuery && matchesStatus
    })
  })

  totalSpent = computed(() =>
    this.filteredOrders().reduce((sum, o) => sum + o.totalCents, 0) / 100
  )

  constructor() {
    // effect(): runs when dependencies change, after render
    effect(() => {
      const count = this.filteredOrders().length
      // Track for analytics — runs whenever filteredOrders changes
      if (count > 0) {
        console.log(`Displaying ${count} orders`)
      }
    })
  }
}

Signal-Based Service

// src/app/services/order.service.ts
import { Injectable, inject, signal, resource } from "@angular/core"
import { HttpClient } from "@angular/common/http"
import { toSignal } from "@angular/core/rxjs-interop"
import { map } from "rxjs/operators"

export interface Order {
  id: string
  customerId: string
  status: "pending" | "processing" | "shipped" | "delivered" | "cancelled"
  totalCents: number
  createdAt: string
}

@Injectable({ providedIn: "root" })
export class OrderService {
  private http = inject(HttpClient)
  private _customerId = signal<string | null>(null)

  setCustomer(id: string) {
    this._customerId.set(id)
  }

  // resource(): reactive async loader — re-fetches when customerId changes
  ordersResource() {
    return resource({
      // Request signal: re-runs loader when this changes
      request: () => this._customerId(),
      loader: async ({ request: customerId, abortSignal }) => {
        if (!customerId) return []

        const response = await fetch(`/api/orders?customerId=${customerId}`, {
          signal: abortSignal,
        })
        if (!response.ok) throw new Error(`HTTP ${response.status}`)
        return response.json() as Promise<Order[]>
      },
    })
  }

  // Convert RxJS observable to signal
  orderStats = toSignal(
    this.http.get<{ total: number; revenue: number }>("/api/orders/stats"),
    { initialValue: { total: 0, revenue: 0 } }
  )

  async cancelOrder(orderId: string): Promise<void> {
    await fetch(`/api/orders/${orderId}/cancel`, { method: "POST" })
  }
}

Signal Inputs and Outputs

// src/app/components/order-card/order-card.component.ts
import {
  Component, input, output, computed, ChangeDetectionStrategy
} from "@angular/core"
import { CurrencyPipe } from "@angular/common"

@Component({
  selector: "app-order-card",
  standalone: true,
  imports: [CurrencyPipe],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="card" [class.highlighted]="isHighlighted()">
      <h3>Order #{{ order().id | slice:0:8 }}</h3>
      <p>{{ order().status }}</p>
      <p>{{ order().totalCents / 100 | currency }}</p>
      @if (order().status === 'pending') {
        <button (click)="handleCancel()">Cancel</button>
      }
    </div>
  `,
})
export class OrderCardComponent {
  // input() replaces @Input() — returns a signal
  order = input.required<Order>()
  highlightedId = input<string | null>(null)

  // output() replaces @Output() EventEmitter
  cancelled = output<string>()
  selected = output<Order>()

  // computed from input signals
  isHighlighted = computed(() => this.order().id === this.highlightedId())

  handleCancel() {
    this.cancelled.emit(this.order().id)
  }
}

Zoneless Configuration

// src/app/app.config.ts — zoneless Angular 19 app
import {
  ApplicationConfig, provideZonelessChangeDetection
} from "@angular/core"
import { provideRouter, withComponentInputBinding } from "@angular/router"
import { provideHttpClient, withFetch, withInterceptors } from "@angular/common/http"
import { authInterceptor } from "./interceptors/auth.interceptor"
import { routes } from "./app.routes"

export const appConfig: ApplicationConfig = {
  providers: [
    // Zoneless change detection — removes Zone.js requirement
    provideZonelessChangeDetection(),
    provideRouter(routes, withComponentInputBinding()),
    provideHttpClient(
      withFetch(),
      withInterceptors([authInterceptor]),
    ),
  ],
}
// src/app/interceptors/auth.interceptor.ts
import {
  HttpInterceptorFn, HttpRequest, HttpHandlerFn
} from "@angular/common/http"
import { inject } from "@angular/core"
import { AuthService } from "../services/auth.service"

export const authInterceptor: HttpInterceptorFn = (
  req: HttpRequest<unknown>,
  next: HttpHandlerFn
) => {
  const auth = inject(AuthService)
  const token = auth.token()  // auth.token is a signal

  if (token) {
    const authReq = req.clone({
      headers: req.headers.set("Authorization", `Bearer ${token}`),
    })
    return next(authReq)
  }

  return next(req)
}

NgRx Signal Store

// src/app/store/order.store.ts — NgRx Signals store
import { signalStore, withState, withComputed, withMethods } from "@ngrx/signals"
import { computed, inject } from "@angular/core"
import { OrderService } from "../services/order.service"

interface OrderState {
  orders: Order[]
  loading: boolean
  error: string | null
  selectedId: string | null
}

export const OrderStore = signalStore(
  { providedIn: "root" },
  withState<OrderState>({
    orders: [],
    loading: false,
    error: null,
    selectedId: null,
  }),
  withComputed(({ orders, selectedId }) => ({
    selectedOrder: computed(() =>
      orders().find(o => o.id === selectedId())
    ),
    pendingOrders: computed(() =>
      orders().filter(o => o.status === "pending")
    ),
    totalRevenue: computed(() =>
      orders().reduce((sum, o) => sum + o.totalCents, 0) / 100
    ),
  })),
  withMethods((store, orderService = inject(OrderService)) => ({
    async loadOrders(customerId: string) {
      patchState(store, { loading: true, error: null })
      try {
        const orders = await orderService.fetchOrders(customerId)
        patchState(store, { orders, loading: false })
      } catch (e) {
        patchState(store, { error: (e as Error).message, loading: false })
      }
    },
    selectOrder(id: string) {
      patchState(store, { selectedId: id })
    },
  }))
)

For the React equivalent fine-grained reactivity model with Zustand or Jotai atom-based state that Angular signals are conceptually similar to, see the React state management guide for atomic state patterns. For the Vue 3 Composition API with ref() and computed() that inspired Angular’s signal design, the Vue guide covers reactivity patterns. The Claude Skills 360 bundle includes Angular Signals skill sets covering reactivity primitives, zoneless setup, and NgRx Signals store. Start with the free tier to try Angular signal component generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free