web-dev-qa-db-de.com

Definieren Sie die AngularJS-Direktive mit TypeScript und dem $ inject-Mechanismus

Kürzlich habe ich begonnen, eines der Angular Projekte, an denen ich mit TypeScript arbeite, umzugestalten. Die Verwendung von TypeScript-Klassen zum Definieren von Controllern ist sehr praktisch und funktioniert dank der Eigenschaft static $inject Array<string> Gut mit verkleinerten JavaScript-Dateien Und Sie erhalten ziemlich sauberen Code, ohne die Angular Abhängigkeiten von der Klassendefinition zu teilen:

 module app {
  'use strict';
  export class AppCtrl {
    static $inject: Array < string > = ['$scope'];
    constructor(private $scope) {
      ...
    }
  }

  angular.module('myApp', [])
    .controller('AppCtrl', AppCtrl);
}

Im Moment suche ich nach einer Lösung, um einen ähnlichen Fall für die Richtliniendefinition zu behandeln. Ich habe eine gute Praxis gefunden, um die Direktiven als Funktion zu definieren:

module directives {

  export function myDirective(toaster): ng.IDirective {
    return {
      restrict: 'A',
      require: ['ngModel'],
      templateUrl: 'myDirective.html',
      replace: true,
      link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ctrls) => 
        //use of $location service
        ...
      }
    };
  }


  angular.module('directives', [])
    .directive('myDirective', ['toaster', myDirective]);
}

In diesem Fall bin ich gezwungen, Angular) Abhängigkeiten in der Direktivendefinition zu definieren, was sehr fehleranfällig sein kann, wenn sich die Definition und die TypeScript-Klasse in verschiedenen Dateien befinden. Was ist der beste Weg, um Direktiven zu definieren Mit TypeScript und dem $inject - Mechanismus suchte ich nach einer guten Möglichkeit, die TypeScript IDirectiveFactory - Schnittstelle zu implementieren, war aber mit den gefundenen Lösungen nicht zufrieden.

44
Milko Lorinkov

Das Verwenden von Klassen und das Erben von ng.IDirective ist die Vorgehensweise bei TypeScript:

class MyDirective implements ng.IDirective {
    restrict = 'A';
    require = 'ngModel';
    templateUrl = 'myDirective.html';
    replace = true;

    constructor(private $location: ng.ILocationService, private toaster: ToasterService) {
    }

    link = (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ctrl: any) => {
        console.log(this.$location);
        console.log(this.toaster);
    }

    static factory(): ng.IDirectiveFactory {
        const directive = ($location: ng.ILocationService, toaster: ToasterService) => new MyDirective($location, toaster);
        directive.$inject = ['$location', 'toaster'];
        return directive;
    }
}

app.directive('mydirective', MyDirective.factory());

Zugehörige Antwort: https://stackoverflow.com/a/29223360/990356

108
tanguy_k

Ich ziehe es vor, ein controller für die Direktive anzugeben und nur die Abhängigkeiten dort einzufügen.

Wenn der Controller und seine Schnittstelle vorhanden sind, tippe ich stark den 4. Parameter der Verknüpfungsfunktion ein auf die Schnittstelle meines Controllers und genieße es, sie von dort aus zu verwenden.

Wenn ich die Abhängigkeitsbedenken vom Link-Teil auf den Controller der Direktive verlagere, kann ich von TypeScript für den Controller profitieren, während ich meine Direktivendefinitionsfunktion kurz und einfach halten kann (im Gegensatz zum Direktivenklassenansatz, bei dem eine statische Factory-Methode für die Direktive angegeben und implementiert werden muss) ):

module app {
"use strict";

interface IMyDirectiveController {
    // specify exposed controller methods and properties here
    getUrl(): string;
}

class MyDirectiveController implements IMyDirectiveController {

    static $inject = ['$location', 'toaster'];
    constructor(private $location: ng.ILocationService, private toaster: ToasterService) {
        // $location and toaster are now properties of the controller
    }

    getUrl(): string {
        return this.$location.url(); // utilize $location to retrieve the URL
    }
}

function myDirective(): ng.IDirective {
    return {
        restrict: 'A',
        require: 'ngModel',
        templateUrl: 'myDirective.html',
        replace: true,

        controller: MyDirectiveController,
        controllerAs: 'vm',

        link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes, controller: IMyDirectiveController): void => {
            let url = controller.getUrl();
            element.text('Current URL: ' + url);
        }
    };
}

angular.module('myApp').
    directive('myDirective', myDirective);
}
33
Mobiletainment

In diesem Fall bin ich gezwungen, angular - Abhängigkeiten in der Richtliniendefinition zu definieren. Dies kann sehr fehleranfällig sein, wenn sich die Definition und die TypeScript-Klasse in verschiedenen Dateien befinden

Lösung:

 export function myDirective(toaster): ng.IDirective {
    return {
      restrict: 'A',
      require: ['ngModel'],
      templateUrl: 'myDirective.html',
      replace: true,
      link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ctrls) => 
        //use of $location service
        ...
      }
    };
  }
  myDirective.$inject = ['toaster']; // THIS LINE
9
basarat

Es ist ein bisschen spät für diese Party. Aber hier ist die Lösung, die ich am liebsten benutze. Ich persönlich halte das für sauberer.

Definieren Sie zuerst eine Hilfsklasse, und Sie können sie überall verwenden. (Sie kann tatsächlich für alles verwendet werden, wenn Sie die Hilfsfunktion ein wenig ändern. Sie können sie für den Konfigurationslauf usw. verwenden.)

module Helper{
    "use strict";

    export class DirectiveFactory {
        static GetFactoryFor<T extends ng.IDirective>(classType: Function): ng.IDirectiveFactory {
            var factory = (...args): T => {
                var directive = <any> classType;
                //return new directive(...args); //TypeScript 1.6
                return new (directive.bind(directive, ...args));
            }
            factory.$inject = classType.$inject;
            return factory;
        }
    }
}

Hier ist Ihr Hauptmodul

module MainAppModule {
    "use strict";

angular.module("App", ["Dependency"])
       .directive(MyDirective.Name, Helper.DirectiveFactory.GetFactoryFor<MyDirective>(MyDirective));

    //I would put the following part in its own file.
    interface IDirectiveScope extends ng.IScope {
    }

    export class MyDirective implements ng.IDirective {

        public restrict = "A";
        public controllerAs = "vm";
        public bindToController = true;    
        public scope = {
            isoVal: "="
        };

        static Name = "myDirective";
        static $inject = ["dependency"];

        constructor(private dependency:any) { }

        controller = () => {
        };

        link = (scope: IDirectiveScope, iElem: ng.IAugmentedJQuery, iAttrs: ng.IAttributes): void => {

        };
    }
}
4
maxisam

Hier ist meine Lösung:

Richtlinie:

import {directive} from '../../decorators/directive';

@directive('$location', '$rootScope')
export class StoryBoxDirective implements ng.IDirective {

  public templateUrl:string = 'src/module/story/view/story-box.html';
  public restrict:string = 'EA';
  public scope:Object = {
    story: '='
  };

  public link:Function = (scope:ng.IScope, element:ng.IAugmentedJQuery, attrs:ng.IAttributes):void => {
    // console.info(scope, element, attrs, this.$location);
    scope.$watch('test', () => {
      return null;
    });
  };

  constructor(private $location:ng.ILocationService, private $rootScope:ng.IScope) {
    // console.log('Dependency injection', $location, $rootScope);
  }

}

Modul (Registerrichtlinie ...):

import {App} from '../../App';
import {StoryBoxDirective} from './../story/StoryBoxDirective';
import {StoryService} from './../story/StoryService';

const module:ng.IModule = App.module('app.story', []);

module.service('storyService', StoryService);
module.directive('storyBox', <any>StoryBoxDirective);

Decorator (fügt das Direktivenobjekt inject und create hinzu):

export function directive(...values:string[]):any {
  return (target:Function) => {
    const directive:Function = (...args:any[]):Object => {
      return ((classConstructor:Function, args:any[], ctor:any):Object => {
        ctor.prototype = classConstructor.prototype;
        const child:Object = new ctor;
        const result:Object = classConstructor.apply(child, args);
        return typeof result === 'object' ? result : child;
      })(target, args, () => {
        return null;
      });
    };
    directive.$inject = values;
    return directive;
  };
}

Ich denke darüber nach, module.directive(...), module.service(...) in Klassendateien zu verschieben, z. StoryBoxDirective.ts Hat aber noch keine Entscheidung getroffen und überarbeitet;)

Sie können das vollständige Funktionsbeispiel hier überprüfen: https://github.com/b091/ts-skeleton

Die Richtlinie ist hier: https://github.com/b091/ts-skeleton/blob/master/src/module/story/StoryBoxDirective.ts

3
b091

Dieser Artikel behandelt es so ziemlich und die Antwort von tanguy_k ist so ziemlich wörtlich wie das Beispiel im Artikel. Es hat auch die ganze Motivation, WARUM Sie die Klasse auf diese Weise schreiben möchten. Vererbung, Typprüfung und andere gute Dinge ...

http://blog.aaronholmes.net/writing-angularjs-directives-as-TypeScript-classes/

3
Louis

Diese Antwort basierte in gewisser Weise auf der Antwort von @ Mobiletainment. Ich schließe es nur ein, weil ich versucht habe, es für Anfänger ein wenig lesbarer und verständlicher zu machen.

module someModule { 

    function setup() { 
        //usage: <some-directive></some-directive>
        angular.module('someApp').directive("someDirective", someDirective); 
    };
    function someDirective(): ng.IDirective{

        var someDirective = {
            restrict: 'E',
            templateUrl: '/somehtml.html',
            controller: SomeDirectiveController,
            controllerAs: 'vm',
            scope: {},
            link: SomeDirectiveLink,
        };

        return someDirective;
    };
    class SomeDirectiveController{

        static $inject = ['$scope'];

        constructor($scope) {

            var dbugThis = true;
            if(dbugThis){console.log("%ccalled SomeDirectiveController()","color:orange");}
        };
    };
    class SomeDirectiveLink{
        constructor(scope: ng.IScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes, controller){
            var dbugThis = true;
            if(dbugThis){console.log("%ccalled SomeDirectiveLink()","color:orange");}
        }
    };
    setup();
}
2
bbuie

Eine andere Lösung besteht darin, eine Klasse zu erstellen, die statische Eigenschaft $ inject anzugeben und zu ermitteln, ob die Klasse mit dem neuen Operator aufgerufen wird. Wenn nicht, rufen Sie den Operator new auf und erstellen Sie eine Instanz der Anweisungsklasse.

hier ist ein Beispiel:

module my {

  export class myDirective {
    public restrict = 'A';
    public require = ['ngModel'];
    public templateUrl = 'myDirective.html';
    public replace = true;
    public static $inject = ['toaster'];
    constructor(toaster) {
      //detect if new operator was used:
      if (!(this instanceof myDirective)) {
        //create new instance of myDirective class:
        return new (myDirective.bind.apply(myDirective, Array.prototype.concat.apply([null], arguments)));
      }
    }
    public link(scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ctrls:any) {

    }
  }

}
1

Alle Optionen in den Antworten gaben mir die Idee, dass zwei Entitäten (ng.IDirective und Controller) zu viel sind, um eine Komponente zu beschreiben. Deshalb habe ich einen einfachen Wrapper-Prototyp erstellt, mit dem sie zusammengeführt werden können. Hier ist eine Zusammenfassung mit dem Prototyp https://Gist.github.com/b1ff/4621c20e5ea705a0f788 .

0
Evgeniy