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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28620479/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:06:50  来源:igfitidea点击:

Using ES6 Classes as Angular 1.x directives

javascriptangularjsclassangularjs-directiveecmascript-6

提问by Boughtmanatee

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. Here's my directive.

我正在做一个小项目来玩转 ES6 带来的好东西,我试图将注册一个类设置为 angular 指令,但我遇到了这个错误“类型错误:无法将类作为函数调用",但从示例中我发现他们只是编写类并将其注册为 angular 作为指令。这是我的指示。

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?

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

回答by michal.chochol

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']),因此您可以根据需要通过作用域使用链接函数中的服务。

回答by Michael Bromley

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 compileand linkfunctions of your directive (if defined) will not work as expected.

但是,这只会在最有限的意义上起作用 - 它不允许依赖注入,compile并且link指令的和函数(如果已定义)将无法按预期工作。

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 linkand compilefunctions to be defined as class methods

  1. 将类定义动态转换为角度兼容的工厂函数

  2. 允许将指令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 hereand here is the code behind register.directive()

在这里查看演示仓库这里是背后的代码register.directive()

回答by bmaggi

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

但是我使用另一种技术解决了它,我想它会更干净一些,它对我来说很好用,但它并不完美......我定义了一个静态方法,它返回 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 ;-)

我相信在这一点上没有其他方法可以使用类 + 指令来经历这样的黑客攻击,只需选择简单的一个 ;-)

回答by legend80s

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 $qin linkfunction, thisin linkwill be undefinedor null. exploring-es6-classes-in-angularjs-1-x#_section-factories

你不能得到$qlink功能,thislinkundefinednullexplore-es6-classes-in-angularjs-1-x#_section-factories

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 调用链接函数时,它不再在类实例的上下文中,因此 this.$interval 将是未定义的

So make use of the controllerfunction in the directive, and inject dependencies or anything that you want to access in the linkfunction.

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

回答by Alon

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.directive('myDirective', myDirective.create)

回答by Statyan

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. This code allows injection inheritance, uses annotated injections and so on. Check this:

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

First step

第一步

Declare base class for all angular controllers\directives\services - InjectableClient. Its main task - set all injected params as propertiesfor 'this'. This behavior can be overridden, see examples below.

声明所有角度控制器\指令\服务的基类 - 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);
        }
    };
}

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. And also "compile()" method, which binds the context of link function to the directive itself. Its allows to use our injected values inside the link function as this.myInjectedService.

使用静态方法“factory()”声明指令的基类,该方法将指令类的 $injected 属性绑定到工厂函数。还有“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;
    }
}

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 注册指令:

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

回答by Egor

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

回答by Howard

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. But I missed the "default" statement when exporting, so I got an error. After I add "default" key word, everything works!

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

I find this method also works in my route configs (also in a function way).

我发现这种方法也适用于我的路由配置(也以功能方式)。

============ Hope you can understand my poor English :)

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

回答by khusnetdinov

I faced the same problem. First time I tried to solve problem via ES6 classes but I have problem with $inject my dependencies. After I realized what angular have couple styles of writing code and I tried. At all I used John Papastyles and I got this works code in my rails app with ES6:

我遇到了同样的问题。我第一次尝试通过 ES6 类解决问题,但我在使用 $inject 我的依赖项时遇到了问题。在我意识到 angular 有几种编写代码的风格后,我尝试了。我完全使用了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. I hope it helps.

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

回答by Roberto_ua

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. This could be prevented by using npm shrinkwrap. According to the latest ES6 spec you can't use a class like this. https://github.com/babel/babel/issues/700

我有一个类似的问题。但是在我的情况下,当我部署到生产环境时,它工作并失败了。它失败了,因为生产有最新版本的 6to5。这可以通过使用来防止npm shrinkwrap。根据最新的 ES6 规范,您不能使用这样的类。https://github.com/babel/babel/issues/700