Angular: Best Practices on Developing with AI
A practical guide for using AI coding assistants effectively in Angular projects, covering project setup, context configuration, architecture patterns, and daily workflows. Includes Claude Code-specific snippets as a ready-to-use reference implementation.
Content Table
- Purpose
- Audience
- Prerequisites
- Setting Up Your Angular Project for AI
- Angular Official AI Resources
- AI Context Files for Angular Projects
- Reusable AI Task Instructions
- Architecture Patterns and Code Snippets
- Daily Workflows with AI Assistants
- Tips and Pitfalls
- References
Purpose
Angular projects benefit greatly from AI-assisted development, but generic LLM output could produce outdated patterns (e.g., NgModules instead of standalone components, *ngIf instead of @if). This guide solves that problem by showing how to configure AI coding assistants with Angular-specific context so they generate idiomatic Angular code from the start.
By the end of this guide you will have:
- An AI context file tailored to your Angular project (with examples for Claude Code, Copilot, Cursor, and others)
- Reusable task instructions for common Angular workflows
- Reference architecture snippets that any AI assistant can follow
- Practical workflows for daily Angular development
Audience
This guide is for Angular developers who want to integrate AI coding assistants into their daily workflow. You should be comfortable with Angular fundamentals (components, services, routing) and have a working Angular project. Prior experience with AI coding assistants is helpful but not required.
Prerequisites
- Node.js (LTS version)
- Angular CLI v19+ or Nx v19+ (for monorepo projects)
- An AI coding assistant — for example, Claude Code, GitHub Copilot, or Cursor
- A working Angular project (new or existing, standalone or Nx monorepo)
Setting Up Your Angular Project for AI
Before writing a single prompt, give your AI assistant the context it needs to understand your project. This section covers the three layers of context you should configure.
Layer 1: Project Context File
Every modern AI coding assistant supports a project-level context file that provides persistent instructions. This file tells the assistant about your project structure, conventions, and constraints.
The content is largely the same across tools — what changes is the file name and location. See AI Context Files for Angular Projects for the full mapping of tools to context files and what to include, and snippets/CLAUDE.md for a ready-to-use Claude Code example you can adapt.
AI Labs: Reusable Task Instructions
Most AI assistants support some form of saved instructions or task templates that can be reused across sessions. For Claude Code, these are called skills — saved instructions stored in .claude/skills/<skill-name>/SKILL.md that the whole team shares via version control.
This guide includes ready-to-use Claude Code skills in the snippets/ directory. See Reusable AI Task Instructions below. The patterns in these skills can be adapted for other tools' custom instruction formats.
Layer 3: MCP Servers (Optional)
Angular CLI MCP Server
Angular CLI v19.2+ includes an experimental MCP server that gives AI assistants direct access to CLI capabilities: code generation, package management, documentation search, and more.
To configure it, run ng mcp in your project and follow the instructions for your editor.
The MCP server provides these tools by default:
| Tool | Description |
|---|---|
ai_tutor | Interactive Angular tutor for projects v20+ |
find_examples | Retrieves curated official code examples |
get_best_practices | Accesses Angular best practices guidance |
list_projects | Enumerates workspace projects from angular.json |
search_documentation | Queries angular.dev docs (requires internet) |
Experimental tools (build, devserver, test, e2e, modernize) can be enabled with the -E flag:
Use --read-only to prevent modifications, or --local-only to restrict to offline-capable tools.
Nx MCP Server (for Nx monorepos)
If your project uses Nx, you can configure the Nx MCP server to give AI assistants access to workspace information, project graphs, generators, and task execution.
The Nx MCP server provides tools for:
- Querying workspace architecture and project dependencies
- Running and inspecting tasks (build, test, lint)
- Accessing generator schemas and running generators
- Visualizing the project graph
- Reading Nx documentation
Playwright MCP Server
The Playwright MCP server gives AI assistants browser interaction capabilities: navigating pages, taking screenshots, and clicking elements. This provides visual context — the assistant can see how your Angular application actually renders and verify UI changes against expectations.
Chrome DevTools MCP Server
The Chrome DevTools MCP server connects AI assistants to Chrome DevTools Protocol, enabling access to console logs, network requests, DOM inspection, and performance data. Useful for debugging runtime issues without manually copying error output into prompts.
Angular Official AI Resources
Angular maintains several AI-specific resources. Reference them rather than recreating the content.
| Resource | URL | Usage |
|---|---|---|
| Best Practices for AI | best-practices.md | System instructions for LLM code generation |
| llms.txt | llms.txt | Index file with links to key resources |
| llms-full.txt | llms-full.txt | Comprehensive Angular documentation for LLMs |
| AI Development Guide | angular.dev/ai | Official guide on developing with AI |
| MCP Server | angular.dev/ai/mcp | Angular CLI MCP setup instructions |
| Web Codegen Scorer | GitHub | Evaluate AI-generated code quality |
Angular also provides specific rules files that embed best practices directly into AI assistants.
These files can be downloaded from the Angular AI page and placed in your project. For Claude Code, the equivalent is your CLAUDE.md file. See AI Context Files for Angular Projects for the full mapping of tools to context files.
AI Context Files for Angular Projects
A well-crafted context file is the single most impactful thing you can do for AI-assisted Angular development. It prevents AI assistants from generating outdated patterns and ensures consistency across the project. The content below applies regardless of which tool you use — only the file name and location differ.
| AI Assistant | Context File | Location |
|---|---|---|
| Claude Code | CLAUDE.md | Project root or .claude/ |
| GitHub Copilot | copilot-instructions.md | .github/ |
| Cursor | .cursorrules | Project root |
What to Include
- Project overview: what the app does, one paragraph
- Tech stack: Angular version, UI library, state management, backend
- Architecture conventions: folder structure, naming rules
- Angular-specific rules: modern patterns to follow, legacy patterns to avoid
- Commands: build, test, lint, serve commands
- Domain context: business terms, API patterns, key entities
Example
A full example context file is provided at snippets/CLAUDE.md. The content can be copied into any tool's context file. Here is the key Angular conventions section:
## Angular Conventions
- Use standalone components exclusively (no NgModules)
- Use signals for state management (`signal()`, `computed()`, `effect()`)
- Use the new control flow syntax: `@if`, `@for`, `@switch` (not `*ngIf`, `*ngFor`, `[ngSwitch]`)
- Use `input()` and `output()` functions (not `@Input()` / `@Output()` decorators)
- Use `inject()` function for dependency injection (not constructor injection)
- Use `ChangeDetectionStrategy.OnPush` on all components
- Use reactive forms (not template-driven forms)
- Use route-level lazy loading for feature areas
- Use `NgOptimizedImage` for static images
- Prefer `class` and `style` bindings over `ngClass` / `ngStyle`
- Distinguish page (container) components from view (presentational) components
- For NgRx projects: use `createActionGroup()`, `createReducer()`, `createSelector()`, `createEffect()`The full
snippets/CLAUDE.mdfile includes project structure, commands, domain context, and more. Copy the content into your tool's context file as a starting point and tailor it to your project.
Reusable AI Task Instructions
Reusable task instructions automate repetitive Angular tasks with consistent quality. The concept applies across tools — saved prompts or templates that encode your team's conventions so every AI interaction follows the same standards.
Skills Across Tools
Several AI assistants now support a skills concept — reusable Markdown files with structured instructions that the assistant can invoke for specific tasks.
| AI Assistant | Skills Location | File Format | Docs |
|---|---|---|---|
| Claude Code | .claude/skills/<name>/SKILL.md | Markdown with YAML frontmatter | Claude Code Skills |
| Cursor | .cursor/skills/<name>/SKILL.md | Markdown with YAML frontmatter | Cursor Skills |
| GitHub Copilot | .github/skills/<name>.md or .claude/skills/<name>/SKILL.md | Markdown with YAML frontmatter | Copilot Skills |
All three tools use directory-based skills centered on a SKILL.md file with YAML frontmatter and free-form Markdown instructions, so most basic syntax and structure is shared; they use the same open format, making skills directly portable between them.
For a community-maintained collection of Angular-specific skills, see angular-skills.
Task Overview
| Skill | Trigger | Purpose |
|---|---|---|
ng-component | /ng-component <name> | Generate a component following project conventions |
ng-service | /ng-service <name> | Generate a service with injection and tests |
ng-test | /ng-test | Write or improve unit tests for Angular code |
ng-review | /ng-review | Review Angular code for best practices |
ng-component
Generates a standalone Angular component with signals, OnPush change detection, and proper file structure.
Location: .claude/skills/ng-component/SKILL.mdFull file: snippets/claude-skills/ng-component/SKILL.md
Usage:
/ng-component user-profile
/ng-component order-list --feature ordersng-service
Generates an Angular service with proper injection, error handling, and a companion .spec.ts file.
Location: .claude/skills/ng-service/SKILL.mdFull file: snippets/claude-skills/ng-service/SKILL.md
Usage:
/ng-service auth
/ng-service product-catalogng-test
Analyzes existing Angular code and writes or improves unit tests. Handles components, services, pipes, and directives.
Location: .claude/skills/ng-test/SKILL.mdFull file: snippets/claude-skills/ng-test/SKILL.md
Usage:
/ng-test src/app/features/auth/login.component.ts
/ng-test src/app/core/services/api.service.tsng-review
Reviews Angular code for adherence to modern best practices and project conventions.
Location: .claude/skills/ng-review/SKILL.mdFull file: snippets/claude-skills/ng-review/SKILL.md
Usage:
/ng-review src/app/features/dashboard/
/ng-review src/app/shared/components/data-table/Architecture Patterns and Code Snippets
This section provides reference patterns that AI assistants can follow when generating code for your project. Include these in your context file or as supporting files in your task instructions.
Project Structure
A recommended Angular project structure for medium-to-large applications:
src/
app/
core/ # Singleton services, guards, interceptors
guards/
auth.guard.ts
interceptors/
api.interceptor.ts
services/
auth.service.ts
api.service.ts
shared/ # Reusable components, directives, pipes
components/
confirm-dialog/
confirm-dialog.component.ts
confirm-dialog.component.html
confirm-dialog.component.scss
confirm-dialog.component.spec.ts
directives/
highlight.directive.ts
pipes/
format-date.pipe.ts
features/ # Feature areas (lazy-loaded)
dashboard/
dashboard.component.ts
dashboard.component.html
dashboard.routes.ts
users/
user-list/
user-list.component.ts
user-list.component.html
user-detail/
user-detail.component.ts
models/
user.model.ts
services/
user.service.ts
users.routes.ts
app.component.ts
app.routes.ts
app.config.ts
environments/
environment.ts
environment.prod.tsKey principles:
core/contains singleton services, guards, and interceptors used application-wideshared/contains reusable, presentational components, directives, and pipesfeatures/groups code by business domain, each with its own routes file for lazy loading- Colocate related files (component + template + styles + tests) in the same folder
Nx Monorepo Project Structure
For teams using Nx, the structure shifts to a library-based architecture:
apps/
my-app/
src/app/
components/
pages/ # App-level container components
landing-page.component.ts
views/ # App-level presentational components
landing-view.component.ts
config/ # App configuration and constants
app-initializer.config.ts
constants/
types/ # App-level types
app.component.ts
app.routes.ts
app.config.ts
libs/ui/
component-library/ # Pure UI components (no business logic)
src/components/
actions/
button.component.ts
display/
logo.component.ts
data-access/ # State management and API layer
src/lib/
api/ # REST API services
items-api.service.ts
clients/ # HTTP, WebSocket, auth token clients
state/ # NgRx feature slices
items/
items.actions.ts
items.reducers.ts
items.selectors.ts
items.effects.ts
config/
data-access-state.config.ts # provideState() and provideEffects()
shared-ui/ # Shared services, guards, directives
src/
services/
guards/
directives/
pipes/
features/ # Feature libraries (lazy-loaded)
dashboard/
src/lib/
components/
pages/
views/
dashboard.routes.tsKey principles:
component-library/has zero business logic — pure UI building blocksdata-access/owns all state and API communicationfeatures/are lazy-loaded and depend ondata-accessandshared-ui, never on each other- Module boundaries are enforced via ESLint
@nx/enforce-module-boundarieswith scope tags - Each library exposes a public API via
index.tsbarrel exports
Page and View Component Pattern
Separating container (page) and presentational (view) components keeps responsibilities clear:
- Page components (
pages/): inject services, manage state, handle navigation, delegate rendering to views - View components (
views/): receive data viainput(), emit events viaoutput(), contain no service injection
// landing-page.component.ts (page / container)
@Component({
selector: 'app-landing-page',
standalone: true,
imports: [LandingViewComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<app-landing-view (signIn)="onSignIn()" (signUp)="onSignUp()" />`
})
export class LandingPageComponent {
private readonly nav = inject(AppNavigationService);
async onSignIn(): Promise<void> {
await this.nav.navigateToSignIn();
}
async onSignUp(): Promise<void> {
await this.nav.navigateToSignUp();
}
}// landing-view.component.ts (view / presentational)
@Component({
selector: 'app-landing-view',
standalone: true,
imports: [HeroSectionComponent, OverviewSectionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-hero-section (signInClicked)="signIn.emit()" />
<app-overview-section />
`
})
export class LandingViewComponent {
signIn = output();
signUp = output();
}Standalone Component Pattern
// user-profile.component.ts
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
import { DatePipe } from '@angular/common';
export interface UserProfile {
id: string;
name: string;
email: string;
createdAt: Date;
}
@Component({
selector: 'app-user-profile',
standalone: true,
imports: [DatePipe],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="profile-card">
@if (user(); as u) {
<h2>{{ u.name }}</h2>
<p>{{ u.email }}</p>
<small>Member since {{ u.createdAt | date:'mediumDate' }}</small>
} @else {
<p>No user selected</p>
}
</div>
`,
styles: `
.profile-card {
padding: 1rem;
border: 1px solid var(--border-color);
border-radius: 8px;
}
`
})
export class UserProfileComponent {
user = input<UserProfile>();
displayName = computed(() => this.user()?.name?.toUpperCase() ?? 'Unknown');
}Patterns demonstrated:
standalone: true(no NgModule needed)ChangeDetectionStrategy.OnPushinput()function instead of@Input()decoratorcomputed()for derived state@ifcontrol flow withasalias- Inline template and styles (suitable for small components)
Signal-Based Service Pattern
// user.service.ts
import { computed, inject, Injectable, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { catchError, EMPTY, tap } from 'rxjs';
export interface User {
id: string;
name: string;
email: string;
}
@Injectable({ providedIn: 'root' })
export class UserService {
private readonly http = inject(HttpClient);
// State
private readonly _users = signal<User[]>([]);
private readonly _loading = signal(false);
private readonly _error = signal<string | null>(null);
// Public selectors
readonly users = this._users.asReadonly();
readonly loading = this._loading.asReadonly();
readonly error = this._error.asReadonly();
readonly userCount = computed(() => this._users().length);
loadUsers(): void {
this._loading.set(true);
this._error.set(null);
this.http.get<User[]>('/api/users').pipe(
tap(users => {
this._users.set(users);
this._loading.set(false);
}),
catchError(err => {
this._error.set(err.message);
this._loading.set(false);
return EMPTY;
})
).subscribe();
}
addUser(user: Omit<User, 'id'>): void {
this.http.post<User>('/api/users', user).pipe(
tap(created => this._users.update(users => [...users, created])),
catchError(err => {
this._error.set(err.message);
return EMPTY;
})
).subscribe();
}
}Patterns demonstrated:
inject()function instead of constructor injectionprovidedIn: 'root'for tree-shakable singleton- Private writable signals with public readonly selectors
computed()for derived stateupdate()for immutable array operations- RxJS for HTTP calls with signal-based state
Lazy-Loaded Feature Routes
// app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
redirectTo: 'dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
loadComponent: () =>
import('./features/dashboard/dashboard.component')
.then(m => m.DashboardComponent)
},
{
path: 'users',
loadChildren: () =>
import('./features/users/users.routes')
.then(m => m.usersRoutes)
},
{
path: 'settings',
loadChildren: () =>
import('./features/settings/settings.routes')
.then(m => m.settingsRoutes),
canActivate: [() => inject(AuthService).isAuthenticated()]
}
];// features/users/users.routes.ts
import { Routes } from '@angular/router';
export const usersRoutes: Routes = [
{
path: '',
loadComponent: () =>
import('./user-list/user-list.component')
.then(m => m.UserListComponent)
},
{
path: ':id',
loadComponent: () =>
import('./user-detail/user-detail.component')
.then(m => m.UserDetailComponent)
}
];Patterns demonstrated:
loadComponentfor single-component routesloadChildrenfor feature route groups- Functional route guards with
inject() - Each feature owns its routes file
Reactive Form Pattern
// edit-user.component.ts
import {
ChangeDetectionStrategy,
Component,
inject,
input,
output
} from '@angular/core';
import {
FormBuilder,
ReactiveFormsModule,
Validators
} from '@angular/forms';
import { User } from '../models/user.model';
@Component({
selector: 'app-edit-user',
standalone: true,
imports: [ReactiveFormsModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<label>
Name
<input formControlName="name" />
@if (form.controls.name.errors?.['required'] && form.controls.name.touched) {
<span class="error">Name is required</span>
}
</label>
<label>
Email
<input formControlName="email" type="email" />
@if (form.controls.email.errors?.['email'] && form.controls.email.touched) {
<span class="error">Invalid email</span>
}
</label>
<button type="submit" [disabled]="form.invalid">Save</button>
</form>
`
})
export class EditUserComponent {
private readonly fb = inject(FormBuilder);
user = input.required<User>();
saved = output<User>();
form = this.fb.nonNullable.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
constructor() {
// Populate form when user input changes
effect(() => {
const u = this.user();
this.form.patchValue({ name: u.name, email: u.email });
});
}
onSubmit(): void {
if (this.form.valid) {
this.saved.emit({ ...this.user(), ...this.form.getRawValue() });
}
}
}Patterns demonstrated:
- Reactive forms with
FormBuilderandnonNullable input.required()for mandatory inputsoutput()function instead of@Output()decorator@ifcontrol flow for validation messageseffect()to sync signal input to reactive form
HTTP Interceptor Pattern
// api.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
export const apiInterceptor: HttpInterceptorFn = (req, next) => {
const auth = inject(AuthService);
const token = auth.token();
if (token) {
req = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
}
return next(req);
};// app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { apiInterceptor } from './core/interceptors/api.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(withInterceptors([apiInterceptor]))
]
};Patterns demonstrated:
- Functional interceptor (
HttpInterceptorFn) instead of class-based inject()inside the interceptor functionprovideHttpClientwithwithInterceptorsin app config- No NgModule — pure function-based configuration
Component Communication with Signals
// parent.component.ts
@Component({
selector: 'app-parent',
standalone: true,
imports: [ChildComponent],
template: `
<app-child
[items]="filteredItems()"
(itemSelected)="onItemSelected($event)"
/>
<p>Selected: {{ selectedItem()?.name ?? 'none' }}</p>
`
})
export class ParentComponent {
private readonly itemService = inject(ItemService);
searchTerm = signal('');
selectedItem = signal<Item | null>(null);
// Derived state: filters items reactively when searchTerm or items change
filteredItems = computed(() => {
const term = this.searchTerm().toLowerCase();
return this.itemService.items().filter(
item => item.name.toLowerCase().includes(term)
);
});
onItemSelected(item: Item): void {
this.selectedItem.set(item);
}
}// child.component.ts
@Component({
selector: 'app-child',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@for (item of items(); track item.id) {
<div (click)="itemSelected.emit(item)" class="item">
{{ item.name }}
</div>
} @empty {
<p>No items found</p>
}
`
})
export class ChildComponent {
items = input.required<Item[]>();
itemSelected = output<Item>();
}Patterns demonstrated:
- Parent manages state with signals
computed()derives a filtered list reactively- Child receives data via
input.required(), emits viaoutput() @forwithtrackfor efficient rendering@emptyblock for empty state
NgRx Store Pattern
For projects using NgRx for state management, follow this pattern for feature state slices:
// items.actions.ts
import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { Item } from '../../types/item.types';
export const ItemsActions = createActionGroup({
source: 'Items',
events: {
'Load Items': emptyProps(),
'Load Items Success': props<{ items: Item[] }>(),
'Load Items Failure': props<{ error: string }>(),
}
});// items.reducers.ts
import { createReducer, on } from '@ngrx/store';
import { EntityAdapter, EntityState, createEntityAdapter } from '@ngrx/entity';
import { ItemsActions } from './items.actions';
export interface ItemsState extends EntityState<Item> {
loading: boolean;
error: string | null;
}
export const itemsAdapter: EntityAdapter<Item> = createEntityAdapter<Item>();
const initialState: ItemsState = itemsAdapter.getInitialState({
loading: false,
error: null
});
export const itemsReducer = createReducer(
initialState,
on(ItemsActions.loadItems, state => ({ ...state, loading: true, error: null })),
on(ItemsActions.loadItemsSuccess, (state, { items }) =>
itemsAdapter.setAll(items, { ...state, loading: false })
),
on(ItemsActions.loadItemsFailure, (state, { error }) => ({
...state, loading: false, error
}))
);// items.selectors.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { ItemsState, itemsAdapter } from './items.reducers';
const selectItemsState = createFeatureSelector<ItemsState>('items');
const { selectAll, selectTotal } = itemsAdapter.getSelectors();
export const ItemsSelectors = {
selectAllItems: createSelector(selectItemsState, selectAll),
selectItemCount: createSelector(selectItemsState, selectTotal),
selectLoading: createSelector(selectItemsState, state => state.loading),
selectError: createSelector(selectItemsState, state => state.error),
};// items.effects.ts
@Injectable()
export class ItemsEffects {
private readonly actions$ = inject(Actions);
private readonly api = inject(ItemsApiService);
readonly loadItems$ = createEffect(() =>
this.actions$.pipe(
ofType(ItemsActions.loadItems),
exhaustMap(() =>
this.api.getItems().pipe(
map(items => ItemsActions.loadItemsSuccess({ items })),
catchError(error => of(ItemsActions.loadItemsFailure({ error: error.message })))
)
)
)
);
}// data-access-state.config.ts — register in app.config.ts
export function provideDataAccessState() {
return [
provideState('items', itemsReducer),
provideEffects(ItemsEffects),
];
}Patterns demonstrated:
createActionGroup()for typed, organized actionsEntityAdapterfor normalized collection state- Memoized selectors exported as a named object
- Effects use
inject()and handle errors withcatchError - Separate API service keeps effects focused on orchestration
- State registration via
provideState()andprovideEffects()
Daily Workflows with AI Assistants
Generating a New Feature
Describe the feature with enough context. Include the data model, user interactions, and where it fits in the app.
Standard Angular CLI project:
Create a "notifications" feature under src/app/features/notifications/ with:
- A NotificationService that fetches from GET /api/notifications and uses signals for state
- A notification-list component that displays notifications with @for
- A notification-badge component that shows unread count using computed()
- Lazy-loaded routes in notifications.routes.ts
- Unit tests for the service and both components
Follow the patterns in the project context file.Nx monorepo project (with NgRx):
Create a "notifications" feature:
- In libs/ui/data-access/src/lib/state/notifications/, create NgRx actions, reducer, selectors, and effects
- In libs/ui/data-access/src/lib/api/, create a NotificationsApiService for GET /api/notifications
- In libs/ui/features/notifications/src/lib/components/, create:
- pages/notifications-page.component.ts (dispatches actions, selects from store)
- views/notification-list-view.component.ts (displays items with @for)
- views/notification-badge-view.component.ts (shows unread count via input)
- Add lazy-loaded routes in libs/ui/features/notifications/src/lib/notifications.routes.ts
- Register the state slice in data-access-state.config.ts
- Unit tests for all artifacts
Follow the patterns in the project context file.Migrating Legacy Code
When migrating from older Angular patterns, be explicit about what to change and what to preserve.
Migrate src/app/legacy/user-management/ from NgModule-based to standalone components:
- Convert UserManagementModule to standalone components with lazy-loaded routes
- Replace *ngIf and *ngFor with @if and @for
- Replace @Input()/@Output() decorators with input()/output() functions
- Replace constructor injection with inject()
- Add ChangeDetectionStrategy.OnPush to all components
- Keep all existing functionality and tests passingWriting Tests
Point the AI assistant at the specific file and describe coverage expectations. The example below uses a Claude Code skill trigger, but the prompt content works with any tool.
/ng-test src/app/features/orders/order.service.ts
Additional requirements:
- Mock HttpClient using HttpTestingController
- Test loadOrders success and error paths
- Test signal state transitions (loading, error, data)
- Test computed property orderCountCode Review
Ask the AI assistant to review a feature directory or specific files. The example below uses a Claude Code skill trigger, but the prompt content works with any tool.
/ng-review src/app/features/checkout/
Focus on:
- Signal usage and reactivity patterns
- Route guard implementation
- Form validation completenessDebugging
Provide the error message and relevant context. AI assistants can read the files and trace the issue.
I'm getting "NG0100: ExpressionChangedAfterItHasBeenCheckedError" in the
dashboard component. The component uses a signal that is updated in ngOnInit
by calling a service method. Help me find and fix the root cause.Tips and Pitfalls
Do
- Keep your context file updated. When your team adopts new conventions, update the file. It is the single source of truth for your AI assistant.
- Use reusable instructions for repetitive tasks. If your team generates the same kind of component repeatedly, a saved instruction template ensures consistency.
- Provide concrete examples. Instead of "use signals", show the AI assistant a 10-line example from your codebase. Patterns in context files beat abstract rules.
- Be specific in prompts. "Create a component" is vague. "Create a standalone component at
src/app/features/orders/order-summary/that takes anOrderinput and displays line items with@for" is actionable. - Iterate. If the output isn't right, tell the assistant what's wrong specifically. "The component should use
input()not@Input()" is more useful than "fix it". - Specify your state management approach. If your project uses NgRx, say so in your context file and prompts. Otherwise AI assistants may default to signal-based services.
- Document Nx library boundaries. If using Nx, include the scope tag rules so the AI assistant respects module boundaries when placing new files.
Avoid
- Don't rely on AI knowing your Angular version. Always state it in your context file. Angular evolves rapidly; v19 patterns differ from v17, and v20 introduces further changes.
- Don't skip the review. AI-generated code can introduce subtle issues: missing
trackexpressions in@for, incorrect form validation, or services not properly error-handled. - Don't generate entire features in one shot. Break large features into smaller requests: service first, then components, then routes, then tests. This produces better results.
- Don't use deprecated patterns in examples. If your context file shows
*ngIfexamples, the AI assistant will follow them. Keep your reference code modern. - Don't ignore MCP servers. The Angular CLI MCP and Nx MCP servers can look up documentation, run generators, and query workspace structure directly, reducing hallucination.
References
Angular and AI
- Angular: Develop with AI: Official Angular AI development guide
- Angular Best Practices for AI: Angular's system instructions for LLMs
- Angular CLI MCP Server: MCP server setup and configuration
- Angular llms.txt: LLM-optimized documentation index
- Web Codegen Scorer: Evaluate AI-generated code quality
Angular Ecosystem
- NgRx Documentation: NgRx Store, Effects, and Entity documentation
- Nx Documentation: Nx monorepo tooling and architecture
- Nx Module Boundaries: Enforcing library dependency rules
Claude Code (used for snippets in this guide)
- Claude Code Skills: Creating and managing Claude Code skills
- Claude Code Memory: CLAUDE.md and persistent context
- Access Claude Code via Cloud Guide: Enterprise Claude Code setup