AngularJS 遷移 controller 到 component

  1. AngularJS 遷移 controller 到 component
    1. 將 controller 改寫成 component
      1. 先整理元件
    2. 先整理沒有相依模組的 code
    3. 相依設定
    4. 結語

AngularJS 遷移 controller 到 component

將 controller 改寫成 component

這個改變,大約在 AngularJS 1.5 版出現。
也是前端框架的大變動時代。

由於有了 component 的概念,也前端也引進封裝的技術,期待可以做到將 HTML/CSS/JS 打包成一包,放在任何網站都不會跑版。

先整理元件

./src/
├── route/
│   └── index.js
├── pages/
│   └── map/
│       ├── map.html
│       ├── view.html
│       └── advanced-search.html
└── js/
    └── map/
        ├── map.js
        ├── view.js
        └── advanced-search.js

html

<div ng-show="MCtrl..." class="container mb-15" style="position:relative;">
  <div ng-show='!Ctrl.showAdvencedSearch' class="">
    <!--  中間略  -->
    <map-view></map-view>
    <advanced-search></advanced-search>
  </div>
</div>

在 controller 寫法中最讓人覺得麻煩的,就是 不同層的 controller 會交疊在一起,在 html 上多層次的呈現,使用者互動是跨 controller 的。但是這也增加了維護上的困難,因為其實人在理解畫面時,並不是這樣的想法,而是現代前端框架使用的 component 的概念,AngularJS 也在後來的版本跟了這個寫法,今天就是要介紹要怎麼改寫成 component 的寫法。

javascript

import angular from 'angular';
import './view';
import './advanced-search';

(function() {
  var app = angular.module("map", ["view","advanced-search"]);
  app.controller("mapController", mapController);
  mapController.$inject = ["$scope", "authService"];

  function mapController($scope, authService) {
    // 中間略
  }
})();

AngularJS 自帶「相依模組管理」,可以在前端做相依模組的設定,但是整個專案變成 webpack 時,就必須在後端做相依模組管理,這個必須在 AngularJS + ESM(或 CommonJS) 的做法重疊的情況,又該怎麼做呢?

route

import angular from 'angular';
import ngRoute from 'angular-route';

export default angular.module('route', [ngRoute])
  .config(['$routeProvider', '$locationProvider', ($routeProvider, $locationProvider) => {
  $locationProvider.hashPrefix('');
  //登入
  $routeProvider
  // 中間略
  .when('/map', { //地圖顯示頁面
    template: require('../pages/map/map.html'),
    controller: 'mapController as Ctrl'
  })
  // 中間略
  .otherwise({
    redirectTo: '/'
  })
}])

先整理沒有相依模組的 code

因為我們要做成 component 所以最後會將檔案整理在一起。
一次整理一組,不要一口氣做全部。 確保每一步都是正確的

./src/
└── components/
    ├── map/
    │   ├── index.html
    │   └── index.js
    ├── view/
    │   ├── index.html
    │   └── index.js
    └── advanced-search/
        ├── index.html
        └── index.js

javascript

import angular from 'angular';
import template from './index.html';

export default angular.module("map", [])
  .component('map', {
    template,
    controller,
    controllerAs: 'Ctrl',
    bindings: {
      isShow: '<'
    }
  }).name;

controller.$inject = ["$scope", "authService"];

function controller($scope, authService) {
  // 中間略
}
  1. html 移到相同目錄,並用 ESM 引入成 template。(webpack 要安裝 html-loader)
  2. controller 內容不變,只有將命名改成 controller。
  3. angular.module 後面的宣告的 controller 改寫成 component
  • module 和 component 的名稱相同 (只是保持慣例,語法允許不同)。
  • 為了和原本的 template 相容要設定 controllerAs: Ctrl
  • 為了隔離 scope ,無法在 template 使用 MCtrl ,要將它設定成傳入的變數 isShow

注意:
angular.module("map", []) 中 module method 的第二個參數,在沒有相依模組的情況之下,還是要給一個空陣列。

html

<div ng-show="Ctrl.isShow" class="container mb-15" style="position:relative;">
  <div ng-show='!Ctrl.showAdvencedSearch' class="">
    <!--  中間略  -->
    <map-view></map-view>
    <advanced-search></advanced-search>
  </div>
</div>

由於目前是由 controller 控製渲染,所以要把 MCtrl 改成 Ctrl,並且將 MCtrl... 移到正確的位置。

route

import angular from 'angular';
import ngRoute from 'angular-route';

export default angular.module('route', [ngRoute])
  .config(['$routeProvider', '$locationProvider', ($routeProvider, $locationProvider) => {
  $locationProvider.hashPrefix('');
  //登入
  $routeProvider
  // 中間略
  .when('/map', { //地圖顯示頁面
    template: `<map is-show="MCtrl..."></map>`
  })
  // 中間略
  .otherwise({
    redirectTo: '/'
  })
}]).name;

因為此 component 會跟著 route 變化而使用,所以使用於 route
route 會變化相對應的 html 出來,讓 AngularJS 呼叫相對應註冊好的 component。
所以,在 route 中,呼叫 template 的名稱 <map is-show="MCtrl..."></map> 要和 component 定義的一樣。
MCtrl.... 在這一層將它的執行結果傳給 map 的 isShow。

相依設定

由於上述程式碼,要引入至根元件中。

main.js

原本的寫法

import angular from 'angular';
import router from '@/route';

import './js/v2/map/map.js';

angular.module('MainApp', [
  router,
  "map"
])

改寫成

import angular from 'angular';
import router from '@/route';

import map from './component/map';

angular.module('MainApp', [
  router,
  map
])

引用時,會引入該 component 的名稱。相容於 AngularJS 的相依設定。
記得將舊的刪除,避免相同名稱重複註冊。

結語

這樣的改寫可以將 AngularJS 專案,更接近於現代前端框架的邏輯,在專案的遷移任務中,可以「修改更少,每一步更穩定」
在尚未移到新專案中,也可以先看看 scope 的切割是否適合遷移到新的框架中。