Define AngularJS directive using TypeScript and $inject mechanism

后端 未结 9 1696
死守一世寂寞
死守一世寂寞 2020-12-12 15:04

Recently I started refactoring one of the Angular projects I am working on with TypeScript. Using TypeScript classes to define controllers is very convenient and works well

相关标签:
9条回答
  • 2020-12-12 15:33

    In this case I am forced to define angular dependencies in the directive definition, which can be very error-prone if the definition and typescript class are in different files

    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
    
    0 讨论(0)
  • Another solution is to create a class, specify static $inject property and detect if the class is being called with the new operator. If not, call new operator and create an instance of the directive class.

    here is an example:

    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) {
    
        }
      }
    
    }
    
    0 讨论(0)
  • 2020-12-12 15:35

    It's a bit late to this party. But here is the solution I prefer to use. I personally think this is cleaner.

    Define a helper class first, and you can use it anywhere.(It actually can use on anything if you change the helper function a bit. You can use it for config run 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;
            }
        }
    }
    

    Here is you main module

    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 => {
    
            };
        }
    }
    
    0 讨论(0)
  • 2020-12-12 15:35

    This answer was somewhat based off @Mobiletainment's answer. I only include it because I tried to make it a little more readable and understandable for beginners.

    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();
    }
    
    0 讨论(0)
  • 2020-12-12 15:45

    Using classes and inherit from ng.IDirective is the way to go with 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());
    

    Related answer: https://stackoverflow.com/a/29223360/990356

    0 讨论(0)
  • 2020-12-12 15:45

    Here is my solution:

    Directive:

    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 (registers directive...):

    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 (adds inject and produce directive object):

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

    I thinking about moving module.directive(...), module.service(...) to classes files e.g. StoryBoxDirective.ts but didn't make decision and refactor yet ;)

    You can check full working example here: https://github.com/b091/ts-skeleton

    Directive is here: https://github.com/b091/ts-skeleton/blob/master/src/module/story/StoryBoxDirective.ts

    0 讨论(0)
提交回复
热议问题