這堂教學課程示範如何為 Angular 應用程式加上一個收集使用者資料的表單。 本課從一個可以正常運作的 Angular 應用程式出發,示範如何為它加上表單。
表單收集到的資料只會送到應用程式的服務,由服務寫入瀏覽器的主控台。 本課不會介紹如何使用 REST API 傳送與接收表單資料。
IMPORTANT: 本教學的這個步驟,建議你在本機環境操作。
你會學到什麼
- 你的應用程式將會有一個表單,讓使用者輸入資料並送到應用程式的服務。
- 服務將會把表單的資料寫入瀏覽器的主控台日誌。
-
加上傳送表單資料的方法
這個步驟會為應用程式的服務加上一個方法,負責接收表單資料並送往目的地。 在本範例中,這個方法會把表單資料寫入瀏覽器的主控台日誌。
在 IDE 的 Edit 窗格中:
在
src/app/housing-service.ts裡,找到HousingService類別,在類別定義的最下方貼上這個方法。src/app/housing-service.ts 中的提交方法
import {Service} from '@angular/core'; import {HousingLocationInfo} from './housinglocation'; @Service() export class HousingService { readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common'; protected housingLocationList: HousingLocationInfo[] = [ { id: 0, name: 'Acme Fresh Start Housing', city: 'Chicago', state: 'IL', photo: `${this.baseUrl}/bernard-hermant-CLKGGwIBTaY-unsplash.jpg`, availableUnits: 4, wifi: true, laundry: true, }, { id: 1, name: 'A113 Transitional Housing', city: 'Santa Monica', state: 'CA', photo: `${this.baseUrl}/brandon-griggs-wR11KBaB86U-unsplash.jpg`, availableUnits: 0, wifi: false, laundry: true, }, { id: 2, name: 'Warm Beds Housing Support', city: 'Juneau', state: 'AK', photo: `${this.baseUrl}/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: `${this.baseUrl}/ian-macdonald-W8z6aiwfi1E-unsplash.jpg`, availableUnits: 1, wifi: true, laundry: false, }, { id: 4, name: 'Happy Homes Group', city: 'Gary', state: 'IN', photo: `${this.baseUrl}/krzysztof-hepner-978RAXoXnH4-unsplash.jpg`, availableUnits: 1, wifi: true, laundry: false, }, { id: 5, name: 'Hopeful Apartment Group', city: 'Oakland', state: 'CA', photo: `${this.baseUrl}/r-architecture-JvQ0Q5IkeMM-unsplash.jpg`, availableUnits: 2, wifi: true, laundry: true, }, { id: 6, name: 'Seriously Safe Towns', city: 'Oakland', state: 'CA', photo: `${this.baseUrl}/phil-hearing-IYfp2Ixe9nM-unsplash.jpg`, availableUnits: 5, wifi: true, laundry: true, }, { id: 7, name: 'Hopeful Housing Solutions', city: 'Oakland', state: 'CA', photo: `${this.baseUrl}/r-architecture-GGupkreKwxA-unsplash.jpg`, availableUnits: 2, wifi: true, laundry: true, }, { id: 8, name: 'Seriously Safe Towns', city: 'Oakland', state: 'CA', photo: `${this.baseUrl}/saru-robert-9rP3mxf8qWI-unsplash.jpg`, availableUnits: 10, wifi: false, laundry: false, }, { id: 9, name: 'Capital Safe Towns', city: 'Portland', state: 'OR', photo: `${this.baseUrl}/webaliser-_TPTXZd9mOo-unsplash.jpg`, availableUnits: 6, wifi: true, laundry: true, }, ]; getAllHousingLocations(): HousingLocationInfo[] { return this.housingLocationList; } getHousingLocationById(id: number): HousingLocationInfo | 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}.`, ); } }確認應用程式能順利建置、沒有錯誤。 繼續下一個步驟之前,請先修正所有錯誤。
-
為詳細資訊頁加上表單功能
這個步驟會為詳細資訊頁加上處理表單互動的程式碼。
在 IDE 的 Edit 窗格中,打開
src/app/details/details.ts:在檔案頂端的
import陳述式之後,加上以下程式碼,匯入 Angular 的表單類別。src/app/details/details.ts 中的表單匯入
import {Component, inject} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {HousingService} from '../housing-service'; import {HousingLocationInfo} from '../housinglocation'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; @Component({ selector: 'app-details', imports: [ReactiveFormsModule], template: ` <article> <img class="listing-photo" [src]="housingLocation?.photo" alt="Exterior photo of {{ housingLocation?.name }}" crossorigin /> <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> `, styleUrl: './details.css', }) export class Details { route: ActivatedRoute = inject(ActivatedRoute); housingService = inject(HousingService); housingLocation: HousingLocationInfo | undefined; applyForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), email: new FormControl(''), }); constructor() { const housingLocationId = parseInt(this.route.snapshot.params['id'], 10); this.housingLocation = this.housingService.getHousingLocationById(housingLocationId); } submitApplication() { this.housingService.submitApplication( this.applyForm.value.firstName ?? '', this.applyForm.value.lastName ?? '', this.applyForm.value.email ?? '', ); } }在
Details裝飾器的中繼資料中,用以下程式碼更新imports屬性:src/app/details/details.ts 中的 imports 指令
import {Component, inject} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {HousingService} from '../housing-service'; import {HousingLocationInfo} from '../housinglocation'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; @Component({ selector: 'app-details', imports: [ReactiveFormsModule], template: ` <article> <img class="listing-photo" [src]="housingLocation?.photo" alt="Exterior photo of {{ housingLocation?.name }}" crossorigin /> <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> `, styleUrl: './details.css', }) export class Details { route: ActivatedRoute = inject(ActivatedRoute); housingService = inject(HousingService); housingLocation: HousingLocationInfo | undefined; applyForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), email: new FormControl(''), }); constructor() { const housingLocationId = parseInt(this.route.snapshot.params['id'], 10); this.housingLocation = this.housingService.getHousingLocationById(housingLocationId); } submitApplication() { this.housingService.submitApplication( this.applyForm.value.firstName ?? '', this.applyForm.value.lastName ?? '', this.applyForm.value.email ?? '', ); } }在
Details類別中、constructor()方法之前,加上以下程式碼來建立表單物件。src/app/details/details.ts 中的範本指令
import {Component, inject} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {HousingService} from '../housing-service'; import {HousingLocationInfo} from '../housinglocation'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; @Component({ selector: 'app-details', imports: [ReactiveFormsModule], template: ` <article> <img class="listing-photo" [src]="housingLocation?.photo" alt="Exterior photo of {{ housingLocation?.name }}" crossorigin /> <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> `, styleUrl: './details.css', }) export class Details { route: ActivatedRoute = inject(ActivatedRoute); housingService = inject(HousingService); housingLocation: HousingLocationInfo | undefined; applyForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), email: new FormControl(''), }); constructor() { const housingLocationId = parseInt(this.route.snapshot.params['id'], 10); this.housingLocation = this.housingService.getHousingLocationById(housingLocationId); } submitApplication() { this.housingService.submitApplication( this.applyForm.value.firstName ?? '', this.applyForm.value.lastName ?? '', this.applyForm.value.email ?? '', ); } }在 Angular 中,
FormGroup和FormControl是讓你建構表單的型別。FormControl型別可以提供預設值,並塑造表單資料的形狀。在本範例中,firstName是string,預設值是空字串。在
Details類別中、constructor()方法之後,加上以下程式碼來處理 Apply now 的點選動作。src/app/details/details.ts 中的範本指令
import {Component, inject} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {HousingService} from '../housing-service'; import {HousingLocationInfo} from '../housinglocation'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; @Component({ selector: 'app-details', imports: [ReactiveFormsModule], template: ` <article> <img class="listing-photo" [src]="housingLocation?.photo" alt="Exterior photo of {{ housingLocation?.name }}" crossorigin /> <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> `, styleUrl: './details.css', }) export class Details { route: ActivatedRoute = inject(ActivatedRoute); housingService = inject(HousingService); housingLocation: HousingLocationInfo | undefined; applyForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), email: new FormControl(''), }); constructor() { const housingLocationId = parseInt(this.route.snapshot.params['id'], 10); this.housingLocation = this.housingService.getHousingLocationById(housingLocationId); } submitApplication() { this.housingService.submitApplication( this.applyForm.value.firstName ?? '', this.applyForm.value.lastName ?? '', this.applyForm.value.email ?? '', ); } }這個按鈕目前還不存在,你會在下一個步驟加上它。在上面的程式碼中,
FormControl可能會回傳null。這段程式碼使用空值合併運算子,在值為null時預設改用空字串。確認應用程式能順利建置、沒有錯誤。 繼續下一個步驟之前,請先修正所有錯誤。
-
為詳細資訊頁加上表單的標記
這個步驟會為詳細資訊頁加上顯示表單的標記。
在 IDE 的 Edit 窗格中,打開
src/app/details/details.ts:在
Details裝飾器的中繼資料中,把template的 HTML 更新為以下程式碼,加上表單的標記。src/app/details/details.ts 中的範本指令
import {Component, inject} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {HousingService} from '../housing-service'; import {HousingLocationInfo} from '../housinglocation'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; @Component({ selector: 'app-details', imports: [ReactiveFormsModule], template: ` <article> <img class="listing-photo" [src]="housingLocation?.photo" alt="Exterior photo of {{ housingLocation?.name }}" crossorigin /> <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> `, styleUrl: './details.css', }) export class Details { route: ActivatedRoute = inject(ActivatedRoute); housingService = inject(HousingService); housingLocation: HousingLocationInfo | undefined; applyForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), email: new FormControl(''), }); constructor() { const housingLocationId = parseInt(this.route.snapshot.params['id'], 10); this.housingLocation = this.housingService.getHousingLocationById(housingLocationId); } submitApplication() { this.housingService.submitApplication( this.applyForm.value.firstName ?? '', this.applyForm.value.lastName ?? '', this.applyForm.value.email ?? '', ); } }範本現在包含一個事件處理器
(submit)="submitApplication()"。Angular 使用在事件名稱外加括號的語法,在範本程式碼中定義事件。等號右邊的程式碼,就是這個事件被觸發時要執行的程式碼。你可以繫結瀏覽器事件,也可以繫結自訂事件。確認應用程式能順利建置、沒有錯誤。 繼續下一個步驟之前,請先修正所有錯誤。

-
測試應用程式的新表單
這個步驟會測試新表單,確認表單資料送交給應用程式後,會出現在主控台日誌中。
- 在 IDE 的 Terminal 窗格中執行
ng serve(如果還沒在執行的話)。 - 在瀏覽器中,於
http://localhost:4200開啟你的應用程式。 - 在瀏覽器中對應用程式按右鍵,從右鍵選單中選擇 Inspect。
- 在開發者工具視窗中,選擇 Console 分頁。 接下來的步驟中,請確保開發者工具視窗保持可見。
- 在你的應用程式中:
- 選擇一個住房地點,點選 Learn more,查看這間房屋的詳細資訊。
- 在房屋的詳細資訊頁中,捲動到最下方,找到新表單。
- 在表單欄位中輸入資料,輸入任何資料都可以。
- 選擇 Apply now 送出資料。
- 在開發者工具視窗中,檢查日誌輸出,找出你的表單資料。
- 在 IDE 的 Terminal 窗格中執行
SUMMARY: 在本課中,你使用 Angular 的表單功能新增了一個表單,並透過事件處理器,把表單收集到的資料連接到元件。
想進一步了解本課介紹的主題,請造訪: