AngularJS Decorating Directives

为什么需要用到 Decorators?

其一是你不想破坏原有的行为或是原有的程序,

去扩增或是覆写现有功能,

之前一篇 JSNLog integrate to AngularJS 就是利用了 Decorator 装饰掉 expectionHandle,

今天这篇主来更深入研究 AngularJS Decorator

首先我们来关注官方的说明文档,

与 Service 最大不同于在它的回传型态是 Array 而不是我们之前看到的 Array 或 Object,

为什么 Angular 会回传 Array ? 这边它的解释写道会有复数以上注册相同的选择器/名字.

不过在实际上这件事理应也是不该发生.

注册相同的名字除了混淆代码之外想不到有什么好处.

让我们先来看看个简单范例:

HTML

  

 JS

angular
  .module('app', [])
  .controller('app', [function(){
    this.name = 'Angular 控管...';
  }])
  .directive('menu', [function(){
    return {
      template: '
  • 1
  • 2
  • 3
', 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

  

JS

angular
  .module('app', [])
  .controller('app', [function(){
    var self = this;
    self.name = 'Angular 控管...';
  }])
  .directive('myLabel', [function(){
    return {
      template: '

{{name}}

', 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

  

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: '

', restrict: 'E', replace: true, scope: { m: '<' }, controller: ctrl, controllerAs: 'self', bindToController: true }; }])

OK, 上述代码的显示结果应该会如下.

点选其中个图片则会 alert 出价钱.

现在有两个需求分别是

  1. 每个图片上面多显示一段文字 : Free Shipping
  2. 点选图片会再 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('Free Shipping

' + '

Try it - JS Bin on jsbin.com

可以看到使用 $controller service 去生成实例后再去扩增行为,

所以再不改源代码的前提下我们完成扩增动作,

越深入研究这 AngularJS 这套框架越觉得伟大厉害,

能够把很多设计切得很漂亮,

文章参考:

Experiment: Decorating Directives