AngularJS 1.5+ Component style with es6-class

  1. AngularJS 1.5+ Component style with es6-class
    1. 套件版本
    2. controller → component
    3. router
    4. binding function
    5. controller of component → ES6 class
    6. 保持 $scope.$broadcast 或 $scope.$on 運作
    7. 想要刪除 myController.$inject = [...]

AngularJS 1.5+ Component style with es6-class

在接手了 AngularJS 的專案到現在,一直在處理寫作風格造成除錯上的困擾。
包含無法完全封裝的 controller + template 的組合,終於可以用 component 打成一包。[1]
而且在一些預設的 (scope之類的) 限制上,也比較容易維護,並且順利的處理掉 route 上,如何使用 component 的寫法。

這一篇就是要介紹,如何將你的 controller 變成 component 而且還要使用 es6 的 class 語法將它變得漂漂亮亮的。
最後還會處理 $inject 的相依注入總是要寫兩次的原始框架設定,也終於可以使用套件魔法處理,只要寫一次就可以了。

套件版本

  • webpack 4
  • babel 7

controller → component

javascript

原本 angularjs 自定 module 的寫法是直接接 .controller() 指定 controller (function)
換成 component 的寫法,要在 angularjs 自定 module 後接著 .component() 並且給予一個定義物件,定義 component 的 templatecontroller 之類的屬性。
建議使用 controllerAs

1
2
angular.module("addReport", [])
.controller("myController", myController);

1
2
3
4
5
6
7
8
9
export default angular.module("injectName", [])
.component("tagName", {
bindings: {
isShow: '<',
},
template,
controller: myController,
controllerAs: 'Ctrl',
}).name;

injectName 通常會和 tagName 同名。

templage

$ctrl 就剩下一種了,無法直接使用外部的 ctrl ,但是可以透過 binding 將變數傳進來。

1
2
3
4
<div ng-if="MCtrl.fun" class="container" style="max-width:800px">
<p><a ng-click="Ctrl.goBack()">返回列表</a></p>
<report-form report="Ctrl.report" submit="Ctrl.submit"></report-form>
</div> <!--container-->

1
2
3
4
<div ng-if="Ctrl.isShow" class="container" style="max-width:800px">
<p><a ng-click="Ctrl.goBack()">返回列表</a></p>
<report-form report="Ctrl.report" submit="Ctrl.submit"></report-form>
</div>

router

在 route 的 when 裡,只要指定 template,並且填上 component 的使用方式。就包含渲染用的 template ,跑什麼 controller。

不建議使用外部指定的 controller

1
2
3
4
5
6
7
8
9
10
function route ($routeProvider, $locationProvider) {
$locationProvider.hashPrefix('');

$routeProvider
//...
.when('/addReport', {
templateUrl: './addReport.html',
controller: 'myController'
})
}

1
2
3
4
5
6
7
8
9
function route ($routeProvider, $locationProvider) {
$locationProvider.hashPrefix('');

$routeProvider
//...
.when('/addReport', {
template: `<tag-name is-show="true"></tag-name>`
})
}

只要把 component 在 html 的使用形式當作 template

binding function

component 要這樣設定的話

1
2
3
binding: {
onSubmit: '&'
}

template 要這樣寫

<form ng-submit="Ctrl.onSubmit({$event: Ctrl.code})">
...
</form>

使用這個 components 要這樣寫

1
2
3
4
5
6
class myController {
constructor () {}
sumit(code) {
// code === Ctrl.code of component
}
}
<my-form on-submit="Ctrl.submit($event)"></my-form>

controller of component → ES6 class

  • 改用 class 要注意要先宣告,再 export
  • 內建相依要在 constructor 設定給 this
    不要在 constructor 做初始化變數的事,改到 $onInit[2]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default angular.module("injectName", [])
.component("tagName", {
bindings: {
isShow: '<',
},
template,
controller: myController,
controllerAs: 'Ctrl',
}).name;

myController.$inject = []

function myController () {
console.log('constructor')
this.$onInit = function () {
console.log('$onInit')
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class myController{
constructor ($routeParams) {
console.log('constructor')
this.$routeParams = $routeParams
}

$onInit () {
console.log('$onInit')
console.log(this.$routeParams.parameter)
}
}

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

保持 $scope.$broadcast$scope.$on 運作

其實 $scope 是要被消滅的,但是一時之間不要修改太大的幅度的話,可以先依照下面的建議放置

  • $scope.$on 是一種事件掛載。放置在 constructor () 裡即可。
  • $scope.$broadcast 是一種事件觸發。放置位置不要移動,改成 this.$scope 的方式使用。

想要刪除 myController.$inject = [...]

要移除 $inject 語法,就要靠 ng-annotate 套件[3]

相依注入的寫法要拿掉,可以說是費了相當大的困難,目前最好的選擇,可以說是 babel-plugin-angularjs-annotate 這個原生後繼者

安裝

$ npm install babel-plugin-angularjs-annotate --save-dev

在 .babelrc file 加上

{
"presets": ["@babel/preset-env"],
"plugins": ["angularjs-annotate"]
}

並且! 修改 webpack.config.js 在 babel 的設定,取消排除 node_modules 雖然會造成編出來的 all.js 變肥大,不過這是另一個可以被解決的問題。

module.exports = {
entry: {},
output: {},
module: {
rules: [{
test: /\.js$/,
//exclude: /(node_modules|vendor)/,
loader: "babel-loader"
}]
}
};


  1. AngularJS 1.5 最佳實務 ↩︎

  2. Converting Angular Controllers to ES6 Classes - codelord.net ↩︎

  3. Implicit Annotation ↩︎