ウェブサイト検索

typescript - Angular 6でサービスを生成する際に、InjectableデコレーターのprovidedInの目的は何ですか?


Angular CLIでサービスを生成する際、Injectableデコレーターに対してデフォルトで'root'となる'providedIn'プロパティを含む追加のメタデータが追加されます。

@Injectable({
  providedIn: 'root',
})

providedIn は正確には何をするのでしょうか?私はこれがサービスをアプリケーション全体の「グローバル」タイプのシングルトンサービスのように利用可能にしているのだと考えていますが、そのようなサービスは AppModule の provider 配列で宣言した方がよりすっきりしませんか?

回答 #1

providedIn: 'root' は、Angular 6 以降、サービスを提供する最も簡単かつ効率的な方法です。

  1. このサービスは、モジュールのプロバイダ配列に追加する必要なく(Angular <= 5 のように)、シングルトンとしてアプリケーション全体で利用可能になります。
  2. サービスが遅延ロードされたモジュール内でのみ使用される場合、そのサービスもそのモジュールと一緒に遅延ロードされます
  3. もしそれが全く使われない場合、ビルドには含まれません(ツリーシェイクされます)。

さらなる情報については、ドキュメントとNgModuleのFAQを読むことを検討してください

ところで:

  1. アプリケーション全体でのシングルトンを望まない場合は、代わりにコンポーネントのプロバイダー配列を使用してください。
  2. 特定のモジュールの外で他の開発者があなたのサービスを使用しないように範囲を制限したい場合は、代わりに NgModule の providers 配列を使用してください。

回答 #2

ドキュメントから

Injectableデコレーターとは何ですか?

クラスをInjectorによる作成に利用可能としてマークします。

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

そのサービス自体は、CLIによって生成されたクラスで、@Injectable()で装飾されています。

providedIn は正確には何をするのですか?

どのインジェクターが注入可能なものを提供するかを決定します。それは、@NgModule または他の InjectorType に関連付けることによるか、またはこの注入可能なものが 'root' インジェクターで提供されるべきであることを指定することによって行われます。ほとんどのアプリでは、これはアプリケーションレベルのインジェクターになります。

providedIn: Type<any> | 'root' | null

providedIn: 'root'

サービスをルートレベルで提供すると、Angular はサービスの単一の共有インスタンスを作成し、それを要求する任意のクラスに注入します。@Injectable() メタデータでプロバイダーを登録することは、サービスが使用されていない場合にコンパイル済みアプリからサービスを削除することで、Angular がアプリを最適化できるようにすることも可能にします。

providedIn: モジュール

サービスが特定の @NgModule で提供されるべきであることを指定することも可能です。例えば、作成したモジュールをインポートしない限り、アプリケーションでサービスを利用可能にしたくない場合、そのサービスはそのモジュールで提供されるべきであると指定することができます。

import { Injectable } from '@angular/core';
import { UserModule } from './user.module';

@Injectable({
  providedIn: UserModule,
})
export class UserService {
}

この方法が好まれるのは、何も注入されない場合にサービスのツリーシェイキング(ツリーシェイキングとは、ビルドプロセスのステップで、コードベースから未使用のコードを削除することです)を可能にするためです。

もしサービスでどのモジュールがそれを提供するべきかを指定することが不可能であれば、モジュール内でサービスのプロバイダーを宣言することもできます:

import { NgModule } from '@angular/core';
import { UserService } from './user.service';

@NgModule({
  providers: [UserService],
})
export class UserModule {
}

回答 #3

これは、Angular 9以降(Ivy後)に対応した最新の返信であり、2022年でも正しいはずです。


要約: これはすべて、サービスのインスタンスがいくつ作成されるか、そして作成後にどこで利用可能にするかを制御することに関するものです。


用語:

  • インジェクタブル - @Injectable で装飾された任意のクラス、例えばサービスのこと。

  • インジェクター - 下位のクラスに注入可能なもの(Injectables)を提供できる Angular のクラス。(これにはすべてのコンポーネントとモジュールが含まれます。)

  • インジェクタースコープ/レベル - 特定のインジェクターの「下」に存在するすべてのクラスインスタンスのスコープ。

  • インジェクタ階層 - インジェクタスコープの優先順位付きツリーで、platform -> root -> module -> component の順に整理されています。

  • Injectableは提供されます - 特定のインジェクターのレベル以下のクラスがそれを要求するたびに、Injectableのインスタンスが与えられます。

  • 注入可能なものは 注入される - クラスコンストラクターがサービスのあるインスタンスを与えられることを要求しているので、Angular はインジェクタ階層で見つけることができる最も近いインスタンスを提供しようとする。

  • ツリーシェイキング - Angularコンパイラーのおかげで自動的に行われる最適化です。使用されていないコードを検出すると、そのコードはアプリの最終コンパイル(または特定の遅延ロードモジュールのコンパイル)から削除されます。

すでに知っておくべき他の用語:クラス、インスタンス、モジュール、コンポーネント、遅延/即時読み込みモジュール。


Q: providedIn は正確に何をするのですか?

どのインジェクターがあなたの注入可能なものを提供するかを決定する設定です。

仮に MyService という Injectable を作成するとして、すべてのオプションが何をするか見ていきましょう。

providedIn: Type<any> | 'root' | 'platform' | 'any' | null
providedIn: 'platform'

Angular は、ページ上のすべての Angular アプリケーションに対して、MyService の単一の共有インスタンスを作成して提供します。(これは高度なユースケース、マイクロフロントエンドのアーキテクチャを使用する場合にのみ関連します。)

providedIn: 'root'

Angular は MyService の単一の共有インスタンスを作成し、アプリケーション内のすべてのクラスに提供します。

providedIn: 'any'/Angular v15 以降非推奨

Angular は MyService の単一の共有インスタンスを作成し、それを 先読みで読み込まれるモジュール内のすべてのクラスに提供します。

しかし、各レイジーロードされたモジュールは、独自の新しい別々のMyServiceのインスタンスを提供します(それはそのモジュール内のクラスでのみ利用可能になります)。

providedIn: MyModule/Angular v15 以降は非推奨

Angularは、MyModuleが読み込まれた場合にのみ、MyServiceのインスタンスを作成します。

もし MyModule が イーガーにロードされる場合、そのインスタンスは今後すべての他のイーガーにロードされたモジュールから利用可能になります。(これは実質的に providedIn: 'root' と同じであることに注意してください。)

しかし、もしMyModuleがレイジーロードされる場合、このインスタンスはMyModule内のクラスに対してのみ提供され、そのモジュールがロードされる時に限られます。

providedIn: MyComponent/Angular v15 以降は非推奨

Angularは、MyComponentがインスタンス化されるたびに、MyServiceの新しいフレッシュなインスタンスを作成します。

このMyServiceインスタンスは、その特定のMyComponentインスタンスの子孫にのみ提供され、コンポーネントインスタンスが破棄されるとすぐに破棄されます。(つまり、このコンポーネントがレンダリングされるたびに新しいMyServiceが作成されることを意味します。)

providedIn: null

MyService は特定のモジュールまたはコンポーネントの providers 配列に追加されることでのみインスタンス化できます。

そのモジュール/コンポーネントがインスタンス化されるたびに、MyService の新しいインスタンスが作成され、特定のスコープでのみ提供されます。(下記の providers 配列の完全な説明を参照してください。)


Q: providers 配列は何をしますか?

どのインジェクターも、providers 配列を使用して設定できます:

@NgModule({
  providers: [MyService],
})

@Component({
  providers: [MyService],
})

すべてのインジェクタブルは、そのprovidedIn設定に関係なく、providers配列に追加できます。

MyService を providers 配列に追加すると、インジェクターはそのスコープ内のクラスに対して完全に別のインスタンスを作成して提供します。(スコープは、上記の providedIn: MyModule および providedIn: MyComponent の例で説明されているものとまったく同じです。)

この提供方法はツリーシェイキングをサポートしていません。このサービスは、誰も使用しなくても常にコンパイルに含まれます。(以下のツリーシェイキングの注意を参照してください。)

Q: なぜ providers 配列と providedIn を同時に使うのでしょうか?

例としての使用ケースは、MyService が providedIn: 'root' で既に共有インスタンスを持っている場合でも、モジュールやコンポーネントに独自の別のインスタンスを持たせたいときです。


追加の注意事項:

Q: providedIn/providers の設定はツリーシェイキングにどのように影響しますか?

もし割り当てられたインジェクタースコープ内の任意のクラス(早期ロードまたは遅延ロード)によって注入されない場合、providedInで構成されたInjectableはツリーシェイクされます。

しかし、いくつかのモジュールやコンポーネントで providers 配列に割り当てられた注入可能なもの は、どこにも注入されていなくても、決してツリーシェイクされません。

ツリーシェイクを最も効果的にするには、常に providers 配列よりも providedIn を使用することを目指すべきです。

Q: AppModule の providers 配列を使う方が見た目がすっきりしていると思う場合、なぜ providedIn: 'root' を使うのでしょうか?

上で説明したように、主な違いは、2つの方法のうち、providedIn がツリーシェイキングに対応しているのに対し、providers 配列は対応していないという点です。

それ以外は、アーキテクチャ上の決定です:もし providedIn を直接 Injectable ファイルに設定すると、Injectable 自体がどのように提供されるかの決定を 所有することになります。契約の所有者を区別することは、大規模なアプリや何百ものモジュール間で協力しなければならないチームにとって重要な影響を持ちます。

Q: AppComponentかAppModuleのどちらにproviders: [MyService]配列を設定するかで違いはありますか?

はい。MyService は AppComponent ではなく AppModule で行った場合のみ、遅延ロードされるモジュールで のみ 提供されます。

(これは、遅延読み込みされるモジュールが Router に依存しており、AppComponent より1つ上のインジェクタースコープである AppModule にインポートされるためです。)

回答 #4

providedIn を使用すると、インジェクタブルはモジュールの providers に追加しなくても、モジュールのプロバイダーとして登録されます。

Docs から

サービス自体は、CLIによって生成され、@Injectableで装飾されたクラスです。デフォルトでは、このデコレーターはprovidedInプロパティで設定されており、サービスのプロバイダーを作成します。この場合、providedIn: 'root'はサービスがルートインジェクターで提供されるべきことを指定します。

回答 #5

@Nipunaによる素晴らしい説明をご覧ください、

例を追加してそれを拡張したいです。

もしあなたが providedIn プロパティなしで Injectable デコレーターを使用するだけなら、例えば、

@Injectable()

その場合、サービスの名前をそれぞれのモジュールの providers 配列に書く必要があります。

こんなふうに;

data.service.ts ↴

import { Injectable } from '@angular/core';

@Injectable()
export class DataService {
    constructor() {}

    // Code . . .
}

app.module.ts ↴

import { AppComponent } from './app.component';
import { DataService } from './core/data.service';

@NgModule({
    declarations: [AppComponent],
    providers: [DataService],    // ⟵ LOOK HERE WE PROVIDED IT
    imports: [...],
    bootstrap: [AppComponent],
})
export class AppModule {}

しかし、このように providedIn: 'root' を使用する場合は:

data.service.ts ↴

import { Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root',
})
export class DataService {
    constructor() {}

    // Code . . .
}

それから、私たちのモジュールはこのようになります:

app.module.ts ↴

import { AppComponent } from './app.component';
import { DataService } from './core/data.service';

@NgModule({
    declarations: [AppComponent],
    providers: [],
    imports: [...],
    bootstrap: [AppComponent],
})
export class AppModule {}

ほら、今回は providers 配列に DataService を追加しなかったんだ、なぜなら必要ないからです。

良い実践

これは役に立つかもしれません、Angularガイドから

サービスの @Injectable デコレーターにあるアプリルートインジェクターでサービスを提供してください。

なぜですか? Angular のインジェクターは階層構造になっています。

なぜですか? ルートインジェクターにサービスを提供すると、そのサービスのインスタンスは共有され、サービスを必要とするすべてのクラスで利用可能になります。サービスがメソッドや状態を共有する場合には理想的です。

なぜですか? サービスの @Injectable デコレーターでサービスを登録すると、Angular CLI のプロダクションビルドで使用されるような最適化ツールがツリーシェイキングを行い、アプリで使用されていないサービスを削除することができます。

なぜですか? これは、2つの異なるコンポーネントがサービスの異なるインスタンスを必要とする場合には理想的ではありません。このシナリオでは、新しく別のインスタンスが必要なコンポーネントレベルでサービスを提供する方がよいでしょう。

回答 #6

`providedIn` は Angular に対して、ルートインジェクターがあなたのサービスのインスタンスを作成する責任があることを伝えます。この方法で提供されるサービスは、アプリケーション全体で自動的に利用可能になり、どのモジュールにもリストする必要はありません。

サービスクラスは自身のプロバイダーとして機能することができるため、@Injectable デコレーターで定義することが、必要な登録のすべてです。

関連記事