Подтвердить что ты не робот

Определить директиву AngularJS с помощью механизма TypeScript и $inject

Недавно я начал реорганизацию одного из проектов Angular, над которыми я работаю с TypeScript. Использование классов TypeScript для определения контроллеров очень удобно и хорошо работает с мини файлами JavaScript благодаря свойству static $inject Array<string>. И вы получаете довольно чистый код без разделения Angular зависимостей от определения класса:

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

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

Сейчас я ищу решение для обработки аналогичного случая для определения директивы. Я нашел хорошую практику для определения директив как функции:

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

В этом случае я вынужден определять зависимости Angular в определении директивы, который может быть очень подвержен ошибкам, если определение и класс TypeScript находятся в разных файлах. Каким образом можно определить директиву с помощью механизма TypeScript и $inject, я искал хороший способ реализовать интерфейс TypeScript IDirectiveFactory, но я не был удовлетворен найденными вами решениями.

4b9b3361

Ответ 1

Использование классов и наследование от ng.IDirective - это путь к 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());

Связанный ответ: fooobar.com/questions/144458/...

Ответ 2

Я предпочитаю указывать controller для директивы и только вводить зависимости там.

С контроллером и его интерфейсом на месте, я сильно набираю 4-й параметр функции ссылки на свой интерфейс контроллера и получаю удовольствие от использования этого оттуда.

Смещение проблемы зависимости от части ссылки на контроллер директивы позволяет мне использовать TypeScript для контроллера, в то время как я могу сохранить свою функцию определения директивы короткой и простой (в отличие от подхода класса директивы, который требует указания и реализации статического factory для директивы):

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

Ответ 3

В этом случае я вынужден определять зависимости angular в определении директивы, которые могут быть очень подвержены ошибкам, если класс определения и typescript находятся в разных файлах

Решение:

 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

Ответ 4

Эта статья в значительной степени охватывает ее, и ответ от tanguy_k довольно дословно представлен в данной статье. У этого также есть все мотивация ПОЧЕМУ вы хотели бы написать класс таким образом. Наследование, проверка типов и другие полезные вещи...

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

Ответ 5

Это немного поздно для этой вечеринки. Но вот решение, которое я предпочитаю использовать. Я лично считаю, что это чище.

Сначала определите вспомогательный класс, и вы можете использовать его в любом месте. (Фактически он может использовать что угодно, если вы немного измените вспомогательную функцию, вы можете использовать его для запуска config и т.д.)

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

Вот основной модуль

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

        };
    }
}

Ответ 6

Вот мое решение:

Директива

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

Ответ 7

Этот ответ был несколько основан на ответе @Mobiletainment. Я включаю только его, потому что я пытался сделать его более понятным и понятным для новичков.

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

Ответ 8

Другое решение - создать класс, указать статическое свойство $injection и определить, вызван ли класс с новым оператором. Если нет, вызовите новый оператор и создайте экземпляр класса директивы.

вот пример:

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

    }
  }

}

Ответ 9

Все варианты ответов дали мне представление о том, что 2 объекта (ng.IDirective и Controller) слишком много для описания компонента. Поэтому я создал простой прототип оболочки, который позволяет объединить их. Вот суть с прототипом https://gist.github.com/b1ff/4621c20e5ea705a0f788.