🐣

Angularの公式チュートリアルをやっていく その15 - Add HTTP communication

2023/06/18に公開

Angularの公式チュートリアル「はじめての Angularアプリ」を進めていきます。

一覧
Angularの公式チュートリアルをやっていく その1 - Introduction
Angularの公式チュートリアルをやっていく その2 - Hello world
Angularの公式チュートリアルをやっていく その3 - Create home component
Angularの公式チュートリアルをやっていく その4 - Create housing location component
Angularの公式チュートリアルをやっていく その5 - Create an interface
Angularの公式チュートリアルをやっていく その6 - Add inputs to components
Angularの公式チュートリアルをやっていく その7 - Add property binding to components
Angularの公式チュートリアルをやっていく その8 - Add dynamic values to templates
Angularの公式チュートリアルをやっていく その9 - Use *ngFor in templates
Angularの公式チュートリアルをやっていく その10 - Angular services
Angularの公式チュートリアルをやっていく その11 - Add routing
Angularの公式チュートリアルをやっていく その12 - Customize the details page
Angularの公式チュートリアルをやっていく その13 - Integrate Angular forms
Angularの公式チュートリアルをやっていく その14 - Add search functionality
Angularの公式チュートリアルをやっていく その15 - Add HTTP communication

今回やるのは、↓の部分です。

https://angular.jp/tutorial/first-app/first-app-lesson-14


アプリにHTTP通信を追加する

これまで、このAngularのアプリケーションはAngularサービス内の静的配列からデータを読み取っていました。

このセクションでは、アプリがHTTPで通信するJSONサーバーを利用することでシミュレートを行い、HTTPリクエストとサーバー通信を行うコードを実装していきます。

JSON serverをインストールする

npmからjson-serverライブラリをインストールします。
このオープンソースのライブラリを利用することで、サーバー通信をシミュレートすることができるようになります。

下のコマンドを実行して、プロジェクトにjson-serverインストールします。

npm install -g json-server

db.jsonを作成する

db.jsonファイルを作成します。
このdb.jsonに設定を定義することで、その設定どおりのレスポンスを返すサーバーをシミュレートすることができます。

db.jsonをプロジェクト直下に手動で作成します。

下のコードを記述します。

db.json
{
  "locations": [
    {
      "id": 0,
      "name": "Acme Fresh Start Housing",
      "city": "Chicago",
      "state": "IL",
      "photo": "/assets/bernard-hermant-CLKGGwIBTaY-unsplash.jpg",
      "availableUnits": 4,
      "wifi": true,
      "laundry": true
    },
    {
      "id": 1,
      "name": "A113 Transitional Housing",
      "city": "Santa Monica",
      "state": "CA",
      "photo": "/assets/brandon-griggs-wR11KBaB86U-unsplash.jpg",
      "availableUnits": 0,
      "wifi": false,
      "laundry": true
    },
    {
      "id": 2,
      "name": "Warm Beds Housing Support",
      "city": "Juneau",
      "state": "AK",
      "photo": "/assets/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg",
      "availableUnits": 1,
      "wifi": false,
      "laundry": false
    },
    {
      "id": 3,
      "name": "Homesteady Housing",
      "city": "Chicago",
      "state": "IL",
      "photo": "/assets/ian-macdonald-W8z6aiwfi1E-unsplash.jpg",
      "availableUnits": 1,
      "wifi": true,
      "laundry": false
    },
    {
      "id": 4,
      "name": "Happy Homes Group",
      "city": "Gary",
      "state": "IN",
      "photo": "/assets/krzysztof-hepner-978RAXoXnH4-unsplash.jpg",
      "availableUnits": 1,
      "wifi": true,
      "laundry": false
    },
    {
      "id": 5,
      "name": "Hopeful Apartment Group",
      "city": "Oakland",
      "state": "CA",
      "photo": "/assets/r-architecture-JvQ0Q5IkeMM-unsplash.jpg",
      "availableUnits": 2,
      "wifi": true,
      "laundry": true
    },
    {
      "id": 6,
      "name": "Seriously Safe Towns",
      "city": "Oakland",
      "state": "CA",
      "photo": "/assets/phil-hearing-IYfp2Ixe9nM-unsplash.jpg",
      "availableUnits": 5,
      "wifi": true,
      "laundry": true
    },
    {
      "id": 7,
      "name": "Hopeful Housing Solutions",
      "city": "Oakland",
      "state": "CA",
      "photo": "/assets/r-architecture-GGupkreKwxA-unsplash.jpg",
      "availableUnits": 2,
      "wifi": true,
      "laundry": true
    },
    {
      "id": 8,
      "name": "Seriously Safe Towns",
      "city": "Oakland",
      "state": "CA",
      "photo": "/assets/saru-robert-9rP3mxf8qWI-unsplash.jpg",
      "availableUnits": 10,
      "wifi": false,
      "laundry": false
    },
    {
      "id": 9,
      "name": "Capital Safe Towns",
      "city": "Portland",
      "state": "OR",
      "photo": "/assets/webaliser-_TPTXZd9mOo-unsplash.jpg",
      "availableUnits": 6,
      "wifi": true,
      "laundry": true
    }
  ]
}


db.jsonファイルを保存して、下のコマンドを実行します。
実行することで、JSON serverに、db.jsonの設定でサーバー通信をシミュレートするように指定します。

json-server --watch db.json


http://localhost:3000/locations に移動してみましょう!
db.jsonで設定したデータが画面上に表示されていればOKです。


されていなければ、何かが間違っているので、これまでの操作を一つずつ見返してみてください。


アプリケーションでサーバーから家データを取得する

これまでTSファイルにベタ書きだった家データを、サーバー通信で取得できるようにコードを変更していきます。

サーバー通信には非同期処理を使用する為、非同期処理があまり得意で無い方はこの機会に学習し直してみましょう。

それでは一気にやっていきます。

下のようにhousing.service.tshome.component.tsdetails.component.tsのコードを変更していきましょう。

housing.service.ts
  import { Injectable } from '@angular/core';
  import { HousingLocation } from './housinglocation';
  
  @Injectable({
    providedIn: 'root',
  })
  export class HousingService {
-   housingLocationList: HousingLocation[] = [
-     {
-       id: 0,
-       name: 'Acme Fresh Start Housing',
-       city: 'Chicago',
-       state: 'IL',
-       photo: '/assets/bernard-hermant-CLKGGwIBTaY-unsplash.jpg',
-       availableUnits: 4,
-       wifi: true,
-       laundry: true,
-     },
-     {
-       id: 1,
-       name: 'A113 Transitional Housing',
-       city: 'Santa Monica',
-       state: 'CA',
-       photo: '/assets/brandon-griggs-wR11KBaB86U-unsplash.jpg',
-       availableUnits: 0,
-       wifi: false,
-       laundry: true,
-     },
-     {
-       id: 2,
-       name: 'Warm Beds Housing Support',
-       city: 'Juneau',
-       state: 'AK',
-       photo: '/assets/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg',
-       availableUnits: 1,
-       wifi: false,
-       laundry: false,
-     },
-     {
-       id: 3,
-       name: 'Homesteady Housing',
-       city: 'Chicago',
-       state: 'IL',
-       photo: '/assets/ian-macdonald-W8z6aiwfi1E-unsplash.jpg',
-       availableUnits: 1,
-       wifi: true,
-       laundry: false,
-     },
-     {
-       id: 4,
-       name: 'Happy Homes Group',
-       city: 'Gary',
-       state: 'IN',
-       photo: '/assets/krzysztof-hepner-978RAXoXnH4-unsplash.jpg',
-       availableUnits: 1,
-       wifi: true,
-       laundry: false,
-     },
-     {
-       id: 5,
-       name: 'Hopeful Apartment Group',
-       city: 'Oakland',
-       state: 'CA',
-       photo: '/assets/r-architecture-JvQ0Q5IkeMM-unsplash.jpg',
-       availableUnits: 2,
-       wifi: true,
-       laundry: true,
-     },
-     {
-       id: 6,
-       name: 'Seriously Safe Towns',
-       city: 'Oakland',
-       state: 'CA',
-       photo: '/assets/phil-hearing-IYfp2Ixe9nM-unsplash.jpg',
-       availableUnits: 5,
-       wifi: true,
-       laundry: true,
-     },
-     {
-       id: 7,
-       name: 'Hopeful Housing Solutions',
-       city: 'Oakland',
-       state: 'CA',
-       photo: '/assets/r-architecture-GGupkreKwxA-unsplash.jpg',
-       availableUnits: 2,
-       wifi: true,
-       laundry: true,
-     },
-     {
-       id: 8,
-       name: 'Seriously Safe Towns',
-       city: 'Oakland',
-       state: 'CA',
-       photo: '/assets/saru-robert-9rP3mxf8qWI-unsplash.jpg',
-       availableUnits: 10,
-       wifi: false,
-       laundry: false,
-     },
-     {
-       id: 9,
-       name: 'Capital Safe Towns',
-       city: 'Portland',
-       state: 'OR',
-       photo: '/assets/webaliser-_TPTXZd9mOo-unsplash.jpg',
-       availableUnits: 6,
-       wifi: true,
-       laundry: true,
-     },
-   ];
+   url = 'http://localhost:3000/locations';
  
    constructor() {}
  
-   getAllHousingLocations(): HousingLocation[] {
-     return this.housingLocationList;
-   }
+   async getAllHousingLocations(): Promise<HousingLocation[]> {
+     const data = await fetch(this.url);
+     return await data.json() ?? [];
+   }
  
-   getHousingLocationById(id: number): HousingLocation | undefined {
-     return this.housingLocationList.find(
-       (housingLocation) => housingLocation.id === id
-     );
-   }
+   async getHousingLocationById(id: number): Promise<HousingLocation | undefined> {
+     const data = await fetch(`${this.url}/${id}`);
+     return await data.json() ?? {};
+   }
  
    submitApplication(firstName: string, lastName: string, email: string) {
      console.log(
        `Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${email}.`
      );
    }
  }
home.component.ts
  import { Component, inject } from '@angular/core';
  import { CommonModule } from '@angular/common';
  import { HousingLocationComponent } from '../housing-location/housing-location.component';
  import { HousingLocation } from '../housinglocation';
  import { HousingService } from '../housing.service';
  
  @Component({
    selector: 'app-home',
    standalone: true,
    imports: [CommonModule, HousingLocationComponent],
    template: `
      <section>
        <form>
          <input type="text" placeholder="Filter by city" #filter />
          <button
            class="primary"
            type="button"
            (click)="filterResults(filter.value)"
          >
            Search
          </button>
        </form>
      </section>
      <section class="results">
        <app-housing-location
          *ngFor="let housingLocation of filteredLocationList"
          [housingLocation]="housingLocation"
        ></app-housing-location>
      </section>
    `,
    styleUrls: ['./home.component.css'],
  })
  export class HomeComponent {
    housingLocationList: HousingLocation[] = [];
    filteredLocationList: HousingLocation[] = [];
    housingService: HousingService = inject(HousingService);
  
-   constructor() {
-     this.housingLocationList = this.housingService.getAllHousingLocations();
-     this.filteredLocationList = this.housingLocationList;
-   }
+   constructor() {
+     this.housingService.getAllHousingLocations().then((housingLocationList: HousingLocation[]) => {
+       this.housingLocationList = housingLocationList;
+       this.filteredLocationList = housingLocationList;
+     });
+   }
  
    filterResults(text: string) {
      if (!text) {
        this.filteredLocationList = this.housingLocationList;
      }
  
      this.filteredLocationList = this.housingLocationList.filter(
        (housingLocation) =>
          housingLocation?.city.toLowerCase().includes(text.toLowerCase())
      );
    }
  }
details.component.ts
  import { Component, inject } from '@angular/core';
  import { CommonModule } from '@angular/common';
  import { ActivatedRoute } from '@angular/router';
  import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
  import { HousingService } from '../housing.service';
  import { HousingLocation } from '../housinglocation';
  
  @Component({
    selector: 'app-details',
    standalone: true,
    imports: [CommonModule, ReactiveFormsModule],
    template: `
      <article>
        <img
          class="listing-photo"
          [src]="housingLocation?.photo"
          alt="Exterior photo of {{ housingLocation?.name }}"
        />
        <section class="listing-description">
          <h2 class="listing-heading">{{ housingLocation?.name }}</h2>
          <p class="listing-location">
            {{ housingLocation?.city }}, {{ housingLocation?.state }}
          </p>
        </section>
        <section class="listing-features">
          <h2 class="section-heading">About this housing location</h2>
          <ul>
            <li>Units available: {{ housingLocation?.availableUnits }}</li>
            <li>Does this location have wifi: {{ housingLocation?.wifi }}</li>
            <li>
              Does this location have laundry: {{ housingLocation?.laundry }}
            </li>
          </ul>
        </section>
        <section class="listing-apply">
          <h2 class="section-heading">Apply now to live here</h2>
          <form [formGroup]="applyForm" (submit)="submitApplication()">
            <label for="first-name">First Name</label>
            <input id="first-name" type="text" formControlName="firstName" />
  
            <label for="last-name">Last Name</label>
            <input id="last-name" type="text" formControlName="lastName" />
  
            <label for="email">Email</label>
            <input id="email" type="email" formControlName="email" />
            <button type="submit" class="primary">Apply now</button>
          </form>
        </section>
      </article>
    `,
    styleUrls: ['./details.component.css'],
  })
  export class DetailsComponent {
    route: ActivatedRoute = inject(ActivatedRoute);
    housingService = inject(HousingService);
    housingLocation: HousingLocation | undefined;
    applyForm = new FormGroup({
      firstName: new FormControl(''),
      lastName: new FormControl(''),
      email: new FormControl(''),
    });
  
-   constructor() {
-     const housingLocationId = Number(this.route.snapshot.params['id']);
-     this.housingLocation =
-       this.housingService.getHousingLocationById(housingLocationId);
-   }
+   constructor() {
+     const housingLocationId = parseInt(this.route.snapshot.params['id'], 10);
+     this.housingService.getHousingLocationById(housingLocationId).then(housingLocation => {
+       this.housingLocation = housingLocation;
+     });
+   }
  
    submitApplication() {
      this.housingService.submitApplication(
        this.applyForm.value.firstName ?? '',
        this.applyForm.value.lastName ?? '',
        this.applyForm.value.email ?? ''
      );
    }
  }


すべて変更できましたでしょうか?

できたら、アプリケーションを再ビルドしてコードに間違いがないか確認します。

ng serve


どうやら大丈夫なようです。
一覧ページも詳細ページも、問題なく表示されているようですね。



非同期処理の動きなどは、詳しい解説がネット上に多くありますので、そちらを探してご確認いただければと思います。



おわり

これにて、Angularの公式チュートリアル「はじめての Angularアプリ」はすべて終了です。

お疲れ様でした。

結構時間がかかりました。

内容だけ見れば、一日で終わっていい程度のボリュームでしたが、記事を書きながらとなるとやはり速度が落ちてしまい、一週間以上かかってしまいました。

流石に時間がかかり過ぎなので、こういったやってみた系はちょっここれからは要件等ですね……

私はこれからもAngularはやっていく予定ですが、皆さんはどうですか?
少しは慣れましたでしょうか?

少しでもお役に立てたのであれば幸いです。

ここまで長い間ありがとうございました。


← 前へ Angularの公式チュートリアルをやっていく その14 - Add search functionality

Discussion