web-dev-qa-db-fra.com

Utilisation de classes ES6 en tant que directives angulaires 1.x

Je fais un petit projet pour jouer avec le sac de cadeaux que l'ES6 apporte, j'essaye de définir une classe comme directive angulaire, mais je rencontre cette erreur "TypeError: impossible d'appeler une classe en tant que fonction ", mais à partir des exemples que j'ai trouvés, ils écrivent simplement la classe et l'enregistrent avec angular comme directive. Voici ma directive.

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

export default dateBlock

et mon index où je l'importe puis le déclare.

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 je manquais une étape cruciale, j'aimerais l'entendre. Question secondaire: est-il plus propre d'importer tous les composants d'applications dans l'index et de tous les enregistrer ou d'exporter l'application et de les importer et les enregistrer dans les composants?

57
Boughtmanatee

De mon point de vue, il n'est pas nécessaire d'utiliser des bibliothèques externes telles que register.js, car vous pouvez créer une directive en tant que classe ES6 de cette façon: 

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)

L'utilisation de directive controller vous permet d'injecter des dépendances, même sans déclaration supplémentaire (ex. MessagesDirective.$inject = ['$scope', '$state', 'MessagesService']), afin que vous puissiez utiliser les services de la fonction link via scope si vous en avez besoin 

63
michal.chochol

Comme mentionné dans un commentaire, la méthode module.directive() attend une fonction factory plutôt qu'un constructeur.

Le moyen le plus simple consiste à envelopper votre classe dans une fonction qui retourne une instance:

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

Toutefois, cela ne fonctionnera que dans le sens le plus limité: il ne permet pas l'injection de dépendance et les fonctions compile et link de votre directive (si elles sont définies) ne fonctionneront pas comme prévu.

En fait, c’est un problème que j’ai examiné de manière assez approfondie et qui s’est avéré assez difficile à résoudre (pour moi du moins). 

J'ai écrit un long article couvrant ma solution, mais en ce qui vous concerne, je peux vous diriger vers la discussion des deux problèmes principaux à résoudre:

  1. Conversion dynamique d'une définition de classe en une fonction d'usine compatible avec les angles

  2. Autoriser les directives link et compile d'une directive à être définies comme méthodes de classe

Je pense que la solution complète implique trop de code à coller ici, mais j’ai mis sur pied un projet de démonstration qui vous permet de définir une directive comme une classe ES6 comme celle-ci:

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

Découvrez le demo repo ici et voici le code derrière register.directive()

48
Michael Bromley

@ Michael a raison sur l'argent:

la méthode module.directive () attend une fonction factory

Cependant, je l'ai résolu en utilisant une autre technique, un peu plus propre, je suppose, ça marche très bien pour moi, mais ce n'est pas parfait .... J'ai défini une méthode statique qui retourne une usine attendue par 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 }

Et dans mon application je fais:

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

Je crois qu’il n’ya pas d’autre moyen d’utiliser les classes + les directives qui passent par des bidouilles comme celle-ci à ce stade, choisissez la plus facile ;-)

22
bmaggi

Une solution plus simple, plus propre et plus lisible ????.

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;

Vous ne pouvez pas obtenir $q dans la fonction link, this dans link sera undefined ou null. exploration-es6-classes-en-angularjs-1-x # _section-fabories

quand Angular appelle la fonction link, il n’est plus dans le contexte de l’instance de la classe, et donc cet intervalle. $ sera indéfini

Utilisez donc la fonction controller dans la directive et injectez des dépendances ou tout élément auquel vous souhaitez accéder dans la fonction link.

19
legend80s

Ma solution:

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 }

et dans le fichier principal de l'application

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

Dans mon projet, j'utilise un sucre de syntaxe pour les injections. Et ES6 simplifie l’utilisation d’usines injectables pour les directives, évitant ainsi une duplication excessive du code. Ce code permet l'héritage d'injection, utilise des injections annotées, etc. Vérifie ça:

Premier pas

Déclarez la classe de base pour tous les contrôleurs angulaires\directives\services - InjectableClient. Sa tâche principale - définir tous les paramètres injectés en tant que propriétés pour 'this'. Ce comportement peut être remplacé, voir les exemples ci-dessous.

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

Par exemple, si nous appelons SomeClassInheritedFromInjectableClient.inject ('$ scope'), dans la directive ou le contrôleur, nous l'utilisons comme suit: 'this. $ Scope'

Deuxième étape

Déclarez la classe de base de la directive avec la méthode statique "factory ()", qui lie la propriété $ injected de la classe de directive à la fonction factory. Et aussi la méthode "compile ()", qui lie le contexte de la fonction link à la directive elle-même. Cela permet d'utiliser nos valeurs injectées à l'intérieur de la fonction link comme 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;
    }
}

Troisième étape

Nous pouvons maintenant déclarer autant de classes de directives que possible. Avec héritage. Et nous pouvons mettre en place des injections de manière simple avec des tableaux de diffusion (il suffit de ne pas oublier d'appeler super méthode). Voir des exemples:

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

La dernière étape

Enregistrez maintenant les directives avec angular de manière simple:

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

Maintenant, testez le 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));

Cela retournera:

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
Statyan

J'avais un problème similaire. Mais dans mon cas, cela a fonctionné et a échoué lorsque j'ai été déployé en production. Et cela a échoué, car la production possède la dernière version de 6to5 . Cela pourrait être évité en utilisant npm shrinkwrap. Selon la dernière spécification ES6, vous ne pouvez pas utiliser une classe comme celle-ci. https://github.com/babel/babel/issues/700

0
Roberto_ua

J'ai rencontré ce problème tout à l'heure et j'ai vu ce sujet. J'ai essayé quelques méthodes fournies dans la discussion, j'ai finalement résolu ce problème d'une manière très 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';
        ...
    }
    ...
}

J'utilise directement function comme argument .directive ('directiveName', factory) et l'exporte, puis l'importe dans la déclaration du module. Mais j'ai manqué l'instruction "par défaut" lors de l'exportation, alors j'ai eu une erreur. Après avoir ajouté la clé "par défaut" Word, tout fonctionne!

Je trouve que cette méthode fonctionne également dans les configs de mon itinéraire (également de manière fonctionnelle).

============ J'espère que vous comprendrez mon anglais médiocre :)

0
Howard
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
Egor

J'ai rencontré le même problème. La première fois que j'ai essayé de résoudre un problème via les classes ES6, j'ai un problème avec $ inject mes dépendances. Après avoir réalisé quels styles de couple avaient plusieurs styles d’écriture et j’ai essayé. Tout ce que j'ai utilisé John Papa styles et j'ai obtenu ce code de travail dans mon application Rails avec 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);

Je sais que je peux apporter de petites améliorations avec des fonctions et des variables de style plus ES6 ..__ J'espère que cela aide. 

0
khusnetdinov