為什麼需要用到 Decorators?
其一是你不想破壞原有的行為或是原有的程式碼,
去擴增或是覆寫現有功能,
之前一篇 JSNLog integrate to AngularJS 就是利用了 Decorator 裝飾掉 expectionHandle,
今天這篇主來更深入研究 AngularJS Decorator
首先我們來關注官方的說明文件,
與 Service 最大不同於在它的回傳型態是 Array<DirectiveObject> 而不是我們之前看到的 Array 或 Object,
為什麼 Angular 會回傳 Array ? 這邊它的解釋寫道會有複數以上註冊相同的選擇器/名字.
不過在實務上這件事理應也是不該發生.
註冊相同的名字除了混淆代碼之外想不到有什麼好處.
讓我們先來看看個簡單範例:
HTML
<div ng-app="app" ng-controller="app as self">
<p ng-bind="self.name"></p>
<menu></menu>
</div>
JS
angular
.module('app', [])
.controller('app', [function(){
this.name = 'Angular 控管...';
}])
.directive('menu', [function(){
return {
template: '<ul><li>1</li><li>2</li><li>3</li></ul>',
restrict: 'A',
replace: true
};
}])
.config(['$provide', function($provide){
$provide.decorator('menuDirective', function($delegate){
var directive = $delegate[0];
directive.restrict = "E";
return $delegate;
});
}]);
Try it - JS Bin on jsbin.com
由範例中可以清楚的看到 restrict 從原本的 'A' 改為 'E' 了,
當然你也可以擴展成 'AE',
接下來看個使用 link 函式的 directive 要如何去擴展.
HTML
<div ng-app="app" ng-controller="app as self">
<p ng-bind="self.name"></p>
<my-label name="Press this section"></my-label>
</div>
JS
angular
.module('app', [])
.controller('app', [function(){
var self = this;
self.name = 'Angular 控管...';
}])
.directive('myLabel', [function(){
return {
template: '<p style="background-color: yellow;">{{name}}</p>',
restrict: 'E',
replace: true,
scope: { name: '@' },
link: function(scope, ele, attrs){
if(angular.isDefined(attrs.name)){
attrs.name = attrs.name + " !";
}
}
};
}])
我們想要加入個觸發 click 事件又不要破壞掉原本 link 函式的行為,
一樣使用 decorator 去裝飾掉, 如果熟知 directive 生命週期的人很快就能聯想到 compile.
我們可以利用 compile 的 element 元素去綁定 click 事件, 再利用 $apply 去通知 angular 去觸發 $digest,
.config(['$provide', function($provide){
$provide.decorator('myLabelDirective', function($delegate){
var directive = $delegate[0];
directive.scope.fn = "&";
var link = directive.link;
directive.compile = function() {
return function(scope, ele, attrs){
link.apply(this, arguments);
ele.bind('click', function(){
scope.$apply(function(){
scope.fn();
});
});
};
};
return $delegate;
});
}]);
Try it - JS Bin on jsbin.com
接下來再講一個工作上的實務應用,
由於工作上撰寫經常被使用的 directive 需要被用在另外的頁面上,
但是在另外的頁面上動作跟行為跟原本的 directive 會有一些不同,
到底是我要把整份代碼以及依賴的代碼搬過去 ? 還是重新再寫一個專門為了那個特殊頁面客製化的 directive?
於是我就使用了 decorator 去裝飾掉原本的也很完美的去達到另外的特殊需求!
HTML
<div ng-app="app" ng-controller="app as self">
<p ng-bind="self.application"></p>
<div class="row">
<techprd ng-repeat="item in self.Models" m="item"></techprd>
</div>
</div>
JS
angular
.module('app', [])
.controller('app', [function(){
var self = this;
self.application = 'Decorator Directive Controller';
self.Models = [];
for(var i = 0; i < 6; i++) {
var price = Math.floor((Math.random() * 1000) + 1);
self.Models.push(price);
}
}])
.directive('techprd', [function(){
ctrl.$inject = ['$window'];
function ctrl($window){
var self = this;
self.image = "https://pixabay.com/static/uploads/photo/2016/06/29/17/14/water-1487304_960_720.jpg";
self.go = function(){
alert(self.m);
};
}
return {
template: '<div class="col-xs-2" ng-click="self.go()"><img ng-src="{{self.image}}" /><p><label>NT$ </label><span ng-bind="self.m"></span></p></div>',
restrict: 'E',
replace: true,
scope: { m: '<' },
controller: ctrl,
controllerAs: 'self',
bindToController: true
};
}])
OK, 上述代碼的顯示結果應該會如下.
點選其中個圖片則會 alert 出價錢.
現在有兩個需求分別是
- 每個圖片上面多顯示一段文字 : Free Shipping
- 點選圖片會再 Console 印出價錢
代碼如下,
.config(['$provide', function($provide){
dectrl.$inject = ['$delegate', '$controller'];
function dectrl($delegate, $controller){
var directive = $delegate[0];
var original = angular.copy(directive.controller);
var arr = directive.template.split('<img ng-src');
directive.template = arr[0] + '<p><span class="label label-primary">Free Shipping</span></p>' + '<img ng-src' + arr[1];
directive.controller = function($window){
var controller = $controller(original, { $window: $window });
var _go = angular.copy(controller.go);
controller.go = function(){
console.log(this.m);
_go.call(this);
};
return controller;
};
return $delegate;
}
$provide.decorator('techprdDirective', dectrl);
}]);
Try it - JS Bin on jsbin.com
可以看到使用 $controller service 去生成實例後再去擴增行為,
所以再不改源代碼的前提下我們完成擴增動作,
越深入研究這 AngularJS 這套框架越覺得偉大厲害,
能夠把很多設計切得很漂亮,
文章參考: