邱敬幃 Pardn Chiu
目錄Angular 基本概念解析什麼是 Angular?為什麼選擇 Angular?建立你的第一個網路應用安裝 Node.js 和 npm:安裝 Angular CLI創建你的第一個 Angular 應用程式Angular 專案結構啟動 Angular 應用程式創建、設計、與互動組件什麼是 Angular 組件?組件模板與樣式設計模板:賦予生命樣式:美化你的組件組件間的通信組件生命週期數據綁定:讓你的應用程式活躍起來單向數據綁定:雙向數據綁定:使用插值表達式和屬性綁定將數據顯示在模板中事件處理:讓你的應用程式對用戶做出反應事件綁定:事件對象:設置路由以及子路由設置路由設置子路由路由守衛懶加載模板驅動表單 vs. 響應式表單模板驅動表單響應式表單表單驗證與錯誤處理HTTP 請求的使用、錯誤處理與測試送出 HTTP 請求處理 HTTP 錯誤測試 HTTP 請求查看 Angular 的官方文檔和教程:系列文章相關連結
標籤

網頁前端新手必讀!Angular 入門指南詳解

打造互動網頁的前端框架介紹 (2)

573次觀看 0收藏 前端開發 框架 Angular
你是否曾經感到困惑,不知道該選擇 React、Angular 還是 Vue.js 或是 jQuery?別擔心,這系列將為你深入解說這三個最受歡迎的前端框架,從學習曲線、性能、社區支持、適用場景等多個方面進行詳細解說,以幫助你做出明智的選擇。

Angular 基本概念解析

Angular 是一個強大的前端框架,它的存在是為了讓前端開發變得更加容易且有組織性。它提供了一個強大的架構,幫助開發者構建互動性豐富的網頁應用程式。

什麼是 Angular?

Angular 是一個開源的前端框架,由 Google 開發和維護。它的主要目標是簡化前端開發,提供一個有組織的方法來構建 Web 應用程式。Angular 使用 TypeScript 作為主要的程式語言,這使得代碼更具可維護性和可擴展性。

Angular 不僅僅是一個 JavaScript 框架,它還包括了一個豐富的生態系統,如 Angular CLI、Angular Material 和 NgRx。Angular 的主要特點之一是它的組件化架構,讓開發者可以將應用程式拆分為多個獨立的組件,每個組件都有自己的視圖和邏輯,這使得代碼結構更清晰且易於維護。

為什麼選擇 Angular?

Angular 提供了許多優點,這些優點使其成為許多人前端開發的首選框架:

  • 豐富的生態系統:
    Angular 生態系統包括了許多強大的工具、庫和插件,簡化了各種任務。不管是路由管理、狀態管理、UI設計,還是測試。這意味著你可以以更高效的方式完成任務。
  • 模組化架構(Modularity):
    Angular 採用了模組化架構,這是一個重要的設計。你可以將相關的代碼打包成一個模組,使代碼更具結構性和可維護性。使你的應用程式更容易測試、維護和擴展。你可以將功能模組化,並在需要時輕鬆地將其添加到你的應用中。
  • 數據雙向綁定(Two-Way Data Binding):
    Angular 引以為傲的一個特點是數據雙向綁定。你的應用程式的模型(Model)和視圖(View)之間有一個動態交集,任何變化都會可以立即反映在視圖中。當你在搜尋框中輸入文字時,不僅會即時顯示搜索結果,連你的 URL 也會同步更新。讓你的應用程式更具互動性。
  • 強大的模板系統:
    Angular 的模板系統提供了一種簡易的方式來創建動態與靜態模板。你可以輕鬆地將數據顯示在模板中,並使用指令來操作 DOM。使你可以創建美觀且高度互動的用戶界面,而不必過多的手動操作 DOM。
  • 強大的型別檢查:
    Angular 使用 TypeScript 作為主要的程式語言,受益於靜態型別檢查。開發時,TypeScript 將幫助你捕獲潛在的錯誤,從而減少錯誤並提高代碼質量。
  • 依賴注入(Dependency Injection):
    Angular 引入了依賴注入,這是一個重要的設計模式。允許你將服務(Services)或其他代碼注入到你的組件(Components)中,這使得代碼更具可測試性且解耦。你可以在應用程式中輕鬆共享和重用代碼,並實現更好的代碼結構。
  • 內建指令和過濾器:
    Angular 提供了許多內建指令(例如:ngFor、ngIf等)和過濾器(例如:date、currency等),它們使你可以輕鬆地操作 DOM 和格式化數據。減少了重複的代碼,並代碼更加整潔。

建立你的第一個網路應用

安裝 Node.js 和 npm:

Angular 開發需要使用 Node.js 和 npm,你可以從 Node.js 官方網站下載並安裝它​。

安裝 Angular CLI

Angular CLI(命令行界面)是一個強大的工具,可以幫助你快速建立和管理 Angular 專案。讓我們安裝它,在終端機中運行以下命令:

  1. npm install -@angular/cli

這個指令將全局安裝 Angular CLI,讓你可以在任何地方使用它。安裝完成後,運行 ng --version 來確認一下,確保工具安裝完畢。

創建你的第一個 Angular 應用程式

使用 Angular CLI,你可以輕鬆地創建一個嶄新的 Angular 應用程式。請在終端機中運行以下命令:

  1. ng new first-app

這個命令將帶領你回答一些問題,例如是否要包含 Angular 路由等。根據你的需求進行選擇。完成後,Angular CLI 將為你創建一個全新的專案,取名為 first-app,並自動安裝所有必要的依賴項。

Angular 專案結構

在 Angular 專案中,你將看到許多檔案和資料夾,它們具有不同的功能。以下是一些主要的檔案和資料夾:

  • src/:這是你的應用程式代碼的主要目錄。
  • src/app/:這是你的應用程式的核心代碼,包括模組、服務和模組。
  • angular.json:這是 Angular 專案的配置文件,包含構建選項和全局配置。
  • package.json:這是你的專案依賴項的清單,包括 Angular 相關的套件。
  • index.html:你的應用程式的入口 HTML 文件。
  • main.ts:應用程式的主入口 TypeScript 文件。
  • app.module.ts:主要的應用程式模組,它定義了你的應用程式的模組和服務。

啟動 Angular 應用程式

現在,讓我們啟動開發伺服器,預覽你的 Angular 應用程式。在終端機中運行以下命令:

  1. ng serve

這將啟動開發伺服器並在本地瀏覽器中預覽你的應用程式。預設情況下,它將運行在 http://localhost:4200/ 上。


創建、設計、與互動組件

什麼是 Angular 組件?

Angular 的組件是前端應用程式的基本構建塊,想像你今天正在玩一組名為 Angular 的積木,每個組件都是你精心設計的元件,包含自己的工具和特殊功能,最後拼裝成一臺飛機等成果。這種模組化的設計方式使你的程式碼更有組織性,易於管理和維護。

讓我們以一個實例來解釋。假設你正在打造一個現代化的電子商務網站,內含商品列表、購物車和用戶註冊功能。你可以為每個功能建立獨立的組件,例如「Products」、「Cart」和「UserRegistration」。這樣的方式使你的程式碼更清晰,每個功能都有自己的專屬工作空間,使代碼更加易於維護、管理和測試。

現在,讓我們看一個實際的例子。假設你正在開發一個天氣的應用程式,你可以創建一個名為 weather 的組件,該組件負責顯示天氣信息。使用 Angular CLI,你可以運行以下命令創建組件:

  1. ng generate component weather

這將創建一個名為 weather 的組件,並生成相應的文件和資料夾。組件的模板、邏輯和樣式通常存放在一個文件夾中,並按照以下結構組織:

  1. weather/
  2.     ├── weather.component.html
  3.     ├── weather.component.ts
  4.     └── weather.component.css

組件模板與樣式設計

模板:賦予生命

組件的模板是它的格局,就像一個店家的牆壁、管線。模板使用 HTML 編寫,但具有 Angular 的模板語法的優勢,讓你可以動態顯示數據,控制組件的外觀,以及處理事件。

讓我們以 weather 為例,假設我們想要顯示城市的名稱、溫度和天氣狀況。以下是一個簡單的模板:

  1. <h1>{{ city }} 天氣</h1>
  2. <p>溫度:{{ temperature }}°C</p>
  3. <p>天氣狀況:{{ condition }}</p>

在這個模板中,我們使用雙大括號 {{ }} 的語法,將組件中的變數動態顯示在模板上。這就是數據綁定的一個範例,我們將在稍後更詳細地討論。

樣式:美化你的組件

每個組件都有自己的風格,就像是店家的裝潢、店面設計。通常,這些風格存放在 .css.scss 檔案中。你可以在樣式檔案中定義樣式規則,然後將它們套用到組件的 HTML 元素上,以自訂組件的外觀。

舉個例子,如果我們想要為 weather 添加一些樣式,我們可以這樣做:

  1. .weather {
  2.     background-color: #eee;
  3.     padding: 0.5rem 1rem;
  4.     border: 0.125rem solid #000;
  5. }
  1. <div class="weather">
  2.     <h1>{{ city }} 天氣</h1>
  3.     <p>溫度:{{ temperature }}°C</p>
  4.     <p>天氣狀況:{{ condition }}</p>
  5. </div>

在這個範例中,我們為 .weather 類添加了一些樣式,你可以輕鬆地設計組件的外觀,使其符合你的應用程式風格。

組件間的通信

在實際應用中,組件之間需要相互通信和共享數據。Angular 提供了多種方式來實現組件間的通信。

輸入和輸出屬性是一種常見的組件間通信方式。透過輸入屬性,你可以將資料傳遞給子組件,而透過輸出屬性,子組件則可以發送事件給父組件。

我們一樣用weather的組件,寫一個 city-selector 來搭配做範例,當用戶在 city-selector 中選擇一個城市時,將該城市的名稱傳遞給 weather 並更新天氣信息。我們可以這樣做:

首先,讓我們創建 city-selector 組件。

  • city-selector.component.html
    1. <div>
    2.     <input type="text" [(ngModel)]="selectedCity" placeholder="輸入城市名稱">
    3.     <button (click)="onCitySelected()">選擇城市</button>
    4. </div>
    將用戶輸入的城市名稱存儲在 selectedCity 變數中,並透過點擊觸發 selectCity。使用 [(ngModel)] 來實現雙向數據綁定,後續還需配置 FormsModule,以支援雙向數據綁定。CitySelectorComponent但接下來,我們先在 city-selector.component.ts 中實現相關的邏輯。
  • city-selector.component.ts
    1. import { Component, Output, EventEmitter } from "@angular/core";
    2. @Component({
    3.     selector: "app-city-selector",
    4.     templateUrl: "./city-selector.component.html",
    5.     styleUrls: ["./city-selector.component.css"]
    6. })
    7. export class CitySelectorComponent {
    8.     @Output() citySelected: EventEmitter<string> = new EventEmitter<string>();
    9.     selectedCity: string = ""; // 使用 ngModel 來綁定輸入框的值
    10.     onCitySelected() {
    11.         if (this.selectedCity) {
    12.             this.citySelected.emit(this.selectedCity); // 發送選擇的城市名稱到父組件
    13.         };
    14.     };
    15. };
    接下來,讓我們使用 weather 組件來接收這個城市名稱並更新天氣信息。
  • weather.component.ts
    typescript
    weather 組件中,我們設置了 citytemperaturecondition 屬性,用於顯示城市名稱、溫度和天氣狀況。然後,我們創建了一個 onCitySelected 方法,用於接收從 city-selector 組件傳遞過來的城市名稱,並根據城市名稱更新天氣信息。
    然後,在你的 weather.component.html 模板中,你可以使用這些屬性來顯示天氣信息。
  • weather.component.html
    1. <div class="weather">
    2.     <h1>{{ city }} 天氣</h1>
    3.     <p>溫度:{{ temperature }}°C</p>
    4.     <p>天氣狀況:{{ condition }}</p>
    5. </div>
    最後我們在主要的應用程式模組 app.module.ts 中導入 FormsModule 並配置 CitySelectorComponentWeatherComponent
  • app.module.ts
    1. // app.module.ts
    2. import { NgModule } from "@angular/core";
    3. import { BrowserModule } from "@angular/platform-browser";
    4. import { FormsModule } from "@angular/forms"; // 導入 FormsModule
    5. import { AppComponent } from "./app.component";
    6. import { CitySelectorComponent } from "./city-selector.component"; // 確保路徑正確
    7. import { WeatherComponent } from "./weather.component"; // 確保路徑正確
    8. @NgModule({
    9.     imports: [
    10.         BrowserModule,
    11.         FormsModule, // 添加 FormsModule 到 imports
    12.     ],
    13.     declarations: [
    14.         AppComponent,
    15.         CitySelectorComponent,
    16.         WeatherComponent,
    17.     ],
    18.     bootstrap: [AppComponent],
    19. })
    20. export class AppModule { };
  • app.component.html
    1. <app-city-selector (citySelected)="onCitySelected($event)"></app-city-selector>
    2. <app-weather [city]="selectedCity"></app-weather>
    這樣,當用戶在搜索框中輸入城市名稱時,它將被傳遞給 WeatherComponent,並顯示相應的天氣信息。

組件生命週期

Angular 組件的生命週期是開發過程中的關鍵,因為它涵蓋了組件從創建到銷毀的整個過程。這些生命週期允許你在不同階段執行自定義邏輯,以確保你的應用程序按照預期運行。

Angular 提供了一系列的生命週期,如 ngOnInitngOnChangesngOnDestroy 等,它們允許你在特定階段執行自定義邏輯。如果你需要在組件初始化時執行某些代碼,你可以使用 ngOnInit

  1. ngOnInit() {
  2.     this.fetchWeatherInfo(this.city);
  3. }

在這個範例中,我們使用了一個更具描述性的函數名 fetchWeatherInfo 來代表獲取天氣信息的操作,這樣可以讓代碼更易讀。請確保在 ngOnInit 中執行的操作是輕量級的,以避免影響組件的性能。

ngOnChanges 通常用於監視組件的輸入屬性變化,但是在使用之前,你應該先檢查新值和舊值是否真的有變化,以避免不必要的操作。這樣可以提高性能並減少不必要的重繪。

  1. ngOnChanges(changes: SimpleChanges) {
  2.     if (changes.city && changes.city.currentValue !== changes.city.previousValue) {
  3.         this.fetchWeatherInfo(this.city);
  4.     }
  5. }

在這個範例中,我們通過比較 currentValuepreviousValue 來確保只有在 city 真正發生變化時才執行 fetchWeatherInfo

最後,在組件銷毀時進行資源的清理工作,以避免內存泄漏。你可以使用 ngOnDestroy 來實現這一點,例如取消訂閱觀察者或清理訂閱的資源。

  1. ngOnDestroy() {
  2.     this.subscription.unsubscribe();
  3. }

數據綁定:讓你的應用程式活躍起來

單向數據綁定:

單向數據綁定是一種將數據從組件傳遞到模板的方式,但不允許模板對數據進行修改。這種綁定方式通常用於將組件中的數據呈現在用戶界面上,這樣用戶就可以查看相關數據。舉例來說,如果我們有一個顯示用戶名的變數,我們可以在模板中這樣使用:

  1. <p>{{ username }}</p>
雙向數據綁定:

這是一個更強大的綁定方式,它不僅允許數據從組件傳遞到模板,還允許模板中的變更即時回傳到組件。這在處理表單元素等需要用戶輸入的情境中非常實用。例如,如果我們有一個用戶名的輸入框,我們可以使用[(ngModel)]實現雙向綁定:

  1. <input [(ngModel)]="username">

現在,當用戶在輸入框中輸入用戶名時,username 變數的值將自動更新,同時如果在組件中修改了 username 的值,輸入框中的內容也會同步更新。

ps. 需要在模塊中導入 FormsModule,並在組件中引入 FormsModule 以使用雙向數據綁定。且需要確保模型屬性與模板中綁定的屬性名稱匹配,否則綁定將不起作用。

使用插值表達式和屬性綁定將數據顯示在模板中

在我們之前的範例中,我們已經看到了插值表達式的使用,它允許我們將組件中的變數值顯示在模板中。另一種方式是使用屬性綁定,它允許我們將組件的屬性值綁定到模板元素的屬性上。

插值表達式:

插值表達式使用雙花括號 {{ }} 來包圍組件變數,將其值插入到模板中。這對於將數據呈現在 HTML 中非常方便。例如:

  1. <p>{{ message }}</p>
屬性綁定:

屬性綁定使用方括號 [ ] 來指定要綁定的 HTML 元素屬性,然後將組件的屬性值賦給它。這對於動態設置 HTML 元素的屬性非常有用。例如:

  1. <img [src]="imageUrl">

另一個常見的用例是屬性綁定到元素的 disabled 屬性,這在按鈕等元素中非常有用:

  1. <button [disabled]="isDisabled">點擊</button>

在這個例子中,isDisabled 變數的值決定了按鈕是否處於禁用狀態。當 isDisabledtrue 時,按鈕將被禁用。

事件處理:讓你的應用程式對用戶做出反應

在Angular中,事件處理是實現與用戶互動的重要部分。你可以透過事件綁定來捕獲和處理各種用戶交互事件,例如點擊、輸入、滑鼠事件等。以下是更詳細的說明和範例:

事件綁定:

事件綁定允許你將模板中的事件(如點擊)與組件中的方法關聯起來,以便在事件發生時執行特定的操作。你可以使用括號 () 來指定事件,並在括號內放入事件名稱,然後指定要調用的組件方法。舉例如下:

  1. <button (click)="onButtonClick()">點擊我</button>
  1. onButtonClick() {
  2.   this.fetchWeatherInfo(this.city);
  3. }

這樣,當用戶點擊按鈕時,onButtonClick 方法將被觸發。

事件對象:

有時候,你可能需要獲取有關事件的更多信息,例如滑鼠事件的坐標或鍵盤事件的按鍵信息。你可以通過將 $event 傳遞給組件方法來獲取事件對象。舉例來說:

  1. <input (keyup)="onKeyUp($event)">
  1. onKeyUp(event: KeyboardEvent) {
  2.   console.log(event.keyCode);
  3. }

這樣,你可以獲取有關按鍵事件的詳細信息並做出相應的處理。


設置路由以及子路由

路由是指導航的基礎,它允許你在不同的視圖之間切換,就像在單頁應用程式中一樣。在 Angular 中,你可以使用 RouterModule 來設置路由。讓我們假設你正在建立一個簡單的博客應用程式,其中包含主頁、文章列表和文章詳細頁面。

設置路由

你需要在應用程式的根模塊中配置路由器:

  1. // app.module.ts
  2. import { NgModule } from "@angular/core";
  3. import { RouterModule, Routes } from "@angular/router";
  4. import { HomeComponent } from "./home.component";
  5. import { BlogListComponent } from "./blog-list.component";
  6. import { BlogSingleComponent } from "./blog-single.component";
  7. const routes: Routes = [
  8.     { path: "", component: HomeComponent },
  9.     { path: "blog", component: BlogListComponent },
  10.     { path: "blog/:id", component: BlogSingleComponent },
  11. ];
  12. @NgModule({
  13.     imports: [RouterModule.forRoot(routes)],
  14.     exports: [RouterModule],
  15. })
  16. export class AppModule {}

在這個例子中,我們定義了三個路由:主頁、文章列表頁面 blog 和文章詳細頁面 blog/:id。這些路由與相應的組件相關聯。

接下來,你需要在應用程式的模板中添加一個 <router-outlet></router-outlet> 標記,這個標記將用來顯示不同路由下的內容:

  1. <nav>
  2.     <a routerLink="/">主頁</a>
  3.     <a routerLink="/blog">文章列表</a>
  4. </nav>
  5. <router-outlet></router-outlet>

你已經設置了基本的路由結構。當用戶點擊頁面上的鏈接時,應用程式將根據路由的定義顯示相應的內容。

設置子路由

你可能希望在一個路由下設置子路由,來實現更複雜的導航結構。假設你的博客應用程式需要一個用於編輯文章的功能,你可以使用子路由來實現:

  1. const routes: Routes = [
  2.     { path: "", component: HomeComponent },
  3.     { path: "blog", component: BlogListComponent },
  4.     {
  5.         path: "blog/:id",
  6.         component: BlogSingleComponent,
  7.         children: [
  8.             { path: "edit", component: BlogEditComponent },
  9.         ],
  10.     },
  11. ];

當用戶訪問 articles/1/edit 時,將顯示 BlogEditComponent

路由守衛

路由守衛允許你在導航到某個路由前執行一些操作。這對於驗證用戶、保護特定頁面或執行特定任務非常有用。在 Angular 中,有四種主要的路由守衛:

  • CanActivate: 檢查是否允許訪問某個路由。
  • CanDeactivate: 檢查是否允許離開某個路由。
  • Resolve: 在路由激活前解析數據。
  • CanLoad: 在懶加載模塊前檢查權限。

假設你希望確保只有登入的用戶才能訪問編輯文章頁面:

  • auth.service.ts
    1. import { Injectable } from "@angular/core";
    2. @Injectable({
    3.     providedIn: "root",
    4. })
    5. export class AuthService {
    6.     private isAuthenticated: boolean = false;
    7.     login() {
    8.         this.isAuthenticated = true;
    9.     }
    10.     logout() {
    11.         this.isAuthenticated = false;
    12.     }
    13.     isAuthenticatedUser(): boolean {
    14.         return this.isAuthenticated;
    15.     }
    16. }
    在這個範例中,AuthService 服務模擬了 loginlogoutisAuthenticatedUser 身份驗證操作的方法。
  • auth.guard.ts
    1. import { Injectable } from "@angular/core";
    2. import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from "@angular/router";
    3. import { Observable } from "rxjs";
    4. import { AuthService } from "./auth.service"; // 導入AuthService
    5. @Injectable({
    6.     providedIn: "root",
    7. })
    8. export class AuthGuard implements CanActivate {
    9.     constructor(private authService: AuthService, private router: Router) {}
    10.     canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    11.         if (this.authService.isAuthenticatedUser()) {
    12.             return true;
    13.         } 
    14.         else {
    15.             // 如果未登入,重定向到登入頁面
    16.             this.router.navigate(["/login"]);
    17.             return false;
    18.         }
    19.     }
    20. }
    在這個範例中,AuthGuard 檢查用戶是否已登入,如果已登入,則允許訪問路由,否則將用戶重定向到登入頁面。接下來,我們可以在路由配置中使用這個守衛。假設我們有一個需要身份驗證的私人頁面,我們可以這樣設置它:
  • app.module.ts
    1. const routes: Routes = [
    2.     {
    3.         path: "articles/:id/edit",
    4.         component: BlogEditComponent,
    5.         canActivate: [AuthGuard],
    6.     },
    7. ];
    當用戶訪問 articles/1/edit 頁面時,將首先觸發 AuthGuard,並檢查是否允許訪問。

懶加載

懶加載是一種優化技術,它允許你將應用程式按需分割成多個模塊,以降低初始加載時間。在 Angular 中,你可以使用懒加载模塊來實現這一目標。先建立一個懒加載模塊:

  • blog-list.module.ts
    1. // 
    2. import { NgModule } from "@angular/core";
    3. import { RouterModule, Routes } from "@angular/router";
    4. import { BlogListComponent } from "./blog-list.component";
    5. const routes: Routes = [
    6.     { path: "", component: BlogListComponent },
    7. ];
    8. @NgModule({
    9.     imports: [RouterModule.forChild(routes)],
    10.     exports: [RouterModule],
    11. })
    12. export class BlogListModule {}
    接著,在路由配置中使用 canLoad 守衛來實現懶加載:
  • app.module.ts
    1. const routes: Routes = [
    2.     {
    3.         path: "blog",
    4.         loadChildren: () => import("./blog-list.module").then((m) => m.BlogListModule),
    5.         canLoad: [AuthGuard],
    6.     },
    7. ];
    這將使 BlogListModule 在用戶首次訪問 /blog 頁面時才被加載。

模板驅動表單 vs. 響應式表單

模板驅動表單

模板驅動表單是一種較簡單的方式來處理表單,它的主要思想是將表單的狀態和行為與模板相關聯。在這種方法中,你可以使用 Angular 模板語法來建立表單,然後通過模板變數來訪問和操作表單控件。以下是一個簡單的模板驅動登入表單:

  1. <form #loginForm="ngForm" (ngSubmit)="onSubmit()">
  2.     <label for="username">名稱:</label>
  3.     <input type="text" id="username" name="username" [(ngModel)]="user.username" required>
  4.     <label for="password">密碼:</label>
  5.     <input type="password" id="password" name="password" [(ngModel)]="user.password" required>
  6.     <button type="submit">登入</button>
  7. </form>

在這個例子中,我們使用 ngForm 指令來建立表單,並使用 [(ngModel)] 雙向數據綁定將表單控件與組件中的屬性關聯起來。當用戶提交表單時,我們呼叫 onSubmit 方法來處理表單數據。

模板驅動表單的優勢在於簡單易上手,特別適用於簡單的表單。但對於複雜的表單,響應式表單可能更適合。

響應式表單

響應式表單是一種更強大和靈活的方式來處理表單。它使用 RxJS 庫來建立表單控件的可觀察對象,這些對象允許你更細粒度地控制表單的狀態和行為。以下是一個簡單的響應式登入表單範例:

  1. import { Component } from "@angular/core";
  2. import { FormBuilder, FormGroup, Validators } from "@angular/forms";
  3. @Component({
  4.     selector: "app-login",
  5.     template: `
  6.         <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
  7.             <label for="username">名稱:</label>
  8.             <input type="text" id="username" formControlName="username">
  9.             <label for="password">密碼:</label>
  10.             <input type="password" id="password" formControlName="password">
  11.             <button type="submit">登入</button>
  12.         </form>
  13.     `,
  14. })
  15. export class LoginComponent {
  16.     loginForm: FormGroup;
  17.     constructor(private fb: FormBuilder) {
  18.         this.loginForm = fb.group({
  19.             username: ["", Validators.required],
  20.             password: ["", Validators.required],
  21.         });
  22.     }
  23.     onSubmit() {
  24.         if (this.loginForm.valid) {
  25.             // 表單驗證通過,處理登入
  26.         }
  27.     }
  28. }

在這個範例中,我們使用 FormBuilder 創建了一個包含驗證規則的 FormGroup,並通過 formControlName 將表單控件綁定到模板中。當表單驗證通過時,我們執行登入操作。

表單驗證與錯誤處理

表單驗證是確保用戶輸入的數據符合預期的一個關鍵部分。不論你使用模板驅動表單還是響應式表單,Angular 都提供了豐富的驗證機制來幫助你實現這一目標。假設用戶名和密碼字段都是必填的。在模板驅動表單中,你可以添加 required 屬性:

  1. <input type="text" id="username" name="username" [(ngModel)]="user.username" required>
  2. <input type="password" id="password" name="password" [(ngModel)]="user.password" required>

在響應式表單中,你則是這樣做:

  1. this.loginForm = fb.group({
  2.     username: ["", [Validators.required]],
  3.     password: ["", [Validators.required]],
  4. });

當用戶未填寫必填字段時,Angular 將自動顯示錯誤消息。你還可以自定義驗證器來滿足特定需求,例如驗證用戶名是否唯一或密碼是否符合一定的複雜性要求。

在錯誤處理方面,Angular 允許你輕鬆地檢測和處理表單中的錯誤。你可以通過以下方式檢測特定控件的錯誤:

  1. const usernameControl = this.loginForm.get("username");
  2. if (usernameControl.hasError("required")) {
  3.   // 用戶名是必填的,執行相應的處理
  4. }

HTTP 請求的使用、錯誤處理與測試

送出 HTTP 請求

Angular 提供了 HttpClient 模組,用於發送 HTTP 請求。使用它,你可以輕鬆地執行 GETPOSTPUTDELETE 等各種類型的請求。以下提供一個範例:

  1. import { HttpClient } from "@angular/common/http";
  2. constructor(private http: HttpClient) {}
  3. getData() {
  4.     return this.http.get("/api/data");
  5. }
  6. postData(data: any) {
  7.     return this.http.post("/api/data", data);
  8. }

在這個範例中,我們定義了 getDatapostData 方法,分別向後端發送 GETPOST 請求。

處理 HTTP 錯誤

在現實應用中,HTTP 請求可能會遇到錯誤,例如服務器錯誤或網絡問題。為了提供更好的用戶體驗,我們需要妥善處理這些錯誤。

  1. getData() {
  2.     return this.http.get("/api/data").pipe(
  3.         catchError((error: any) => {
  4.             // 在這裡處理錯誤,例如顯示錯誤消息或記錄錯誤
  5.             return throwError("錯誤");
  6.         })
  7.     );
  8. }

在這個範例中,我們使用 catchError 來執行相應的處理。根據實際需求進行錯誤處理。

測試 HTTP 請求

測試是確保應用程式穩定性的關鍵部分。對於 HTTP 請求,你可以使用 Angular 的測試工具來撰寫單元測試和集成測試。假設我們有一個 DataService 需要測試:

  • data.service.ts
    1. import { Injectable } from "@angular/core";
    2. import { HttpClient } from "@angular/common/http";
    3. import { Observable } from "rxjs";
    4. @Injectable({
    5.     providedIn: "root",
    6. })
    7. export class DataService {
    8.     constructor(private http: HttpClient) {}
    9.     getData(): Observable<any> {
    10.         return this.http.get("/api/data");
    11.     }
    12. }
    接下來編寫一個單元測試,來測試 DataServicegetData
  • data.service.spec.ts
    1. import { TestBed, inject } from "@angular/core/testing";
    2. import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
    3. import { DataService } from "./data.service";
    4. describe("DataService", () => {
    5.     let service: DataService;
    6.     let httpMock: HttpTestingController;
    7.     beforeEach(() => {
    8.         TestBed.configureTestingModule({
    9.             imports: [HttpClientTestingModule],
    10.             providers: [DataService],
    11.         });
    12.         service = TestBed.inject(DataService);
    13.         httpMock = TestBed.inject(HttpTestingController);
    14.     });
    15.     it("應該被建立", () => {
    16.         expect(service).toBeTruthy(); // 確保 service 變數不是 null 或 undefined
    17.     });
    18.     it("應該透過 GET 從 API 獲取數據", () => {
    19.         const testData = { message: "這是一個測試" };
    20.         service.getData().subscribe((data) => {
    21.             expect(data).toEqual(testData); // 驗證數據是否如預期
    22.         });
    23.         const req = httpMock.expectOne("/api/data");
    24.         expect(req.request.method).toEqual("GET"); // 方法是 GET
    25.         req.flush(testData); // 模擬 HTTP 請求
    26.         httpMock.verify();
    27.     });
    28.     afterEach(() => {
    29.         httpMock.verify();
    30.     });
    31. });

查看 Angular 的官方文檔和教程:

Angular 的官方文檔是一個非常棒的學習資源,它包括了導覽、教程和詳細的API參考。通過閱讀文檔和完成官方教程,你可以快速地瞭解 Angular 的基本概念和使用方法。


系列文章


相關連結