18年07月18日

再見了 AngularJS!

AngularJS跟CoffeeScript一直以來都是我最喜歡的組合,到現在也是!但是最到後都是要轉到…AngularX!話雖如此,我也是經過2年的掙扎才作出轉變的,期間也試用過Vue.js跟React,可是最後還是選擇了Google

為甚麼?

在應用一個新技術的時候,我經常問自己很多問題,值不值得轉變,如果你有也同樣的煩惱,不妨看看,這些就是我對這次轉變的看法

為甚麼要離開AngularJS?

AngularJS也有一定的歷史了,從2010年到現在一直在變化著,可是由於Angular 2的出現,現在已經幾乎定止更新了,而我想他們進行大改動的契機就是ES2015和TypeScript的興起吧!我是有Angular 2才知道有Typescript的存在,Microsoft和Google合作,誰會不注意呢?但是它的不成熟令我討厭Angular 2,雖然短短兩年就證明了Angular選用是一個明智的選擇

為甚麼是2年後?

其實不只2年了,2年是只從RC開始計算的,實際我從Alpha的時候就開始學習了,當時還是用SystemJS、沒有cli、沒有webpack、TypeScript非常不成熟和麻煩(想想那個該死的reference),而且社群還沒有準備好升到Angular2,所以很多模組都還是在開發階段,可想而知用戶體驗是最惡劣的那種,所以我卻步了。到後來RC讓我再度失望,直至2年後多方面的改善(也包括Typescript),而且明顯模組已經追得上其他Framework了,所以我決定正式在工作上使用

為甚麼AngularX

最大的原因是我一直在用AngularJS,所以AngularX自然就是我的first choice,然後它內建Typescript亦是我的考慮因素,畢竟官方支援與社群支援完全是兩回事,而且官方是 Google…我相信其他Framework有它們的好處,當初我從React跟AngularJS二選一的時候就選了AngularJS,所以沒甚麼好說的,然後Vue給我的感覺是太隨便、太自由,這或許是它的優點,但同時可能成為團隊開發當中致命的缺點!

就因為想進步 (๑•̀ㅂ•́)و

讓自己的技術跟上潮流,沒有比這個讓我更有動力的了!

有甚麼轉變?

最讓我感到困擾的莫過於rxjsngrx了!簡單來說rxjs就是用Observable取代Promise,改為可取消的Streaming請求,原本兩行原碼變成要寫成數十行才能運行的魔法๛ก(ー̀ωー́ก);ngrx是把原本只要Service裡面加上Getter和Setter,升級到需要細分成Service、Effect、Action和Reducer四個部份,然後各自寫上過百行原碼才能運行的神之設計 Σ(°ཀ°)…當然!這是我是真的認為有意義才會採用的,而概括而言是變複雜了但更容易maintain

從Promise到Observable

看一個Ajax request就會明白了,在AngularJS我們會這樣寫…

Copy Code

$http.get('api/users/me').then((me) => {
  $scope.me = me;
});

然後Binding方面是這樣

Copy Code

<p>{{ me.name }}</p>

很簡單對吧?再看看AngularX + rxjs,其實也不難!

Copy Code

export class AppComponent {
  me$: Observable<any>;

  constructor(http: HttpClient) {
    this.me$ = http.get('api/users/me');
  }
}

使用async監測Observable

Copy Code

<p>{{ (me$ | async)?.name }}</p>

Bye Bye! Controller + Scope!

我相信很多AngularJS的同學都對Controller+Scope這個組合不陌生,它的缺點就是完全無法做maintenance!即使後來有人提倡完全不用Controller而改由Directive控制所有component,也還是不能避免有parent的情況

就像這樣UserCtrl內還是能夠碰到AppCtrlvariable,這就是我們想極力避免的parent-children架構

Copy Code

<div ng-controller="AppCtrl as app">
  <div ng-controller="UserCtrl as user">{{app.version}}</div>
</div>

後來在AngularX幾乎所有view都可以使用Component取代,而且內建了selector、一個html template和只有該頁面能使用的CSS,即Encapsulation

Copy Code

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {}

所以現在這個設計就把variable集中class內,需要分享的時候就使用service或是之後會提到的ngrx

Copy Code

<app-root>
  <app-user></app-user>
</app-root>

自私用的CSS(Encapsulation)

在AngularJS全部CSS都是global使用的,所以不免要用大量id或是class去區分,這亦是以往的正規做法,自從AngularX就支援了Encapsulation,簡單說就是封裝了的styles只會指派到一個component,這樣寫起來也方便多了,而且這個設計原意是為了提升讀取速度,該部份的原碼只會在頁面讀取時載入,克服了SAP(Single Page Application)的一個缺陷

app.component.css

Copy Code

p {
  color: yellow;
}

只有在App Component內的<p>才會變成黃色

Copy Code

<p>黑色</p>
<app-root>
  <p>黃色</p>
</app-root>

Ngrx的State Sharing

雖然這個不是AngularX固有的,但是也不得不提起它,由於實在太複雜所以我也不打算在這裡放原碼了,上面提到我們想避免parent-children設計,所以解決方法就是做state sharing,甚麼是state sharing?

這是AngularJS parent-children的設計,我們會用controller取得variable,然後跟其他兩個component分享,ProductCtrl就是parent<app-shopping-cart><app-wishlist>就是children

Copy Code

<div ng-controller="ProductCtrl as product">
  <app-shopping-cart ng-model="product.items"></app-shopping-cart>
  <app-wishlist ng-model="product.items"></app-wishlist>
</div>

如果轉換成ngrx的風格,我們會使用@ngrx/storeproduct轉成state,然後就能夠跟不同的component分享了,我們不再需要指派到ngModel

Copy Code

<app-shopping-cart></app-shopping-cart>
<app-wishlist></app-wishlist>

而是在Shopping CartWishlist內透過store取得同樣的資源

Copy Code

export class ShoppingCartComponent {
    items$: Observable<ProductItem[]>;

    constructor(private store: Store<fromStore.ProductsState>) {
        this.stores$ = this.store.select(fromStore.getProducts);
    }
}
<li *ngFor="let item of items$ | async">{{ item.name }}</li>

感想

一字記之曰「難」!難於學習新的技術,難於想把它做好,不妨說最初我也是用Promise很快就做完了第一個版本,後來改成ngrx才發現是真正的挑戰,Github wiki看不懂的時候就看其他人寫的文章,再有疑惑的時候就看影片、看Angular Conference,再從中找到適合自己的風格和結構,架出來之後就會發現維護變得簡單多了!再回頭看看有不少跟我同期畢業的Web Developer,都工作好幾年了還是很小使用Framework的經驗,這又證明了自己再次領先一步了!

d(`・∀・)b