Claude CodeでAngular開発を加速する実践ガイド:CLI・Standalone・Forms・検証まで
Claude CodeでAngular開発を進める手順をCLI、standalone component、Reactive Forms、HttpClient、テスト指示まで実例で解説。
Angular開発でClaude Codeを使う価値は、きれいなコンポーネントを一発生成することだけではありません。既存の設計を読み、Angular CLIの作法に沿ってファイルを作り、standalone component、Reactive Forms、HttpClient、ユニットテスト、E2E検証まで同じ流れで進められる点にあります。
初心者がつまずきやすいのは、Angularの用語が多いことです。standalone componentは「NgModuleに登録しなくても、自分で必要な依存をimportsに書けるコンポーネント」です。Reactive Formsは「フォームの値と検証ルールをTypeScript側で明示的に管理する仕組み」です。HttpClientは「バックエンドAPIと通信するためのAngular標準サービス」です。ここを押さえると、Claude Codeへの指示もかなり具体的になります。
この記事では、問い合わせチケット登録画面を題材にします。Masaが実際の管理画面改修で試したとき、Claude Codeに「Angularでフォームを作って」とだけ頼むと、見た目は動きそうでも、テストなし、API型なし、失敗時のUIなしの差分になりました。逆に、対象ファイル、禁止事項、検証コマンド、レビュー観点まで渡すと、レビューに耐える差分へ近づきました。
公式情報は作業中に必ず確認してください。Angular CLIはAngular CLI reference、フォームはReactive forms、HTTP通信はHttpClient guide、テストはAngular testing guide、Claude Codeの運用はAnthropicのClaude Code workflowsが基準になります。関連する社内ルール作りはCLAUDE.mdベストプラクティスとテスト戦略ガイドも合わせて読むと実務に落とし込みやすいです。
全体像:Claude Codeに任せる範囲を決める
Claude Codeは、作業範囲が狭く、検証方法が明確なほど安定します。ここでいうharnessは「エージェントの足場」、つまりClaude Codeが安全に作業するための枠です。Angularでは、この枠を次のように書きます。
| 領域 | Claude Codeに任せること | 人間が決めること |
|---|---|---|
| Angular CLI | ng generateの候補、ファイル配置、依存関係の確認 | 既存プロジェクトの命名規則、ルーティング方針 |
| standalone component | imports、テンプレート、Signal、イベント処理 | UI文言、アクセシビリティ、画面仕様 |
| Reactive Forms | FormGroup、Validator、送信状態、エラー表示 | 入力ルール、業務上の必須項目 |
| service / HttpClient | 型付きAPI、リトライしない境界、テストダブル | API契約、認証、エラー分類 |
| unit / e2e | テスト観点、正常系と失敗系、検証コマンド | どこまで自動化するか、リリース判断 |
概念図にすると、Claude Codeは中心ではなく、各レイヤーをつなぐ作業者です。設計判断を丸投げせず、差分を小さく区切るのがコツです。
flowchart LR
P[Claude Code prompt] --> C[Angular CLI]
C --> A[Standalone component]
A --> F[Reactive Forms]
A --> S[Ticket service]
S --> H[HttpClient]
F --> U[Unit tests]
H --> U
A --> E[E2E tests]
U --> R[Review prompt]
E --> R
Angular CLIで最小構成を作る
新規検証なら、まずCLIで土台を作ります。既存リポジトリでは、このコマンドをそのまま実行するのではなく、Claude Codeにpackage.json、angular.json、src/appを読ませて、同じ生成規則に合わせてもらいます。
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
Claude Codeへの最初の指示は、生成より前に調査から始めます。
Angularプロジェクトを読んでください。まず package.json、angular.json、src/app を確認し、
standalone component を使っているか、テストランナー、HttpClientのprovider設定、
既存の命名規則をまとめてください。まだファイルは編集しないでください。
この一文を入れるだけで、古いNgModule前提のコード、Karma前提のテスト、存在しないパスへの生成がかなり減ります。調査後に「この範囲だけ編集して」と区切ると、他の作業者が触っているファイルにも干渉しにくくなります。
serviceとHttpClientを先に固める
フォームを先に作るより、API型とserviceを先に固めるほうがレビューしやすくなります。UIは変わりやすいですが、API契約はチームで合意する必要があるからです。
src/app/data/ticket.service.tsの例です。ObservableはRxJSの「非同期データの流れ」を表す型です。AngularのHttpClientは型引数を受け取れるため、レスポンスの形を明示できます。
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でprovideHttpClient()を登録します。Claude Codeには「HttpClientModuleをimportsに追加しない。provideHttpClientを使う」と明記します。
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用の日本語メッセージを混ぜることです。TicketServiceが「送信に失敗しました」という文言を返し始めると、多言語化や画面ごとの表示制御が難しくなります。serviceはAPI通信と型に集中させ、表示はcomponent側で扱います。
standalone componentとReactive Formsを組み合わせる
次に、フォームcomponentを作ります。Reactive Formsでは、入力値、検証ルール、送信可否をTypeScript側で追えるため、Claude Codeにテストを書かせやすいです。
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 }),
});
}
}
ここでの落とし穴は3つあります。1つ目はFormsModuleとReactiveFormsModuleを混ぜて、ngModelとformControlNameを同じ入力に置くことです。2つ目は送信中の二重クリック対策を忘れることです。3つ目はform.valueをそのままAPIに送ることです。nullableな値が混ざるので、nonNullableとgetRawValue()を使うほうが安全です。
Claude Codeには次のように依頼します。
src/app/features/tickets/ticket-intake に standalone component を実装してください。
Reactive Formsを使い、ngModelは使わないでください。
送信中はボタンをdisabledにし、API失敗時はrole="alert"で表示してください。
TicketServiceの型を使い、anyは追加しないでください。
実装後に関連するunit testも追加してください。
ユースケース別の頼み方
ユースケース1は、管理画面の登録フォームです。Claude Codeには「画面を作る」ではなく、「フォーム項目、検証ルール、送信中状態、失敗時表示、成功時表示」を列挙します。業務アプリでは、見た目よりも状態遷移の漏れがバグになります。
ユースケース2は、既存APIのservice化です。コンポーネント内にfetchやHttpClient呼び出しが散らばっている場合、「API型を作る」「serviceへ移動する」「componentはserviceをinjectする」「HTTPテストを追加する」と分けます。これにより、後から認証ヘッダーやinterceptorを入れても追いやすくなります。
ユースケース3は、バグ修正と回帰テストです。たとえば「送信失敗後にボタンがdisabledのまま戻らない」という不具合では、Claude Codeに再現手順、期待結果、失敗しているテスト名を渡します。finalizeでsavingを戻す実装を提案してもらい、同時に失敗系テストを追加させます。
ユースケース4は、NgModuleからstandaloneへの段階移行です。Angular公式にはstandalone移行のガイドがありますが、実プロジェクトでは一気に全部変えると危険です。Claude Codeには「このfeature配下だけ」「routingとproviderは触らない」「テストが通る範囲で」と制限すると、差分をレビューできます。
unit testとHttpClient testingを入れる
Angularの新しいCLIプロジェクトでは、公式テストガイドがVitestを前提に説明しています。既存プロジェクトではKarma/Jasmineの可能性もあるので、Claude Codeに必ず現在のテストランナーを確認させます。
TicketServiceのHTTPテスト例です。実ネットワークには出さず、HttpTestingControllerでリクエストを検証します。
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');
expect(ticket.priority).toBe('high');
});
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' });
});
});
componentテストでは、serviceをmockします。ここで「DOMの文言だけを見るテスト」に偏ると壊れやすいので、入力、送信、service呼び出し、成功表示を一連で確認します。
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { vi } from 'vitest';
import { TicketIntakeComponent } from './ticket-intake.component';
import { TicketService } from '../../../data/ticket.service';
describe('TicketIntakeComponent', () => {
let fixture: ComponentFixture<TicketIntakeComponent>;
const createTicket = vi.fn();
beforeEach(async () => {
createTicket.mockReturnValue(
of({
id: 'T-101',
title: 'Billing export is stuck',
email: 'ops@example.com',
priority: 'high',
message: 'The monthly billing export has not finished for two hours.',
createdAt: '2026-06-02T09:00:00.000Z',
}),
);
await TestBed.configureTestingModule({
imports: [TicketIntakeComponent],
providers: [{ provide: TicketService, useValue: { createTicket } }],
}).compileComponents();
fixture = TestBed.createComponent(TicketIntakeComponent);
fixture.detectChanges();
});
it('submits a valid ticket', () => {
const component = fixture.componentInstance;
component.form.setValue({
title: 'Billing export is stuck',
email: 'ops@example.com',
priority: 'high',
message: 'The monthly billing export has not finished for two hours.',
});
component.submit();
expect(createTicket).toHaveBeenCalledWith(component.form.getRawValue());
expect(component.savedTicket()?.id).toBe('T-101');
});
});
E2E検証とレビュー指示をセットにする
E2Eは「ブラウザ上でユーザー操作が通るか」を見るテストです。Angular CLIにはng e2eコマンドがありますが、プロジェクトにE2Eターゲットが必要です。Playwrightを使うチームなら、npm init playwright@latestで追加し、npx playwright testをCIに入れるのが現実的です。
import { test, expect } from '@playwright/test';
test('creates a support ticket', async ({ page }) => {
await page.route('**/api/tickets', async (route) => {
await route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({
id: 'T-200',
title: 'Billing export is stuck',
email: 'ops@example.com',
priority: 'high',
message: 'The monthly billing export has not finished for two hours.',
createdAt: '2026-06-02T09:00:00.000Z',
}),
});
});
await page.goto('/');
await page.getByLabel('Title').fill('Billing export is stuck');
await page.getByLabel('Email').fill('ops@example.com');
await page.getByLabel('Priority').selectOption('high');
await page.getByLabel('Message').fill('The monthly billing export has not finished for two hours.');
await page.getByRole('button', { name: 'Create ticket' }).click();
await expect(page.getByText('Saved ticket T-200')).toBeVisible();
});
最後に、Claude Codeへレビュー指示を投げます。ここで「良さそう?」と聞くと抽象的な返答になります。観点を固定します。
今回のAngular差分を批判的にレビューしてください。
観点:
1. standalone componentとしてimportsとproviderが正しいか
2. Reactive Formsでnullable値や二重送信の問題がないか
3. TicketServiceがUI責務を持っていないか
4. HttpClientテストが実ネットワークに出ないか
5. unit testとE2Eで失敗系が不足していないか
6. any、不要なsubscribe、メモリリーク、アクセシビリティの問題がないか
ファイル名と行番号つきで、公開前に直すべき順に指摘してください。
収益につなげるCTAと運用ルール
Angularの記事は、単なるサンプル集で終わると収益化しにくいです。読者は「自分のAngularプロジェクトにClaude Codeをどう入れるか」を知りたいので、記事末尾のCTAは自然に実務支援へつなげます。たとえば、チーム導入ならClaude Code研修・導入相談へ、設定例が欲しい読者には無料チートシートへ、設計全体を見直したい読者にはハーネスエンジニアリング入門へ誘導します。
運用ルールとしては、Claude Codeに一度で全部やらせないことです。調査、service、component、unit test、E2E、レビューを分けると、差分が読みやすくなります。複数人で同時作業しているときは「このslugだけ」「このfeature配下だけ」「未コミット変更は戻さない」と明示します。これはAI向けの気遣いではなく、チーム開発の事故を減らすための作業契約です。
この記事で紹介した内容を実際に試した結果
Masaが小さなAngular検証プロジェクトで試したところ、最初に「フォームを作って」と頼んだ場合は、ngModel混在、API型の不足、失敗系テストなしの差分になりました。次に、Angular CLIの確認、provideHttpClient、Reactive Forms、HttpTestingController、Playwrightの検証観点まで指定したところ、手直しは主に文言とCSSに絞れました。特に効果があった指示は「anyを追加しない」「serviceにUI文言を入れない」「unit testとE2Eを別々に追加する」の3つです。Claude CodeはAngularを速く書く道具ですが、公開できる品質にするには、範囲指定とレビュー指示が同じくらい重要でした。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
ObsidianメモをCLAUDE.mdに変えるClaude Code運用: 文脈を毎回説明しない仕組み
Obsidianの作業メモからCLAUDE.md用の運用ノートを作り、Claude Codeに安定した文脈を渡す方法。
Claude Code Revenue CTA Routing: 記事からPDF、Gumroad、相談へ送る設計
PVだけで終わらせず、読者の状態に合わせて無料PDF、Gumroad教材、導入相談へ分岐するCTA設計です。
Claude Codeチーム引き継ぎルール: レビュー、権限、収益導線まで渡す実務手順
Claude Codeの作業をチームで渡すための証拠、権限、ロールバック、無料PDF/Gumroad/相談導線の実務ルール。