[ng] 從實例來看 ng 應用程序架構

為了避免腦袋久了變成水母,所以趁現在記憶鮮明的時候先寫下來,這樣以後被起底才會有東西可以爆料。AngularJS 也不算很新,現在大家都在玩 ReactJS 了,反觀我自己關心的 Famo.us 草都這麼高了(比畫胸口

希望有時間回去整理 Famo.us 啊(遠目


參考

Gantt chart component for AngularJS

架構規劃

粗略說明一下,

  • core 核心的檔案都可以放在這裡面
    • logic 屬於邏輯的程式放這裡
    • ui 屬於使用者介面處理的程式放這裡
    • gagawulala.js 主要的 App
    • gagawulala.css 主要的樣式檔案
  • plugins 額外的套件(或者是自己寫的額外的套件放這
  • templates 樣版檔案放這裡

從 Directive 開始

不管你要做什麼事情,總是要自己做一個 Directive 才夠潮,像是 gaGawuLala 之類的!

/* gagawulala.js */

angular.module('gaGawuLala', [])
  .directive('gaGawuLala', [function() {
  }]);

然後就可以開始定義這個 Directive 初始化應該做些什麼事情,裡面的 $scope 到底需要什麼,需要的話給個 controller 或是 link 來做些比較繁雜的事情。至於 controllerlink 差異在哪裡,可以參考這一篇文章

簡單的說就是,

Before compilation? – Controller

After compilation? – Link

邏輯層

能放在 logic 的大部分(或者全部)都是 factory,這可以當作個人喜好。所以,裡面我們也就會有一個 gagawulala.factory.js,來定義整個 gaGawuLala 這個應用程序物件。

/* gagawulala.factory.js */

angular.module('gaGawuLala')
  .factory('gaGawuLala', ['gaGawuLalaOptions', function(Options) {
    var gaGawuLala = function($scope, $element) {
      var self = this;

      this.scope = $scope;
      this.element = $element;

      this.options = new Options($scope, {
        /* other options here. */
      });
    };

    gaGawuLala.prototype.getMySize = function() {
      return 30;
    };

    return gaGawuLala;
  }]);

然後我們用到了一個叫做 gaGawuLalaOptionsfactory,目的是要整合物件,所以寫個簡單的合併來用(可以參考這一段原始碼。關於 factory 的部分就不再贅述。

使用者介面層

處理跟使用者有關的部分,大抵上都在這裡,像是 Directive、Filter 或是 Service 之類的東西。這裡特別記錄一下關於參考資料中,所使用的 DirectiveBuilder 這個 Service!

/* gaGawuLalaDirectiveBuilder.service.js */
angular.module('gaGawuLala')
  .service('gaGawuLalaDirectiveBuilder', ['$templateCache', function($templateCache) {
    var DirectiveBuilder = function DirectiveBuilder(directiveName, templateUrl, require, restrict) {
      /* 載入了 directiveName, templateUrl, require 與 restrict 做預處理 */
      /* 中間省略一千字 */
    };

    return DirectiveBuilder;
  }]);

原始碼可參考這一段

這個作法真的頗高明,這樣我在其他的 Directive 要做初始化的時候,只需這樣做即可,

angular.module('gaGawuLala')
  .directive('gaGawuLalaMySize', ['gaGawuLalaDirectiveBuilder', function(Builder) {
    var builder = new Builder('gaGawuLalaMySize');
    return builder.build();
  }]);

如果我需要加入 controller 也不麻煩,只要這樣,

angular.module('gaGawuLala')
  .directive('gaGawuLalaMySize', ['gaGawuLalaDirectiveBuilder', function(Builder) {
    var builder = new Builder('gaGawuLalaMySize');

    builder.controller = function($scope, $element) {
      /* 嗡嗡嗡 */
    };

    return builder.build();
  }]);

外掛

外掛可以是別人的,也可以是自己的。但是,通常要修改或是使用別人寫好的模組,都會有水土不服的情況發生,最快能避免的方式,大概就是自己再包一層,用來解決一些套件無法,或是需要修改才能做到的事情。

當然,我們也不會希望去動到別人寫好的模組,不然以後升級的債會還不完。

/* gaGawuLala.plugin18x.directive.js */

angular.module('gaGawuLala.plugin18x', ['gaGawuLala', 'ui-tree'])
  .directive('gaGawuLalaPlugin18x', ['$compile', '$document', function($compile, $document) {

    return {
      restrict: 'E',
      require: '^gaGawuLala',
      scope: {
        enabled: '=?',
        header: '=?'
      },
      link: function(scope, element, attrs, gaGawuLalaCtrl) {
        /* 以下省略一萬行 */
      }
    };
  }]);

當中可以看到 require: '^gaGawuLala' 這個用法是美妙的,他用來告訴這個 Directive 必須要取得 gaGawuLala 開頭的這個 Controller 才可繼續執行。可以當做是把某個 Directive 必須要相依在某一個 Directive/Controller 底下的概念。

舉例來說,

.directive('screen', function() {
    return {
        restrict: 'E',
        scope: {
          hd: '=?'
        },
        controller: function() {
            this.doSomethingScreeny = function() {
                alert("screeny!");
            }
        }
    }
})
.directive('decode', function() {
    return {
        restrict: 'E',
        require: '^screen',
        scope: {
          4k: '=?'
        },
        link: function($scope, $element, $attrs, screenCtrl) {
            $scope.decodeIt = function() {
                screenCtrl.doSomethingScreeny();
            }
        }
    }
})

然後你的 DOM 會類似這個樣子,

<div screen>
  <div decode>
    <button type="button" ng-click="decodeIt()">Decode</button>
  </div>
</div>

然而,既然他都是拿 Controller 了,所以,當你的 require 對象,不存在 Controller 的時候,那就會無效,換句話說,你拿到的 screenCtrl 就會是 undefined 了。

樣版

就是寫寫 HTML 這樣,比較有趣的是,我們可以用 $compile 來做一些,原本樣版裡面沒有的東西。例如外掛裡面的特殊樣版,不可能將他整理到核心樣版裡面。所以,在外掛裡面的 Directive 就可以裡用 $compile 的方式,將樣版塞入核心樣版裡(神不知鬼不覺 XD

不過,有好有壞,全部都放核心樣版,當外掛關掉,變成累贅。而外掛如果分開來放,要除錯的時候,有時相對麻煩一些。不過,以開發的流程來看,分開放或許會好一點(畢竟不需要從壓縮過的結果去除錯啊!

小結

今年聽說強迫休耕,難得去年年底的時候稻子收得不錯的說!而且是新種,今年留頭搞不好也不錯!政府打壓農民無極限!

所以,是時候該進軍 ReactJS 了嗎?說好的 Famo.us 呢!

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei