[無瑕的前端-3] 終卷

Angular、TypeScript實作前端MVC架構

前言

在中卷中,我們將所有前端功能的職責都區分好了,並且也順利地結合到原本的MVC框架中。而這一篇文章要介紹的東西是如何將網站共用的功能獨立成一個一個的元件,比如說ERP系統很多頁面都會有簽核功能、檔案上傳的功能、又或者是搜尋的功能,如果能把這些東西封裝成元件,那接下來如果有新的程式功能要上線,只需要很快速地套用即可,可讓程式開發速度變得更有效率,風格也會更一致。

Component介紹

如果是以webform的角度來看component,那它有點像是當初的ascx,如果以ASP.NET MVC的角度來看component,那它有點像是mvc自定義的Html helper。如果以物件導向的角度來看,它不過就是把一些複雜的Html封裝起來,供需要的人調用,而不需了解細節。

舉例來講,如果要實做一個線上簽核功能,對開發者來講簽核功能可能只是一個按鈕,但這個按鈕封裝了很多功能,按下它之後可能會彈出一個視窗,按下submit,會去後端呼叫對應的api,完成之後又需要把事件回拋給搭載它的程式頁面。光是這些功能就需要不少的程式碼,如都要開發人員一行一行的手刻的話,不僅沒有效率,也違反了程式開發的DRY原則。為了避免這些困擾,component會是一個非常好的解決方案。

 

假設需求是...

練程式我覺得最好的方式就是練套路,看到功能之後,就去想有哪些需求或情境可以套上去,不然都太抽象不容易記住。

針對component,我思考了以前在webform上開發的線上簽核功能,簽核功能其實非常複雜,但需求其實很單純。

  1. 需讓程式頁面可對簽呈component設定單據的編號
  2. 單據結案時簽呈component需即時將此狀態以事件的方式告知程式頁面以更新顯示內容

第一個需求很基本,因為component是共用的,因此,必須指派單據編號給簽呈component是非常正常的需求,所以不能寫死在component裡。第二個需求就比較複雜一點,但在開發component時也常會遇到,就是component必須在自身狀態改變時將此訊息適時地回傳給搭載它的頁面。

而只要這兩個問題能解決,簽呈component說穿了就是一堆屬性的設定,和狀態的回傳罷了。

 

實作範例

我們在中卷的時候已經開出了components的資料夾,但目前未存放任何東西,components基本上也是靠資料夾來分類,所以就先建一個Approve資料夾,然後新增approve.ts。

 export class ApproveController {
        constructor() {
           
        }

        Submit() {
            console.log('呼叫後端簽呈api');
        }
    }

首先新增一個ApproveController的類別,供簽呈component使用。component跟一般頁面一樣,最基本也是包含兩個元素,controller和html。這個controller很簡單,沒有任何屬性,但提供一個方法Submit,用來呼叫後端api。畢竟,使用簽呈component的開發者並不需要知道簽呈後端api的位置,甚至也不需要知道他的使用者在按下簽呈component頁面上那個按鈕會觸發這個方法。

 angular.module('Hank.Chen.Components')
        .component('approve', {
            templateUrl: "TypeScripts/Components/approve.detail.html",
            controller: ApproveController,
            bindings: {
                docno: '=',
            }
        });

接下來宣告component,它需要吃兩個參數,第一個參數是component名稱,換句話說就是tag的名稱,會在之後的Html頁面上用到,接下來設定屬性templateUrl,它指的是這個component的顯示頁面是誰,再來設定屬性controller,顧名思義就是設定component顯示頁面的controller。

前兩個屬性的設定跟angular.UI.Route套件所提供的設定State的方式很類似,但要特別注意的是angular.UI.Route在設定controller時,是指派一個字串,而component是指派一個物件。

再來是設定屬性bindings,冒號左邊是自定義的屬性名稱,冒號右邊的=,意思只是代表雙向繫結,也就是說當主頁面綁定屬性給component時,當component對此屬性做修改,則主頁面的這個屬性內容也會被一併影響,假如希望component更動屬性時,不要修改到原本頁面的值,可以設定成 < 。

完整的component程式碼如下

module Hank.Chen.Components {
    export class ApproveController {
        constructor() {
           
        }

        Submit() {
            console.log('呼叫後端簽呈api');
        }
    }

    angular.module('Hank.Chen.Components')
        .component('approve', {
            templateUrl: "TypeScripts/Components/approve.detail.html",
            controller: ApproveController,
            bindings: {
                docno: '='
            }
        });
}

再來是設計html,為了減少複雜度,很單純只秀docno就好,這裡只有一個重點,就是component的controller參考預設是$ctrl,假如看這名稱不爽的話,只需要設定controllerAs屬性即可。

<span>單據編號: {{$ctrl.docno}}</span>

到這裡簡單的component設定已經完成了,接下來的工作就是讓頁面能夠使用它。這邊就偷懶了,直接拿前面做好的member把他的Name當成docno注入。兩個重點提示,tag名稱approve就是component的名稱,而component在bindings區塊設定的內容,其實就是這個自定義tag的屬性而已。

<div>
     <approve docno="MemberCtrl.member.Name"></approve>
</div>

到這裡初步算是完成了,至少第一個需求達到囉。

 

結論

最後來回顧一下完成的東西,我們做到了封裝簽呈component的工作,對使用簽呈component的開發者而言,他們只需要關心單據的號碼,而無須關心畫面怎設計(由簽呈component 指定的templateUrl負責),也無須關心內部的邏輯(由簽呈component指定的controller負責),達到關注點分離的效果,也大幅的加快開發速度,而未來如簽呈內部邏輯有異動,因為異動的部分已經被簽呈component封裝了,所以,也會將影響降到最低。

最後,至於如何讓component跟主頁面溝通,因為比較複雜一點,為了避免文章篇幅過常,就留待[無暇的前端]番外篇來做補充。