繁体   English   中英

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

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

我正在做一个小项目来玩围绕ES6带来的好东西,我正在尝试将一个类设置为一个角度指令,但我遇到了这个错误“TypeError:不能将一个类称为函数“但是从示例中我发现他们只是编写类并将其作为指令注册角色。 这是我的指示。

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)

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

从我的角度来看,不需要使用像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'] ),所以你可以通过范围使用链接功能中的服务需要。

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

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

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

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

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

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

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

  2. 允许将指令的linkcompile函数定义为类方法

我认为完整的解决方案涉及太多的代码粘贴在这里,但我已经整理了一个工作的演示项目,它允许您将指令定义为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这里是register.directive()的代码

@Michael是对的钱:

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

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

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)

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

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

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;

你不能在link函数中获得$qthislink中将是undefinednull 探索-ES6类合angularjs-1-X#_section-工厂

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

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

我的解决方案

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)

在我的项目中,我使用语法糖进行注射。 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"}

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

我遇到了同样的问题。 我第一次尝试通过ES6类解决问题,但我有$ inject我的依赖项的问题。 在我意识到角度有几种样式的编写代码后我尝试了。 我使用了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);

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

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

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

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)参数,然后将其导出,稍后在模块声明中导入它。 但是我在导出时错过了“默认”语句,所以我收到了一个错误。 添加“默认”关键字后,一切正常!

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

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

暂无
暂无

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

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