🐣

Angularの公式チュートリアルをやっていく その13 - Integrate Angular forms

2023/06/17に公開

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-12


入力フォームを追加する

このセクションでは入力フォームを作成します。

入力されたデータをどうやって取得するのか、やっていきます。

HousingServiceにデータ受け取りメソッドを追加する

HousingServiceにデータを受け取るメソッドを追加します。

今回はconsole.logで受け取ったデータを出力するだけですが、本来は受け取ったデータをバリデーションしたり、バックエンドに送信したりするロジックを実装したりすることになります。

下のようにhousing.service.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,
    },
  ];

  constructor() {}

  getAllHousingLocations(): HousingLocation[] {
    return this.housingLocationList;
  }

  getHousingLocationById(id: number): HousingLocation | undefined {
    return this.housingLocationList.find(
      (housingLocation) => housingLocation.id === id
    );
  }
  
+ submitApplication(firstName: string, lastName: string, email: string) {
+  console.log(`Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${email}.`);
+ }
}


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

ng serve



詳細ページにフォーム機能を追加する

詳細ページに、フォーム機能を追加します。

追加するのはフォームの裏側の機能だけで、実際に表示されるテンプレート部分は次の項目で追加されます。

下のようにdetails.component.tsを変更します。

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],
+   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>
      </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);
    }
    
+   submitApplication() {
+     this.housingService.submitApplication(
+       this.applyForm.value.firstName ?? '',
+       this.applyForm.value.lastName ?? '',
+       this.applyForm.value.email ?? ''
+     );
+   }
  }


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

ng serve



フォームのマークアップを詳細ページに追加する

実際に表示されるテンプレート部分を追加していきます。

下のようにdetails.component.tsを変更します。

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);
    }
  
    submitApplication() {
      this.housingService.submitApplication(
        this.applyForm.value.firstName ?? '',
        this.applyForm.value.lastName ?? '',
        this.applyForm.value.email ?? ''
      );
    }
  }


追加すると下のように画面になります。

確かにフォームが追加されていますね。



入力フォームのシステムの動き

FormGroupには、フォームに入力されたデータがリアルタイムで格納されています。

details.component.ts
applyForm = new FormGroup({
  firstName: new FormControl(''),
  lastName: new FormControl(''),
  email: new FormControl(''),
});


例えば、私がFIRST NAMEの入力場所に画像の様に「Koha」まで入力したら、FormGroupfirstNameにも「Koha」までの値が格納されたことになります。

details.component.ts
applyForm = new FormGroup({
  firstName: new FormControl(''),  // Kohaまでが格納される
  lastName: new FormControl(''),
  email: new FormControl(''),
});


Angularは、FormGroupやFormControlを使用することで、フォームの各フィールドの値を簡単に取得したり、検証ルールを追加したり、特定の状況でフィールドを無効にしたりすることが可能になります。

これらの機能はリアクティブフォームとして知られており、Angularの強力な特徴の一つです。

そして、「Apply now」ボタンをクリックすることによって、(submit)="submitApplication()"のコードの機能でsubmitApplication()メソッドが実行され、FormGroupのデータを引数にしてhousingService.submitApplicationが実行されます。

今回の場合、下記のようにhousingService.submitApplicationにはコンソールにデータを出力するロジックだけが実装されているため、コンソールに入力した文字列が出てくるだけです。

housing.service.ts
submitApplication(firstName: string, lastName: string, email: string) {
 console.log(`Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${email}.`);
}

アプリの新しいフォームをテストする

フォームがちゃんと機能するか確かめてみましょう。

フォームにデータを入力して、入力したデータがコンソールに出力されるかテストします。

以下のようにフォームに入力してみます。


そして、「Apply now」ボタンをクリック

おお! しっかりとコンソールに入力した内容が出力されました!


これにてIntegrate Angular formsは完了です。


← 前へ Angularの公式チュートリアルをやっていく その12 - Customize the details page
→ 次へ Angularの公式チュートリアルをやっていく その14 - Add search functionality

Discussion