español - Definir la directiva AngularJS usando TypeScript y el mecanismo $ inject
diferencias entre angular y angularjs (9)
En este caso, me veo forzado a definir dependencias angulares en la definición de directiva, que puede ser muy propensa a errores si la definición y la clase de mecanografía están en archivos diferentes.
Solución:
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
Recientemente empecé a refactorizar uno de los proyectos angulares en los que estoy trabajando con TypeScript. El uso de clases de TypeScript para definir controladores es muy conveniente y funciona bien con archivos JavaScript minimizados gracias a static $inject Array<string>
propiedad static $inject Array<string>
. Y obtienes código bastante limpio sin dividir dependencias angulares de la definición de clase:
module app {
''use strict'';
export class AppCtrl {
static $inject: Array < string > = [''$scope''];
constructor(private $scope) {
...
}
}
angular.module(''myApp'', [])
.controller(''AppCtrl'', AppCtrl);
}
En este momento estoy buscando una solución para manejar casos similares para la definición de directiva. Encontré una buena práctica para definir las directivas como función:
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]);
}
En este caso, me veo forzado a definir dependencias angulares en la definición de directivas, que pueden ser muy propensas a errores si la definición y la clase de TypeScript están en archivos diferentes. ¿Cuál es la mejor manera de definir directivas con mecanografiado y el mecanismo $inject
, estaba buscando una buena manera de implementar la interfaz TypeScript IDirectiveFactory
, pero las soluciones que encontré no me satisficieron.
Aquí está mi solución:
Directiva:
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);
}
}
Módulo (directiva de registros ...):
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);
Decorador (agrega el objeto de directiva de inyección y producción):
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;
};
}
Estoy pensando en mover module.directive(...)
, module.service(...)
a los archivos de las clases, por ejemplo, StoryBoxDirective.ts
pero StoryBoxDirective.ts
no StoryBoxDirective.ts
decisión y el refactor;)
Puede verificar el ejemplo de trabajo completo aquí: https://github.com/b091/ts-skeleton
La directiva está aquí: https://github.com/b091/ts-skeleton/blob/master/src/module/story/StoryBoxDirective.ts
Es un poco tarde para esta fiesta. Pero aquí está la solución que prefiero usar. Personalmente creo que esto es más limpio.
Primero defina una clase de ayuda y podrá usarla en cualquier lugar (en realidad puede usar cualquier cosa si cambia la función de ayuda un poco. Puede usarla para la ejecución de configuración, etc.)
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;
}
}
}
Aquí está su módulo principal
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 => {
};
}
}
Esta respuesta se basó en cierta medida en la respuesta de @ Mobiletainment. Solo lo incluyo porque traté de hacerlo un poco más legible y comprensible para los principiantes.
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();
}
Este artículo prácticamente lo cubre y la respuesta de tanguy_k es prácticamente el ejemplo dado en el artículo. También tiene toda la motivación de POR QUÉ le gustaría escribir la clase de esta manera. Herencia, verificación de tipos y otras cosas buenas ...
http://blog.aaronholmes.net/writing-angularjs-directives-as-typescript-classes/
Otra solución es crear una clase, especificar la propiedad $ inject estática y detectar si la clase se está llamando con el nuevo operador. De lo contrario, llame al nuevo operador y cree una instancia de la clase directiva.
Aquí hay un ejemplo:
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) {
}
}
}
Prefiero especificar un controller
para la directiva e inyectar únicamente las dependencias allí .
Con el controlador y su interfaz en su lugar, escribo fuertemente el 4º parámetro de la función de enlace en la interfaz de mi controlador y disfruto utilizándolo desde allí.
Cambiar la preocupación de dependencia de la parte de enlace al controlador de la directiva me permite beneficiarme de TypeScript para el controlador mientras puedo mantener mi función de definición de directiva corta y simple (a diferencia del enfoque de clase directiva que requiere especificar e implementar un método de fábrica estático para la directiva )
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);
}
Todas las opciones en las respuestas me dieron una idea de que 2 entidades (ng.IDirective y Controller) son demasiado para describir un componente. Así que creé un prototipo de envoltura simple que permite fusionarlos. Aquí hay una esencia con el prototipo https://gist.github.com/b1ff/4621c20e5ea705a0f788 .
Usar clases y heredar de ng.IDirective es el camino a seguir con 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());
Respuesta relacionada: https://.com/a/29223360/990356