最近我开始重构我正在使用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
接口的好方法,但我对我找到的解决方案并不满意。
答案 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());
答案 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的要点。