web-dev-qa-db-fra.com

Définir une directive AngularJS à l'aide de TypeScript et du mécanisme $ inject

Récemment, j'ai commencé à refactoriser l'un des Angular sur lesquels je travaille avec TypeScript. Utiliser des classes TypeScript pour définir des contrôleurs est très pratique et fonctionne bien avec les fichiers JavaScript minifiés grâce à static $inject Array<string> propriété. Et vous obtenez un code assez propre sans fractionner les dépendances Angular $ angulaires) à partir de la définition de la classe:

 module app {
  'use strict';
  export class AppCtrl {
    static $inject: Array < string > = ['$scope'];
    constructor(private $scope) {
      ...
    }
  }

  angular.module('myApp', [])
    .controller('AppCtrl', AppCtrl);
}

En ce moment, je cherche une solution pour traiter un cas similaire pour la définition de la directive. J'ai trouvé une bonne pratique pour définir les directives comme fonction:

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]);
}

Dans ce cas, je suis obligé de définir des dépendances Angular dans la définition de directive, ce qui peut être très sujet aux erreurs si la définition et la classe TypeScript se trouvent dans des fichiers différents. Quel est le meilleur moyen de définir une directive avec TypeScript et le $inject mécanisme, je cherchais un bon moyen de mettre en œuvre l'interface TypeScript IDirectiveFactory mais je n'étais pas satisfait des solutions que j'ai trouvées.

44
Milko Lorinkov

Utiliser des classes et hériter de ng.IDirective est la voie à suivre avec 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());

Réponse associée: https://stackoverflow.com/a/29223360/990356

108
tanguy_k

Je préfère spécifier un controller pour la directive et uniquement y injecter les dépendances.

Avec le contrôleur et son interface en place, I taper avec force le 4ème paramètre de la fonction de liaison sur l'interface de mon contrôleur et profiter de son utilisation à partir de là.

Le fait de déplacer le problème de dépendance de la partie lien vers le contrôleur de la directive me permet de tirer parti de TypeScript pour le contrôleur tout en conservant une fonction de définition de directive courte et simple (contrairement à l'approche de classe directive qui impose de spécifier et d'implémenter une méthode d'usine statique pour la directive. ):

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);
}
33
Mobiletainment

Dans ce cas, je suis obligé de définir des dépendances angular dans la définition de la directive, ce qui peut être très sujet aux erreurs si la définition et la classe TypeScript se trouvent dans des fichiers différents

Solution:

 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
9
basarat

C'est un peu tard pour cette fête. Mais voici la solution que je préfère utiliser. Personnellement, je pense que c'est plus propre.

Définissez d'abord une classe d'assistance, et vous pourrez l'utiliser n'importe où (elle peut en réalité être utilisée à n'importe quel endroit si vous modifiez un peu la fonction d'assistance. Vous pouvez l'utiliser pour la configuration, 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;
        }
    }
}

Voici votre module 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 => {

        };
    }
}
4
maxisam

Voici ma solution:

Directif:

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);
  }

}

Module (directive registres ...):

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);

Décorateur (ajoute les objets injecter et produire directive):

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;
  };
}

Je pense à déplacer module.directive(...), module.service(...) vers des fichiers de classes, par exemple. StoryBoxDirective.ts Mais n'a pas encore pris de décision ni refactorisé;)

Vous pouvez consulter un exemple complet de travail ici: https://github.com/b091/ts-skeleton

La directive est ici: https://github.com/b091/ts-skeleton/blob/master/src/module/story/StoryBoxDirective.ts

3
b091

Cet article le couvre assez bien et la réponse de tanguy_k est à peu près identique à l'exemple donné dans l'article. Il a également toute la motivation de POURQUOI vous voudriez écrire la classe de cette façon. Héritage, dactylographie et autres bonnes choses ...

http://blog.aaronholmes.net/writing-angularjs-directives-as-TypeScript-classes/

3
Louis

Cette réponse était quelque peu basée sur la réponse de @ Mobiletainment. Je ne l'inclue que parce que j'ai essayé de le rendre un peu plus lisible et compréhensible pour les débutants.

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();
}
2
bbuie

Une autre solution consiste à créer une classe, à spécifier la propriété static $ inject et à détecter si la classe est appelée avec l'opérateur new. Sinon, appelez new operator et créez une instance de la classe de directive.

voici un exemple:

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) {

    }
  }

}
1
Szymon Wygnański

Toutes les options dans les réponses m'ont donné l’idée que 2 entités (ng.IDirective et Controller) sont trop complexes pour décrire un composant. J'ai donc créé un prototype de wrapper simple qui permet de les fusionner. Voici un résumé avec le prototype https://Gist.github.com/b1ff/4621c20e5ea705a0f788 .

0
Evgeniy