使用ES6类作为Angular 1.x指令

时间:2015-02-20 01:57:51

标签: javascript angularjs class angularjs-directive ecmascript-6

我正在做一个小项目来玩ES6带来的好东西,我试图将一个类注册成一个角度指令,但我遇到了这个错误& #34; TypeError:不能将一个类称为函数",但是从示例中我发现它们只是编写类并将其作为指令注册到angular。这是我的指示。

class dateBlock {
  constructor () {
    this.template = '/app/dateblock/dateblock.html';
    this.restrict = 'AE';
    this.scope = {};
  }
};

export default dateBlock

和我导入它的索引然后声明它。

import calendarController from './calendar/calendar.js'
import dateBlock from './dateblock/dateblock.js'

function setup($stateProvider) {
    $stateProvider
      .state('base', {
        url: '',
        controller: calendarController,
        templateUrl: '/app/calendar/calendar.html'
      });
    };

setup.$inject = ['$stateProvider']

var app = angular.module('calApp',['ngAnimate','ui.router','hmTouchEvents', 'templates'])
  .config(setup)
  .controller('calendarController', calendarController)
  .directive('dateBlock', dateBlock)

如果我错过了一些关键步骤,我很乐意听到它。另外一个问题是,将所有应用程序组件导入索引并在那里注册它们或导出应用程序并在组件中导入和注册更清晰吗?

10 个答案:

答案 0 :(得分:63)

从我的角度来看,没有必要使用像register.js这样的外部库,因为你可以用这种方式创建指令作为ES6类:

class MessagesDirective {
    constructor() {
        this.restrict = 'E'
        this.templateUrl = 'messages.html'
        this.scope = {}
    }

    controller($scope, $state, MessagesService) {
        $scope.state = $state;
        $scope.service = MessagesService;
    }

    link(scope, element, attrs) {
        console.log('state', scope.state)
        console.log('service', scope.service)
    }
}
angular.module('messages').directive('messagesWidget', () => new MessagesDirective)

使用指令控制器允许您注入依赖项,即使没有附加声明(例如MessagesDirective.$inject = ['$scope', '$state', 'MessagesService']),因此如果需要,可以通过作用域使用链接功能中的服务。

答案 1 :(得分:48)

正如评论中所提到的,module.directive()方法需要工厂函数而不是构造函数。

最简单的方法是将类包装在返回实例的函数中:

angular.module('app')
    .directive('dateBlock', () => new DateBlock());

但是,这只能在最有限的意义上工作 - 它不允许依赖注入,并且指令的compilelink函数(如果已定义)将无法按预期工作。< / p>

事实上,这是一个我已经广泛研究过的问题,结果证明这个问题相当棘手(至少对我而言)。

我写了一篇涵盖我的解决方案的大量文章,但就您而言,我可以指出您对需要解决的两个主要问题的讨论:

  1. Dynamically converting a class definition into an angular-compatible factory function

  2. Allowing a directive's link and compile functions to be defined as class methods

  3. 完整的解决方案涉及太多的代码粘贴在这里,我想,但我已经组建了一个工作演示项目,它允许您将指令定义为ES6类,如下所示:

    class MyDirective {
        /*@ngInject*/
        constructor($interval) {
            this.template = '<div>I\'m a directive!</div>';
            this.restrict = 'E';
            this.scope = {}
            // etc. for the usual config options
    
            // allows us to use the injected dependencies
            // elsewhere in the directive (e.g. compile or link function)
            this.$interval = $interval;
        }
    
        // optional compile function
        compile(tElement) {
            tElement.css('position', 'absolute');
        }
    
        // optional link function
        link(scope, element) {
            this.$interval(() => this.move(element), 1000);
        }
    
        move(element) {
            element.css('left', (Math.random() * 500) + 'px');
            element.css('top', (Math.random() * 500) + 'px');
        }
    }
    
    // `register` is a helper method that hides all the complex magic that is needed to make this work.
    register('app').directive('myDirective', MyDirective);
    

    查看demo repo herehere is the code behind register.directive()

答案 2 :(得分:22)

<迈克尔@迈克尔是对的钱:

  

module.directive()方法需要工厂函数

然而我用另一种技术解决了它,我认为它有点清洁,它对我来说很好,但它并不完美...... 我定义了一个静态方法,它返回module()

所期望的工厂
class VineDirective {
    constructor($q) {
        this.restrict = 'AE';
        this.$q = $q;
    }

    link(scope, element, attributes) {
        console.log("directive link");
    }

    static directiveFactory($q){
        VineDirective.instance = new VineDirective($q);
        return VineDirective.instance;
    }
}

VineDirective.directiveFactory.$inject = ['$q'];

export { VineDirective }

在我的应用中,我做了:

angular.module('vineyard',[]).directive('vineScroller', VineDirective.directiveFactory)

我相信没有其他方法可以使用类似于这样的黑客攻击的类+指令,只需选择简单的方法; - )

答案 3 :(得分:19)

更简单,更清晰,更易读的解决方案。

class ClipBoardText {

  constructor() {
    console.log('constructor');

    this.restrict = 'A';
    this.controller = ClipBoardTextController;
  }

  link(scope, element, attr, ctr) {

    console.log('ctr', ctr);
    console.log('ZeroClipboard in link', ctr.ZeroClipboard);
    console.log('q in link', ctr.q);

  }

  static directiveFactory() {
    return new ClipBoardText();
  }
}

// do not $inject like this
// ClipBoardText.$inject = ['$q'];

class ClipBoardTextController {
  constructor(q) {
    this.q = q;
    this.ZeroClipboard = 'zeroclipboard';
  }
}

ClipBoardTextController.$inject = ['$q'];


export default ClipBoardText.directiveFactory;

您无法在$q函数中获得linkthis中的linkundefinednullexploring-es6-classes-in-angularjs-1-x#_section-factories

  

当Angular调用链接函数时,它不再位于类实例的上下文中,因此。$ interval将是未定义的

因此,请在指令中使用controller函数,并在link函数中注入要访问的依赖项或任何内容。

答案 4 :(得分:5)

我的解决方案:

class myDirective {
   constructor( $timeout, $http ) {
       this.restrict = 'E';
       this.scope = {};

       this.$timeout = $timeout;
       this.$http = $http;
   }
   link() {
       console.log('link myDirective');
   }
   static create() {
       return new myDirective(...arguments);
   }
}

myDirective.create.$inject = ['$timeout', '$http'];

export { myDirective }

并在主app文件中

app.directive('myDirective', myDirective.create)

答案 5 :(得分:3)

在我的项目中,我使用语法糖进行注射。 ES6使得可注射工厂用于指令避免过多的重复代码非常简单。此代码允许注入继承,使用带注释的注入等。检查一下:

第一步

为所有angular controllers \ directives \ services声明基类--InjectableClient。 它的主要任务 - 将所有注入的参数设置为“this”的属性。可以覆盖此行为,请参阅下面的示例。

class InjectionClient {

    constructor(...injected) {
        /* As we can append injections in descendants we have to process only injections passed directly to current constructor */ 
        var injectLength = this.constructor.$inject.length;
        var injectedLength = injected.length;
        var startIndex = injectLength - injectedLength;
        for (var i = startIndex; i < injectLength; i++) {
            var injectName = this.constructor.$inject[i];
            var inject = injected[i - startIndex];
            this[injectName] = inject;
        }
    }

    static inject(...injected) {
        if (!this.$inject) { 
            this.$inject = injected; 
        } else {
            this.$inject = injected.concat(this.$inject);
        }
    };
}

例如,如果我们调用SomeClassInheritedFromInjectableClient.inject('$ scope'),在指令或控制器中我们将它用作'this。$ scope'

第二步

使用静态方法“factory()”声明指令的基类,它将指令类的$ inject属性绑定到工厂函数。还有“compile()”方法,它将链接函数的上下文绑定到指令本身。它允许在链接函数中使用我们注入的值作为this.myInjectedService。

class Directive extends InjectionClient {
    compile() {
        return this.link.bind(this);
    }

    static factory() {
        var factoryFunc = (...injected) => {
            return new this(...injected);
        }
        factoryFunc.$inject = this.$inject;
        return factoryFunc;
    }
}

第三步

现在我们可以声明尽可能多的指令类。继承。我们可以使用扩展数组以简单的方式设置注入(只是不要忘记调用超级方法)。见例子:

class DirectiveFirst extends Directive {
}

DirectiveFirst.inject('injA', 'injB', 'injC');


class DirectiveSecond extends DirectiveFirst {

    constructor(injD, ...injected) {
        super(...injected);
        this.otherInjectedProperty = injD;
    }
}
// See appended injection does not hurt the ancestor class
DirectiveSecond.inject('injD');

class DirectiveThird extends DirectiveSecond {

    constructor(...injected) {
        // Do not forget call the super method in overridden constructors
        super(...injected);
    }
}    

最后一步

现在以简单的方式注册带有角度的指令:

angular.directive('directiveFirst', DirectiveFirst.factory());
angular.directive('directiveSecond', DirectiveSecond.factory());
angular.directive('directiveThird', DirectiveThird.factory());

现在测试代码:

var factoryFirst = DirectiveFirst.factory();
var factorySec = DirectiveSecond.factory();
var factoryThird = DirectiveThird.factory();


var directive = factoryFirst('A', 'B', 'C');
console.log(directive.constructor.name + ' ' + JSON.stringify(directive));

directive = factorySec('D', 'A', 'B', 'C');
console.log(directive.constructor.name + ' ' + JSON.stringify(directive));

directive = factoryThird('D', 'A', 'B', 'C');
console.log(directive.constructor.name + ' ' + JSON.stringify(directive));

这将返回:

DirectiveFirst {"injA":"A","injB":"B","injC":"C"}
DirectiveSecond {"injA":"A","injB":"B","injC":"C","otherInjectedProperty":"D"}
DirectiveThird {"injA":"A","injB":"B","injC":"C","otherInjectedProperty":"D"}

答案 6 :(得分:0)

我有类似的问题。但就我而言,当我部署到生产环境时它起作用并失败了。它失败了,因为生产有最新版本的6to5。 使用npm shrinkwrap可以防止这种情况。 根据最新的ES6规范,你不能使用这样的课程。 https://github.com/babel/babel/issues/700

答案 7 :(得分:0)

我遇到了同样的问题。我第一次尝试通过ES6类解决问题,但我有$ inject我的依赖项的问题。在我意识到角度有几种样式的编写代码后我尝试了。我使用John Papa样式,我在我的rails应用程序中使用ES6获得了这个代码:

((angular) => {
 'use strict';

  var Flash = ($timeout) => {
   return {
     restrict: 'E',
     scope: {
       messages: '=messages'
     },
     template: (() => {
       return "<div class='alert flash-{{ message[0] }}' ng-repeat = 'message in messages'>" +
                "<div class= 'close' ng-click = 'closeMessage($index)' data-dismiss = 'alert' > × </div>" +
                "<span class= 'message' >{{ message[1] }}</ span>" +
              "</ div>";
     }),
     link: (scope) => {
       scope.closeMessage = (index) => {
         scope.messages.splice(index, 1)
       };

      $timeout(() => {
        scope.messages = []
      }, 5000);
    }
  }
};

Flash.$inject = ['$timeout'];

angular.module('Application').directive('ngFlash', Flash);

})(window.angular);

我知道我可以用更多ES6风格的函数和变量做一些改进。 我希望它有所帮助。

答案 8 :(得分:0)

class ToggleShortcut{
constructor($timeout, authService, $compile, $state){

    var initDomEvents = function ($element, $scope) {

        var shortcut_dropdown = $('#shortcut');

        $compile(shortcut_dropdown)($scope);

        $scope.goToShortCutItem = function(state, params){
            var p = params || null;

            if(state === 'app.contacts.view'){
                var authProfile = authService.profile;
                if(authProfile){
                    p = {
                        id:authProfile.user_metadata.contact_id
                    };
                }
            }

            $state.go(state, p);
            window.setTimeout(shortcut_buttons_hide, 300);
        };

        $element.on('click', function () {
            if (shortcut_dropdown.is(":visible")) {
                shortcut_buttons_hide();
            } else {
                shortcut_buttons_show();
            }

        });

        // SHORTCUT buttons goes away if mouse is clicked outside of the area
        $(document).mouseup(function (e) {
            if (shortcut_dropdown && !shortcut_dropdown.is(e.target) && shortcut_dropdown.has(e.target).length === 0) {
                shortcut_buttons_hide();
            }
        });

        // SHORTCUT ANIMATE HIDE
        function shortcut_buttons_hide() {
            shortcut_dropdown.animate({
                height: "hide"
            }, 300, "easeOutCirc");
            $('body').removeClass('shortcut-on');

        }

        // SHORTCUT ANIMATE SHOW
        function shortcut_buttons_show() {
            shortcut_dropdown.animate({
                height: "show"
            }, 200, "easeOutCirc");
            $('body').addClass('shortcut-on');
        }
    };

    var link = function($scope, $element){
        $timeout(function(){
            initDomEvents($element, $scope);
        });
    };

    this.restrict = 'EA';
    this.link = link;
}
}

toggleShortcut.$inject = ['$timeout', 'authService', '$compile', '$state'];

function toggleShortcut($timeout, authService, $compile, $state){
return new ToggleShortcut($timeout, authService, $compile, $state);
}

angular.module('app.layout').directive('toggleShortcut', toggleShortcut);

答案 9 :(得分:0)

我刚才遇到了这个问题,我看到了这个话题。尝试讨论中提供的一些方法,我终于以一种非常简单的方式解决了这个问题:

export default function archiveTreeDirective() {
    'ngInject';

    return {
        restrict: 'E',
        scope: {
            selectedNodes: "="
        },
        templateUrl: 'app/components/directives/archiveTree/archiveTree.html',
        controller: ArchiveTreeController,
        controllerAs: 'vm',
        bindToController: true
    };
}

class ArchiveTreeController {
    constructor() {
        'ngInject';
        ...
    }
    ...
}

我直接使用函数作为.directive('directiveName',factory)参数,并将其导出,稍后在模块声明中导入它。但是我在导出时错过了“默认”语句,所以我收到了一个错误。在我添加“默认”关键字后,一切正常!

我发现这种方法也适用于我的路线配置(也是以函数方式)。

============ 希望你能理解我可怜的英语:)