Angularjsにおける階層的依存性注入


どのようにangularjsプロジェクトの階層依存性注入を実装するには-プロ、落とし穴と何を意識している.
写真https://libreshot.com そばMartin Vorel

依存性注入( DI )-短い説明
状態が現在の実行の範囲の外に保たれて、オブジェクトの作成または実行中にその状態を提供するグローバルサービスを要求することによってアクセスできます.複数の状態は、それぞれの異なるキーを使用して保持することができます.

Angularjsにおける依存性注入
Angularjsでは、依存性注入はフレームワークの一部として提供されます.
その主なメカニズムの一つは、コンポーネント/ディレクティブとサービスの作成です.サービス、またはファクトリ関数は、フレームワークDIマネージャーに登録され、それらのインスタンスを作成時にコンポーネントに注入するように求められます.
例えば、簡単な映画DBアプリケーションが表示されます.ここでは、メインのアプリケーションモジュールを作成します.
const moviesApp = angular.module('movies', []);
最初のサービスは、サービス情報を保持するサーバーへのアクセスを提供する認証サービスです.
AngularJSが組み込まれたHTTP HTTPクライアントインスタンスの注入にAngularjsにサービスを依頼することに注意してください.
class AuthService {
  static $inject = ['$http'];

  private token: string;

  constructor($http: IHttpService) {}

  getToken() {
    if (_.isNil(this.token)) {
      this.token = $http.get('my-site.example.com/auth');
    }

    return this.token;
  }
}

moviesApp.service('auth', AuthService);
typesciprt/es 6クラスと静的インジェクト変換
function AuthService($http) {
  this.$http = $http;
}
AuthService.$inject = ['$http'];
Angularjsは、探します$inject サービスファクトリ関数をマーキングした後、
  • DIに行き、$ Inject配列の必須キーに対応する状態を求めます.
  • 工場の機能をアクティブにし、要求された注射を提供します.
  • 我々のアプリの別のサービスを書く-MoviesService — 我々はそれに依存し、我々が構築した前のサービスを必要とすることができます.
    class MoviesService {
      static $inject = ['$http', 'auth'];
    
      movies = Promise.resolve([]);
    
      constructor(
        private $http: IHttpService,
        private auth: AuthService,
      ) {}
    
      getMovies() {
        if (_.isNil(this.movies)) {
          this.movies = this.auth.getToken()
            .then((token) => {
              return $http.get('my-site.example.com/movies', {
                headers: {
                  Authorization: token,
                },
              });
            });
        }
        return this.movies;
      }
    }
    
    moviesApp.service('movies', MoviesService);
    
    所持MoviesService , プレゼンテーションコンポーネントにページを表示するために使用できます.
    class MoviesList {
      static $inject = ['movies'];
    
      constructor(
        movies: MoviesService
      ) 
    }
    
    const MoviesListComponent = {
      template: `
        <h1>Movies</h1>
        <ul>
          <li ng-repeat="movie in ($ctrl.movies.movies | promiseAsync) track by movie.id">
            {{ movie.name }} - {{ movie.year }} - {{ movie.rating }}
          </li>
        </ul>
      `,
      controller: MoviesList
    };
    
    moviesApp.component('moviesList', MoviesListComponent);
    
    ここでは、コンポーネントはmovies 工事に投入するサービス.
    Angularjsは、それがサービスのためにした同じ仕事をします.必要な依存関係インスタンスをDIマネージャーから収集し、コンポーネントインスタンスを構築し、必要な依存関係を提供します.

    問題- 1つだけの注入レベル
    私たちは2つの映画のリストのコンポーネントを、それぞれの別のサイトからそれぞれの映画のリストを表示するには、インスタンスを言うことができます.
    <movies-list-my-site-a />
    
    <movies-list-my-site-b /> 
    
    そのシナリオでは、ビルドするのは難しいMovieListSiteA , MovieListSiteB オリジナルの論理に似た構成要素MovieList コンポーネント.両方が同じAuthサービスを必要とする同じ映画サービスを必要とするならば、彼らは異なるAuthトークンと異なる目標サーバーを持つことができません.
    ある意味でAuthはSingletonの1つのインスタンスであり、それはAngularjsのメインDI Manager - Injector -によって保持されているキーAuthだけです.
    異なったが、同様のシナリオは複数の映画を選択したいです、そして、各々のために、サブページは複数の階層の構成要素でその映画につき詳細の現在のリストを示します.我々がそうしたならばCurrentSelectedMovie サービスは、すべてのコンポーネントインスタンスの要求の間でグローバルに共有されます.

    必要なネストされたレベルの
    角度/2では、書き直されたdiは、主なルートアプリだけでなく、それぞれのモジュールとcomponent level . 各コンポーネントは以前のように依存の注入を求めることができ、また、そのレベルでサービスインスタンスを登録することができます.
    @Component({
      ...
      providers: [{ provide: AuthService }]
    })
    export class EastAndorMovieList
    
    例えば、ATUSサービスがルート・アプリケーション・モジュールによって提供されているなら、コンポーネントはAuthキーの下でAuthキーを提供すると宣言できます.注射を要求する子コンポーネントauth サービスは、親コンポーネントオーバーライドサービスを取得し、ルートモジュールサービスではありません.

    必要なネストされたレベルの
    angularjsは、サービス/ファクトリ/コンポーネントのコンストラクタの注入メカニズムでのネストされたレベルのDIをサポートしていませんが、それは階層的なDIを実装するために使用できるいくつかの他の興味深いメカニズムを持っています.
    エンターrequire .
    AngularJSディレクティブとコンポーネント宣言では、GangulersがDOMツリーを検索し、指定されたコントローラを求めるように指示するプロパティを指定できます.見つかった場合、要求ディレクティブにそれを注入します.
    同じ要素にngmodelディレクティブコントローラを必要とする例
    moviesApp.directive('printout', ['$sce', function printout($sce) {
      return {
        restrict: 'A',
        require: {
          ngModel: ''
        },
        link: (scope, element, attrs, requireCtrls) {
          requireCtrls.ngModel.$render = function() {
            element.html($sce.getTrustedHtml(requireCtrls.ngModel.$viewValue || ''));
          };
        }
      };
    }]);
    
    <div ng-model="$ctrl.myModel" printout />
    
    必要なコンポーネントを使用するのは、コンポーネントがディレクティブの型と同じ原理です.
    angular.component('printout', {
      template: `<div>{{ $ctrl.model | json:2 }}</div>,
      require: {
        ngModel: '',
      },
      controller: ['$sce', '$element', function controller($sce, $element) {
        this.$onInit = () {
          this.ngModel.$render = function() {
            $element.html($sce.getTrustedHtml(this.ngModel.$viewValue || ''));
          };
        };
      }],
    });
    
    サービスを定義できず、階層的に要求する.ディレクティブ/コンポーネントは.サービスとしてのディレクティブを作成するにはどうすればよいですか?

    サービス指令
    The auth and movie サービスディレクティブにリファクタリングされたサービスは次のようになります.
    class AuthService {
      static $inject = ['$http'];
    
      private token: string;
    
      constructor($http: IHttpService) {}
    
      getToken() {
        if (_.isNil(this.token)) {
          this.token = $http.get('my-site.example.com/auth');
        }
    
        return this.token;
      }
    }
    
    angular.directive('auth', [function auth() {
      return {
        restrict: 'A',
        controller: AuthService,
      };
    }]);
    
    /////////////////////////
    
    class MoviesService {
      static $inject = ['$http'];
    
      movies = Promise.resolve([]);
    
      constructor(
        private $http: IHttpService,
      ) {}
    
      getMovies() {
        // require directives are avaiable when and after $onInit.
        if (_.isNil(this.auth)) {
          return [];
        }
    
        if (_.isNil(this.movies)) {
          this.movies = this.auth.getToken()
            .then((token) => {
              return $http.get('my-site.example.com/movies', {
                headers: {
                  Authorization: token,
                },
              });
            });
        }
        return this.movies;
      }
    }
    
    angular.directive('movies', [function movies() {
      return {
        restrict: 'A',
        require: {
          auth: '^',
        },
        controller: MoviesService,
      };
    }]);
    
    DOMツリーの上位レベルで使用する場合:
    <movies-app auth movies>
       ...
    </movies-app>
    
    その後、コンポーネントでは、それらを必要と使用することができます.
    class MoviesList {  
    }
    
    const MoviesListComponent = {
      template: `
        <h1>Movies</h1>
        <ul>
          <li ng-repeat="movie in ($ctrl.movies.movies | promiseAsync) track by movie.id">
            {{ movie.name }} - {{ movie.year }} - {{ movie.rating }}
          </li>
        </ul>
      `,
      require: {
        movies: '^',
      },
      controller: MoviesList
    };
    
    moviesApp.component('moviesList', MoviesListComponent);
    
    <movies-app auth movies>
       <movies-list />
    </movies-app>
    
    今、新しいAuthサービスは、AudioKeyを使用してAuthキー上の任意のレベルで定義することができますので、メインAuthサービスをオーバーライドしたい場合は、Auth Directive Serviceを変更して、カスタムサブDIトークンで希望のサービスを返すようにAuth Directive Serviceを変更することです.
    class AuthService {
      static $inject = ['$http'];
    
      private token: string;
    
      constructor($http: IHttpService) {}
    
      getToken() {
        if (_.isNil(this.token)) {
          this.token = $http.get('my-site.example.com/auth');
        }
    
        return this.token;
      }
    }
    
    class EastAndorAuthService {
      static $inject = ['$http'];
    
      private token: string;
    
      constructor($http: IHttpService) {}
    
      getToken() {
        if (_.isNil(this.token)) {
          this.token = $http.get('east-andor.example.com/auth');
        }
    
        return this.token;
      }
    }
    
    // using the same `auth` key to register EastAndoAuthService
    angular.directive('auth', [function auth() {
      return {
        restrict: 'A',
        controller: ['$attrs', '$injector', function controller($attrs, $injector) {
          this.service = switchOn({
            '': () => $injector.invoke(AuthService),
            eastAndor: () => $injector.invoke(EastAndorAuthService),
          }, $attrs.auth);
        }],
      };
    }]);
    
    <movies-app auth movies>
       <movies-list />   <movies-list auth="east-andor" movies />   <div auth="volcan">
         <movies-list movies />
       </div>
    </movies-app>
    
  • $ Injectorのテクニックを使用して、ムービーディレクティブは、これを適応し、使用する必要があります.Auth代わりにサービス.Auth
  • 他の簡単なケースでは、同じクラスを使用して、異なるロジックを格納し、属性をカスタマイズしてカスタマイズできます.
  • サービスディレクティブは、他のサービスディレクティブを必要とすることもできます.サービスディレクティブに変換されたムービーサービスは、AutoServiceディレクティブを必要としなければなりません.

  • 考慮すべき点
  • 角度/2とは異なり、すべてのアプリケーションの文字列トークンごとに1つのディレクティブを定義することができます.ディレクティブ名の意味はグローバルです.異なった動作を返すことを望むとき、上記のような媒介者論理技術を使用する必要がある.
  • Angle/2とは異なり、使用しているコンポーネントは、テンプレートでサービスディレクティブを宣言することはできません.これは、そのタグまたはその上に適用するコントローラディレクティブを必要とすることができます.
  • これはいくつかの解決策を適用することができますが、どちらも完璧です使用する面倒です.
  • ディレクティブ/コンポーネントだけがサービスディレクティブを消費することができます.サービスのムービーがサービスディレクティブAuthを使用する必要がある場合、サービスは要求ディレクティブを使用するサービスディレクティブに変換する必要があります.
  • たとえば、ポイント2に対して、コンポーネントはそのテンプレート内でディレクティブを使用できますが、必要に応じて、そのインスタンスをコンポーネントに提供する属性式を実行することで、サービスインスタンスを提供できます.
    例:
    <div auth="east-andor" on-auth-service="$ctrl.auth = service"
    
    このテクニックの主な欠点は、サービスが$ oninitサイクルでさえ利用できないということです.
    別の解決策は、元の名前の中にあるディレクティブを使用して、名前が変更された元のコンポーネントを、プレフィックス-ベースを含めるように呼び出します.
    angular.component('movieList', {
      template: `
        <movie-list-base auth="easy-andor" 
          some-binding="$ctrl.someBinding 
        />
      `,
      bindings: {
        // same as original movie list
      }
    })
    

    概要
    面倒な価値のあるangularjsの階層的なDIのためのこの手法は、アプリケーションが階層的な状態を使用して得ることができるどのくらいの利得に依存しているかどうか.
    しかし、見られるように、それは使用することが可能であり、それはAngularjsの州管理技術の兵器庫の別のテクニックとして利用可能です.