Angular’s opinionated structure is a strength when you know how to work with it. These recipes give you prompts that produce Angular code following the framework’s conventions — standalone components, signals for reactivity, dependency injection done right, and RxJS patterns that do not leak subscriptions.
Standalone component recipes with signals and new control flow syntax
Service and dependency injection patterns with proper typing
RxJS recipes for complex async flows without subscription leaks
Testing prompts for TestBed, component harnesses, and HTTP mocking
Scenario: You need a product card component using Angular’s latest standalone components and signals API.
Tip
Create a standalone Angular component at src/app/components/product-card/product-card.component.ts. Use the input() function for typed inputs: product (required, Product type), showActions (optional, default true). Use output() for typed outputs: addToCart emits Product, viewDetails emits string (product ID). Use computed() for derived state: formattedPrice (formats product.price with currency), isOnSale (checks if discountPercentage > 0), discountedPrice (computed from price and discount). Use the new @if, @for, @switch control flow syntax in the template instead of *ngIf and *ngFor. Style with Tailwind CSS. Include a skeleton loading state. Write the spec file using TestBed with provideExperimentalZonelessChangeDetection() for signal-based testing. Cover all input combinations and output emissions.
Expected output: Component TypeScript file, inline template with new control flow, and spec file.
Scenario: Your auth service uses BehaviorSubjects and manual subscriptions everywhere. Modernize it with signals while keeping RxJS for HTTP.
Tip
Refactor src/app/services/auth.service.ts to use signals for state and RxJS only for async operations. (1) Replace BehaviorSubject<User | null> with signal<User | null>(null). (2) Create computed signals: isAuthenticated = computed(() => this.currentUser() !== null), userRole = computed(() => this.currentUser()?.role ?? 'guest'). (3) Keep HttpClient calls as Observables but convert results to signal updates: login returns Observable<void>, internally calls the API and sets the user signal. (4) Use effect() to persist auth state to localStorage. (5) Add toObservable(this.currentUser) for Observable compatibility during migration. (6) Implement token refresh: an HttpInterceptor that catches 401 responses, calls refreshToken(), retries the original request, and logs out if refresh fails. (7) Register as providedIn: 'root'. Write tests verifying login sets the signal, logout clears it, and the interceptor handles token refresh.
Expected output: Refactored service, HTTP interceptor, and comprehensive test file.
Scenario: Your registration form needs cross-field validation, async validation, and conditional fields.
Tip
Create a registration form using Angular’s typed reactive forms. (1) Define the form with NonNullableFormBuilder: email (required, email format, async uniqueness validator debouncing 300ms), password (required, minLength 8, pattern requiring uppercase+number+special), confirmPassword (required, must match password via cross-field validator), accountType (‘personal’ | ‘business’), companyName (required only when accountType is ‘business’). (2) Create custom validators: matchField(fieldName) for password confirmation, asyncEmailUnique() returning AsyncValidatorFn. (3) Create reusable ValidationMessage component accepting FormControl and error-to-message map. (4) Dynamically show/hide companyName based on accountType. (5) Disable submit until form is valid and not pending. Test form validation including async email check with fakeAsync.
Expected output: Form component, custom validators, ValidationMessage component, and tests with fakeAsync.
Scenario: Your admin section has 8 pages and loads everything upfront. Lazy-load it as standalone routes.
Tip
Refactor the admin section to use lazy-loaded standalone routes. (1) Create src/app/admin/admin.routes.ts with routes for dashboard, users, settings, reports. Each loads with loadComponent: () => import('./pages/admin-dashboard.component'). (2) In main routes, lazy-load admin: { path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES), canActivate: [authGuard], data: { roles: ['admin'] } }. (3) Create functional authGuard using inject(AuthService) to check authentication and role. (4) Create AdminLayoutComponent with sidebar navigation, breadcrumbs, and router-outlet. (5) Create provideAdminServices() for route-level admin services destroyed on navigation away. (6) Verify the admin chunk loads only when navigating to /admin. Test the guard redirects unauthorized users.
Expected output: Admin routes, layout component, functional guard, route-level providers, and guard tests.
Scenario: Your app needs centralized state but classic NgRx actions/reducers/effects feels heavy. Use SignalStore.
Tip
Implement state management using @ngrx/signals SignalStore for products. (1) Create src/app/stores/products.store.ts using signalStore() with state: products array, selectedProductId, filters object, loading boolean, error string. (2) Add withComputed(): filteredProducts (applies all filters), selectedProduct (finds by ID), totalCount, hasActiveFilters. (3) Add withMethods(): loadProducts() (calls API), selectProduct(id), setFilter(key, value), clearFilters(), addProduct, updateProduct, deleteProduct with optimistic removal. (4) Add withHooks() for onInit to load products. (5) Add rxMethod for search debounce. (6) Provide at feature route level. Write tests verifying computed values update, methods call API, and optimistic updates roll back on error.
Expected output: SignalStore file, API service, and comprehensive store tests.
Scenario: Your app needs auth tokens, logging, error handling, and caching as HTTP interceptors.
Tip
Create functional HTTP interceptors in src/app/interceptors/. (1) authInterceptor — adds Bearer token, skips public endpoints. (2) loggingInterceptor — logs method, URL, timing; in dev mode logs bodies. (3) errorInterceptor — catches 401 (refresh + retry), 403 (redirect), 429 (toast with countdown), 500+ (generic toast). Sends to error tracking. (4) cachingInterceptor — caches GET responses in a Map with TTL, skips if Cache-Control: no-cache. (5) Register in app.config.ts with provideHttpClient(withInterceptors([...])) in correct order (auth first, caching, logging, error last). Test each interceptor in isolation with HttpTestingController.
Expected output: Four interceptor files, app config registration, and individual test files.
Scenario: Your app renders forms from a backend JSON schema. You need a dynamic form generator.
Tip
Build a dynamic form system. (1) Define FormFieldConfig interface: type (text, email, number, select, checkbox, radio, textarea, date), key, label, validators (required, minLength, maxLength, pattern, custom), options (for select/radio), defaultValue, dependsOn (conditional visibility). (2) Create DynamicFormComponent receiving FormFieldConfig[], building a FormGroup dynamically, rendering each field using @switch on type, handling conditional visibility. (3) Create individual field components for each type. (4) The form emits (formSubmit) with typed value and (formChange) on every change. (5) Create FormSchemaService fetching schema from API with caching. (6) Test that a JSON schema produces correct fields, validation works, and conditional fields toggle properly.
Expected output: Form config interface, dynamic form component, field components, schema service, and tests.
Scenario: Pages show a flash of empty content before data loads. You need resolvers that load data before rendering.
Tip
Implement functional route resolvers. (1) Create productResolver as ResolveFn<Product> reading id from params, calling ProductService.getById(id), redirecting to /products with toast on not-found. (2) Create userProfileResolver resolving the authenticated user’s profile. (3) In route config, add resolve: { product: productResolver }. (4) In components, access resolved data via input() with the resolve key. (5) Add a global RouteLoadingComponent showing during resolver execution using Router.events. (6) Add error handling: if a resolver throws, redirect to error page. Test resolvers return correct data and handle errors.
Expected output: Two resolver functions, loading component, error handler, and resolver tests.
Scenario: Your templates repeat the same DOM manipulation. You need custom directives for common behaviors.
Tip
Create directives in src/app/directives/. (1) ClickOutsideDirective — emits on clicks outside the host element. (2) IntersectionDirective — emits when the element enters/leaves viewport. (3) PermissionDirective — structural directive like *appPermission="'admin'" showing/hiding based on role. (4) AutofocusDirective — focuses after configurable delay, handles SSR. (5) LongPressDirective — emits after 500ms hold, cancels on leave. (6) TooltipDirective — shows tooltip on hover with configurable position and delay. Each directive should be standalone and importable individually. Write tests for each using component harness testing.
Expected output: Six directive files and six test files.
Scenario: Your Angular app needs SSR for SEO and initial load performance with full hydration.
Expected output: SSR config, platform service, storage service, transfer state integration, and tests.
Scenario: Your monolithic Angular app needs to be split into independently deployable micro-frontends.
Tip
Set up Module Federation for Angular micro-frontends. (1) Configure the shell with @angular-architects/module-federation: define remotes in webpack config. (2) Configure a remote (products): expose ProductsModule. (3) Create loadRemoteModule utility with error handling and retry. (4) In shell routing, lazy-load the remote. (5) Create shared library for types, communication service, and shared UI. (6) Configure sharedMappings for Angular core to ensure single instance. (7) Add fallback UI when a remote fails. Test shell loads remote and shared state synchronizes.
Expected output: Host and remote configs, loader utility, shared library, fallback component, and tests.
Scenario: Your templates have inline string manipulation everywhere. Extract into reusable pipes.
Tip
Create standalone pipes in src/app/pipes/. (1) RelativeTimePipe — transforms Date to “2 minutes ago”, etc. (2) TruncatePipe — truncates to length with optional word boundary. (3) FileSizePipe — bytes to KB/MB/GB with configurable decimals. (4) HighlightPipe — wraps matching substrings in mark tags using DomSanitizer. (5) PluralizePipe — count with singular/plural forms. (6) InitialsPipe — extracts initials from name. (7) CurrencyFormatPipe — locale-aware formatting with Intl.NumberFormat. All standalone and tree-shakeable. Test each with edge cases (null, undefined, empty string, zero, negative).
Expected output: Seven pipe files with edge case tests for each.