用 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: 'ops@example.com',
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 流程。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
从Obsidian到CLAUDE.md的Claude Code流程:不再反复解释上下文
把 Obsidian 工作笔记整理成 CLAUDE.md 运行说明,让 Claude Code 每次都带着正确上下文开始。
Claude Code 收入 CTA 路由:从文章分流到 PDF、Gumroad 与咨询
用 Claude Code 按读者意图把文章流量分到免费 PDF、Gumroad 教材或咨询入口。
Claude Code 团队交接规则: 把审查证据、权限、回滚和收入路径一起交付
面向团队的 Claude Code 交接格式: 证据、权限、回滚、免费 PDF、Gumroad 与咨询路径都要可审查。