Определить директиву AngularJS с помощью механизма TypeScript и $inject
Недавно я начал реорганизацию одного из проектов Angular, над которыми я работаю с TypeScript. Использование классов TypeScript для определения контроллеров очень удобно и хорошо работает с мини файлами JavaScript благодаря свойству static $inject Array<string>
. И вы получаете довольно чистый код без разделения 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
, но я не был удовлетворен найденными вами решениями.
Ответы
Ответ 1
Использование классов и наследование от 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());
Связанный ответ: fooobar.com/questions/144458/...
Ответ 2
Я предпочитаю указывать controller
для директивы и только вводить зависимости там.
С контроллером и его интерфейсом на месте, я сильно набираю 4-й параметр функции ссылки на свой интерфейс контроллера и получаю удовольствие от использования этого оттуда.
Смещение проблемы зависимости от части ссылки на контроллер директивы позволяет мне использовать TypeScript для контроллера, в то время как я могу сохранить свою функцию определения директивы короткой и простой (в отличие от подхода класса директивы, который требует указания и реализации статического factory для директивы):
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);
}
Ответ 3
В этом случае я вынужден определять зависимости angular в определении директивы, которые могут быть очень подвержены ошибкам, если класс определения и 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
Ответ 4
Эта статья в значительной степени охватывает ее, и ответ от tanguy_k довольно дословно представлен в данной статье. У этого также есть все мотивация ПОЧЕМУ вы хотели бы написать класс таким образом. Наследование, проверка типов и другие полезные вещи...
http://blog.aaronholmes.net/writing-angularjs-directives-as-typescript-classes/
Ответ 5
Это немного поздно для этой вечеринки. Но вот решение, которое я предпочитаю использовать. Я лично считаю, что это чище.
Сначала определите вспомогательный класс, и вы можете использовать его в любом месте. (Фактически он может использовать что угодно, если вы немного измените вспомогательную функцию, вы можете использовать его для запуска config и т.д.)
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 => {
};
}
}
Ответ 6
Вот мое решение:
Директива
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 (добавляет инъекционный объект и создает директивный объект):
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
Ответ 7
Этот ответ был несколько основан на ответе @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();
}
Ответ 8
Другое решение - создать класс, указать статическое свойство $injection и определить, вызван ли класс с новым оператором. Если нет, вызовите новый оператор и создайте экземпляр класса директивы.
вот пример:
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) {
}
}
}
Ответ 9
Все варианты ответов дали мне представление о том, что 2 объекта (ng.IDirective и Controller) слишком много для описания компонента. Поэтому я создал простой прототип оболочки, который позволяет объединить их. Вот суть с прототипом https://gist.github.com/b1ff/4621c20e5ea705a0f788.