使用TypeScript和$ inject机制定义AngularJS指令

最近我开始重新构build我正在使用TypeScript的Angular项目之一。 使用TypeScript类来定义控制器非常方便,并且由于static $inject Array<string>属性,所以可以很好地处理缩小的JavaScript文件。 而且你可以得到相当干净的代码,不需要从类定义中分解angular依赖关系:

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

现在我正在寻找解决scheme来处理指令定义的类似情况。 我发现一个很好的做法来定义指令作为函数:

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

在这种情况下,我不得不在定义定义中定义Angular依赖关系,如果定义和TypeScript类在不同的文件中,这可能非常容易出错。 用typecript和$inject机制来定义指令的最好方法是什么,我正在寻找一种实现TypeScript IDirectiveFactory接口的好方法,但是我并不满意我find的解决scheme。

使用类并从ng.IDirectiveinheritance是使用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()); 

相关的答案: https : //stackoverflow.com/a/29223360/990356

我更喜欢为该指令指定一个controller ,并仅在其中注入依赖关系

在控制器及其接口的情况下,我强烈地将链接function的第四个参数input到控制器的接口,并从那里使用它。

将依赖关系从链接部分转移到指令的控制器允许我从控制器的TypeScript中受益,同时我可以保持我的指令定义函数简洁而简单(不像指令类的方法需要为指令指定和实现静态工厂方法):

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

在这种情况下,我不得不在定义定义中定义angular度依赖关系,如果定义和typecript类在不同的文件中,这可能非常容易出错

解:

  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 

这篇文章几乎涵盖了它,tanguy_k的答案几乎是逐字的文中给出的例子。 它也有所有的动机为什么你想这样写class。 inheritance,types检查和其他好东西…

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

这个派对有点晚了。 但是,这是我喜欢使用的解决scheme。 我个人认为这是更清洁的。

首先定义一个helper类,然后你可以在任何地方使用它(如果你稍微改变一下helper函数,它实际上可以用在任何东西上,你可以用它来configuration运行等)

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

这是你的主要模块

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

这是我的解决scheme:

指示:

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

模块(寄存器指令…):

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

装饰者(添加注入和产生指令对象):

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

我考虑将module.directive(...)module.directive(...)移动到类文件,例如StoryBoxDirective.ts但是没有做出决定和重构;)

你可以在这里查看完整的工作示例: https : //github.com/b091/ts-skeleton

指令在这里: https : //github.com/b091/ts-skeleton/blob/master/src/module/story/StoryBoxDirective.ts

这个答案有点基于@Mobiletainment的答案。 我只是把它包括进去,因为我试图让它对初学者更具可读性和可理解性。

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

另一个解决scheme是创build一个类,指定静态的$ inject属性,并检测是否正在使用new运算符调用该类。 如果不是,则调用new运算符并创build指令类的实例。

这里是一个例子:

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

答案中的所有选项给了我一个想法,即2个实体(ng.IDirective和Controller)对于描述组件来说太多了。 所以我创build了一个简单的包装原型,允许合并它们。 这是原型https://gist.github.com/b1ff/4621c20e5ea705a0f788的要点。;