Реализация директив angularjs как классов в Typescript
Итак, взглянув на некоторые из примеров директив angularjs в typescript, большинство людей соглашаются использовать функции вместо классов при их реализации.
Я бы предпочел использовать их как класс и попытался реализовать их следующим образом:
module directives
{
export class search implements ng.IDirective
{
public restrict: string;
public templateUrl: string;
constructor()
{
this.restrict = 'AE';
this.templateUrl = 'directives/search.html';
}
public link($scope: ng.IScope, element: JQuery, attributes: ng.IAttributes)
{
element.text("Hello world");
}
}
}
Теперь это работает отлично. Тем не менее, мне нужно иметь изолированную область видимости с некоторыми атрибутами, и я изо всех сил пытаюсь выяснить, как включить это в сам класс.
логика диктует, что, поскольку я могу иметь
public restrict: string;
public templateUrl: string;
Я должен иметь что-то вроде:
public scope;
Но я не уверен, что это правильно или как продолжить оттуда (например, как добавить атрибуты в область).
Кто-нибудь знает, как это решить? (надеюсь, без необходимости возвращаться к функции, если это возможно)
Спасибо,
Ответы
Ответ 1
Предполагая, что то, что у вас есть, работает без выделенной области, следующее должно работать с изолированной областью:
module directives
{
export class search implements ng.IDirective
{
public restrict = 'AE';
public templateUrl = 'directives/search.html';
public scope = {
foo:'=',
bar:'@',
bas:'&'
};
public link($scope: ng.IScope, element: JQuery, attributes: ng.IAttributes)
{
element.text("Hello world");
}
}
}
Ответ 2
Создание директив в качестве классов может быть проблематичным, так как вам по-прежнему необходимо задействовать функцию factory для ее конкретизации. Например:
export class SomeDirective implements ng.IDirective {
public link = () => {
}
constructor() {}
}
Что не работает
myModule.directive('someDirective', SomeDirective);
Так как директивы не вызываются с помощью 'new', а называются только factory функциями. Это вызовет проблемы с тем, что фактически возвращает функция конструктора.
Что делает (с оговорками)
myModule.directive(() => new SomeDirective());
Это работает отлично, если вы не используете какой-либо IoC, но как только вы начинаете вводить инъекции, вам необходимо поддерживать повторяющиеся списки параметров для вашей функции factory и вашего контрструктора.
export class SomeDirective implements ng.IDirective {
...
constructor(someService: any) {}
}
myModule.directive('someDirective', ['someService', (someService) => new SomeDirective(someService)]);
Еще один вариант, если это то, что вы предпочитаете, но важно понять, как фактически используется регистрация директив.
Альтернативный подход
То, что на самом деле ожидается angular, является директивной функцией factory, поэтому что-то вроде:
export var SomeDirectiveFactory = (someService: any): ng.IDirective => {
return {
link: () => {...}
};
}
SomeDirectiveFactory.$inject = ['someService']; //including $inject annotations
myModule.directive('someDirective', SomeDirectiveFactory);
Это дает возможность использовать $inject аннотации, так как angular нуждается в том, чтобы она была в функции factory в этом случае.
Вы всегда можете вернуть экземпляр своего класса из функции factory:
export var SomeDirectiveFactory = (someService: any): ng.IDirective => {
return new SomeDirective(someService);
}
SomeDirectiveFactory.$inject = ['someService']; //including $inject annotations
Но на самом деле зависит от вашего варианта использования, сколько дублирования списков параметров вы в порядке и т.д.
Ответ 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 (добавляет инъекционный объект и создает директивный объект):
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
Ответ 4
Здесь, наконец, я получил директиву как класс плюс наследование. В производной директиве я расширяю область действия и определяю templateUrl.
Вы можете переопределить любые методы из базовой директивы
,
Клавиша должна была возвращать из конструктора экземпляр директивы. Конструктор вызовов .ngularjs без ключевого слова new. В этом случае этот имеет тип window
Я проверил несколько строк, чтобы проверить тип экземпляра this, и в случае окна я создаю новый экземпляр директивы. (См. Класс Activator из примера ниже)
module Realty.directives {
export class BaseElementWithLabel implements ng.IDirective {
public restrict = 'E';
public replace = true;
public scope = {
label: '@',
model: '=',
icon: '@',
readonlyElement: '=',
remark: '@'
}
constructor(extendedScope) {
if (!(this instanceof Window)) {
extendedScope = extendedScope || {};
this.scope = angular.extend(this.scope, extendedScope);
}
}
link(scope: ng.IScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes, controller, transclude) {
scope['vm'] = {};
}
}
}
module Realty.directives {
export class textareaWithLabel extends Realty.directives.BaseElementWithLabel implements ng.IDirective {
templateUrl = 'directives/element-form/textarea-with-label-template.html';
constructor() {
super({
rows: '@'
});
return Realty.Activator.Activate(this, textareaWithLabel, arguments);
}
};
};
module Realty {
export class Activator {
public static Activate(instance: any, type, arguments) {
if (instance instanceof type) {
return instance;
}
return new(type.bind.apply(type, Array.prototype.concat.apply([null], arguments)));
}
}
}