淺談 Angular 專案實戰

敘帝利發表於2019-03-01

為什麼使用 Angular

我不是 Angular 的佈道者,但如今自稱 Angular 派,使用 Angular 做專案讓我有一種興奮感。目前的三大主流前端框架都研究過,部落格中也有三者的相關教程,最早接觸的是 React,但是並沒有實際的專案經驗,只做過一些 Demo 。使用 Vue 做過一個比較複雜的移動端大資料專案,技術棧採用 Framework7 + Vue + Vuex,整體效果還是滿意的。

因為去年的專案幾乎都是後臺管理系統,所以框架用的不多,主要還是傳統方式開發,後期為了改善前端開發體驗,逐步在向框架靠攏。通過第三方 Bootstrap 框架對比發現,大多都有 Angular 版本,而且元件庫是最全的,React 和 Vue 版本的元件庫相對匱乏一些。事實證明使用 Angular 開發大型後臺管理系統具有獨特的優勢。另一方面, Angular 是困難度複雜度的一個縮影,它匯聚了設計模式、設計哲學、工程化思想,對於前端開發是質的飛越。 除此之外,Angular 的文件讓我著迷,除了基本的教程之外,其核心知識是最讓我津津樂道的地方,不僅可以瞭解技術內幕,甚至可以學習很多基礎知識,都非常實用,對於前端新手以及業餘愛好者都有很大的幫助作用。

使用 Angular 開發需要非常多的前置知識,比如 TypeScript、RxJS 等,所以學習成本比較高,這也是很多人望而卻步的一個原因。在經過很長時間的學習及準備之後,終於在今年有了專案實戰的機會,專案很小,是整個系統中的一個獨立模組,但是幾乎所有知識都有涉獵,可謂“麻雀雖小五臟俱全”。本文就是對該專案的一些總結及思考。

搭建開發環境

開發環境的搭建非常簡單,使用 Angular CLI 幾乎可以完成所有工作,但是在與後端聯調介面的時候,還需要做一些自定義配置。以下是 proxy.config.json 檔案的基本設定:

{
  "/api": {
    "target": "http://localhost:3000",
    "secure": false
  }
}
複製程式碼

Angular CLI 的使用貫穿整個專案,從開發到打包無處不在,這也是 Angular 工程化的體現。因為 CLI 的引數非常多,必須仔細閱讀文件,合理設定引數,所有的需求幾乎都能在引數中找到。其中使用 ng build 打包後可能會有資源引用錯誤的問題,可以看一下使用 ng build 構建後資源地址引用錯誤的問題

在聯調介面時,可能還會遇到傳輸 Cookie 的問題,具體可以參見 關於 Angular 跨域請求攜帶 Cookie 的問題

選擇 UI 庫

因為專案比較小,開發之初打算自己寫元件,比如分頁,但實際情況比較複雜,尤其剛接觸 Angular,對於元件互動、非同步資料還有點懵,嘗試寫了一下,仍然有很多問題,所以最終還是選擇比較成熟的 UI 庫。

UI 庫的選擇需要根據樣式決定,比如我的專案使用的是 Bootstrap,所以 UI 庫選擇了和 Bootstrap 相關的 ngx-bootstrap。對於後臺管理系統,常用的元件無外乎彈窗、分頁、標籤頁等。對於更復雜的系統,也可以根據自己的情況選擇其他元件更豐富的 UI 庫,比如 PrimeNG 等。

元件庫主要使用了彈窗及分頁,其中 ngx-bootstrap 的彈窗是一個比價優秀的元件,資訊輸入及提示都會用到。以下是一個自定義 Alert 彈窗,通過 Service 共享元件即可。

modal-alert.component.html 中的程式碼是整個元件的 HTML 結構,有兩個變數及一個例項方法。

<div class="modal-header">
  <h4 class="modal-title pull-left">{{title}}</h4>
  <button type="button" class="close pull-right" aria-label="Close" (click)="bsModalRef.hide()">
    <span aria-hidden="true">&times;</span>
  </button>
</div>
<div class="modal-body">
  {{content}}
</div>
<div class="modal-footer">
  <button type="button" class="btn btn-default" (click)="bsModalRef.hide()">關閉</button>
</div>
複製程式碼

modal-alert.component.ts 中定義變數及元件例項。

import { Component, OnInit } from `@angular/core`;

import { BsModalRef } from `ngx-bootstrap/modal/bs-modal-ref.service`;

@Component({
  selector: `app-modal-alert`,
  templateUrl: `./modal-alert.component.html`,
  styleUrls: [`./modal-alert.component.css`]
})
export class ModalAlertComponent implements OnInit {

  title: string;
  content: string;
 
  constructor(public bsModalRef: BsModalRef) {}
 
  ngOnInit() {
  }

}
複製程式碼

modal.service.ts 中定義了元件的公共方法 modalAlert()

export class ModalService {
  modalRef: BsModalRef;
 
  constructor(private modalService: BsModalService, private http: HttpClient) { }

  modalAlert(msg: string) {
    const initialState = {
      content: msg,
      title: `提示資訊`
    };
    this.modalRef = this.modalService.show(ModalAlertComponent,
      {
        initialState: initialState,
        class: `modal-sm`
      }
    );
  }
}
複製程式碼

最後還需要在 app.module.ts 中定義 entryComponents,比如:

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  ...
  entryComponents: [ModalAlertComponent, ModalConfirmComponent]
})
複製程式碼

還有一點需要注意,在使用模板引用變數時,不要和函式名重名,有時圖省事可能會忽略這一點。比如以下程式碼會報錯:

<ng-template #Alert>
  ...
  <div class="modal-footer">
    <button type="button" class="btn btn-primary" (click)="Alert()">確定</button>
  </div>
</ng-template>
複製程式碼

表單的多樣性

Angular 提供了兩種表單,模板驅動表單響應式表單。其中模板驅動表單簡單靈活,適用於不復雜的表單資料。

關於表單這一塊,我們將 Angular 和 Vue 放在一起說,Vue 的表單繫結就屬於模板驅動表單。不過 Angular 的模板驅動表單並沒有核取方塊的多選繫結,如果有這個需求,可以選擇更加靈活強大的響應式表單進行資料繫結。其實,對於陣列形式的資料可以使用天然的 select 多選框實現。比如以下程式碼:

<div class="form-group">
  <label for="power">Hero Power</label>
  <select class="form-control"  id="power"
          multiple
          required
          [(ngModel)]="model.power" 
          name="power">
    <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
  </select>
</div>
複製程式碼

關於陣列型別的資料,在 Vue 中有兩種繫結方法,分別是核取方塊及 select 多選框。然而核取方塊的 value 值只有 true 或者 false,而 select 多選框的 value 值就是陣列。所以 Vue 對核取方塊的多選操作進行了處理,而 Angular 沒有,需要你自己處理。通過 Angular 的響應式表單可以很容易實現。但是對於模板驅動表單也可以用另類的方式實現,比如手動實現一個雙向資料繫結,雖然有點麻煩,但卻是可行的。關於這個話題我放到下一篇文章中說明。

官方文件中關於表單的內容非常詳細,從使用者輸入到繫結再到校驗,比著葫蘆畫瓢就可以輕鬆實現雙向資料繫結。我非常喜歡 Angular 中 [()] (盒子裡的香蕉)這種資料繫結方式,通過閱讀官方文件的核心知識,對於雙向資料繫結的認識有了質的提高。

管道之資料對映

管道的用處非常大,就我個人而言,時間轉換及資料對映比較常見。我主要想討論一下資料對映的問題。起初打算自己寫關於資料對映的管道,但是想了想,難道不同的資料對映都單獨寫一個管道?然後我就想有沒有自帶的管道實現資料對映,仔細翻了翻文件,最後終於找到了,I18nPluralPipe 就是用於對映資料的。我們用一個最常見的資料對映例子說明,比如儲存性別資料時,1 表示男,2 表示女。

@Component({
  selector: `i18n-plural-pipe`,
  template: `<div>{{ sex | i18nPlural: sexMapping }}</div>`
})
export class I18nPluralPipeComponent {
  sex: string = `1`;
  sexMapping: {[k: string]: string} = {`=1`: `男`, `=2`: `女`, `other`: `其他`};
}
複製程式碼

I18nPluralPipe 使用了 ICU 格式,確實長見識了。這個管道真的很好用,至少不用對每一個資料對映都寫一個專用管道了。

上方示例程式碼中, sexMapping 使用介面中的可索引的型別進行定義。

非同步開發之 RxJS

關於 RxJS 是一個比較複雜的話題,我也沒有完全弄明白。Angular 官網的定義如下:

響應式程式設計是一種面向資料流和變更傳播的非同步程式設計正規化(Wikipedia)。RxJS(響應式擴充套件的 JavaScript 版)是一個使用可觀察物件進行響應式程式設計的庫,它讓組合非同步程式碼和基於回撥的程式碼變得更簡單 (RxJS Docs)。

關於非同步開發的歷史在面試中有遇到過,可以說的東西很多,比如回撥函式、Promise、迭代器和生成器、async 和 await,除此之外,RxJS 中的可觀察物件(Observable)應該是下一個更強大的非同步程式設計方式。Angular 官網對可觀察物件(Observable)和承諾(Promise)進行了對比

需要特別注意的就是,只有當訂閱 Observable 的例項時,它才會開始釋出值。 訂閱時要先呼叫該例項的 subscribe() 方法,並把一個觀察者物件傳給它,用來接收通知。我剛開始使用時,也是因為這個原因被坑了一把。以下是一個很簡單的官方示例:

import { ajax } from `rxjs/ajax`;

// 建立一個傳送 AJAX 請求的 Observable 物件
const apiData = ajax(`/api/data`);
// 訂閱請求
apiData.subscribe(res => console.log(res.status, res.response));
複製程式碼

總結

這個簡單的小專案用了大約一週多的時間,Angular 算是入門了,關於 Angular 還有非常多值得深究的知識。整體而言,Angular + TypeScript 的開發方式非常舒服,VSCode 對 TS 的支援非常完美,語法提示、自動補全都很方便,強型別語言是前端開發的趨勢。使用 Angular 開發,正如我文章開頭提到的一樣,不僅僅是學習一個框架,而是學習一種思想,瞭解更加優秀的開發模式、開源專案,可以讓自己始終站在技術的前沿,這是我最大的收穫。

相關文章