Copy-paste Claude Code prompts for Angular 18 — signals, standalone components, the new control flow, inject(), and Jest tests. No NgModules.
Angular changed a lot in the last 18 months — signals, standalone, new control flow, inject(). Claude's defaults straddle old and new patterns. A short policy block in CLAUDE.md collapses the variance to one consistent style.
Build a feature component ProjectListComponent at
src/app/features/projects/project-list/project-list.component.ts.
Behaviour:
- Loads projects via a ProjectsService (already exists — read it first)
- Shows a search input that filters the list
- Sorts by name or createdAt (toggle button)
- Empty state when no items match
Constraints:
- Standalone component (standalone: true, imports: [...]).
- All component state as signals: items = signal<Project[]>([]),
searchQuery = signal(''), sortKey = signal<'name'|'createdAt'>('name').
- filtered = computed(() => ...) derived from items + searchQuery + sortKey.
- Template uses @for and @if (new control flow), NOT *ngFor / *ngIf.
- Dependencies via inject(): private projectsService = inject(ProjectsService).
- Use the AsyncPipe ONLY for the initial load observable. After loading,
pipe results into the signal.
- Tests: ng-mocks + Jest. Mount, type into the search, assert filtered count.
Add an interceptor src/app/core/interceptors/auth.interceptor.ts that:
- Adds Authorization: Bearer to all requests under /api
- On 401: calls authService.refreshToken(), retries the original request
once, and only fails if the refresh also fails
- On refresh failure: calls authService.logout() and rethrows
- Concurrent 401s share ONE refresh (don't fire N refresh calls)
Constraints:
- Functional interceptor (export const authInterceptor: HttpInterceptorFn = ...).
- Registered in app.config.ts provideHttpClient(withInterceptors([authInterceptor])).
- Use a Subject or signal to debounce concurrent refreshes.
- Tests: HttpTestingController-based unit tests covering each branch.
The src/app/features/billing/ folder still uses NgModules. Convert it to standalone.
Steps:
1. List every component, directive, pipe declared in BillingModule.
2. For each: mark standalone: true, move its declarations into imports.
3. Update the lazy-loaded route: replace loadChildren returning a module
with loadComponent returning the root component, or loadChildren returning
a route array.
4. Delete BillingModule and BillingRoutingModule.
5. Update app.routes.ts to point at the new lazy route.
Constraints:
- One commit per component to keep the diff reviewable — but produce them all
in one prompt response, separated by clear --- markers.
- Don't touch files outside features/billing/ except app.routes.ts.
## Stack
Angular 18, TypeScript strict, Jest + ng-mocks, Tailwind v4, NgRx Signals.
## Conventions
- Standalone components — no NgModule.
- Component state: signals. Streams: RxJS, sparingly.
- Dependencies: inject() at top of class. No constructor DI.
- Templates: new control flow (@if @for @switch). No structural directive *.
- Folder layout: features//.component.ts colocated with .html, .scss, .spec.
Related: refactoring prompts.