Использование классов 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)
Если бы я пропустил какой-то важный шаг, я бы с удовольствием это услышал. Кроме того, вопрос о том, чище ли импортировать все компоненты приложений в индекс и регистрировать их там или экспортировать, а также импортировать и регистрировать в компонентах?
Ответы
Ответ 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) и экспортирую ее, а затем импортирую в декларацию модуля. Но при экспорте я пропустил оператор "по умолчанию", поэтому получил ошибку. После того, как я добавлю ключевое слово по умолчанию, все работает!
Я считаю, что этот метод также работает в моих конфигах маршрута (также функциональным способом).
============ Надеюсь, вы понимаете мой плохой английский :)