Run a structured bug-focused code review for selected files, folders, or the whole workspace.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Build, refactor, and architect Angular state with the @ngrx/signals Signal Store. Use whenever the user mentions signalStore, withState, withMethods, withComputed, withHooks, withLinkedState, withFeature, signalStoreFeature, withEntities, rxMethod, signalMethod, patchState, the SignalStore events plugin (withReducer, withEventHandlers), or wants to refactor BehaviorSubject/service-based state to Signal Store. Also use for designing custom store features, request status, optimistic updates, entity collections, and the store/service split in Angular. Do NOT use for @ngrx/store (classic Redux), createReducer/createEffect, or generic RxJS questions unrelated to Signal Store.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Modern, lightweight, signal-based state management for Angular. This skill produces idiomatic, type-safe, well-architected @ngrx/signals code.
Trigger on any of these signals:
signalStore, signalStoreFeature, withState, withMethods, withComputed, withLinkedState, withHooks, withProps, withFeature, withEntities, rxMethod, signalMethod, patchState, getState, watchState, signalState, or @ngrx/signals/events / @ngrx/signals/testing.BehaviorSubject/Subject or a class with mutable fields into a Signal Store.withRequestStatus, withSelectedEntity, withLogger, etc.) or asks how to compose features.Do NOT use for classic @ngrx/store (actions, reducers, effects, separate-file selectors), Redux DevTools wiring without Signal Store, or pure RxJS pipeline questions.
A Signal Store is a tree of composed features. Each feature contributes some combination of state, computed signals, methods, props, and lifecycle hooks. Composition happens at definition time:
export const FooStore = signalStore(
{ providedIn: 'root' }, // optional Angular provider config
withState(initialState), // declares state slices (signals)
withComputed(...), // derived signals
withProps(...), // non-reactive deps & values (often via inject())
withMethods(...), // actions / mutations
withHooks({ onInit, onDestroy })
);
Three rules every Signal Store author needs to internalize:
patchState(store, updater). Direct mutation does not exist. Updaters can be a partial object, a function (state) => Partial<State>, or a custom updater that returns one of those.inject(Store) or TestBed.inject(Store). Provide them at root, on a route, or on a component (providers: [Store]) for component-scoped state.signalStoreFeature(...), not by extending classes.For 80% of feature stores, this is the shape. Copy it, adapt it, then read the references for anything non-obvious.
import { computed, inject } from '@angular/core';
import {
patchState, signalStore, withComputed, withMethods, withState,
} from '@ngrx/signals';
import { Foo, FooApi } from './foo.api';
import {
setError, setFulfilled, setPending, withRequestStatus,
} from './with-request-status';
type FooState = {
items: Foo[];
filter: string;
};
const initialState: FooState = {
items: [],
filter: '',
};
export const FooStore = signalStore(
{ providedIn: 'root' },
withState<FooState>(initialState),
withRequestStatus(),
withComputed(({ items, filter }) => ({
filteredItems: computed(() =>
items().filter((i) => i.name.includes(filter()))
),
count: computed(() => items().length),
})),
withMethods((store, api = inject(FooApi)) => ({
setFilter(filter: string): void {
patchState(store, { filter });
},
async loadAll(): Promise<void> {
patchState(store, setPending());
try {
const items = await api.getAll();
patchState(store, { items }, setFulfilled());
} catch (e) {
patchState(store, setError(e instanceof Error ? e.message : 'Failed'));
}
},
})),
);
For collections with stable IDs, swap items: T[] in state for withEntities<T>(). See references/entities.md.
providers: [Store]. App-wide? { providedIn: 'root' }. Per-route? Provide on the route. Don't put route-scoped state in root.inject()s. Both pieces become independently testable; the backend can be swapped without touching the store.withState<FooState>(initialState) — never inferred-only. Future patchState callers depend on the type to catch typos.signalStoreFeature(...) so they are reusable across stores.async method for a single awaited call.rxMethod<Input>(pipe(...)) when you need RxJS operators (debounceTime, switchMap, retry, cancellation).signalMethod<Input>((value) => ...) when input is a signal/value and the body is synchronous — no RxJS overhead.{ injector } — calling outside one is deprecated.TestBed.inject(Store), mock injected services with provide:, assert on signals + method effects. Seed protected state with patchState(unprotected(store), ...) from @ngrx/signals/testing.These are the mistakes that show up most often. Keep these patterns visible whenever generating code.
updateEntity — function form for state-dependent updates// ❌ Wrong: read entityMap manually then patch with a partial — race-prone
// and leaks store internals into the method body.
const current = store.entityMap()[id];
patchState(store, updateEntity({ id, changes: { completed: !current.completed } }));
// ✅ Right: function-form changes — atomic, no manual lookup.
patchState(store, updateEntity({
id,
changes: (todo) => ({ completed: !todo.completed }),
}));
removeEntities — predicate form, not filter+map// ❌ Wrong: pulls everything into the method body, fragile if signal shape changes.
const ids = store.entities().filter((t) => t.completed).map((t) => t.id);
patchState(store, removeEntities(ids));
// ✅ Right: predicate is evaluated against each entity by the updater.
patchState(store, removeEntities((todo) => todo.completed));
getState, restore on error// ❌ Wrong: no rollback path — the UI lies if the API call fails.
patchState(store, addEntity(item));
await api.add(item);
// ✅ Right: snapshot, patch optimistically, restore on failure.
const previous = getState(store);
patchState(store, addEntity(item));
try {
await api.add(item);
} catch (e) {
patchState(store, { entityMap: previous.entityMap, ids: previous.ids });
throw e;
}
// ❌ Wrong: reaching into the store from the feature, can't compose into patchState calls.
withMethods((store) => ({ setPending() { patchState(store, { requestStatus: 'pending' }); } }))
// ✅ Right: standalone updater functions that return Partial<State>.
// Compose with other updaters in a single patchState call.
export function setPending(): RequestStatusState { return { requestStatus: 'pending' }; }
patchState(store, setAllEntities(items), setFulfilled());
// ❌ Wrong: bare signalStoreFeature — no compile error if used without withEntities.
export function withSelectedEntity<T>() {
return signalStoreFeature(withState({ selectedId: null }), /* ... */);
}
// ✅ Right: declare prerequisite via { state: type<EntityState<T>>() }.
// Now misuse is a compile error.
export function withSelectedEntity<T extends { id: EntityId }>() {
return signalStoreFeature(
{ state: type<EntityState<T>>() },
withState<{ selectedId: EntityId | null }>({ selectedId: null }),
withComputed(({ selectedId, entityMap }) => ({
selectedEntity: computed(() => {
const id = selectedId();
return id == null ? null : (entityMap()[id] ?? null);
}),
})),
withMethods((store) => ({
select(id: EntityId): void { patchState(store, { selectedId: id }); },
clearSelection(): void { patchState(store, { selectedId: null }); },
})),
);
}
BehaviorSubject — what maps to what| Before | After |
|---|---|
BehaviorSubject<T> field | state slice in withState<FooState>(...) |
.next(...) | patchState(store, ...) |
.value | signal getter store.foo() (or getState(store)) |
combineLatest / map derived obs | withComputed(...) returning computed(...) |
| HTTP method on the service | new FooApi service injected by the store |
Component subscribes via | async | reads signal: {{ store.foo() }} (no async pipe) |
When generating code:
@ngrx/signals / @ngrx/signals/entities / @ngrx/signals/rxjs-interop.patchState for mutations — never assign to signals directly.inject(SomeApi); the store does not import HttpClient.setPending(), setError(msg)) when the same update happens in multiple places.withEntities<T>() over a hand-rolled T[] array when items have stable ids.signalMethod over rxMethod when no RxJS operator is needed.Read the one reference file that matches the task. Most tasks need only one.
| Task | Read |
|---|---|
| Plain feature store, computed, methods | references/api-reference.md |
rxMethod / signalMethod / RxJS bridge | references/api-reference.md |
withLinkedState, watchState, events plugin, resource extensions | references/api-reference.md |
| Entity collection (CRUD with IDs) | references/entities.md |
Custom reusable feature (withXxx) | references/custom-features.md |
| Architecture: scoping, optimistic, BS-refactor | references/patterns.md |
| Tests: TestBed, mocks, signalMethod tests | references/testing.md |
If a task spans patterns (e.g., "entity store with optimistic updates and request status"), the scaffold above plus entities.md is usually enough.
If the request or existing code shows any of these, point it out:
@ngrx/store with @ngrx/signals. Different packages. createReducer/createEffect/@Effect is classic NgRx, not Signal Store.HttpClient.T[] arrays for entity data. With stable IDs, use withEntities<T>().store.count.set(...) breaks encapsulation. Expose a method.rxMethod for trivial sync logic. Use signalMethod or a plain method.effect or method that resets selectedId when items change). Use withLinkedState.withState({ count: 0 }) works; withState<CounterState>({ count: 0 }) makes future patches type-safe.withEffects (events plugin) is now withEventHandlers; entity idKey is now selectId via entityConfig; rxMethod(...).unsubscribe() is now .destroy().아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Run a structured bug-focused code review for selected files, folders, or the whole workspace.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Generates Angular code and provides architectural guidance. Trigger when creating projects, components, services, or HTTP communication, or for best practices on reactivity (signals, linkedSignal, resource, httpResource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, naming conventions, or CLI tooling.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Generate an Angular Signal Forms component with schema validation and dynamic field management for a given entity.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Write disciplined Behavior-Driven Development specs and tests with Gherkin / Cucumber. Use whenever the user mentions BDD, Gherkin, Cucumber, .feature files, Given/When/Then, scenarios, Scenario Outline, Background, step definitions, acceptance criteria, executable specifications, living documentation, the Three Amigos, or Example Mapping. Also trigger when the user asks to "write tests in plain English", convert acceptance criteria into tests, design step definitions, refactor an imperative or UI-coupled feature file, or wire up BDD with Vitest (unit/component) or Playwright (end-to-end). Do NOT use for pure unit tests with no business-readable contract, pure performance/load tests, or generic test-framework questions unrelated to behavior specs.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Generate an NgRx Signal Store with complete CRUD operations (API service, store, tests) for a given entity.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Review the current branch against its base branch: lint, tests, architecture and Angular 22 conventions, reported by severity.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.