Using ES6 classes as Angular 1.x directives

I am doing a small project to play in the good quality bag that ES6 brings, I am trying to set the class register as an angular directive, but I ran into this error “TypeError: cannot call a class as a function”, but from the examples I find, they just write the class and register it with angular as a directive. Here is my directive.

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

and my index, where I import it, and then declare it.

 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) 

If I missed some important step, I would love to hear it. In addition, the question is whether it is cleaner to import all application components into the index and register them there or export, as well as import and register in the components?

+57
javascript angularjs ecmascript-6 class angularjs-directive
Feb 20 '15 at 1:57
source share
10 answers

From my point of view, there is no need to use external libraries such as register.js, because you can create a directive as an ES6 class as follows:

 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) 

Using a directory controller allows you to enter dependencies even without an additional declaration (for example, MessagesDirective.$inject = ['$scope', '$state', 'MessagesService'] ), so you can use services in the link function through the scope if you need to.

+63
Nov 15 '15 at 0:10
source share

As mentioned in the comment, the module.directive() method expects a factory function, not a constructor.

The easiest way is to wrap the class in a function that returns an instance:

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

However, this will work only in the most limited sense - it does not allow dependency injection, and the compile and link functions of your directive (if defined) will not work properly.

Actually, this is a problem that I studied quite widely, and it turned out to be quite difficult to solve (at least for me).

I wrote an extensive article on my decision, but as far as you know, I can point out a discussion of two main issues that need to be addressed:

The complete solution includes too much code to embed here, I think, but I put together a working demo project that allows you to define the directive as an ES6 class as follows:

 class MyDirective { /*@ngInject*/ constructor($interval) { this.template = '<div>I\'ma directive!</div>'; this.restrict = 'E'; this.scope = {} // etc. for the usual config options // allows us to use the injected dependencies // elsewhere in the directive (eg 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); 

Check out the demo here and here is the code <<27>

+48
Feb 20 '15 at 17:07
source share

@ Michael is right for the money:

module.directive () method expects factory function

However, I solved it using a different technique, a little cleaner, I suppose this works fine for me, it is not ideal, though ... I have defined a static method that returns the factory expected by 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 } 

And in my application, I:

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

I believe that there is no other way to use the classes + directives that go through such hacks at this moment, just choose a simple one ,-)

+22
Jul 25 '15 at 5:55
source share

A simpler, more understandable and understandable 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; 

You cannot get $q in the link function, this link will be undefined or null . exploring-ES6-classes-in-angularjs-1 # _section-plants

when Angular calls the link function, it is no longer in the context of the class instance, and therefore this. $ interval will be undefined

So use the controller function in the directive and insert the dependencies or whatever you want to access the link function.

+19
Dec 15 '15 at 5:20
source share

My decision:

 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 } 

and in the main application file

 app.directive('myDirective', myDirective.create) 
+5
Oct. 16 '16 at 18:14
source share

In my project, I use syntactic sugar for injection. And ES6 simplifies the use of injection factories for directives, avoiding too much code duplication. This code allows inheritance of injections, uses annotated injections and so on. Check this:

First step

Declare a base class for all angular controllers \ directives \ services - InjectableClient. The main task is to set all entered parameters as properties for 'this'. This behavior can be overridden, see Examples below.

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

For example, if we call SomeClassInheritedFromInjectableClient.inject ('$ scope'), in the directive or controller we will use it as' this. $ scope '

Second step

Declare the base class for the directive with the static method "factory ()", which associates the $ injected property of the directive class with the factory function. As well as the compile () method, which associates the context of the link function with the directive itself. This allows us to use our entered values ​​inside the communication function like 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; } } 

Third step

Now we can declare as many directive classes as possible. With inheritance. And we can easily inject using distributed arrays (just remember to call the super method). Examples:

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

Last step

Now register the directives with angular in a simple way:

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

Now check the code:

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

This will return:

 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
Mar 02 '16 at 2:02
source share

I had a similar problem. But in my case, it worked and failed when I was deployed to production. And this did not work, because the production has the latest version of 6to5. This can be prevented by using npm shrinkwrap . According to the latest ES6 specification, you cannot use such a class. https://github.com/babel/babel/issues/700

0
Feb 20 '15 at 17:05
source share

I ran into the same problem. The first time I tried to solve the problem through ES6 classes, but I have a problem with $ injection of my dependencies. After I realized that angular has several styles of coding, and I tried. I generally used John Papa , and I got this work code in my rails application with 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); 

I know that I can slightly improve the enhancements to functions and variables in a more ES6 style. I hope this helps.

0
Oct 31 '15 at 8:32
source share
 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
Jan 23 '17 at 10:18
source share

I ran into this problem just now, and I saw this topic. After trying out a few of the methods presented in the discussion, I finally solved this problem in a very simple way:

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

I directly use the function as an argument .directive ('directiveName', factory) and export it and then import it into the module declaration. But when exporting, I skipped the "default" operator, so I got an error. After I add the default keyword, everything works!

I believe that this method also works in my route configs (also in a functional way).

============= I hope you understand my bad English :)

0
Jun 12
source share



All Articles