ES6-klassen gebruiken als Angular 1.x-richtlijnen

Ik ben bezig met een klein project om te spelen met de goodybag die de ES6 met zich meebrengt, ik probeer een klasse te registreren als een hoekige richtlijn, maar ik kom deze fout tegen “TypeError: Cannot call a class als een functie”, maar uit de voorbeelden die ik vind, schrijven ze gewoon de klasse en registreren deze met angular als richtlijn. Hier is mijn richtlijn.

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

en mijn index waar ik het importeer en vervolgens declareer.

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)

Als ik een cruciale stap heb gemist, hoor ik het graag. Ook bijvraag is het schoner om alle app-componenten in de index te importeren en ze daar allemaal te registreren of de app te exporteren en te importeren en te registreren binnen de componenten?


Antwoord 1, autoriteit 100%

Vanuit mijn oogpunt is het niet nodig om externe bibliotheken zoals register.js te gebruiken, omdat je op deze manier een richtlijn als een ES6-klasse kunt maken:

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)

Door de instructiecontroller te gebruiken, kunt u afhankelijkheden invoegen, zelfs zonder aanvullende declaratie (bijv. MessagesDirective.$inject = ['$scope', '$state', 'MessagesService']), zodat u kan services gebruiken in de link-functie via scope als je dat nodig hebt.


Antwoord 2, autoriteit 73%

Zoals vermeld in een opmerking, verwacht de methode module.directive()een fabrieksfunctie in plaats van een constructor.

De meest eenvoudige manier zou zijn om je klasse in een functie te verpakken die een instantie retourneert:

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

Dit werkt echter alleen in de meest beperkte zin – het staat geen afhankelijkheidsinjectie toe en de functies compileen linkvan uw richtlijn (indien gedefinieerd) zullen werkt niet zoals verwacht.

In feite is dit een probleem waar ik vrij uitgebreid naar heb gekeken en het bleek vrij lastig op te lossen (voor mij althans).

Ik heb een uitgebreid artikel geschreven over mijn oplossing, maar wat jou betreft kan ik je wijzen op de bespreking van de twee belangrijkste problemen die moeten worden opgelost:

  1. Een klasse dynamisch converteren definitie in een hoekcompatibele fabrieksfunctie

  2. Een richtlijn toestaan ​​linken compile-functies die moeten worden gedefinieerd als klassenmethoden

De volledige oplossing omvat te veel code om hier te plakken, denk ik, maar ik heb een werkend demoproject samengesteld waarmee je een richtlijn als een ES6-klasse als volgt kunt definiëren:

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

Bekijk de demo repo hieren hier is de code achter register.directive()


Antwoord 3, autoriteit 33%

@Michael heeft gelijk met het geld:

de methode module.directive() verwacht een fabrieksfunctie

Ik heb het echter opgelost met een andere techniek, een beetje schoner denk ik, het werkt prima voor mij, het is echter niet perfect…
Ik heb een statische methode gedefinieerd die de fabriek retourneert die wordt verwacht door 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 }

En in mijn app doe ik:

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

Ik geloof dat er op dit moment geen andere manier is om klassen + richtlijnen te gebruiken die door dergelijke hacks gaan, kies gewoon de makkelijke 😉


Antwoord 4, autoriteit 30%

Een eenvoudigere, schonere en beter leesbare oplossing 🚀.

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;

U kunt $qniet krijgen in de functie link, thisin linkzal undefinedof null. exploring-es6- klassen-in-angularjs-1-x#_section-factories

wanneer Angular de link-functie aanroept, bevindt deze zich niet langer in de context van de klasse-instantie, en daarom is dit.$interval niet gedefinieerd

Maak dus gebruik van de controllerfunctie in de richtlijn, en voeg afhankelijkheden of iets anders toe waartoe je toegang wilt hebben in de linkfunctie.


Antwoord 5, autoriteit 9%

Mijn oplossing:

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 }

en in het hoofdapp-bestand

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

Antwoord 6, autoriteit 5%

In mijn project gebruik ik een syntaxissuiker voor injecties. En ES6 maakt het vrij eenvoudig om injecteerbare fabrieken te gebruiken voor richtlijnen om te veel dubbele code te vermijden. Deze code staat injectie-overerving toe, maakt gebruik van geannoteerde injecties, enzovoort. Controleer dit:

Eerste stap

Declareer de basisklasse voor alle hoekcontrollers\directives\services – InjectableClient. Zijn hoofdtaak – stel alle geïnjecteerde parameters in als eigenschappenvoor ‘this’. Dit gedrag kan worden overschreven, zie onderstaande voorbeelden.

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

Als we bijvoorbeeld SomeClassInheritedFromInjectableClient.inject(‘$scope’), in richtlijn of controller aanroepen, gebruiken we het als ‘this.$scope’

Tweede stap

Declareer de basisklasse voor de richtlijn met de statische methode “factory()”, die de eigenschap $injected van de richtlijnklasse bindt aan de fabrieksfunctie. En ook de methode “compile()”, die de context van de linkfunctie aan de richtlijn zelf bindt. Het maakt het mogelijk om onze geïnjecteerde waarden in de linkfunctie te gebruiken als 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;
    }
}

Derde stap

Nu kunnen we zoveel mogelijk richtlijnklassen declareren. Met erfenis. En we kunnen injecties op een eenvoudige manier opzetten met spread-arrays (vergeet niet de supermethode aan te roepen). Zie voorbeelden:

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

De laatste stap

Registreer nu op een eenvoudige manier richtlijnen met angular:

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

Test nu de 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));

Dit keert terug:

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

Antwoord 7, autoriteit 2%

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

Antwoord 8

Ik had een soortgelijk probleem. Maar in mijn geval werkte het en mislukte het toen ik het in productie nam. En het mislukte omdat de productie de nieuwste versie van 6to5 heeft.
Dit kan worden voorkomen door npm shrinkwrapte gebruiken.
Volgens de laatste ES6 specificatie kun je zo’n klasse niet gebruiken. https://github.com/babel/babel/issues/700


Antwoord 9

Ik had hetzelfde probleem. De eerste keer dat ik probeerde het probleem op te lossen via ES6-klassen, maar ik heb een probleem met $ inject my dependencies. Nadat ik me realiseerde welke hoekig een paar schrijfstijlen hebben, heb ik het geprobeerd. Ik gebruikte helemaal John Papa-stijlen en ik kreeg deze werkcode in mijn rails-app met 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);

Ik weet dat ik kleine verbeteringen kan aanbrengen met functies en variabelen in meer ES6-stijl.
Ik hoop dat het helpt.


Antwoord 10

Ik kwam dit probleem zojuist tegen en zag dit onderwerp. Ik heb een aantal methoden geprobeerd die in de discussie werden aangeboden, ik heb dit probleem uiteindelijk op een heel eenvoudige manier opgelost:

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

Ik gebruik de functie rechtstreeks als het argument .directive(‘directiveName’,factory) en exporteer het, en importeer het later in moduledeclaratie. Maar ik miste de “default” -verklaring bij het exporteren, dus ik kreeg een foutmelding. Nadat ik het “standaard” sleutelwoord heb toegevoegd, werkt alles!

Ik vind dat deze methode ook werkt in mijn routeconfiguraties (ook op een functionele manier).

============
Ik hoop dat je mijn slechte Engels kunt begrijpen 🙂

Other episodes