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:
-
Convertir dinámicamente una definición de clase en una función de fábrica compatible con angular
-
Permitir que el
link
una directiva y las funciones decompile
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);