web-dev-qa-db-ja.com

TypeScriptと$ injectメカニズムを使用してAngularJSディレクティブを定義する

最近、TypeScriptで作業しているAngularプロジェクトの1つをリファクタリングし始めました。TypeScriptクラスを使用してコントローラを定義するのは非常に便利で、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インターフェイスを実装する良い方法を探していましたが、見つけた解決策に満足していませんでした。

44
Milko Lorinkov

クラスを使用し、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());

関連する回答: https://stackoverflow.com/a/29223360/990356

108
tanguy_k

ディレクティブにcontrollerを指定し、単にそこに依存関係を挿入を指定することを好みます。

コントローラーとそのインターフェースが適切に配置された状態で、私はコントローラーのインターフェースにリンク関数の4番目のパラメーターを強く入力し、そこからそれを活用することを楽しんでいます。

依存関係の懸念をリンク部分からディレクティブのコントローラーにシフトすると、ディレクティブ定義関数を短くシンプルに保ちながら、コントローラーのTypeScriptを活用できます(ディレクティブの静的ファクトリーメソッドを指定して実装する必要があるディレクティブクラスアプローチとは異なります) ):

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

この場合、ディレクティブ定義で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
9
basarat

このパーティーには少し遅れています。しかし、ここに私が使用したい解決策があります。私は個人的にこれはきれいだと思います。

最初にヘルパークラスを定義し、どこでも使用できます(ヘルパー関数を少し変更すると、実際に何でも使用できます。configrunなどに使用できます)。

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

        };
    }
}
4
maxisam

私の解決策は次のとおりです。

指令:

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

デコレータ(インジェクトを追加し、ディレクティブオブジェクトを生成):

_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

3
b091

この記事はほとんどそれをカバーしており、tanguy_kからの答えは記事で与えられた例とほぼ同じです。また、このようにクラスを記述したい理由がすべてあります。継承、型チェック、その他の良いこと...

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

3
Louis

この回答は、@ 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();
}
2
bbuie

別の解決策は、クラスを作成し、静的な$ injectプロパティを指定し、クラスがnew演算子で呼び出されているかどうかを検出することです。そうでない場合は、new演算子を呼び出して、ディレクティブクラスのインスタンスを作成します。

以下に例を示します。

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

回答のすべてのオプションは、2つのエンティティ(ng.IDirectiveとController)がコンポーネントを説明するには多すぎるという考えを与えてくれました。そこで、それらをマージできる単純なラッパープロトタイプを作成しました。ここにプロトタイプを持つ要点があります https://Gist.github.com/b1ff/4621c20e5ea705a0f788

0
Evgeniy