it-swarm.com.ru

Использование классов ES6 в качестве директив Angular 1.x

Я делаю небольшой проект, чтобы поиграть с полезной сумкой, которую приносит ES6, я пытаюсь настроить регистрацию класса как директиву angular, но я сталкиваюсь с этой ошибкой "TypeError: Cannot вызвать класс как функцию ", но из примеров, которые я нашел, они просто пишут класс и регистрируют его с помощью 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)

Если бы я пропустил какой-то важный шаг, я бы хотел это услышать. Еще один побочный вопрос: может ли чище импортировать все компоненты приложения в индекс и зарегистрировать их там или экспортировать приложение, а также импортировать и зарегистрировать в компонентах?

57
Boughtmanatee

С моей точки зрения, нет необходимости использовать внешние библиотеки, такие как 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']), поэтому вы можете использовать сервисы в функции связи через контекст, если вам нужно. 

63
michal.chochol

Как упоминалось в комментарии, метод module.directive() ожидает фабричную функцию, а не конструктор.

Самый простой способ - обернуть ваш класс в функцию, которая возвращает экземпляр:

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

Однако это будет работать только в самом ограниченном смысле - оно не позволяет внедрять зависимости, а функции вашей директивы (если определены) compile и link не будут работать должным образом.

На самом деле, это проблема, которую я изучил достаточно широко, и решить ее оказалось довольно сложно (по крайней мере, для меня). 

Я написал обширную статью, посвященную моему решению, но, что касается вас, я могу указать вам на обсуждение двух основных проблем, которые необходимо решить:

  1. Динамическое преобразование определения класса в совместимую с углом фабричную функцию

  2. Разрешение функций директивы link и compile быть определенными как методы класса

Полное решение включает в себя слишком много кода для вставки, я думаю, но я собрал работающий демонстрационный проект, который позволяет вам определять директиву как класс 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);

Посмотрите демо репо здесь и вот код register.directive()

48
Michael Bromley

@ Майкл прав на деньги:

метод 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)

Я полагаю, что нет другого способа использовать директивы классов +, которые сейчас проходят через подобные хаки, просто выберите самый простой ;-)

22
bmaggi

Более простое, понятное и удобочитаемое решение.

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 в функции link, this в link будет undefined или null. исследуя-es6-классы-в-angularjs-1-x # _section-фабрики

когда Angular вызывает функцию link, она больше не находится в контексте экземпляра класса, и поэтому this. $ interval будет неопределенным

Так что используйте функцию controller в директиве и вставьте зависимости или все, что вы хотите получить доступ в функции link.

19
legend80s

Мое решение:

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

В моем проекте я использую синтаксический сахар для инъекций. И ES6 делает довольно простым использование инъекционных фабрик для директив, избегая слишком большого количества дублирующегося кода. Этот код разрешает наследование инъекций, использует аннотированные инъекции и так далее. Проверь это:

Первый шаг

Объявите базовый класс для всех угловых контроллеров\директив\сервисов - 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 ()", который связывает свойство $ 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;
    }
}

Третий шаг

Теперь мы можем объявить как можно больше директивных классов. С наследством. И мы можем установить инъекции простым способом с массивами распространения (просто не забудьте вызвать метод super). Смотрите примеры:

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 простым способом:

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

У меня была похожая проблема. Но в моем случае это сработало и потерпело неудачу, когда я развернул в производство. И это не удалось, потому что в производстве установлена ​​последняя версия 6to5 . Это можно предотвратить с помощью npm shrinkwrap. Согласно последней спецификации ES6, вы не можете использовать такой класс, как этот. https://github.com/babel/babel/issues/700

0
Roberto_ua

Я столкнулся с этой проблемой только сейчас, и я увидел эту тему. Опробовав несколько методов, представленных в обсуждении, я наконец решил эту проблему очень простым способом:

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) и экспортирую ее, а затем импортирую в декларацию модуля. Но при экспорте я пропустил оператор "default", поэтому получил ошибку. После того, как я добавил «по умолчанию» ключ Word, все работает!

Я считаю, что этот метод также работает в моих конфигах маршрута (также функциональным способом).

============ Надеюсь, вы понимаете мой плохой английский :)

0
Howard
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);
0
Egor

Я столкнулся с той же проблемой. В первый раз я пытался решить проблему с помощью классов ES6, но у меня есть проблема с $ инъекцией моих зависимостей. После того, как я понял, что angular есть пара стилей написания кода, и я попробовал. Вообще я использовал John Papa styles и получил этот код работ в своем приложении 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 ... Я надеюсь, это поможет. 

0
khusnetdinov