wake-up-neo.com

Verwendung von ES6-Klassen als Angular 1.x-Direktiven

Ich mache ein kleines Projekt, um die Goody-Bag zu spielen, die die ES6 mitbringt. Ich versuche, eine Klasse als Winkeldirektive registrieren zu lassen, aber ich bin auf diesen Fehler gestoßen ", aber aus den Beispielen finde ich, dass sie nur die Klasse schreiben und sie mit eckig als Direktive registrieren. Hier ist meine Anweisung.

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

export default dateBlock

und meinen Index, wo ich ihn importiere und dann deklariere.

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)

Wenn ich einen entscheidenden Schritt verpasst hätte, würde ich es sehr gerne hören. Nebenfrage ist es sauberer, alle Apps-Komponenten in den Index zu importieren und dort zu registrieren oder die App zu exportieren und die Komponenten zu importieren und zu registrieren.

57
Boughtmanatee

Aus meiner Sicht besteht keine Notwendigkeit, externe Bibliotheken wie register.js zu verwenden, da Sie folgendermaßen eine Anweisung als ES6-Klasse erstellen können: 

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)

Durch die Verwendung des Direktive Controllers können Sie Abhängigkeiten auch ohne zusätzliche Deklaration (z. B. MessagesDirective.$inject = ['$scope', '$state', 'MessagesService']) einfügen, sodass Sie Dienste in der Link-Funktion über den Gültigkeitsbereich verwenden können, wenn Sie dies benötigen. 

63
michal.chochol

Wie in einem Kommentar erwähnt, erwartet die module.directive()-Methode eine Factory-Funktion und keinen Konstruktor.

Der einfachste Weg wäre, Ihre Klasse in eine Funktion zu verpacken, die eine Instanz zurückgibt:

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

Dies funktioniert jedoch nur im engsten Sinne - es erlaubt keine Abhängigkeitseinspritzung und die compile- und link-Funktionen Ihrer Direktive (falls definiert) funktionieren nicht wie erwartet.

In der Tat ist dies ein Problem, das ich ausführlich untersucht habe, und es stellte sich als ziemlich schwierig heraus (zumindest für mich). 

Ich habe einen ausführlichen Artikel über meine Lösung geschrieben, aber für Sie kann ich Sie auf die Diskussion der zwei Hauptprobleme verweisen, die gelöst werden müssen:

  1. Dynamische Umwandlung einer Klassendefinition in eine winkelkompatible Factory-Funktion

  2. Die link- und compile-Funktionen einer Direktive als Klassenmethoden definieren lassen

Die vollständige Lösung beinhaltet zu viel Code, um hier eingefügt zu werden, denke ich, aber ich habe ein funktionierendes Demoprojekt zusammengestellt, mit dem Sie eine Direktive als eine ES6-Klasse definieren können:

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

Schauen Sie sich das Demo Repo hier an und hier ist der Code hinter register.directive()

48
Michael Bromley

@Michael hat Recht auf das Geld:

die module.directive () -Methode erwartet eine Factory-Funktion

Wie auch immer, ich habe es mit einer anderen Technik gelöst, ein bisschen sauberer, denke ich. Es funktioniert gut für mich, aber es ist nicht perfekt ... Ich definierte eine statische Methode, die die von module () erwartete Factory zurückgibt.

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 }

Und in meiner App mache ich:

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

Ich glaube, dass es keine andere Möglichkeit gibt, Klassen und Direktiven zu verwenden, die zu diesem Zeitpunkt solche Hacks durchlaufen, wähle einfach die einfache aus ;-)

22
bmaggi

Eine einfachere, sauberere und besser lesbare Lösung.

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;

Sie können $q in link nicht erhalten, this in link wird undefined oder null sein. exploring-es6-classes-in-anglejs-1-x # _section-factories

wenn Angular die Link-Funktion aufruft, befindet sich diese nicht mehr im Kontext der Klasseninstanz, und daher ist dieses $ Intervall nicht definiert

Nutzen Sie also die Funktion controller in der Direktive und fügen Sie Abhängigkeiten oder alles ein, auf das Sie in der Funktion link zugreifen möchten.

19
legend80s

Meine Lösung:

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 }

und in der Haupt-App-Datei

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

In meinem Projekt verwende ich einen Syntaxzucker für Injektionen. Und ES6 macht es ziemlich einfach, injizierbare Fabriken für Anweisungen zu verwenden, wobei zu viel doppelten Code vermieden wird. Dieser Code ermöglicht die Vererbung von Injektionen, verwendet kommentierte Injektionen und so weiter. Überprüfen Sie dies:

Erster Schritt

Deklarieren Sie die Basisklasse für alle Winkelsteuerungen\Anweisungen\Dienste - InjectableClient. Seine Hauptaufgabe - setze alle injizierten Parameter als Eigenschaften für 'this'. Dieses Verhalten kann überschrieben werden, siehe Beispiele unten.

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

Wenn wir beispielsweise SomeClassInheritedFromInjectableClient.inject ('$ scope') aufrufen, verwenden wir es in Direktive oder Controller als 'this. $ Scope'.

Zweiter Schritt

Deklarieren Sie die Basisklasse für die Direktive mit der statischen Methode "factory ()", die die Eigenschaft $ directed der Direktive-Klasse an die Factory-Funktion bindet. Und auch "compile ()" -Methode, die den Kontext der Link-Funktion an die Direktive selbst bindet. Es erlaubt, unsere injizierten Werte in der Link-Funktion als this.myInjectedService zu verwenden.

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

Dritter Schritt

Jetzt können wir so viele Anweisungsklassen wie möglich deklarieren. Mit Erbschaft. Und wir können Injektionen auf einfache Weise mit Spread-Arrays einrichten (vergessen Sie nicht die Call-Supermethode). Beispiele sehen:

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

Der letzte Schritt

Registrieren Sie jetzt Anweisungen mit Winkeln auf einfache Weise:

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

Testen Sie jetzt den 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));

Dies wird zurückkehren:

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

Ich hatte ein ähnliches Problem. In meinem Fall funktionierte es jedoch und schlug fehl, als ich in der Produktion eingesetzt wurde. Und es ist fehlgeschlagen, weil in der Produktion die neueste Version von 6to5 ..__ vorhanden ist. Dies könnte durch die Verwendung von npm shrinkwrap..__ verhindert werden. Gemäß der neuesten ES6-Spezifikation können Sie eine solche Klasse nicht verwenden. https://github.com/babel/babel/issues/700

0
Roberto_ua

Ich bin gerade auf dieses Problem gestoßen und habe dieses Thema gesehen. Ich habe ein paar Methoden aus der Diskussion ausprobiert und dieses Problem auf einfache Weise gelöst:

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

Ich benutze function direkt als .directive-Argument ('directName', factory), exportiere es und importiere es später in der Moduldeklaration. Ich habe jedoch die "default" -Anweisung beim Exportieren verpasst, daher habe ich einen Fehler erhalten. Nachdem ich "Standard" -Schlüsselwort hinzugefügt habe, funktioniert alles!

Ich finde diese Methode funktioniert auch in meinen Routenkonfigurationen (auch auf Funktionsweise).

=============... Ich hoffe, du kannst mein schlechtes Englisch verstehen :)

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

Ich hatte das gleiche Problem. Zum ersten Mal habe ich versucht, ein Problem über ES6-Klassen zu lösen, aber ich habe ein Problem mit $ inject meine Abhängigkeiten. Nachdem mir klar wurde, welche Winkel verschiedene Schreibstile haben, versuchte ich es. Überhaupt habe ich John Papa styles verwendet und diesen Works-Code in meiner Rails-App mit ES6 erhalten:

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

Ich weiß, dass ich mit Funktionen und Variablen etwas mehr ES6-Stil verbessern kann. Ich hoffe, es hilft. 

0
khusnetdinov