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

时间:2014-11-13 22:59:11

标签: angularjs typescript directive inject

最近我开始重构我正在使用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);
}

现在我正在寻找解决方案来处理指令定义的类似情况。我找到了一个很好的做法,将指令定义为函数:

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类在不同的文件中,则可能非常容易出错。使用typescript和$inject机制定义指令的最佳方法是什么,我正在寻找一种实现TypeScript IDirectiveFactory接口的好方法,但我对我找到的解决方案并不满意。

9 个答案:

答案 0 :(得分:108)

使用类和继承自ng.IDirective是使用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

答案 1 :(得分:33)

我更喜欢为指令指定 controller ,并且在那里注入依赖

控制器及其接口到位后,我强烈地键入链接功能的第4个参数到我控制器的界面,并享受从那里开始使用它。

将依赖关系从链接部分转移到指令的控制器允许我从控制器的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);
}

答案 2 :(得分:9)

  

在这种情况下,我被迫在指令定义中定义角度依赖关系,如果定义和typescript类在不同的文件中,这可能非常容易出错

解决方案:

 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

答案 3 :(得分:4)

这次聚会有点晚了。但这是我更喜欢使用的解决方案。我个人认为这更清洁。

首先定义一个帮助器类,你可以在任何地方使用它。(如果你稍微更改了辅助函数,它实际上可以用在任何东西上。你可以用它来进行配置运行等。)

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

        };
    }
}

答案 4 :(得分:3)

这篇文章几乎涵盖了它,来自tanguy_k的答案几乎是文章中给出的例子。它也有你想要以这种方式写课的所有动机。继承,类型检查和其他好东西......

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

答案 5 :(得分:3)

这是我的解决方案:

指令:

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

Decorator(添加inject和产生指令对象):

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.service(...)移动到类文件,例如StoryBoxDirective.ts但尚未作出决定和重构;)

您可以在此处查看完整的工作示例:https://github.com/b091/ts-skeleton

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

答案 6 :(得分:2)

这个答案有点基于@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();
}

答案 7 :(得分:1)

另一种解决方案是创建一个类,指定static $ inject属性并检测是否使用new运算符调用该类。如果没有,请调用new运算符并创建指令类的实例。

这是一个例子:

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

    }
  }

}

答案 8 :(得分:0)

答案中的所有选项让我知道2个实体(ng.IDirective和Controller)太多,无法描述组件。所以我创建了一个简单的包装器原型,允许合并它们。以下是原型https://gist.github.com/b1ff/4621c20e5ea705a0f788的要点。