用 Claude Code 做 Angular 开发:CLI、Standalone、表单、HTTP 与测试实战
面向真实 Angular 项目,讲解如何用 Claude Code 处理 CLI、standalone component、Reactive Forms、HttpClient、单元测试与 E2E。
在 Angular 项目里使用 Claude Code,重点不是让它一次性“生成一个漂亮页面”。真正有价值的做法,是让它先读懂项目,再按 Angular CLI、standalone component、Reactive Forms、HttpClient、测试和代码审查的顺序推进。这样生成的差异更小,也更容易被团队 review。
standalone component 可以理解为“不依赖 NgModule 注册、自己声明 imports 的组件”。Reactive Forms 是“把表单值和校验规则放在 TypeScript 中显式管理”的方式。HttpClient 是 Angular 官方提供的后端通信 API。把这些词先讲清楚,给 Claude Code 的指令才不会停留在“帮我写个表单”。
参考资料建议固定使用官方文档:Angular CLI、Reactive forms、HttpClient、Angular testing 以及 Claude Code workflows。站内延伸可以看 TypeScript 技巧、测试策略 和 CLAUDE.md 最佳实践。
先限定 Claude Code 的工作边界
给 Claude Code 一个 harness,也就是“代理工作的安全框架”。它应该知道能改哪些文件、不能改哪些文件、项目使用什么测试命令,以及 review 时要看哪些问题。
先阅读这个 Angular 项目。请查看 package.json、angular.json、src/app,
说明项目是否使用 standalone component、当前测试工具是什么、
HttpClient 在哪里提供、命名规范有哪些。现在不要编辑文件。
| 层级 | 交给 Claude Code | 人来决定 |
|---|---|---|
| Angular CLI | 生成命令、文件路径、依赖检查 | 路由归属、团队命名规范 |
| Component | imports、模板、signals、事件处理 | UI 文案、可访问性、发布风险 |
| Reactive Forms | FormGroup、校验、提交状态 | 业务规则、错误提示语 |
| Service / HTTP | 类型、API 方法、测试后端 | API 合约、认证、错误分类 |
| 测试 | 单元和 E2E 用例 | 覆盖率目标、是否合并 |
flowchart LR
P[Prompt] --> C[Angular CLI]
C --> A[Standalone component]
A --> F[Reactive Forms]
A --> S[Ticket service]
S --> H[HttpClient]
F --> U[Unit tests]
A --> E[E2E tests]
U --> R[Review]
E --> R
用 Angular CLI 建立可验证的起点
新项目可以从 CLI 开始。老项目不要直接套命令,而是先让 Claude Code 读取配置。
npm install -g @angular/cli
ng new support-desk-angular --standalone --routing --style css
cd support-desk-angular
ng generate component features/tickets/ticket-intake --standalone
ng generate service data/ticket
ng test
这里的重点是“先确认基线”。如果 ng test 在修改前就失败,Claude Code 后面会把旧问题和新问题混在一起。多人协作时,还要明确“只处理这个功能目录,不回滚其他未提交修改”。
先写 typed service,再写 UI
表单之前先定义 API service,review 会更清楚。下面的文件可以放在 src/app/data/ticket.service.ts。
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
export type TicketPriority = 'low' | 'medium' | 'high';
export interface TicketDraft {
title: string;
email: string;
priority: TicketPriority;
message: string;
}
export interface Ticket extends TicketDraft {
id: string;
createdAt: string;
}
@Injectable({ providedIn: 'root' })
export class TicketService {
private readonly http = inject(HttpClient);
private readonly baseUrl = '/api/tickets';
listTickets(): Observable<Ticket[]> {
return this.http.get<Ticket[]>(this.baseUrl);
}
createTicket(draft: TicketDraft): Observable<Ticket> {
return this.http.post<Ticket>(this.baseUrl, draft);
}
}
standalone 应用通常在 app.config.ts 中提供 HttpClient。
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideHttpClient()],
};
常见失败是让 service 同时处理 UI 文案、重试策略和本地状态。service 应该保持朴素:负责端点、类型和通信,不负责“页面上怎么说”。
组合 standalone component 与 Reactive Forms
这个组件用 Reactive Forms 管理输入,用 signal 管理保存状态。让 Claude Code 生成时要明确“不要使用 ngModel”。
import { Component, computed, inject, signal } from '@angular/core';
import { ReactiveFormsModule, FormControl, FormGroup, Validators } from '@angular/forms';
import { finalize } from 'rxjs';
import { Ticket, TicketService, TicketPriority } from '../../../data/ticket.service';
@Component({
selector: 'app-ticket-intake',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="submit()" aria-label="Support ticket form">
<label>Title <input type="text" formControlName="title" /></label>
<label>Email <input type="email" formControlName="email" /></label>
<label>
Priority
<select formControlName="priority">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</label>
<label>Message <textarea formControlName="message"></textarea></label>
@if (form.hasError('submit')) {
<p role="alert">Ticket could not be saved. Please try again.</p>
}
@if (savedTicket(); as ticket) {
<p role="status">Saved ticket {{ ticket.id }}</p>
}
<button type="submit" [disabled]="isSubmitDisabled()">
{{ saving() ? 'Saving...' : 'Create ticket' }}
</button>
</form>
`,
})
export class TicketIntakeComponent {
private readonly ticketService = inject(TicketService);
readonly saving = signal(false);
readonly savedTicket = signal<Ticket | null>(null);
readonly form = new FormGroup({
title: new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.minLength(5)] }),
email: new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] }),
priority: new FormControl<TicketPriority>('medium', { nonNullable: true }),
message: new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.minLength(20)] }),
});
readonly isSubmitDisabled = computed(() => this.form.invalid || this.saving());
submit(): void {
this.form.markAllAsTouched();
this.form.setErrors(null);
if (this.form.invalid) return;
this.saving.set(true);
this.ticketService.createTicket(this.form.getRawValue()).pipe(
finalize(() => this.saving.set(false)),
).subscribe({
next: (ticket) => {
this.savedTicket.set(ticket);
this.form.reset({ title: '', email: '', priority: 'medium', message: '' });
},
error: () => this.form.setErrors({ submit: true }),
});
}
}
容易踩坑的地方有三个:把 ngModel 和 formControlName 混用;忘记在保存中禁用按钮;把可能包含 nullable 值的 form.value 直接发给 API。这里用 nonNullable 和 getRawValue() 可以减少类型漏洞。
三个以上的实际用例
第一个用例是后台录入表单。提示词要写字段、校验、提交中状态、成功和失败显示,而不是只说“做得专业一点”。
第二个用例是把组件里的 HTTP 请求抽成 service。让 Claude Code 添加接口类型、移动 HttpClient 调用、保留 UI 文案在组件中,并补上 HTTP 测试。
第三个用例是修复回归 bug。例如“请求失败后按钮一直 disabled”,要给出复现步骤、期望结果和测试命令,让 Claude Code 同时补失败路径测试。
第四个用例是从 NgModule 逐步迁移到 standalone。不要让它一次改完整个项目,可以限制在一个 feature 目录,保持 routing 和 provider 不动。
单元测试、E2E 与 review 指令
HttpClient 测试不能打真实网络。使用 Angular 的 testing backend 可以检查请求方法和请求体。
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { TicketService } from './ticket.service';
describe('TicketService', () => {
let service: TicketService;
let http: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [TicketService, provideHttpClient(), provideHttpClientTesting()],
});
service = TestBed.inject(TicketService);
http = TestBed.inject(HttpTestingController);
});
afterEach(() => http.verify());
it('creates a ticket through the API', () => {
const draft = {
title: 'Billing export is stuck',
email: '[email protected]',
priority: 'high' as const,
message: 'The monthly billing export has not finished for two hours.',
};
service.createTicket(draft).subscribe((ticket) => expect(ticket.id).toBe('T-100'));
const req = http.expectOne('/api/tickets');
expect(req.request.method).toBe('POST');
expect(req.request.body).toEqual(draft);
req.flush({ ...draft, id: 'T-100', createdAt: '2026-06-02T09:00:00.000Z' });
});
});
E2E 方面,如果项目有 Angular CLI 的 E2E target,可以跑 ng e2e;如果团队使用 Playwright,就用受控 API 响应检查完整用户路径。
请批判性 review 这次 Angular 差异。
检查 standalone imports/provider、Reactive Forms nullable 值、二次提交、
service 是否混入 UI 职责、HttpClient 测试是否打真实网络、
unit/E2E 是否覆盖失败路径、是否出现 any 和可访问性问题。
请按优先级列出文件名和行号。
结论和实测结果
Claude Code 很适合加速 Angular 开发,但前提是任务被拆小。推荐顺序是:读取项目、确认 CLI 和测试工具、写 typed service、写 standalone form、补 HttpClient test、补 E2E、最后做审查。
我在一个小型 Angular 验证项目中试过,两种提示词差异很明显。只说“帮我做一个表单”时,结果混入了 ngModel,没有失败路径测试,service 里还出现了 UI 文案。改成明确要求 Reactive Forms、provideHttpClient、HttpTestingController、不允许 any 之后,主要修改点只剩文案和样式。需要团队导入时,可以把这些规则写入 CLAUDE.md,并从 Claude Code 培训与咨询 设计固定的 review 流程。
相关文章
用Claude Code高效开发Svelte与SvelteKit
面向初学者的Claude Code与Svelte/SvelteKit实战指南,覆盖runes、路由、表单actions、测试和安全改动方式。
用 Claude Code 开发 Storybook:UI 目录、交互测试与 CI 实战
用 Claude Code 搭建 Storybook:配置、TypeScript stories、交互测试、CI 与 Chromatic 检查。
用 Claude Code 开发 CLI 工具:Node.js、TypeScript、测试与安全发布
用Claude Code构建Node.js/TypeScript CLI:参数、配置、stdin/stdout、退出码、测试、CI和密钥安全。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
让 Claude Code 真正进入可验证的工作流
先用免费 PDF 固定基础,再用 Gumroad 教材复用工作流;如果涉及团队导入、权限或收入路径,可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。