简体   繁体   English

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

[英]Using ES6 Classes as Angular 1.x directives

I'm doing a small project to play around the goody bag the ES6 is bringing, I'm trying to set register a class as an angular directive, but I'm running into this error "TypeError: Cannot call a class as a function", but from the examples I'm finding they just write the class and register it with angular as a directive. 我正在做一个小项目来玩围绕ES6带来的好东西,我正在尝试将一个类设置为一个角度指令,但我遇到了这个错误“TypeError:不能将一个类称为函数“但是从示例中我发现他们只是编写类并将其作为指令注册角色。 Here's my directive. 这是我的指示。

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

export default dateBlock

and my index where I import it and then declare it. 和我的索引,我导入它,然后声明它。

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)

If I missed some crucial step I'd love to hear it. 如果我错过了一些关键的步骤,我很乐意听到它。 Also side question is it cleaner to import all the apps components to the index and register them all there or export the app and import and register within the components? 另外一个问题是,将所有应用程序组件导入索引并在那里注册它们或导出应用程序并导入和注册组件更清晰吗?

From my point of view, there is no need to use external libraries like register.js, because you can create directive as a ES6 class in this way: 从我的角度来看,不需要使用像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)

Using directive controller allows you to inject dependencies, even without additional declaration (ex. MessagesDirective.$inject = ['$scope', '$state', 'MessagesService'] ), so you can use services in link function via scope if you need. 使用指令控制器允许你注入依赖项,即使没有额外的声明(例如MessagesDirective.$inject = ['$scope', '$state', 'MessagesService'] ),所以你可以通过范围使用链接功能中的服务需要。

As mentioned in a comment, the module.directive() method expects a factory function rather than a constructor. 正如注释中所提到的, module.directive()方法需要工厂函数而不是构造函数。

The most simple way would be to wrap your class in a function that returns an instance: 最简单的方法是将类包装在返回实例的函数中:

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

However, this will only work in the most limited sense - it does not allow for dependency injection and the compile and link functions of your directive (if defined) will not work as expected. 但是,这只能在最有限的意义上工作 - 它不允许依赖注入,并且指令的compilelink函数(如果已定义)将无法按预期工作。

In fact, this is a problem I have looked into quite extensively and it turned out to be fairly tricky to solve (for me at least). 事实上,这是一个我已经广泛研究过的问题,事实证明这个问题相当棘手(至少对我而言)。

I wrote an extensive article covering my solution, but as far as you are concerned I can point you to the discussion of the two main issues that need to be resolved: 我写了一篇涵盖我的解决方案的大量文章,但就您而言,我可以指出您对需要解决的两个主要问题的讨论:

  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 允许将指令的linkcompile函数定义为类方法

The full solution involves too much code to paste here, I think, but I have put together a working demo project which allows you to define a directive as an ES6 class like this: 我认为完整的解决方案涉及太多的代码粘贴在这里,但我已经整理了一个工作的演示项目,它允许您将指令定义为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);

Check out the demo repo here and here is the code behind register.directive() 查看这里demo repo这里是register.directive()的代码

@Michael is right on the money: @Michael是对的钱:

the module.directive() method expects a factory function module.directive()方法需要一个工厂函数

However I solved it using another technique, a little cleaner I suppose, It works fine for me, it's not perfect though... I defined a static method that returns a the factory expected by 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 }

And in my app I do: 在我的应用程序中,我做:

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

I believe there's no other way to use classes + directives that going through hacks like this at this point, just pick the easy one ;-) 我相信没有其他方法可以使用类似于这样的黑客攻击的类+指令,只需选择容易的方法;-)

A simpler, cleaner and more readable solution 🚀. 更简单,更清晰,更易读的解决方案🚀。

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;

You cannot get $q in link function, this in link will be undefined or null . 你不能在link函数中获得$qthislink中将是undefinednull exploring-es6-classes-in-angularjs-1-x#_section-factories 探索-ES6类合angularjs-1-X#_section-工厂

when Angular invokes the link function, it is no longer in the context of the class instance, and therefore this.$interval will be undefined 当Angular调用链接函数时,它不再在类实例的上下文中,因此。$ interval将是未定义的

So make use of the controller function in the directive, and inject dependencies or anything that you want to access in the link function. 因此,请使用指令中的controller函数,并在link函数中注入要访问的依赖项或任何内容。

My solution: 我的解决方案

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 }

and in the main app file 并在主app文件中

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

In my project I use a syntax sugar for injections. 在我的项目中,我使用语法糖进行注射。 And ES6 makes it pretty simple to use injectable factories for directives avoiding too much duplicate code. ES6使得可注射工厂用于指令避免过多的重复代码非常简单。 This code allows injection inheritance, uses annotated injections and so on. 此代码允许注入继承,使用带注释的注入等。 Check this: 检查一下:

First step 第一步

Declare base class for all angular controllers\\directives\\services - InjectableClient. 声明所有angular controllers \\ directives \\ services的基类--InjectableClient。 Its main task - set all injected params as properties for 'this'. 它的主要任务 - 将所有注入的参数设置为'this'的属性 This behavior can be overridden, see examples below. 可以覆盖此行为,请参阅下面的示例。

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);
        }
    };
}

For example, if we call SomeClassInheritedFromInjectableClient.inject('$scope'), in directive or controller we will use it as 'this.$scope' 例如,如果我们调用SomeClassInheritedFromInjectableClient.inject('$ scope'),在指令或控制器中我们将它用作'this。$ scope'

Second step 第二步

Declare the base class for directive with static method "factory()", which binds $injected property of directive class to factory function. 使用静态方法“factory()”声明指令的基类,它将指令类的$ inject属性绑定到工厂函数。 And also "compile()" method, which binds the context of link function to the directive itself. 还有“compile()”方法,它将链接函数的上下文绑定到指令本身。 Its allows to use our injected values inside the link function as this.myInjectedService. 它允许在链接函数中使用我们的注入值作为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;
    }
}

Third step 第三步

Now we can declare as much directive classes as possible. 现在我们可以声明尽可能多的指令类。 With inheritance. 继承。 And we can set up injections in simple way with spread arrays (just dont forget call super method). 我们可以使用扩展数组以简单的方式设置注入(只是不要忘记调用超级方法)。 See examples: 见例子:

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);
    }
}    

The last step 最后一步

Now register directives with angular in simple way: 现在以简单的方式注册带有角度的指令:

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

Now test the code: 现在测试代码:

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));

This will return: 这将返回:

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"}

I had a similar problem. 我遇到了类似的问题。 But in my case it worked and failed when I deployed to production. 但就我而言,当我部署到生产环境时它起作用并失败了。 And it failed because production has the latest version of 6to5. 它失败了,因为生产有最新版本的6to5。 This could be prevented by using npm shrinkwrap . 这可以通过使用npm shrinkwrap来防止。 According to the latest ES6 spec you can't use a class like this. 根据最新的ES6规范,你不能使用这样的类。 https://github.com/babel/babel/issues/700 https://github.com/babel/babel/issues/700

I faced the same problem. 我遇到了同样的问题。 First time I tried to solve problem via ES6 classes but I have problem with $inject my dependencies. 我第一次尝试通过ES6类解决问题,但我有$ inject我的依赖项的问题。 After I realized what angular have couple styles of writing code and I tried. 在我意识到角度有几种样式的编写代码后我尝试了。 At all I used John Papa styles and I got this works code in my rails app with ES6: 我使用了John Papa样式,我在ES6的rails应用程序中获得了这个代码:

((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);

I know that I can do little bit improvements with functions and variables in more ES6 style. 我知道我可以用更多ES6风格的函数和变量做一些改进。 I hope it helps. 我希望它有所帮助。

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);

I ran into this problem just now and I saw this topic. 我刚才遇到了这个问题,我看到了这个话题。 Tried some methods provided in discussion, I finally solved this problem in a very simple way: 试过讨论中提供的一些方法,我终于以一种非常简单的方式解决了这个问题:

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';
        ...
    }
    ...
}

I directly use function as the .directive('directiveName',factory) argument, and export it, later import it in module declaration. 我直接使用函数作为.directive('directiveName',factory)参数,然后将其导出,稍后在模块声明中导入它。 But I missed the "default" statement when exporting, so I got an error. 但是我在导出时错过了“默认”语句,所以我收到了一个错误。 After I add "default" key word, everything works! 添加“默认”关键字后,一切正常!

I find this method also works in my route configs (also in a function way). 我发现这个方法也适用于我的路由配置(也是以函数方式)。

============ Hope you can understand my poor English :) ============希望你能理解我可怜的英语:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM