Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

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

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

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

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

Ознакомьтесь с демо-репо здесь и вот код < <27 >

Ответ 3

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

метод module.directive() ожидает функцию factory

Однако я решил это, используя другую технику, немного чище, я полагаю, это отлично работает для меня, это не идеально, хотя... Я определил статический метод, который возвращает factory, ожидаемый модулем()

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)

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

Ответ 4

Более простое, понятное и понятное решение 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;

Вы не можете получить $q в функции link, this link будет undefined или null. исследуя-ES6-классы-в-angularjs-1-х # _section-заводы

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

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

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

Ответ 6

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

Первый шаг

Объявить базовый класс для всех angular контроллеров\директив\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()", который связывает $injected свойство класса директивы с функцией factory. А также метод "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 простым способом:

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

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

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

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