階層型インジェクター
Angularのインジェクターには、注入可能なオブジェクトのアプリケーション内での可視性を思いどおりにするために活用できるルールがあります。 これらのルールを理解することで、プロバイダーをアプリケーションレベル、コンポーネント内、またはディレクティブ内で宣言する必要があるかどうかを判断できます。
Angularで構築するアプリケーションは非常に大きくなる可能性があり、この複雑さを管理する1つの方法は、アプリケーションを明確に定義されたコンポーネントツリーに分割することです。
ページのセクションには、アプリケーションの残りの部分とは完全に独立して動作する部分があり、そのセクションに必要なサービスやその他の依存関係のローカルコピーが持つことができます。 これらのアプリケーションセクションが使用するサービスの一部は、アプリケーションの他の部分やコンポーネントツリーの上位にある親コンポーネントと共有される場合がありますが、他の依存関係はプライベートであることを目的としています。
階層型の依存性の注入を使用すると、アプリケーションのセクションを分離してアプリケーションの残りの部分と共有されていない独自のプライベート依存関係を与えるか、親コンポーネントが特定の依存関係を子コンポーネントのみに共有してコンポーネントツリーの残りの部分には共有しないようにできます。階層型の依存性の注入により、必要な場合にのみ、アプリケーションのさまざまな部分間で依存関係を共有できます。
インジェクター階層のタイプ
Angularには、次の2つのインジェクター階層があります。
インジェクター階層 | 詳細 |
---|---|
EnvironmentInjector 階層 |
@Injectable() または ApplicationConfig の providers 配列を使用して、この階層で EnvironmentInjector を構成します。 |
ElementInjector 階層 |
各 DOM 要素で暗黙的に作成されます。 ElementInjector は、 @Directive() または @Component() の providers プロパティで構成しない限り、デフォルトでは空です。 |
NgModule ベースのアプリケーション
NgModule
ベースのアプリケーションの場合、@NgModule()
または @Injectable()
アノテーションを使用して、ModuleInjector
階層で依存関係を提供できます。
EnvironmentInjector
EnvironmentInjector
は、次のいずれかの方法で構成できます。
@Injectable()
のprovidedIn
プロパティを使用してroot
またはplatform
を参照するApplicationConfig
のproviders
配列を使用する
ツリーシェイクと @Injectable()
@Injectable()
の providedIn
プロパティを使用することは、 ApplicationConfig
の providers
配列を使用するよりも好ましいです。 @Injectable()
の providedIn
を使用すると、最適化ツールはツリーシェイクを実行して、アプリケーションで使用されていないサービスを削除できます。これにより、バンドルサイズが小さくなります。
ツリーシェイクは、特にライブラリにとって有用です。なぜなら、ライブラリを使用するアプリケーションは、ライブラリを注入する必要がない場合があるからです。
EnvironmentInjector
は、 ApplicationConfig.providers
によって構成されます。
providedIn
を使用して、次のように @Injectable()
を使用してサービスを提供します。
import { Injectable } from '@angular/core';@Injectable({ providedIn: 'root' // <--ルート EnvironmentInjector でこのサービスを提供します})export class ItemService { name = 'telephone';}
@Injectable()
デコレーターは、サービスクラスを識別します。
providedIn
プロパティは、特定の EnvironmentInjector
(ここでは root
)を構成します。これにより、サービスは root
EnvironmentInjector
で使用可能になります。
ModuleInjector
NgModule
ベースのアプリケーションの場合、ModuleInjector
は、次のいずれかの方法で構成できます。
@Injectable()
のprovidedIn
プロパティを使用してroot
またはplatform
を参照する@NgModule()
のproviders
配列を使用する
ModuleInjector
は、 @NgModule.providers
および NgModule.imports
プロパティによって構成されます。 ModuleInjector
は、 NgModule.imports
を再帰的にたどることによって到達できるすべてのプロバイダー配列をフラット化したものです。
子 ModuleInjector
階層は、他の @NgModules
を遅延ロードするときに作成されます。
プラットフォームインジェクター
root
の上にさらに2つのインジェクター、追加の EnvironmentInjector
と NullInjector()
があります。
Angularが main.ts
の次の内容でアプリケーションをブートストラップする方法を検討してください。
bootstrapApplication(AppComponent, appConfig);
bootstrapApplication()
メソッドは、 ApplicationConfig
インスタンスによって構成されたプラットフォームインジェクターの子インジェクターを作成します。
これが root
EnvironmentInjector
です。
platformBrowserDynamic()
メソッドは、プラットフォーム固有の依存関係を含む PlatformModule
によって構成されたインジェクターを作成します。
これにより、複数のアプリケーションがプラットフォーム構成を共有できます。
たとえば、ブラウザには、実行中のアプリケーションの数にかかわらず、URLバーは1つだけです。
platformBrowser()
関数を使用して extraProviders
を提供することにより、プラットフォームレベルで追加のプラットフォーム固有のプロバイダーを構成できます。
階層の次の親インジェクターは NullInjector()
で、ツリーのトップです。
ツリーをそこまで上に移動して NullInjector()
内でサービスを検索した場合、 @Optional()
を使用していない限り、エラーが発生します。最終的にはすべて NullInjector()
で終了し、エラーを返すか、 @Optional()
の場合、 null
を返します。
@Optional()
の詳細については、このガイドの @Optional()
セクション を参照してください。
次の図は、前の段落で説明したように、 root
ModuleInjector
とその親インジェクターの関係を表しています。
root
という名前は特別なエイリアスですが、他の EnvironmentInjector
階層にはエイリアスがありません。
動的にロードされたコンポーネントが作成されるたびに EnvironmentInjector
階層を作成するオプションがあります。たとえば、ルーターを使用すると、子 EnvironmentInjector
階層が作成されます。
ApplicationConfig
インスタンスを bootstrapApplication()
メソッドに渡して構成するか、 root
で独自のサービスにすべてのプロバイダーを登録したかにかかわらず、すべての要求はルートインジェクターに転送されます。
@Injectable() vs. ApplicationConfig
bootstrapApplication()
の ApplicationConfig
でアプリケーション全体のプロバイダーを構成すると、 @Injectable()
メタデータの root
で構成されたプロバイダーがオーバーライドされます。
これは、複数のアプリケーションで共有されるサービスのデフォルト以外を構成する場合に行うことができます。
コンポーネントルーターの構成にデフォルト以外の ロケーション戦略 が含まれている場合、 ApplicationConfig
の providers
リストにそのプロバイダーをリストすることによって、その例を示します。
providers: [ { provide: LocationStrategy, useClass: HashLocationStrategy }]
NgModule
ベースのアプリケーションの場合、アプリケーション全体のプロバイダーを AppModule
の providers
で構成します。
ElementInjector
Angularは、各DOM要素に対して ElementInjector
階層を暗黙的に作成します。
@Component()
デコレーターの providers
または viewProviders
プロパティを使用してサービスを提供すると、 ElementInjector
が構成されます。
たとえば、次の TestComponent
は、次のようにサービスを提供することで ElementInjector
を構成します。
@Component({ … providers: [{ provide: ItemService, useValue: { name: 'lamp' } }]})export class TestComponent
HELPFUL: 解決ルール セクションを参照して、 EnvironmentInjector
ツリー、 ModuleInjector
、および ElementInjector
ツリーの関係を理解してください。
コンポーネントでサービスを提供すると、そのサービスは、そのコンポーネントインスタンスの ElementInjector
を介して使用可能になります。
解決ルール セクションで説明されている可視性ルールに基づいて、子コンポーネント/ディレクティブでも可視化される可能性があります。
コンポーネントインスタンスが破棄されると、そのサービスインスタンスも破棄されます。
@Directive()
と @Component()
コンポーネントは特殊なタイプのディレクティブであるため、 @Directive()
に providers
プロパティがあるように、 @Component()
にも providers
プロパティがあります。
つまり、ディレクティブとコンポーネントは、 providers
プロパティを使用してプロバイダーを構成できます。
providers
プロパティを使用してコンポーネントまたはディレクティブのプロバイダーを構成すると、そのプロバイダーはそのコンポーネントまたはディレクティブの ElementInjector
に属します。
同じ要素上のコンポーネントとディレクティブは、インジェクターを共有します。
解決ルール
コンポーネント/ディレクティブのトークンを解決する場合、Angularは次の2つのフェーズで解決します。
ElementInjector
階層の親に対して。EnvironmentInjector
階層の親に対して。
コンポーネントが依存関係を宣言すると、Angularはその依存関係を自身の ElementInjector
で満たそうとします。
コンポーネントのインジェクターにプロバイダーがない場合、そのリクエストを親コンポーネントの ElementInjector
に渡します。
リクエストは、Angularがリクエストを処理できるインジェクターを見つけるか、祖先 ElementInjector
階層を使い果たすまで、転送され続けます。
Angularがいずれの ElementInjector
階層でもプロバイダーを見つけられない場合、リクエストが発生した要素に戻り、 EnvironmentInjector
階層を調べます。
それでもAngularがプロバイダーを見つけられない場合、エラーをスローします。
同じDIトークンのプロバイダーを異なるレベルで登録した場合、Angularが最初に遭遇するプロバイダーが、依存関係を解決するために使用するプロバイダーです。 たとえば、サービスを必要とするコンポーネントにプロバイダーがローカルに登録されている場合、 Angularは同じサービスの別のプロバイダーを探しません。
HELPFUL: NgModule
ベースのアプリケーションの場合、Angularは ElementInjector
階層でプロバイダーが見つからない場合、 ModuleInjector
階層を検索します。
解決修飾子
Angularの解決動作は、 @Optional()
と@Self()
、@SkipSelf()
および@Host()
で変更できます。
これらのそれぞれを @angular/core
からインポートし、コンポーネントクラスコンストラクターまたはサービスを注入するときの inject
構成でそれぞれを使用します。
修飾子の種類
解決修飾子は、次の3つのカテゴリーに分類されます。
- Angularが探しているものが見つからない場合の処理、つまり
@Optional()
- 検索を開始する場所、つまり
@SkipSelf()
- 検索を停止する場所、
@Host()
と@Self()
デフォルトでは、Angularは常に現在の Injector
から始めて、すべてを上に検索し続けます。
修飾子を使用すると、開始位置(または self 位置)と終了位置を変更できます。
さらに、次の修飾子をすべて組み合わせることができます。
@Host()
と@Self()
@SkipSelf()
と@Self()
@Optional()
@Optional()
を使用すると、注入するサービスをオプションとして扱うことができます。
そのため、実行時に解決できない場合、Angularはサービスをエラーをスローするのではなく、 null
として解決します。
次の例では、サービス OptionalService
はサービス ApplicationConfig
や@NgModule()
、コンポーネントクラスで提供されていないため、アプリケーションのどこにも使用できません。
src/app/optional/optional.component.ts
export class OptionalComponent { constructor(@Optional() public optional?: OptionalService) {}}
@Self()
@Self()
を使用すると、Angularは現在のコンポーネントまたはディレクティブの ElementInjector
のみを調べます。
@Self()
の良いユースケースは、サービスを注入することですが、現在のホスト要素で使用可能な場合のみです。
この状況でエラーを回避するには、 @Self()
と @Optional()
を組み合わせます。
たとえば、次の SelfComponent
では、コンストラクターで注入された LeafService
に注目してください。
src/app/self-no-data/self-no-data.component.ts
@Component({ selector: 'app-self-no-data', templateUrl: './self-no-data.component.html', styleUrls: ['./self-no-data.component.css']})export class SelfNoDataComponent { constructor(@Self() @Optional() public leaf?: LeafService) { }}
この例では親プロバイダーがあり、サービスを注入すると値が返されます。しかし @Self()
と @Optional()
を使用してサービスを注入すると、 null
が返されます。これは、 @Self()
がインジェクターに現在のホスト要素の検索を停止するように指示するためです。
別の例では、 FlowerService
のプロバイダーを備えたコンポーネントクラスを示しています。
この場合、インジェクターは現在の ElementInjector
より先を見ずに、 FlowerService
を見つけて、チューリップ 🌷
を返します。
src/app/self/self.component.ts
import {Component, Self} from '@angular/core';import {FlowerService} from '../flower.service';@Component({ standalone: true, selector: 'app-self', templateUrl: './self.component.html', styleUrls: ['./self.component.css'], providers: [{provide: FlowerService, useValue: {emoji: '🌷'}}],})export class SelfComponent { constructor(@Self() public flower: FlowerService) {}}// This component provides the FlowerService so the injector// doesn't have to look further up the injector tree
@SkipSelf()
@SkipSelf()
は @Self()
の逆です。
@SkipSelf()
を使用すると、Angular は現在のインジェクターではなく、親 ElementInjector
でサービスの検索を開始します。
そのため、親 ElementInjector
が emoji
にシダ 🌿
値を使用していたが、コンポーネントの providers
配列にカエデの葉 🍁
が含まれている場合、Angular はカエデの葉 🍁
を無視して、シダ 🌿
を使用します。
これをコードで確認するために、親コンポーネントが使用する emoji
の次の値を想定します。これは、このサービスと同じです。
src/app/leaf.service.ts
export class LeafService { emoji = '🌿';}
子コンポーネントに、異なる値、カエデの葉 🍁
が含まれていると想像してください。ただし、親の値を使用したいとします。
これが、 @SkipSelf()
を使用するタイミングです。
src/app/skipself/skipself.component.ts
@Component({ selector: 'app-skipself', templateUrl: './skipself.component.html', styleUrls: ['./skipself.component.css'], // Angular はこの LeafService インスタンスを無視します providers: [{ provide: LeafService, useValue: { emoji: '🍁' } }]})export class SkipselfComponent { // コンストラクターで @SkipSelf() を使用します constructor(@SkipSelf() public leaf: LeafService) { }}
この場合、 emoji
に対して取得する値は、カエデの葉 🍁
ではなく、シダ 🌿
になります。
@SkipSelf()
と @Optional()
@SkipSelf()
と @Optional()
を使用すると、値が null
の場合にエラーを防ぐことができます。
次の例では、 Person
サービスがコンストラクターで注入されています。
@SkipSelf()
は、Angularに現在のインジェクターをスキップするように指示し、 @Optional()
は Person
サービスが null
の場合にエラーを防ぎます。
class Person { constructor(@Optional() @SkipSelf() parent?: Person) {}}
@Host()
@Host()
を使用すると、インジェクターツリーでプロバイダーを検索する際の終点としてコンポーネントを指定できます。
ツリーの上位にサービスインスタンスがある場合でも、Angularは検索を続行しません。
@Host()
は次のように使用します。
src/app/host/host.component.ts
@Component({ selector: 'app-host', templateUrl: './host.component.html', styleUrls: ['./host.component.css'], // サービスを提供します providers: [{ provide: FlowerService, useValue: { emoji: '🌷' } }]})export class HostComponent { // サービスを注入するときにコンストラクターで @Host() を使用します constructor(@Host() @Optional() public flower?: FlowerService) { }}
HostComponent
に @Host()
があるため、 HostComponent
の親が flower.emoji
値として何を使用しているかに関係なく、 HostComponent
はチューリップ 🌷
を使用します。
テンプレートの論理構造
コンポーネントクラスでサービスを提供する場合、サービスは、サービスの提供場所と方法に応じて、 ElementInjector
ツリー内で可視になります。
Angularテンプレートの基になる論理構造を理解すると、サービスの構成と、その可視性の制御のための基礎が得られます。
コンポーネントは、次の例のようにテンプレートで使用されます。
<app-root> <app-child></app-child>;</app-root>
HELPFUL: 通常、コンポーネントとそのテンプレートは別々のファイルに宣言します。
注入システムの動作を理解するために、それらを組み合わせた論理ツリーの視点から見ると便利です。
用語 論理 は、アプリケーションのDOMツリーであるレンダリングツリーと区別しています。
コンポーネントテンプレートの場所を示すために、このガイドでは <#VIEW>
疑似要素を使用します。この疑似要素は、レンダリングツリーには実際に存在せず、メンタルモデルの目的でのみ存在します。
以下は、 <app-root>
と <app-child>
のビューツリーを1つの論理ツリーに結合した例です。
<app-root> <#VIEW> <app-child> <#VIEW> …ここにコンテンツが挿入されます… </#VIEW> </app-child> </#VIEW></app-root>
<#VIEW>
の区切りの考え方を理解することは、特にコンポーネントクラスでサービスを構成する場合に重要です。
例: @Component()
でサービスを提供する
@Component()
(または @Directive()
)デコレーターを使用してサービスを提供する方法は、サービスの可視性を決めます。
次のセクションではproviders
とviewProviders
、@SkipSelf()
および@Host()
を使用してサービスの可視性を変更する方法について説明します。
コンポーネントクラスでは、次の2つの方法でサービスを提供できます。
配列 | 詳細 |
---|---|
providers 配列を使用する |
@Component({ providers: [SomeService] }) |
viewProviders 配列を使用する |
@Component({ viewProviders: [SomeService] }) |
次の例では、Angularアプリケーションの論理ツリーが表示されます。
テンプレートのコンテキストでインジェクターがどのように動作するかを示すために、論理ツリーはアプリケーションのHTML構造を表します。
たとえば、論理ツリーは、 <child-component>
が <parent-component>
の直接の子であることを示します。
論理ツリーでは、 @Provide
、 @Inject
、および @ApplicationConfig
という特殊な属性が表示されます。
これらは実際の属性ではなく、内部で何が起こっているかを説明するためにここにあります。
Angular サービス属性 | 詳細 |
---|---|
@Inject(Token)=>Value |
論理ツリーのこの場所に Token が注入されている場合、その値は Value になります。 |
@Provide(Token=Value) |
論理ツリーのこの場所に Token が Value で提供されていることを示します。 |
@ApplicationConfig |
この場所でフォールバック EnvironmentInjector を使用する必要があることを示します。 |
アプリケーション構造の例
この例では、 emoji
の値が赤いハイビスカス 🌺
である、 root
に提供される FlowerService
があります。
src/app/flower.service.ts
@Injectable({ providedIn: 'root'})export class FlowerService { emoji = '🌺';}
AppComponent
と ChildComponent
のみが含まれるアプリケーションを検討してください。
最も基本的なレンダリングされたビューは、次のようなネストされたHTML要素のように見えます。
<app-root> <!-- AppComponent セレクター --> <app-child> <!-- ChildComponent セレクター --> </app-child></app-root>
ただし、裏側では、Angularはインジェクションリクエストを解決するときに、次のような論理ビュー表現を使用します。
<app-root> <!-- AppComponent セレクター --> <#VIEW> <app-child> <!-- ChildComponent セレクター --> <#VIEW> </#VIEW> </app-child> </#VIEW></app-root>
ここでの <#VIEW>
は、テンプレートのインスタンスを表しています。
各コンポーネントには、独自の <#VIEW>
があることに注意してください。
この構造を理解することで、サービスの提供方法と注入方法を把握し、サービスの可視性を完全に制御できます。
次に、 <app-root>
が FlowerService
を注入しているとします。
src/app/app.component.ts
export class AppComponent { constructor(public flower: FlowerService) {}}
結果を視覚化するために、 <app-root>
テンプレートにバインディングを追加します。
src/app/app.component.html
<p>Emoji from FlowerService: {{flower.emoji}}</p>
ビューに出力されるのは次のとおりです。
Emoji from FlowerService: 🌺
論理ツリーでは、これは次のように表されます。
<app-root @ApplicationConfig @Inject(FlowerService) flower=>"🌺"> <#VIEW> <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p> <app-child> <#VIEW> </#VIEW> </app-child> </#VIEW></app-root>
<app-root>
が FlowerService
を要求すると、インジェクターは FlowerService
トークンを解決します。
トークンの解決は次の2つのフェーズで行われます。
インジェクターは、論理ツリー内の開始位置と検索の終了位置を決定します。 インジェクターは開始位置から始めて、論理ツリーの各ビューレベルでトークンを検索します。 トークンが見つかると、そのトークンが返されます。
トークンが見つからない場合、インジェクターはリクエストを委任する最も近い親
EnvironmentInjector
を検索します。
この例の場合、制約は次のとおりです。
<app-root>
に属する<#VIEW>
から始めて、<app-root>
で終了します。- 通常、検索の開始点は注入ポイントです。
ただし、この場合、
<app-root>
はコンポーネントです。@Component
は特殊で、独自のviewProviders
も含まれています。そのため、検索は<app-root>
に属する<#VIEW>
から開始されます。 これは、同じ場所に一致するディレクティブでは発生しません。 - 終了位置は、コンポーネント自体と同じになります。なぜなら、これはこのアプリケーションの最上位コンポーネントだからです。
- 通常、検索の開始点は注入ポイントです。
ただし、この場合、
ApplicationConfig
によって提供されるElementInjector
は、インジェクショントークンがElementInjector
階層で見つからない場合のフォールバックインジェクターとして機能します。
providers
配列を使用する
次に、 ChildComponent
クラスで、今後のセクションでより複雑な解決ルールを説明するために、 FlowerService
のプロバイダーを追加します。
src/app/child.component.ts
@Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'], // プロバイダー配列を使用してサービスを提供します providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }]})export class ChildComponent { // サービスを注入します constructor( public flower: FlowerService) { }}
FlowerService
が @Component()
デコレーターで提供されるようになったため、 <app-child>
がサービスを要求すると、インジェクターは <app-child>
の ElementInjector
ほど遠くまでしか見なくてもよくなります。
インジェクターは、インジェクターツリーをさらに検索する必要はありません。
次のステップは、 ChildComponent
テンプレートにバインディングを追加することです。
src/app/child.component.html
<p>Emoji from FlowerService: {{flower.emoji}}</p>
新しい値をレンダリングするために、ビューにひまわりも表示されるように、 AppComponent
テンプレートの下部に <app-child>
を追加します。
Child ComponentEmoji from FlowerService: 🌻
論理ツリーでは、これは次のように表されます。
<app-root @ApplicationConfig @Inject(FlowerService) flower=>"🌺"> <#VIEW> <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p> <app-child @Provide(FlowerService="🌻") @Inject(FlowerService)=>"🌻"> <!-- 検索はここで終了します --> <#VIEW> <!-- 検索はここで開始します --> <h2>Child Component</h2> <p>Emoji from FlowerService: {{flower.emoji}} (🌻)</p> </#VIEW> </app-child> </#VIEW></app-root>
<app-child>
が FlowerService
を要求すると、インジェクターは <app-child>
に属する <#VIEW>
(@Component()
から注入されるため <#VIEW>
が含まれています)から始めて、 <app-child>
で終了します。
この場合、 FlowerService
は、 <app-child>
の providers
配列で、ひまわり 🌻
を使用して解決されます。
インジェクターは、インジェクターツリーをさらに検索する必要はありません。
FlowerService
を見つけるとすぐに停止し、赤いハイビスカス 🌺
は見えません。
viewProviders
配列を使用する
viewProviders
配列は、 @Component()
デコレーターでサービスを提供する別の方法です。
viewProviders
を使用すると、サービスは <#VIEW>
で可視になります。
HELPFUL: ステップは providers
配列を使用する場合と同じですが、 viewProviders
配列を使用する点が異なります。
ステップバイステップの手順については、このセクションを続行してください。 自分で設定できる場合は、サービスの可用性を変更する に進んでください。
デモのために、 AnimalService
を作成して、 viewProviders
を示します。
最初に、 emoji
プロパティがクジラ 🐳
である AnimalService
を作成します。
src/app/animal.service.ts
import { Injectable } from '@angular/core';@Injectable({ providedIn: 'root'})export class AnimalService { emoji = '🐳';}
FlowerService
と同じパターンに従って、 AppComponent
クラスに AnimalService
を注入します。
src/app/app.component.ts
export class AppComponent { constructor( public flower: FlowerService, public animal: AnimalService) {}}
HELPFUL: FlowerService
に関連するコードはすべてそのままにしておくことができます。これにより、 AnimalService
との比較が可能になります。
viewProviders
配列を追加し、 <app-child>
クラスにも AnimalService
を注入しますが、 emoji
に異なる値を与えます。
ここでは、犬 🐶
の値があります。
src/app/child.component.ts
@Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'], // サービスを提供します providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }], viewProviders: [{ provide: AnimalService, useValue: { emoji: '🐶' } }]})export class ChildComponent { // サービスを注入します constructor( public flower: FlowerService, public animal: AnimalService) { }...}
ChildComponent
と AppComponent
のテンプレートにバインディングを追加します。
ChildComponent
テンプレートに、次のバインディングを追加します。
src/app/child.component.html
<p>Emoji from AnimalService: {{animal.emoji}}</p>
さらに、 AppComponent
テンプレートにも同じものを追加します。
src/app/app.component.html
<p>Emoji from AnimalService: {{animal.emoji}}</p>s
これで、ブラウザに両方の値が表示されます。
AppComponentEmoji from AnimalService: 🐳Child ComponentEmoji from AnimalService: 🐶
この viewProviders
の例の論理ツリーは次のとおりです。
<app-root @ApplicationConfig @Inject(AnimalService) animal=>"🐳"> <#VIEW> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService=>"🐶")> <!-- ^^viewProviders を使用すると、AnimalService は <#VIEW> で使用可能になります--> <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p> </#VIEW> </app-child> </#VIEW></app-root>
FlowerService
の例と同様に、 AnimalService
は <app-child>
の @Component()
デコレーターで提供されています。
つまり、インジェクターは最初にコンポーネントの ElementInjector
を調べるため、犬 🐶
の AnimalService
値が見つかります。
インジェクターは、 ElementInjector
ツリーをさらに検索する必要も、 ModuleInjector
を検索する必要もありません。
providers
と viewProviders
の違い
viewProviders
フィールドは、概念的には providers
と似ていますが、1つの顕著な違いがあります。
viewProviders
で構成されたプロバイダーは、コンポーネントの論理的な子になる投影されたコンテンツには可視ではありません。
providers
と viewProviders
の違いを確認するために、別のコンポーネントを例に追加して、 InspectorComponent
と呼びます。
InspectorComponent
は、 ChildComponent
の子になります。
inspector.component.ts
で、コンストラクターに FlowerService
と AnimalService
を注入します。
src/app/inspector/inspector.component.ts
export class InspectorComponent { constructor(public flower: FlowerService, public animal: AnimalService) { }}
providers
または viewProviders
配列は必要ありません。
次に、 inspector.component.html
に、以前のコンポーネントと同じマークアップを追加します。
src/app/inspector/inspector.component.html
<p>Emoji from FlowerService: {{flower.emoji}}</p><p>Emoji from AnimalService: {{animal.emoji}}</p>
InspectorComponent
を ChildComponent
の imports
配列に追加することを忘れないでください。
src/app/child/child.component.ts
@Component({ ... imports: [InspectorComponent]})
次に、 child.component.html
に次を追加します。
src/app/child/child.component.html
...<div class="container"> <h3>Content projection</h3> <ng-content></ng-content></div><h3>Inside the view</h3><app-inspector></app-inspector>
<ng-content>
を使用するとコンテンツを投影でき、 ChildComponent
テンプレート内の <app-inspector>
は、 InspectorComponent
を ChildComponent
の子コンポーネントにします。
次に、コンテンツ投影を活用するために、 app.component.html
に次を追加します。
src/app/app.component.html
<app-child> <app-inspector></app-inspector></app-child>
これで、ブラウザには、以前の例は省略して、次のものがレンダリングされます。
...Content projectionEmoji from FlowerService: 🌻Emoji from AnimalService: 🐳Emoji from FlowerService: 🌻Emoji from AnimalService: 🐶
これらの4つのバインディングは、 providers
と viewProviders
の違いを示しています。
犬の絵文字 🐶
は、 ChildComponent
の <#VIEW>
内に宣言され、投影されたコンテンツには可視ではないことを覚えておいてください。
代わりに、投影されたコンテンツには、クジラ 🐳
が表示されます。
ただし、次の出力セクションでは InspectorComponent
は ChildComponent
の実際の子コンポーネントです。そして InspectorComponent
は <#VIEW>
の内側にあるため、 AnimalService
を要求すると、犬 🐶
が表示されます。
論理ツリー内の AnimalService
は、次のようになります。
<app-root @ApplicationConfig @Inject(AnimalService) animal=>"🐳"> <#VIEW> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService=>"🐶")> <!-- ^^viewProviders を使用すると、AnimalService は <#VIEW> で使用可能になります--> <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p> <div class="container"> <h3>Content projection</h3> <app-inspector @Inject(AnimalService) animal=>"🐳"> <p>Emoji from AnimalService: {{animal.emoji}} (🐳)</p> </app-inspector> </div> <app-inspector> <#VIEW @Inject(AnimalService) animal=>"🐶"> <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p> </#VIEW> </app-inspector> </#VIEW> </app-child> </#VIEW></app-root>
<app-inspector>
の投影されたコンテンツには、クジラ 🐳
が表示され、犬 🐶
は表示されません。これは、犬 🐶
が <app-child>
の <#VIEW>
の内側にあるためです。
<app-inspector>
は、 <#VIEW>
の内側にある場合にのみ、犬 🐶
を表示できます。
提供されたトークンの可視性
可視性デコレーターは、論理ツリー内でインジェクショントークンの検索を開始する場所と終了する場所を制御します。
これを行うには、可視性デコレーターを注入ポイント、つまり constructor()
に配置し、宣言ポイントに配置しないでください。
インジェクターが FlowerService
の検索を開始する場所を変更するには、 <app-child>
の @Inject
宣言に @SkipSelf()
を追加します。
この宣言は、 child.component.ts
に示すように、 <app-child>
のコンストラクターにあります。
constructor(@SkipSelf() public flower: FlowerService) { }
@SkipSelf()
を使用すると、 <app-child>
インジェクターは、 FlowerService
を自身で検索しません。
代わりに、インジェクターは <app-root>
の ElementInjector
で FlowerService
の検索を開始し、何も見つかりません。
次に、 <app-child>
の ModuleInjector
に戻り、 <app-child>
と <app-root>
が同じ ModuleInjector
を共有しているため、赤いハイビスカス 🌺
値が見つかります。
UIには次のように表示されます。
Emoji from FlowerService: 🌺
論理ツリーでは、この同じ考え方は次のようになります。
<app-root @ApplicationConfig @Inject(FlowerService) flower=>"🌺"> <#VIEW> <app-child @Provide(FlowerService="🌻")> <#VIEW @Inject(FlowerService, SkipSelf)=>"🌺"> <!-- SkipSelf を使用すると、インジェクターはツリー上の次のインジェクター(app-root)を調べます --> </#VIEW> </app-child> </#VIEW></app-root>
<app-child>
はひまわり 🌻
を提供しますが、アプリケーションは赤いハイビスカス 🌺
をレンダリングします。これは、 @SkipSelf()
によって現在のインジェクター(app-child
)が自身をスキップして親を調べるためです。
@Host()
を追加すると(@SkipSelf()
に加えて)、結果は null
になります。
これは、 @Host()
が検索の上限を <app-child>
の <#VIEW>
に制限するためです。
論理ツリーでの考え方は次のとおりです。
<app-root @ApplicationConfig @Inject(FlowerService) flower=>"🌺"> <#VIEW> <!-- ここで検索を終了して null を返します--> <app-child @Provide(FlowerService="🌻")> <!-- ここで検索を開始します --> <#VIEW @Inject(FlowerService, @SkipSelf, @Host, @Optional)=>null> </#VIEW> </app-parent> </#VIEW></app-root>
ここではサービスとその値は同じですが、 @Host()
によってインジェクターは <#VIEW>
より先を FlowerService
について検索できなくなるため、見つからずに null
を返します。
@SkipSelf()
と viewProviders
覚えておいてください。 <app-child>
は、 viewProviders
配列で AnimalService
を提供し、その値は犬 🐶
です。
インジェクターは、 <app-child>
の ElementInjector
を AnimalService
について調べるだけなので、クジラ 🐳
は見えません。
FlowerService
の例と同様に、コンストラクターに @SkipSelf()
を追加すると、インジェクターは現在の <app-child>
の ElementInjector
を AnimalService
について検索しません。
代わりに、インジェクターは <app-root>
の ElementInjector
で検索を開始します。
@Component({ standalone: true, selector: 'app-child', … viewProviders: [ { provide: AnimalService, useValue: { emoji: '🐶' } }, ],})
論理ツリーは、 <app-child>
に @SkipSelf()
がある場合、次のようになります。
<app-root @ApplicationConfig @Inject(AnimalService=>"🐳")> <#VIEW><!-- 検索はここで開始します --> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService, SkipSelf=>"🐳")> <!--@SkipSelf を追加します --> </#VIEW> </app-child> </#VIEW></app-root>
<app-child>
に @SkipSelf()
があると、インジェクターは <app-root>
の ElementInjector
で AnimalService
の検索を開始し、クジラ 🐳
を見つけます。
@Host()
と viewProviders
@Host()
を単独で使用した場合、結果は犬 🐶
になります。なぜなら、インジェクターは <app-child>
の <#VIEW>
自体で AnimalService
を見つけるためです。
ChildComponent
は、 viewProviders
を構成して、犬の絵文字が AnimalService
値として提供されるようにします。
また、コンストラクターに @Host()
があることもわかります。
@Component({ standalone: true selector: 'app-child', … viewProviders: [ { provide: AnimalService, useValue: { emoji: '🐶' } }, ]})export class ChildComponent { constructor(@Host() public animal: AnimalService) { }}
@Host()
によって、インジェクターは <#VIEW>
の端に出会うまで検索します。
<app-root @ApplicationConfig @Inject(AnimalService=>"🐳")> <#VIEW> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService, @Host=>"🐶")> <!-- @Host はここで検索を停止します --> </#VIEW> </app-child> </#VIEW></app-root>
3番目の動物、ハリネズミ 🦔
を含む viewProviders
配列を、 app.component.ts
の @Component()
メタデータに追加します。
@Component({ standalone: true, selector: 'app-root', templateUrl: './app.component.html', styleUrls: [ './app.component.css' ], viewProviders: [ { provide: AnimalService, useValue: { emoji: '🦔' } }, ],})
次に、 child.component.ts
で AnimalService
の注入のコンストラクターに @SkipSelf()
と @Host()
を追加します。
以下は、 <app-child>
のコンストラクターにある @Host()
と @SkipSelf()
です。
export class ChildComponent { constructor( @Host() @SkipSelf() public animal: AnimalService) { }}
@Host()
と @SkipSelf()
が providers
配列にある FlowerService
に適用された場合、結果は null
になりました。これは、 @SkipSelf()
が <app-child>
インジェクターで検索を開始しますが、 @Host()
は <#VIEW>
での検索を停止するためです。そこには FlowerService
はありません。
論理ツリーでは、 FlowerService
は <app-child>
で可視であり、 <#VIEW>
では可視ではないことがわかります。
ただし、 AppComponent
の viewProviders
配列で提供されている AnimalService
は可視です。
論理ツリーの表現は、これが理由を示しています。
<app-root @ApplicationConfig @Inject(AnimalService=>"🐳")> <#VIEW @Provide(AnimalService="🦔") @Inject(AnimalService, @Optional)=>"🦔"> <!-- ^^@SkipSelf() はここで開始し、 @Host() はここで停止します^^ --> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService, @SkipSelf, @Host, @Optional)=>"🦔"> <!-- @SkipSelf を追加します ^^--> </#VIEW> </app-child> </#VIEW></app-root>
@SkipSelf()
は、インジェクターが AnimalService
の検索を要求が発生した <app-child>
ではなく、 <app-root>
で開始するように指示し、 @Host()
は <app-root>
の <#VIEW>
で検索を停止します。
AnimalService
は viewProviders
配列を介して提供されるため、インジェクターは <#VIEW>
でハリネズミ 🦔
を見つけます。
例: ElementInjector
のユースケース
さまざまなレベルで1つ以上のプロバイダーを構成する機能により、便利な可能性が開かれます。
シナリオ:サービスの分離
アーキテクチャ上の理由から、サービスへのアクセスを属するアプリケーションドメインに制限する必要がある場合があります。
たとえば、悪役のリストを表示する VillainsListComponent
を構築するとします。
この悪役は、 VillainsService
から取得されます。
VillainsService
をルートの AppModule
で提供すると、 VillainsService
がアプリケーションのすべての場所で可視になります。
後で VillainsService
を変更した場合、誤ってこのサービスに依存し始めた他のコンポーネントで何かが壊れる可能性があります。
代わりに、次のように VillainsListComponent
の providers
メタデータで VillainsService
を提供する必要があります。
src/app/villains-list.component.ts (metadata)
@Component({ selector: 'app-villains-list', templateUrl: './villains-list.component.html', providers: [VillainsService]})export class VillainsListComponent {}
VillainsService
を VillainsListComponent
メタデータで提供し、他の場所では提供しないと、サービスは VillainsListComponent
とそのサブコンポーネントツリーのみに使用可能になります。
VillainService
は、 VillainsListComponent
に対してシングルトンです。なぜなら、それが宣言されている場所だからです。
VillainsListComponent
が破棄されない限り VillainService
のインスタンスは同じです。ただし VillainsListComponent
のインスタンスが複数ある場合、 VillainsListComponent
の各インスタンスには、独自の VillainService
インスタンスが1つずつあります。
シナリオ:複数の編集セッション
多くのアプリケーションでは、ユーザーは同時に複数のオープンタスクで作業できます。 たとえば、税務申告の作成アプリケーションでは、作成者は複数の税務申告で作業し、1日を通してそれらを切り替えることができます。
そのシナリオを示すために、スーパーヒーローのリストを表示する HeroListComponent
を考えてみてください。
ヒーローの税務申告を開くために、作成者はヒーローの名前をクリックすると、その申告を編集するためのコンポーネントが開きます。 選択したヒーローの税務申告は、それぞれ独自のコンポーネントで開き、複数の申告を同時に開くことができます。
各税務申告コンポーネントには、次の機能があります。
- 独自の税務申告の編集セッションを持ちます
- 別のコンポーネントの申告に影響を与えずに税務申告を変更できます
- 税務申告の変更を保存するか、キャンセルする機能があります
HeroTaxReturnComponent
に、変更を管理および復元するためのロジックがあるとします。
これは、ヒーローの税務申告にとっては簡単なタスクです。
現実世界では、豊富な税務申告データモデルでは、変更管理が複雑になります。
この例のように、この管理をヘルパーサービスに委任できます。
HeroTaxReturnService
は、単一の HeroTaxReturn
をキャッシュし、その申告への変更を追跡し、保存または復元できます。
また、注入によって取得したアプリケーション全体のシングルトン HeroService
に委任します。
src/app/hero-tax-return.service.ts
import { Injectable } from '@angular/core';import { HeroTaxReturn } from './hero';import { HeroesService } from './heroes.service';@Injectable()export class HeroTaxReturnService { private currentTaxReturn!: HeroTaxReturn; private originalTaxReturn!: HeroTaxReturn; constructor(private heroService: HeroesService) {} set taxReturn(htr: HeroTaxReturn) { this.originalTaxReturn = htr; this.currentTaxReturn = htr.clone(); } get taxReturn(): HeroTaxReturn { return this.currentTaxReturn; } restoreTaxReturn() { this.taxReturn = this.originalTaxReturn; } saveTaxReturn() { this.taxReturn = this.currentTaxReturn; this.heroService.saveTaxReturn(this.currentTaxReturn).subscribe(); }}
以下は、 HeroTaxReturnService
を使用する HeroTaxReturnComponent
です。
src/app/hero-tax-return.component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';import { HeroTaxReturn } from './hero';import { HeroTaxReturnService } from './hero-tax-return.service';@Component({ selector: 'app-hero-tax-return', templateUrl: './hero-tax-return.component.html', styleUrls: [ './hero-tax-return.component.css' ], providers: [ HeroTaxReturnService ]})export class HeroTaxReturnComponent { message = ''; @Output() close = new EventEmitter<void>(); get taxReturn(): HeroTaxReturn { return this.heroTaxReturnService.taxReturn; } @Input() set taxReturn(htr: HeroTaxReturn) { this.heroTaxReturnService.taxReturn = htr; } constructor(private heroTaxReturnService: HeroTaxReturnService) {} onCanceled() { this.flashMessage('Canceled'); this.heroTaxReturnService.restoreTaxReturn(); } onClose() { this.close.emit(); } onSaved() { this.flashMessage('Saved'); this.heroTaxReturnService.saveTaxReturn(); } flashMessage(msg: string) { this.message = msg; setTimeout(() => this.message = '', 500); }}
編集対象の税務申告 は、 @Input()
プロパティを介して到着します。これは、ゲッターとセッターで実装されています。
セッターは、コンポーネント自身の HeroTaxReturnService
インスタンスを、受信した申告で初期化します。
ゲッターは常に、そのサービスがヒーローの現在の状態であると判断したものを返します。
コンポーネントは、この税務申告を保存および復元することもサービスに要求します。
これは、サービスがアプリケーション全体のシングルトンである場合に機能しません。 すべてのコンポーネントは同じサービスインスタンスを共有し、各コンポーネントは別のヒーローに属する税務申告を上書きします。
これを防ぐために、コンポーネントレベルのインジェクター HeroTaxReturnComponent
を構成して、コンポーネントメタデータの providers
プロパティを使用してサービスを提供します。
src/app/hero-tax-return.component.ts (providers)
providers: [HeroTaxReturnService]
HeroTaxReturnComponent
には、 HeroTaxReturnService
の独自の提供者がいます。
覚えておいてください。すべてのコンポーネントの インスタンス には、独自のインジェクターがあります。
コンポーネントレベルでサービスを提供すると、コンポーネントの すべての インスタンスは、サービスのプライベートインスタンスを1つずつ取得することが保証されます。これにより、税務申告が上書きされないようにします。
HELPFUL: シナリオコードの残りは、ドキュメントの他の場所で学習できる他のAngular機能とテクニックに依存しています。
シナリオ:特殊なプロバイダー
別のレベルでサービスを再度提供するもう1つの理由は、コンポーネントツリーのさらに深い場所で、そのサービスの より特殊な 実装を置き換えるためです。
たとえば、タイヤサービスの情報を含み、他のサービスに依存して車の詳細情報を提供する Car
コンポーネントを検討してください。
ルートインジェクター((A) とマーク付け)は、 CarService
と EngineService
について、汎用的な プロバイダーを使用します。
Car
コンポーネント (A)。コンポーネント (A) は、車のタイヤサービスデータを表示し、車の詳細情報を提供するための汎用サービスを指定します。子コンポーネント (B)。コンポーネント (B) は、コンポーネント (B) で行われていることに適した特別な機能を持つ、
CarService
とEngineService
の独自の 特殊な プロバイダーを定義します。コンポーネント (B) の子である子コンポーネント (C)。コンポーネント (C) は、
CarService
の独自の、さらに 特殊な プロバイダーを定義します。
裏側では、各コンポーネントはコンポーネント自体に定義された、0、1または複数のプロバイダーで独自のインジェクターを設定します。
最も深いコンポーネント (C) で Car
のインスタンスを解決すると、そのインジェクターは次を生成します。
- インジェクター (C) によって解決された
Car
のインスタンス - インジェクター (B) によって解決された
Engine
- ルートインジェクター (A) によって解決された
Tires
。