From 93d689ffa8cc11131fc3297bec57b210949132f5 Mon Sep 17 00:00:00 2001 From: Kapunahele Wong Date: Tue, 13 Oct 2020 08:57:54 -0400 Subject: [PATCH] docs: edit dependency-injection.md (#39255) PR Close #39255 --- .../src/app/heroes/hero.service.3.ts | 2 +- .../guide/dependency-injection-providers.md | 41 ++- aio/content/guide/dependency-injection.md | 292 +++--------------- aio/content/guide/providers.md | 16 + 4 files changed, 89 insertions(+), 262 deletions(-) diff --git a/aio/content/examples/dependency-injection/src/app/heroes/hero.service.3.ts b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.3.ts index 9008bf15c3..402c5292ea 100644 --- a/aio/content/examples/dependency-injection/src/app/heroes/hero.service.3.ts +++ b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.3.ts @@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'; import { HEROES } from './mock-heroes'; @Injectable({ - // we declare that this service should be created + // declares that this service should be created // by the root application injector. providedIn: 'root', }) diff --git a/aio/content/guide/dependency-injection-providers.md b/aio/content/guide/dependency-injection-providers.md index 7f6b0032c8..dc3906be6c 100644 --- a/aio/content/guide/dependency-injection-providers.md +++ b/aio/content/guide/dependency-injection-providers.md @@ -17,9 +17,33 @@ You can, however, configure an injector with an alternative provider in order to You can configure an injector with a service class, you can provide a substitute class, an object, or a factory function. + +{@a token} + +{@a injection-token} + +## Dependency injection tokens + +When you configure an [injector](guide/glossary#injector) with a [provider](guide/glossary#provider), you are associating that provider with a [dependency injection token](guide/glossary#di-token), or DI token. +The injector allows Angular create a map of any internal dependencies. +The DI token acts as a key to that map. + +The dependency value is an instance, and the class type serves as a lookup key. +Here, the injector uses the `HeroService` type as the token for looking up `heroService`. + + + +When you define a constructor parameter with the `HeroService` class type, Angular knows to inject the service associated with that `HeroService` class token: + + + + +Though classes provide many dependency values, the expanded `provide` object lets you associate different kinds of providers with a DI token. + + {@a provide} -## Defining providers +## Defining providers The class provider syntax is a shorthand expression that expands into a provider configuration, defined by the [`Provider` interface](api/core/Provider). The following example is the class provider syntax for providing a `Logger` class in the `providers` array. @@ -34,19 +58,20 @@ Angular expands the `providers` value into a full provider object as follows. The expanded provider configuration is an object literal with two properties. -1. The `provide` property holds the [token](guide/dependency-injection#token) that serves as the key for both locating a dependency value and configuring the injector. -2. The second property is a provider definition object, which tells the injector how to create the dependency value. +* The `provide` property holds the [token](#token) +that serves as the key for both locating a dependency value and configuring the injector. + +* The second property is a provider definition object, which tells the injector how to create the dependency value. The provider-definition key can be `useClass`, as in the example. It can also be `useExisting`, `useValue`, or `useFactory`. Each of these keys provides a different type of dependency, as discussed below. - {@a class-provider} -## Configuring the injector to use alternative class providers +## Specifying an alternative class provider -To configure the injector to return a different class that provides the same service, you can use the `useClass` property. -In this example, the injector returns a `BetterLogger` instance when using the `Logger` token. +Different classes can provide the same service. +For example, the following code tells the injector to return a `BetterLogger` instance when the component asks for a logger using the `Logger` token. @@ -198,7 +223,7 @@ You inject both `Logger` and `UserService` into the factory provider so the inje * The `useFactory` field specifies that the provider is a factory function whose implementation is `heroServiceFactory`. -* The `deps` property is an array of [provider tokens](guide/dependency-injection#token). +* The `deps` property is an array of [provider tokens](#token). The `Logger` and `UserService` classes serve as tokens for their own class providers. The injector resolves these tokens and injects the corresponding services into the matching `heroServiceFactory` factory function parameters. diff --git a/aio/content/guide/dependency-injection.md b/aio/content/guide/dependency-injection.md index 38fbfc6afb..c66cb9382b 100644 --- a/aio/content/guide/dependency-injection.md +++ b/aio/content/guide/dependency-injection.md @@ -1,217 +1,80 @@ # Dependency injection in Angular -Dependency injection (DI), is an important application design pattern. -Angular has its own DI framework, which is typically -used in the design of Angular applications to increase their efficiency and modularity. - Dependencies are services or objects that a class needs to perform its function. -DI is a coding pattern in which a class asks for dependencies from external sources rather than creating them itself. +Dependency injection, or DI, is a design pattern in which a class requests dependencies from external sources rather than creating them. -In Angular, the DI framework provides declared dependencies to a class when that class is instantiated. This guide explains how DI works in Angular, and how you use it to make your apps flexible, efficient, and robust, as well as testable and maintainable. +Angular's DI framework provides dependencies to a class upon instantiation. +You can use Angular DI to increase flexibility and modularity in your applications.
- You can run the of the sample app that accompanies this guide. +See the for a working example containing the code snippets in this guide.
-Start by reviewing this simplified version of the _heroes_ feature -from the [The Tour of Heroes](tutorial/). This simple version doesn't use DI; we'll walk through converting it to do so. +## Creating an injectable service - - - - - - - - - - - - - - - -`HeroesComponent` is the top-level heroes component. -Its only purpose is to display `HeroListComponent`, which displays a list of hero names. - -This version of the `HeroListComponent` gets heroes from the `HEROES` array, an in-memory collection -defined in a separate `mock-heroes` file. - - - - -This approach works for prototyping, but is not robust or maintainable. -As soon as you try to test this component or get heroes from a remote server, -you have to change the implementation of `HeroesListComponent` and -replace every use of the `HEROES` mock data. - - -## Create and register an injectable service - -The DI framework lets you supply data to a component from an injectable _service_ class, defined in its own file. To demonstrate, we'll create an injectable service class that provides a list of heroes, and register that class as a provider of that service. - -
- -Having multiple classes in the same file can be confusing. We generally recommend that you define components and services in separate files. - -If you do combine a component and service in the same file, -it is important to define the service first, and then the component. If you define the component before the service, you get a run-time null reference error. - -It is possible to define the component first with the help of the `forwardRef()` method as explained in this [blog post](https://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html). - -You can also use forward references to break circular dependencies. -See an example in the [DI Cookbook](guide/dependency-injection-in-action#forwardref). - -
- -### Create an injectable service class - -The [Angular CLI](cli) can generate a new `HeroService` class in the `src/app/heroes` folder with this command. +To generate a new `HeroService` class in the `src/app/heroes` folder use the following [Angular CLI](cli) command. ng generate service heroes/hero -The command creates the following `HeroService` skeleton. +This command creates the following default `HeroService`. -The `@Injectable()` is an essential ingredient in every Angular service definition. The rest of the class has been written to expose a `getHeroes` method that returns the same mock data as before. (A real app would probably get its data asynchronously from a remote server, but we'll ignore that to focus on the mechanics of injecting the service.) +The `@Injectable()` decorator specifies that Angular can use this class in the DI system. +The metadata, `providedIn: 'root'`, means that the `HeroService` is visible throughout the application. + +Next, to get the hero mock data, add a `getHeroes()` method that returns the heroes from `mock.heroes.ts`. +For clarity and maintainability, it is recommended that you define components and services in separate files. -{@a injector-config} -{@a bootstrap} +If you do combine a component and service in the same file, it is important to define the service first, and then the component. +If you define the component before the service, Angular returns a run-time null reference error. -### Configure an injector with a service provider - -The class we have created provides a service. The `@Injectable()` decorator marks it as a service -that can be injected, but Angular can't actually inject it anywhere until you configure -an Angular [dependency injector](guide/glossary#injector) with a [provider](guide/glossary#provider) of that service. - -The injector is responsible for creating service instances and injecting them into classes like `HeroListComponent`. -You rarely create an Angular injector yourself. Angular creates injectors for you as it executes the app, starting with the _root injector_ that it creates during the [bootstrap process](guide/bootstrapping). - -A provider tells an injector _how to create the service_. -You must configure an injector with a provider before that injector can create a service (or provide any other kind of dependency). - -A provider can be the service class itself, so that the injector can use `new` to create an instance. -You might also define more than one class to provide the same service in different ways, -and configure different injectors with different providers. - -
- -Injectors are inherited, which means that if a given injector can't resolve a dependency, -it asks the parent injector to resolve it. -A component can get services from its own injector, -from the injectors of its component ancestors, -from the injector of its parent NgModule, or from the `root` injector. - -* Learn more about the [different kinds of providers](guide/dependency-injection-providers). - -* Learn more about how the [injector hierarchy](guide/hierarchical-dependency-injection) works. - -
- -You can configure injectors with providers at different levels of your app, by setting a metadata value in one of three places: - -* In the `@Injectable()` decorator for the service itself. - -* In the `@NgModule()` decorator for an NgModule. - -* In the `@Component()` decorator for a component. - -The `@Injectable()` decorator has the `providedIn` metadata option, where you can specify the provider of the decorated service class with the `root` injector, or with the injector for a specific NgModule. - -The `@NgModule()` and `@Component()` decorators have the `providers` metadata option, where you can configure providers for NgModule-level or component-level injectors. - -
- -Components are directives, and the `providers` option is inherited from `@Directive()`. You can also configure providers for directives and pipes at the same level as the component. - -Learn more about [where to configure providers](guide/hierarchical-dependency-injection). - -
{@a injector-config} {@a bootstrap} ## Injecting services -In order for `HeroListComponent` to get heroes from `HeroService`, it needs to ask for `HeroService` to be injected, rather than creating its own `HeroService` instance with `new`. +Injecting services results in making them visible to a component. -You can tell Angular to inject a dependency in a component's constructor by specifying a **constructor parameter with the dependency type**. Here's the `HeroListComponent` constructor, asking for the `HeroService` to be injected. +To inject a dependency in a component's `constructor()`, supply a constructor argument with the dependency type. +The following example specifies the `HeroService` in the `HeroListComponent` constructor. +The type of `heroService` is `HeroService`. -Of course, `HeroListComponent` should do something with the injected `HeroService`. -Here's the revised component, making use of the injected service, side-by-side with the previous version for comparison. - - - - - - - - -`HeroService` must be provided in some parent injector. The code in `HeroListComponent` doesn't depend on where `HeroService` comes from. -If you decided to provide `HeroService` in `AppModule`, `HeroListComponent` wouldn't change. - -{@a singleton-services} -{@a component-child-injectors} - -### Injector hierarchy and service instances - -Services are singletons _within the scope of an injector_. That is, there is at most one instance of a service in a given injector. - -There is only one root injector for an app. Providing `UserService` at the `root` or `AppModule` level means it is registered with the root injector. There is just one `UserService` instance in the entire app and every class that injects `UserService` gets this service instance _unless_ you configure another provider with a _child injector_. - -Angular DI has a [hierarchical injection system](guide/hierarchical-dependency-injection), which means that nested injectors can create their own service instances. -Angular regularly creates nested injectors. Whenever Angular creates a new instance of a component that has `providers` specified in `@Component()`, it also creates a new _child injector_ for that instance. -Similarly, when a new NgModule is lazy-loaded at run time, Angular can create an injector for it with its own providers. - -Child modules and component injectors are independent of each other, and create their own separate instances of the provided services. When Angular destroys an NgModule or component instance, it also destroys that injector and that injector's service instances. - -Thanks to [injector inheritance](guide/hierarchical-dependency-injection), -you can still inject application-wide services into these components. -A component's injector is a child of its parent component's injector, and inherits from all ancestor injectors all the way back to the application's _root_ injector. Angular can inject a service provided by any injector in that lineage. - -For example, Angular can inject `HeroListComponent` with both the `HeroService` provided in `HeroComponent` and the `UserService` provided in `AppModule`. - -{@a testing-the-component} - -## Testing components with dependencies - -Designing a class with dependency injection makes the class easier to test. -Listing dependencies as constructor parameters may be all you need to test application parts effectively. - -For example, you can create a new `HeroListComponent` with a mock service that you can manipulate -under test. - - - -
- -Learn more in the [Testing](guide/testing) guide. - -
+For more information, see and [Providing dependencies in modules](guide/providers) and [Hierarchical injectors](guide/hierarchical-dependency-injection). {@a service-needs-service} -## Services that need other services +## Using services in other services -Services can have their own dependencies. `HeroService` is very simple and doesn't have any dependencies of its own. Suppose, however, that you want it to report its activities through a logging service. You can apply the same *constructor injection* pattern, -adding a constructor that takes a `Logger` parameter. +When a service depends on another service, follow the same pattern as injecting into a component. +In the following example `HeroService` depends on a `Logger` service to report its activities. -Here is the revised `HeroService` that injects `Logger`, side by side with the previous service for comparison. +First, import the `Logger` service. +Next, inject the `Logger` service in the `HeroService` `constructor()` by specifying `private logger: Logger` within the parentheses. + +When you create a class whose `constructor()` has parameters, specify the type and metadata about those parameters so that Angular can inject the correct service. + +Here, the `constructor()` specifies a type of `Logger` and stores the instance of `Logger` in a private field called `logger`. + + +The following code tabs feature the `Logger` service and two versions of `HeroService`. +The first version of `HeroService` does not depend on the `Logger` service. +The revised second version does depend on `Logger` service. @@ -227,90 +90,13 @@ Here is the revised `HeroService` that injects `Logger`, side by side with the p -The constructor asks for an injected instance of `Logger` and stores it in a private field called `logger`. The `getHeroes()` method logs a message when asked to fetch heroes. -Notice that the `Logger` service also has the `@Injectable()` decorator, even though it might not need its own dependencies. In fact, the `@Injectable()` decorator is **required for all services**. +In this example, the `getHeroes()` method uses the `Logger` service by logging a message when fetching heroes. -When Angular creates a class whose constructor has parameters, it looks for type and injection metadata about those parameters so that it can inject the correct service. -If Angular can't find that parameter information, it throws an error. -Angular can only find the parameter information _if the class has a decorator of some kind_. -The `@Injectable()` decorator is the standard decorator for service classes. +
-
+## What's next - The decorator requirement is imposed by TypeScript. TypeScript normally discards parameter type information when it [transpiles](guide/glossary#transpile) the code to JavaScript. TypeScript preserves this information if the class has a decorator and the `emitDecoratorMetadata` compiler option is set `true` in TypeScript's `tsconfig.json` configuration file. The CLI configures `tsconfig.json` with `emitDecoratorMetadata: true`. - - This means you're responsible for putting `@Injectable()` on your service classes. - -
- -{@a token} - -{@a injection-token} - -### Dependency injection tokens - -When you configure an injector with a provider, you associate that provider with a [DI token](guide/glossary#di-token). -The injector maintains an internal *token-provider* map that it references when -asked for a dependency. The token is the key to the map. - -In simple examples, the dependency value is an *instance*, and -the class *type* serves as its own lookup key. -Here you get a `HeroService` directly from the injector by supplying the `HeroService` type as the token: - - - -The behavior is similar when you write a constructor that requires an injected class-based dependency. -When you define a constructor parameter with the `HeroService` class type, -Angular knows to inject the service associated with that `HeroService` class token: - - - - -Many dependency values are provided by classes, but not all. The expanded *provide* object lets you associate different kinds of providers with a DI token. - -* Learn more about [different kinds of providers](guide/dependency-injection-providers). - -{@a optional} - -### Optional dependencies - -`HeroService` *requires* a logger, but what if it could get by without -one? - -When a component or service declares a dependency, the class constructor takes that dependency as a parameter. -You can tell Angular that the dependency is optional by annotating the -constructor parameter with `@Optional()`. - - - - - - -When using `@Optional()`, your code must be prepared for a null value. If you -don't register a logger provider anywhere, the injector sets the -value of `logger` to null. - -
- -`@Inject()` and `@Optional()` are _parameter decorators_. They alter the way the DI framework provides a dependency, by annotating the dependency parameter on the constructor of the class that requires the dependency. - -Learn more about parameter decorators in [Hierarchical Dependency Injectors](guide/hierarchical-dependency-injection). - -
- -## Summary - -You learned the basics of Angular dependency injection in this page. -You can register various kinds of providers, -and you know how to ask for an injected object (such as a service) by -adding a parameter to a constructor. - -Dive deeper into the capabilities and advanced feature of the Angular DI system in the following pages: - -* Learn more about nested injectors in -[Hierarchical Dependency Injection](guide/hierarchical-dependency-injection). - -* Learn more about [DI tokens and providers](guide/dependency-injection-providers). - -* [Dependency Injection in Action](guide/dependency-injection-in-action) is a cookbook for some of the interesting things you can do with DI. +* [Dependency providers](guide/dependency-injection-providers) +* [DI tokens and providers](guide/dependency-injection-providers) +* [Dependency Injection in Action](guide/dependency-injection-in-action) diff --git a/aio/content/guide/providers.md b/aio/content/guide/providers.md index a246b3b1ea..2d7b8e1d94 100644 --- a/aio/content/guide/providers.md +++ b/aio/content/guide/providers.md @@ -81,6 +81,22 @@ The router works at the root level so if you put providers in a component, even Register a provider with a component when you must limit a service instance to a component and its component tree, that is, its child components. For example, a user editing component, `UserEditorComponent`, that needs a private copy of a caching `UserService` should register the `UserService` with the `UserEditorComponent`. Then each new instance of the `UserEditorComponent` gets its own cached service instance. +{@a singleton-services} +{@a component-child-injectors} + +## Injector hierarchy and service instances + +Services are singletons within the scope of an injector, which means there is at most one instance of a service in a given injector. + +Angular DI has a [hierarchical injection system](guide/hierarchical-dependency-injection), which means that nested injectors can create their own service instances. +Whenever Angular creates a new instance of a component that has `providers` specified in `@Component()`, it also creates a new child injector for that instance. +Similarly, when a new NgModule is lazy-loaded at run time, Angular can create an injector for it with its own providers. + +Child modules and component injectors are independent of each other, and create their own separate instances of the provided services. When Angular destroys an NgModule or component instance, it also destroys that injector and that injector's service instances. + +For more information, see [Hierarchical injectors](guide/hierarchical-dependency-injection). + +