Definiera AngularJS-direktiv med TypeScript och $inject-mekanism


Nyligen började jag refaktorera ett av de Angular-projekt jag arbetar med i TypeScript. Att använda TypeScript-klasser för att definiera controllers är väldigt bekvämt och fungerar bra med minifierade JavaScript-filer tack vare static $inject Array<string>-egenskapen. Och du får ganska ren kod utan att behöva dela upp Angular-beroenden från klassdefinitionen:

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

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

Just nu söker jag efter en lösning för att hantera ett liknande fall för direktivdefinitionen. Jag har hittat en god praxis att definiera direktiven som funktion:

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

I det här fallet är jag tvungen att definiera Angular-beroenden i direktivdefinitionen, vilket kan vara mycket felbenäget om definitionen och TypeScript-klassen finns i olika filer. Vad är det bästa sättet att definiera ett direktiv med TypeScript och $inject-mekanismen? Jag letade efter ett bra sätt att implementera TypeScript IDirectiveFactory-gränssnittet men jag var inte nöjd med de lösningar jag hittade.

Svar #1

Att använda klasser och ärva från ng.IDirective är vägen att gå med 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());

Relaterat svar: https://stackoverflow.com/a/29223360/990356

Svar #2

Jag föredrar att specificera en controller för direktivet och enbart injicera beroenden där.

Med kontrollern och dess gränssnitt på plats, typsätter jag starkt den fjärde parametern i länkfunktionen till mitt kontrollerns gränssnitt och njuter av att använda den därifrån.

Att flytta beroendefrågan från länkdelen till direktivets kontrollant gör att jag kan dra nytta av TypeScript för kontrollanten samtidigt som jag kan hålla min direktivdefinitionsfunktion kort och enkel (till skillnad från direktivklassmetoden som kräver att man specificerar och implementerar en statisk fabriksmetod för direktivet):

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

Relaterade artiklar