Define una directiva de AngularJS usando TypeScript y el mecanismo $inject
Recientemente comencé a refactorizar uno de los proyectos de Angular en los que estoy trabajando con TypeScript. Usar clases de TypeScript para definir controladores es muy conveniente y funciona bien con archivos JavaScript minificados gracias a la propiedad static $inject Array<string>. Y obtienes un código bastante limpio sin separar las dependencias de Angular de la definición de la 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 un caso similar para la definición de directivas. 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 obligado a definir las dependencias de Angular en la definición de la directiva, lo cual puede ser muy propenso a errores si la definición y la clase de TypeScript están en archivos diferentes. ¿Cuál es la mejor manera de definir una directiva con TypeScript y el mecanismo $inject? Estaba buscando una buena manera de implementar la interfaz IDirectiveFactory de TypeScript, pero no quedé satisfecho con las soluciones que encontré.
Respuesta #1
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://stackoverflow.com/a/29223360/990356
Respuesta #2
Prefiero especificar un controller para la directiva y únicamente inyectar las dependencias allí.
Con el controlador y su interfaz en su lugar, escribo fuertemente el cuarto parámetro de la función link a la interfaz de mi controlador y disfruto utilizándolo desde allí.
Desplazar la preocupación de dependencia de la parte del 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 de 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);
}
