tutorial ecmascript javascript angularjs class angularjs-directive ecmascript-6

javascript - ecmascript - Uso de clases ES6 como directivas angulares 1.x



angularjs ecmascript 6 (10)

Estoy haciendo un pequeño proyecto para jugar con la bolsa de regalos que trae el ES6, estoy tratando de configurar registrar una clase como directiva angular, pero me encuentro con este error "TypeError: No se puede llamar a una clase como una función ", pero a partir de los ejemplos que encuentro, simplemente escriben la clase y la registran con angular como directiva. Aquí está mi directiva.

class dateBlock { constructor () { this.template = ''/app/dateblock/dateblock.html''; this.restrict = ''AE''; this.scope = {}; } }; export default dateBlock

y mi índice donde lo importo y luego lo declaro.

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)

Si me perdiera algún paso crucial, me encantaría escucharlo. Además, ¿es más claro importar todos los componentes de las aplicaciones al índice y registrarlos todos allí o exportar la aplicación e importar y registrar dentro de los componentes?


@Michael tiene razón en el dinero:

el método module.directive () espera una función de fábrica

Sin embargo, lo resolví usando otra técnica, un poco más limpio, supongo, funciona bien para mí, aunque no es perfecto ... Definí un método estático que devuelve la fábrica esperada por el módulo ()

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 }

Y en mi aplicación hago:

angular.module(''vineyard'',[]).directive(''vineScroller'', VineDirective.directiveFactory)

Creo que no hay otra manera de usar las clases + directivas que atraviesen hacks como este en este momento, solo elija el fácil ;-)


Como se menciona en un comentario, el método module.directive() espera una función de fábrica en lugar de un constructor.

La forma más simple sería envolver su clase en una función que devuelva una instancia:

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

Sin embargo, esto solo funcionará en el sentido más limitado: no permite la inyección de dependencia y las funciones de compile y link de su directiva (si está definida) no funcionarán como se esperaba.

De hecho, este es un problema que he investigado bastante y resultó ser bastante difícil de resolver (al menos para mí).

Escribí un extenso artículo que cubre mi solución, pero en lo que a usted respecta, puedo señalarle la discusión de los dos problemas principales que deben resolverse:

  1. Convertir dinámicamente una definición de clase en una función de fábrica compatible con angular

  2. Permitir que el link una directiva y las funciones de compile se definan como métodos de clase

La solución completa implica demasiado código para pegar aquí, creo, pero he creado un proyecto de demostración que le permite definir una directiva como una clase ES6 como esta:

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

Echa un vistazo al repositorio de demostración aquí y aquí está el código detrás de register.directive()


Desde mi punto de vista, no hay necesidad de usar bibliotecas externas como register.js, porque puedes crear directivas como una clase ES6 de esta manera:

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)

El uso del controlador directivo le permite inyectar dependencias, incluso sin una declaración adicional (por ejemplo, MessagesDirective.$inject = [''$scope'', ''$state'', ''MessagesService''] ), por lo que puede usar los servicios en la función de enlace a través del alcance si usted necesitar.


En mi proyecto uso una sintaxis de azúcar para inyecciones. Y ES6 hace que sea bastante simple usar fábricas inyectables para directivas, evitando demasiado código duplicado. Este código permite la herencia de inyecciones, utiliza inyecciones anotadas, etc. Mira esto:

Primer paso

Declare la clase base para todos los controladores angulares / directivas / servicios - InjectableClient. Su tarea principal: establecer todos los parámetros inyectados como propiedades para ''esto''. Este comportamiento se puede anular, consulte los ejemplos a continuación.

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

Por ejemplo, si llamamos a SomeClassInheritedFromInjectableClient.inject (''$ scope''), en la directiva o el controlador lo usaremos como ''this. $ Scope''

Segundo paso

Declare la clase base para la directiva con el método estático "factory ()", que vincula la propiedad inyectada $ de la clase directiva a la función factory. Y también el método "compile ()", que une el contexto de la función de enlace a la propia directiva. Permite usar nuestros valores inyectados dentro de la función de enlace como 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; } }

Tercer paso

Ahora podemos declarar tantas clases directivas como sea posible. Con herencia. Y podemos configurar inyecciones de manera simple con matrices distribuidas (simplemente no olvides llamar al método super). Ver ejemplos:

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

El último paso

Ahora registre directivas con angular de manera simple:

angular.directive(''directiveFirst'', DirectiveFirst.factory()); angular.directive(''directiveSecond'', DirectiveSecond.factory()); angular.directive(''directiveThird'', DirectiveThird.factory());

Ahora prueba el código:

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

Esto devolverá:

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


Me encontré con este problema hace un momento y vi este tema. Probé algunos métodos proporcionados en la discusión, finalmente resolví este problema de una manera muy simple:

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''; ... } ... }

Utilizo directamente la función como argumento .directive (''directiveName'', factory), y lo exporto, luego lo importo en la declaración del módulo. Pero me perdí la declaración "predeterminada" al exportar, así que recibí un error. Después de agregar la palabra clave "predeterminada", ¡todo funciona!

Creo que este método también funciona en mis configuraciones de ruta (también de forma funcional).

============ Espero que puedas entender mi pobre inglés :)


Me enfrenté al mismo problema. La primera vez que traté de resolver el problema a través de las clases de ES6, pero tengo un problema con $ inyectar mis dependencias. Después me di cuenta de qué angular tiene un par de estilos de escritura de código y lo intenté. En absoluto usé estilos de John Papa y obtuve este código de trabajo en mi aplicación de rieles con 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);

Sé que puedo hacer pequeñas mejoras con funciones y variables en más estilo ES6. Espero que ayude.


Mi solución:

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 }

y en el archivo principal de la aplicación

app.directive(''myDirective'', myDirective.create)


Tuve un problema similar. Pero en mi caso funcionó y falló cuando me puse en producción. Y falló porque la producción tiene la última versión de 6to5. Esto podría evitarse utilizando npm shrinkwrap . De acuerdo con la última especificación ES6, no puede usar una clase como esta. https://github.com/babel/babel/issues/700


Una solución más simple, limpia y legible 🚀.

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;

No puede obtener $q en la función de link , this en el link será undefined o null . explore-es6-classes-in-angularjs-1-x # _section-factories

cuando Angular invoca la función de enlace, ya no está en el contexto de la instancia de clase y, por lo tanto, este intervalo $. será indefinido

Por lo tanto, utilice la función del controller en la directiva e inyecte dependencias o cualquier cosa a la que desee acceder en la función de link .


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