Dev Study
← 解説「Network タブ — 「画面に出ない」の半分は通信」に戻る

サンプルコードで身につける: Network タブ — 「画面に出ない」の半分は通信

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1404 — URLの書き間違いを見つける

一覧が表示されないときにNetwork タブを見ると、リクエストが赤字の404になっていることがあります。コードを何時間も眺めるより、実際に飛んだURLを見るほうが早く原因にたどり着けます。

// バグ: ユーザー一覧が表示されない(コンソールにもエラーが出ている)

import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-user-list',
  template: '@for (u of users(); track u.id) { <p>{{ u.name }}</p> }',
})
export class UserList {
  private http = inject(HttpClient);

  // 原因: 正しいパスは /api/users(複数形)なのに単数形で書いた
  users = toSignal(this.http.get<{ id: number; name: string }[]>(
    '/api/user',
  ));
}

// Network タブでの見え方:
//   Name: user   Status: 404(赤字)
//   → リクエスト自体は飛んでいる。URLが存在しないだけ
// 直し方: '/api/users' に修正する。
// APIのパスはサーバー側の定義かAPI仕様書と突き合わせて確認する

2200 なのに出ない — Response の形が違う

ステータス200でも、返ってきたJSONの形が期待と違えば画面には出ません。Response タブで生のJSONを見ると、配列がオブジェクトに包まれているといった食い違いを一目で発見できます。

// バグ: Status 200 なのに一覧が空のまま

// Network タブ → 該当リクエスト → Response タブで生のJSONを見る:
//   { "data": [ { "id": 1, "name": "佐藤" } ], "total": 1 }
//   ^^^^^^^^ 配列が data プロパティに包まれている!

type User = { id: number; name: string };

// 原因: コードは「配列がそのまま返る」前提で型を書いていた
// users = toSignal(this.http.get<User[]>('/api/users'));
// → 実際はオブジェクトなので @for が回らず、一覧は空になる

// 直し方: 実際の形に合わせて型を定義し、map で配列を取り出す
type UsersResponse = { data: User[]; total: number };

// users = toSignal(
//   this.http.get<UsersResponse>('/api/users').pipe(
//     map((res) => res.data),
//   ),
// );
//
// 教訓: get<T> の T は願望にすぎない。実際の形は Response タブが正

3Payload タブ — 送った中身を疑う

登録が失敗するとき、サーバーに届いた内容が意図と違うことがあります。Payload タブで実際に送ったJSONを見ると、フィールド名の食い違いのような地味なミスを発見できます。

// バグ: ユーザー登録すると 400 Bad Request が返る

register() {
  // 原因: サーバーの仕様は userName(キャメルケース)なのに
  //       username(全部小文字)で送っていた
  this.http.post('/api/users', {
    username: this.name(),
    age: this.age(),
  }).subscribe();
}

// Network タブでの調査:
//   1. Status 400 のリクエストを選ぶ
//   2. Payload タブ … 自分が実際に送った中身
//        { "username": "佐藤", "age": 28 }
//   3. Response タブ … サーバーからの説明
//        { "error": "userName is required" }
//   → 送った名前と要求された名前を並べれば食い違いは一目瞭然
//
// 直し方: { userName: this.name(), age: this.age() } に修正。
// 400 系は「こちらの送り方」を疑うのが定石

4CORS エラーの正体を見分ける

CORSはコンソールの赤いメッセージだけ見ても解決できません。Network タブで応答ヘッダーを確認し、原因がサーバー側の許可設定にあることを掴むのが本質です。開発中はプロキシ設定で回避するのが定番です。

// 症状(コンソール):
//   Access to fetch at 'http://localhost:3000/api/users' from origin
//   'http://localhost:4200' has been blocked by CORS policy
//
// Network タブでの本質的な確認:
//   該当リクエストを選ぶ → Headers タブ → Response Headers を見る
//   Access-Control-Allow-Origin: (無い!)
//   → サーバーが「4200番からのアクセスを許可する」と言っていない
//
// 見分けのポイント:
//   ・サーバーは応答している(200のことも多い)のにJSで受け取れない
//   ・ブラウザが受け取りを拒否している状態。フロントのコードは無罪
//
// 直し方は2通り:
//   1. サーバー側で許可ヘッダーを返す(本質的な解決)
//   2. 開発中は proxy.conf.json でAngular側から中継する
//      { "/api": { "target": "http://localhost:3000" } }
//      → ng serve --proxy-config proxy.conf.json で起動

5Network に何も出ない — subscribe 忘れ

エラーすら出ないのにデータが来ないときは、Network タブにリクエスト自体が現れていないかを見ます。HttpClient の Observable は subscribe されるまで送信されないため、購読忘れが典型的な原因です。

// バグ: 削除ボタンを押しても何も起きない。エラーも出ない

deleteUser(id: number) {
  // 原因: HttpClient の戻り値は「予約票」にすぎず、
  //       subscribe しない限りリクエストは1件も飛ばない
  this.http.delete('/api/users/' + id);
}

// Network タブでの見え方:
//   ボタンを押しても一覧に何も追加されない
//   → 「失敗した」のではなく「そもそも送っていない」
//   404 や 500 が並ぶバグとはまったく別の原因だと分かる

// 直し方: subscribe して初めて送信される
// deleteUser(id: number) {
//   this.http.delete('/api/users/' + id).subscribe(() => {
//     this.users.update((list) => list.filter((u) => u.id !== id));
//   });
// }
//
// 教訓: 「エラーが出ない不具合」はNetworkタブの空欄が最大のヒント