docs: edit di providers doc (#39403)

Edits copy and removes tokens and treeshaking
sections to reduce content duplication and keep
info focused. Moves provideParent() from di-navtree
to di providers.

PR Close #39403
This commit is contained in:
Kapunahele Wong 2020-10-22 14:16:51 -04:00 committed by Andrew Kushnir
parent 9aaa86967d
commit 156f9f30ae
8 changed files with 127 additions and 264 deletions

View File

@ -205,7 +205,7 @@ export class Provider8Component {
template, template,
/* /*
// #docregion providers-9-interface // #docregion providers-9-interface
// FAIL! Can't use interface as provider token // Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })] [{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
// #enddocregion providers-9-interface // #enddocregion providers-9-interface
*/ */
@ -217,7 +217,7 @@ export class Provider9Component implements OnInit {
log: string; log: string;
/* /*
// #docregion provider-9-ctor-interface // #docregion provider-9-ctor-interface
// FAIL! Can't inject using the interface as the parameter type // Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ } constructor(private config: AppConfig){ }
// #enddocregion provider-9-ctor-interface // #enddocregion provider-9-ctor-interface
*/ */

View File

@ -93,7 +93,7 @@ or in the `@NgModule()` or `@Component()` metadata
When you provide the service at the root level, Angular creates a single, shared instance of `HeroService` When you provide the service at the root level, Angular creates a single, shared instance of `HeroService`
and injects it into any class that asks for it. and injects it into any class that asks for it.
Registering the provider in the `@Injectable()` metadata also allows Angular to optimize an app Registering the provider in the `@Injectable()` metadata also allows Angular to optimize an app
by removing the service from the compiled app if it isn't used. by removing the service from the compiled app if it isn't used, a process known as *tree-shaking*.
* When you register a provider with a [specific NgModule](guide/architecture-modules), the same instance of a service is available to all components in that NgModule. To register at this level, use the `providers` property of the `@NgModule()` decorator. * When you register a provider with a [specific NgModule](guide/architecture-modules), the same instance of a service is available to all components in that NgModule. To register at this level, use the `providers` property of the `@NgModule()` decorator.

View File

@ -79,7 +79,7 @@ Here are some things to consider in migrating application functionality to a lib
* Consider how you provide services to client applications. * Consider how you provide services to client applications.
* Services should declare their own providers (rather than declaring providers in the NgModule or a component), so that they are *tree-shakable*. This allows the compiler to leave the service out of the bundle if it never gets injected into the application that imports the library. For more about this, see [Tree-shakable providers](guide/dependency-injection-providers#tree-shakable-providers). * Services should declare their own providers, rather than declaring providers in the NgModule or a component. Declaring a provider makes that service *tree-shakable*. This practice allows the compiler to leave the service out of the bundle if it never gets injected into the application that imports the library. For more about this, see [Tree-shakable providers](guide/architecture-services#providing-services).
* If you register global service providers or share providers across multiple NgModules, use the [`forRoot()` and `forChild()` design patterns](guide/singleton-services) provided by the [RouterModule](api/router/RouterModule). * If you register global service providers or share providers across multiple NgModules, use the [`forRoot()` and `forChild()` design patterns](guide/singleton-services) provided by the [RouterModule](api/router/RouterModule).

View File

@ -447,7 +447,7 @@ But they did neither.
When you use a class this way, it's called a *class interface*. When you use a class this way, it's called a *class interface*.
As mentioned in [DI Providers](guide/dependency-injection-providers#interface-not-valid-token), As mentioned in [DI Providers](guide/dependency-injection-providers#di-and-interfaces),
an interface is not a valid DI token because it is a TypeScript artifact that doesn't exist at run time. an interface is not a valid DI token because it is a TypeScript artifact that doesn't exist at run time.
Use this abstract class interface to get the strong typing of an interface, Use this abstract class interface to get the strong typing of an interface,
and also use it as a provider token in the way you would a normal class. and also use it as a provider token in the way you would a normal class.

View File

@ -1,49 +1,41 @@
# Dependency providers # Dependency providers
A dependency [provider](guide/glossary#provider) configures an injector By configuring providers, you can make services available to the parts of your application that need them.
with a [DI token](guide/glossary#di-token),
which that injector uses to provide the concrete, runtime version of a dependency value.
The injector relies on the provider configuration to create instances of the dependencies
that it injects into components, directives, pipes, and other services.
You must configure an injector with a provider, or it won't know how to create the dependency. A dependency [provider](guide/glossary#provider) configures an injector with a [DI token](guide/glossary#di-token), which that injector uses to provide the runtime version of a dependency value.
The most obvious way for an injector to create an instance of a service class is with the class itself.
If you specify the service class itself as the provider token, the default behavior is for the injector to instantiate that class with `new`.
In the following typical example, the `Logger` class itself provides a `Logger` instance. ## Specifying a provider token
If you specify the service class as the provider token, the default behavior is for the injector to instantiate that class with `new`.
In the following example, the `Logger` class provides a `Logger` instance.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger"> <code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger">
</code-example> </code-example>
You can, however, configure an injector with an alternative provider, You can, however, configure an injector with an alternative provider in order to deliver some other object that provides the needed logging functionality.
in order to deliver some other object that provides the needed logging functionality.
For instance:
* You can provide a substitute class.
* You can provide a logger-like object. You can configure an injector with a service class, you can provide a substitute class, an object, or a factory function.
* Your provider can call a logger factory function.
{@a provide} {@a provide}
## The `Provider` object literal ## Defining providers
The class-provider syntax is a shorthand expression that expands The class provider syntax is a shorthand expression that expands into a provider configuration, defined by the [`Provider` interface](api/core/Provider).
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.
The following code snippets shows how a class that is given as the `providers` value is expanded into a full provider object.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger"> <code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger">
</code-example> </code-example>
Angular expands the `providers` value into a full provider object as follows.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-3" > <code-example path="dependency-injection/src/app/providers.component.ts" region="providers-3" >
</code-example> </code-example>
The expanded provider configuration is an object literal with two properties. The expanded provider configuration is an object literal with two properties.
* The `provide` property holds the [token](guide/dependency-injection#token) 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.
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 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. The provider-definition key can be `useClass`, as in the example.
It can also be `useExisting`, `useValue`, or `useFactory`. It can also be `useExisting`, `useValue`, or `useFactory`.
Each of these keys provides a different type of dependency, as discussed below. Each of these keys provides a different type of dependency, as discussed below.
@ -51,185 +43,168 @@ Each of these keys provides a different type of dependency, as discussed below.
{@a class-provider} {@a class-provider}
## Alternative class providers ## Configuring the injector to use alternative class providers
Different classes can provide the same service. To configure the injector to return a different class that provides the same service, you can use the `useClass` property.
For example, the following code tells the injector In this example, the injector returns a `BetterLogger` instance when using the `Logger` token.
to return a `BetterLogger` instance when the component asks for a logger
using the `Logger` token.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-4" > <code-example path="dependency-injection/src/app/providers.component.ts" region="providers-4" >
</code-example> </code-example>
{@a class-provider-dependencies} {@a class-provider-dependencies}
### Class providers with dependencies ### Configuring class providers with dependencies
Another class, `EvenBetterLogger`, might display the user name in the log message. If the alternative class providers have their own dependencies, specify both providers in the `providers` metadata property of the parent module or component.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-5"></code-example>
In this example, `EvenBetterLogger` displays the user name in the log message.
This logger gets the user from an injected `UserService` instance. This logger gets the user from an injected `UserService` instance.
<code-example path="dependency-injection/src/app/providers.component.ts" region="EvenBetterLogger"></code-example> <code-example path="dependency-injection/src/app/providers.component.ts" region="EvenBetterLogger"></code-example>
The injector needs providers for both this new logging service and its dependent `UserService`. Configure this alternative logger with the `useClass` provider-definition key, like `BetterLogger`. The following array specifies both providers in the `providers` metadata option of the parent module or component. The injector needs providers for both this new logging service and its dependent `UserService`.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-5"></code-example>
{@a aliased-class-providers} {@a aliased-class-providers}
### Aliased class providers ### Aliasing class providers
Suppose an old component depends upon the `OldLogger` class. To alias a class provider, specify the alias and the class provider in the `providers` array with the `useExisting` property.
`OldLogger` has the same interface as `NewLogger`, but for some reason
you can't update the old component to use it.
When the old component logs a message with `OldLogger`, In the following example, the injector injects the singleton instance of `NewLogger` when the component asks for either the new or the old logger.
you want the singleton instance of `NewLogger` to handle it instead. In this way, `OldLogger` is an alias for `NewLogger`.
In this case, the dependency injector should inject that singleton instance
when a component asks for either the new or the old logger.
`OldLogger` should be an *alias* for `NewLogger`.
If you try to alias `OldLogger` to `NewLogger` with `useClass`, you end up with two different `NewLogger` instances in your app.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6a"></code-example>
To make sure there is only one instance of `NewLogger`, alias `OldLogger` with the `useExisting` option.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6b"></code-example> <code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6b"></code-example>
Be sure you don't alias `OldLogger` to `NewLogger` with `useClass`, as this creates two different `NewLogger` instances.
{@a provideparent}
## Aliasing a class interface
Generally, writing variations of the same parent alias provider uses [forwardRef](guide/dependency-injection-in-action#forwardref) as follows.
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alex-providers" header="dependency-injection-in-action/src/app/parent-finder.component.ts"></code-example>
To streamline your code, you can extract that logic into a helper function using the `provideParent()` helper function.
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="provide-the-parent" header="dependency-injection-in-action/src/app/parent-finder.component.ts"></code-example>
Now you can add a parent provider to your components that's easier to read and understand.
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="alice-providers" header="dependency-injection-in-action/src/app/parent-finder.component.ts"></code-example>
### Aliasing multiple class interfaces
To alias multiple parent types, each with its own class interface token, configure `provideParent()` to accept more arguments.
Here's a revised version that defaults to `parent` but also accepts an optional second parameter for a different parent class interface.
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="provide-parent" header="dependency-injection-in-action/src/app/parent-finder.component.ts"></code-example>
Next, to use `provideParent()` with a different parent type, provide a second argument, here `DifferentParent`.
<code-example path="dependency-injection-in-action/src/app/parent-finder.component.ts" region="beth-providers" header="dependency-injection-in-action/src/app/parent-finder.component.ts"></code-example>
{@a value-provider} {@a value-provider}
## Value providers ## Injecting an object
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
To inject an object you have already created,
configure the injector with the `useValue` option
The following code defines a variable that creates such an object to play the logger role.
<code-example path="dependency-injection/src/app/providers.component.ts" region="silent-logger"></code-example>
To inject an object, configure the injector with the `useValue` option.
The following provider object uses the `useValue` key to associate the variable with the `Logger` token. The following provider object uses the `useValue` key to associate the variable with the `Logger` token.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-7"></code-example> <code-example path="dependency-injection/src/app/providers.component.ts" region="providers-7"></code-example>
In this example, `SilentLogger` is an object that fulfills the logger role.
<code-example path="dependency-injection/src/app/providers.component.ts" region="silent-logger"></code-example>
{@a non-class-dependencies} {@a non-class-dependencies}
### Non-class dependencies ### Injecting a configuration object
Not all dependencies are classes. A common use case for object literals is a configuration object.
Sometimes you want to inject a string, function, or object. The following configuration object includes the title of the application and the address of a web API endpoint.
Apps often define configuration objects with lots of small facts,
like the title of the application or the address of a web API endpoint.
These configuration objects aren't always instances of a class.
They can be object literals, as shown in the following example.
<code-example path="dependency-injection/src/app/app.config.ts" region="config" header="src/app/app.config.ts (excerpt)"></code-example> <code-example path="dependency-injection/src/app/app.config.ts" region="config" header="src/app/app.config.ts (excerpt)"></code-example>
{@a interface-not-valid-token} To provide and inject the configuration object, specify the object in the `@NgModule()` `providers` array.
**TypeScript interfaces are not valid tokens** <code-example path="dependency-injection/src/app/app.module.ts" region="providers" header="src/app/app.module.ts (providers)"></code-example>
The `HERO_DI_CONFIG` constant conforms to the `AppConfig` interface. {@a injectiontoken}
Unfortunately, you cannot use a TypeScript interface as a token.
In TypeScript, an interface is a design-time artifact, and doesn't have a runtime representation (token) that the DI framework can use. ### Using an `InjectionToken` object
You can define and use an `InjectionToken` object for choosing a provider token for non-class dependencies.
The following example defines a token, `APP_CONFIG` of the type `InjectionToken`.
<code-example path="dependency-injection/src/app/app.config.ts" region="token" header="src/app/app.config.ts"></code-example>
The optional type parameter, `app.config`, and the token description, `<AppConfig>` specify the token's purpose.
Next, register the dependency provider in the component using the `InjectionToken` object of `APP_CONFIG`.
<code-example path="dependency-injection/src/app/providers.component.ts" header="src/app/providers.component.ts" region="providers-9"></code-example>
Now you can inject the configuration object into the constructor with `@Inject()` parameter decorator.
<code-example path="dependency-injection/src/app/app.component.2.ts" region="ctor" header="src/app/app.component.ts"></code-example>
{@a di-and-interfaces}
#### Interfaces and dependency injection
Though the TypeScript `AppConfig` interface supports typing within the class, the `AppConfig` interface plays no role in dependency injection.
In TypeScript, an interface is a design-time artifact, and doesn't have a runtime representation, or token, that the DI framework can use.
When the transpiler changes TypeScript to JavaScript, the interface disappears because JavaScript doesn't have interfaces.
Since there is no interface for Angular to find at runtime, the interface cannot be a token, nor can you inject it.
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9-interface"></code-example> <code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9-interface"></code-example>
<code-example path="dependency-injection/src/app/providers.component.ts" region="provider-9-ctor-interface"></code-example> <code-example path="dependency-injection/src/app/providers.component.ts" region="provider-9-ctor-interface"></code-example>
<div class="alert is-helpful">
This might seem strange if you're used to dependency injection in strongly typed languages where an interface is the preferred dependency lookup key.
However, JavaScript, doesn't have interfaces, so when TypeScript is transpiled to JavaScript, the interface disappears.
There is no interface type information left for Angular to find at runtime.
</div>
One alternative is to provide and inject the configuration object in an NgModule like `AppModule`.
<code-example path="dependency-injection/src/app/app.module.ts" region="providers" header="src/app/app.module.ts (providers)"></code-example>
Another solution to choosing a provider token for non-class dependencies is
to define and use an `InjectionToken` object.
The following example shows how to define such a token.
<code-example path="dependency-injection/src/app/app.config.ts" region="token" header="src/app/app.config.ts"></code-example>
The type parameter, while optional, conveys the dependency's type to developers and tooling.
The token description is another developer aid.
Register the dependency provider using the `InjectionToken` object:
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9"></code-example>
Now you can inject the configuration object into any constructor that needs it, with
the help of an `@Inject()` parameter decorator.
<code-example path="dependency-injection/src/app/app.component.2.ts" region="ctor" header="src/app/app.component.ts"></code-example>
<div class="alert is-helpful">
Although the `AppConfig` interface plays no role in dependency injection,
it supports typing of the configuration object within the class.
</div>
{@a factory-provider} {@a factory-provider}
{@a factory-providers} {@a factory-providers}
## Factory providers ## Using factory providers
Sometimes you need to create a dependent value dynamically, To create a changeable, dependent value based on information unavailable before run time, you can use a factory provider.
based on information you won't have until run time.
For example, you might need information that changes repeatedly in the course of the browser session.
Also, your injectable service might not have independent access to the source of the information.
In cases like this you can use a *factory provider*. In the following example, only authorized users should see secret heroes in the `HeroService`.
Factory providers can also be useful when creating an instance of a dependency from Authorization can change during the course of a single application session, as when a different user logs in .
a third-party library that wasn't designed to work with DI.
For example, suppose `HeroService` must hide *secret* heroes from normal users. To keep security-sensitive information in `UserService` and out of `HeroService`, give the `HeroService` constructor a boolean flag to control display of secret heroes.
Only authorized users should see secret heroes.
Like `EvenBetterLogger`, `HeroService` needs to know if the user is authorized to see secret heroes.
That authorization can change during the course of a single application session,
as when you log in a different user.
Imagine that you don't want to inject `UserService` directly into `HeroService`, because you don't want to complicate that service with security-sensitive information.
`HeroService` won't have direct access to the user information to decide
who is authorized and who isn't.
To resolve this, give the `HeroService` constructor a boolean flag to control display of secret heroes.
<code-example path="dependency-injection/src/app/heroes/hero.service.ts" region="internals" header="src/app/heroes/hero.service.ts (excerpt)"></code-example> <code-example path="dependency-injection/src/app/heroes/hero.service.ts" region="internals" header="src/app/heroes/hero.service.ts (excerpt)"></code-example>
You can inject `Logger`, but you can't inject the `isAuthorized` flag. Instead, you can use a factory provider to create a new logger instance for `HeroService`. To implement the `isAuthorized` flag, use a factory provider to create a new logger instance for `HeroService`.
A factory provider needs a factory function.
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="factory" header="src/app/heroes/hero.service.provider.ts (excerpt)"></code-example> <code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="factory" header="src/app/heroes/hero.service.provider.ts (excerpt)"></code-example>
Although `HeroService` has no access to `UserService`, the factory function does. The factory function has access to `UserService`.
You inject both `Logger` and `UserService` into the factory provider You inject both `Logger` and `UserService` into the factory provider so the injector can pass them along to the factory function.
and let the injector pass them along to the factory function.
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="provider" header="src/app/heroes/hero.service.provider.ts (excerpt)"></code-example> <code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="provider" header="src/app/heroes/hero.service.provider.ts (excerpt)"></code-example>
* The `useFactory` field tells Angular that the provider is a factory function whose implementation is `heroServiceFactory`. * 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](guide/dependency-injection#token).
The `Logger` and `UserService` classes serve as tokens for their own class providers. 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 factory function parameters. The injector resolves these tokens and injects the corresponding services into the matching `heroServiceFactory` factory function parameters.
Notice that you captured the factory provider in an exported variable, `heroServiceProvider`. Capturing the factory provider in the exported variable, `heroServiceProvider`, makes the factory provider reusable.
This extra step makes the factory provider reusable.
You can configure a provider of `HeroService` with this variable wherever you need it.
In this sample, you need it only in `HeroesComponent`,
where `heroServiceProvider` replaces `HeroService` in the metadata `providers` array.
The following shows the new and the old implementations side-by-side. The following side-by-side example shows how `heroServiceProvider` replaces `HeroService` in the `providers` array.
<code-tabs> <code-tabs>
@ -241,115 +216,3 @@ The following shows the new and the old implementations side-by-side.
</code-tabs> </code-tabs>
## Predefined tokens and multiple providers
Angular provides a number of built-in injection-token constants that you can use to customize the behavior of
various systems.
For example, you can use the following built-in tokens as hooks into the frameworks bootstrapping and initialization process.
A provider object can associate any of these injection tokens with one or more callback functions that take app-specific initialization actions.
* [PLATFORM_INITIALIZER](api/core/PLATFORM_INITIALIZER): Callback is invoked when a platform is initialized.
* [APP_BOOTSTRAP_LISTENER](api/core/APP_BOOTSTRAP_LISTENER): Callback is invoked for each component that is bootstrapped. The handler function receives the ComponentRef instance of the bootstrapped component.
* [APP_INITIALIZER](api/core/APP_INITIALIZER): Callback is invoked before an app is initialized. All registered initializers can optionally return a Promise. All initializer functions that return Promises must be resolved before the application is bootstrapped. If one of the initializers fails to resolves, the application is not bootstrapped.
The provider object can have a third option, `multi: true`, which you can use with `APP_INITIALIZER`
to register multiple handlers for the provide event.
For example, when bootstrapping an application, you can register many initializers using the same token.
```
export const APP_TOKENS = [
{ provide: PLATFORM_INITIALIZER, useFactory: platformInitialized, multi: true },
{ provide: APP_INITIALIZER, useFactory: delayBootstrapping, multi: true },
{ provide: APP_BOOTSTRAP_LISTENER, useFactory: appBootstrapped, multi: true },
];
```
Multiple providers can be associated with a single token in other areas as well.
For example, you can register a custom form validator using the built-in [NG_VALIDATORS](api/forms/NG_VALIDATORS) token,
and provide multiple instances of a given validator provider by using the `multi: true` property in the provider object.
Angular adds your custom validators to the existing collection.
The Router also makes use of multiple providers associated with a single token.
When you provide multiple sets of routes using [RouterModule.forRoot](api/router/RouterModule#forroot)
and [RouterModule.forChild](api/router/RouterModule#forchild) in a single module,
the [ROUTES](api/router/ROUTES) token combines all the different provided sets of routes into a single value.
<div class="alert is-helpful">
Search for [Constants in API documentation](api?type=const) to find more built-in tokens.
</div>
<div class="alert is-helpful">
Note that the reference to the array returned for a `multi` provider is shared between all the
places where the token is injected. We recommend avoiding mutations of the array (especially for
predefined tokens) as it may lead to unexpected behavior in other parts of the app that inject
the same token. You can prevent the value from being mutated by setting its type to `ReadonlyArray`.
</div>
You can use `ReadonlyArray` to type your `multi` provider, so TypeScript triggers an error in case
of unwanted array mutations:
```
constructor(@Inject(MULTI_PROVIDER) multiProvider: ReadonlyArray<MultiProvider>) {
}
```
{@a tree-shakable-provider}
{@a tree-shakable-providers}
## Tree-shakable providers
Tree shaking refers to a compiler option that removes code from the final bundle if the app doesn't reference that code.
When providers are tree-shakable, the Angular compiler removes the associated
services from the final output when it determines that your application doesn't use those services.
This significantly reduces the size of your bundles.
<div class="alert is-helpful">
Ideally, if an application isn't injecting a service, Angular shouldn't include it in the final output.
However, Angular has to be able to identify at build time whether the app will require the service or not.
Because it's always possible to inject a service directly using `injector.get(Service)`,
Angular can't identify all of the places in your code where this injection could happen,
so it has no choice but to include the service in the injector.
Thus, services in the NgModule `providers` array or at component level are not tree-shakable.
</div>
The following example of non-tree-shakable providers in Angular configures a service provider for the injector of an NgModule.
<code-example path="dependency-injection/src/app/tree-shaking/service-and-module.ts" header="src/app/tree-shaking/service-and-modules.ts"></code-example>
You can then import this module into your application module
to make the service available for injection in your app,
as in the following example.
<code-example path="dependency-injection/src/app/tree-shaking/app.module.ts" header="src/app/tree-shaking/app.modules.ts"></code-example>
When `ngc` runs, it compiles `AppModule` into a module factory, which contains definitions for all the providers declared in all the modules it includes. At runtime, this factory becomes an injector that instantiates these services.
Tree-shaking doesn't work here because Angular can't decide to exclude one chunk of code (the provider definition for the service within the module factory) based on whether another chunk of code (the service class) is used. To make services tree-shakable, the information about how to construct an instance of the service (the provider definition) needs to be a part of the service class itself.
### Creating tree-shakable providers
You can make a provider tree-shakable by specifying it in the `@Injectable()` decorator on the service itself, rather than in the metadata for the NgModule or component that depends on the service.
The following example shows the tree-shakable equivalent to the `ServiceModule` example above.
<code-example path="dependency-injection/src/app/tree-shaking/service.ts" header="src/app/tree-shaking/service.ts"></code-example>
The service can be instantiated by configuring a factory function, as in the following example.
<code-example path="dependency-injection/src/app/tree-shaking/service.0.ts" header="src/app/tree-shaking/service.0.ts"></code-example>
<div class="alert is-helpful">
To override a tree-shakable provider, configure the injector of a specific NgModule or component with another provider, using the `providers: []` array syntax of the `@NgModule()` or `@Component()` decorator.
</div>

View File

@ -40,8 +40,8 @@ using and results in smaller bundle sizes.
Tree-shaking is especially useful for a library Tree-shaking is especially useful for a library
because the application which uses the library may not have because the application which uses the library may not have
a need to inject it. Read more a need to inject it. Read more
about [tree-shakable providers](guide/dependency-injection-providers#tree-shakable-providers) about [tree-shakable providers](guide/architecture-services#providing-services)
in [DI Providers](guide/dependency-injection-providers). in [Introduction to services and dependency injection](guide/architecture-services).
</div> </div>

View File

@ -3,7 +3,7 @@
This page provides a conceptual overview of a dependency injection technique that is recommended for library developers. This page provides a conceptual overview of a dependency injection technique that is recommended for library developers.
Designing your library with *lightweight injection tokens* helps optimize the bundle size of client applications that use your library. Designing your library with *lightweight injection tokens* helps optimize the bundle size of client applications that use your library.
You can manage the dependency structure among your components and injectable services to optimize bundle size by using [tree-shakable providers](guide/dependency-injection-providers#tree-shakable-providers). You can manage the dependency structure among your components and injectable services to optimize bundle size by using [tree-shakable providers](guide/architecture-services#introduction-to-services-and-dependency-injection).
This normally ensures that if a provided component or service is never actually used by the app, the compiler can eliminate its code from the bundle. This normally ensures that if a provided component or service is never actually used by the app, the compiler can eliminate its code from the bundle.
However, due to the way Angular stores injection tokens, it is possible that such an unused component or service can end up in the bundle anyway. However, due to the way Angular stores injection tokens, it is possible that such an unused component or service can end up in the bundle anyway.
@ -89,7 +89,7 @@ These effectively change `constructor(@Optional() other: OtherComponent)` to `co
<div class="alert is helpful"> <div class="alert is helpful">
For all services, a library should use [tree-shakable providers](guide/dependency-injection-providers#tree-shakable-providers), providing dependencies at the root level rather than in component constructors. For all services, a library should use [tree-shakable providers](guide/architecture-services#introduction-to-services-and-dependency-injection), providing dependencies at the root level rather than in component constructors.
</div> </div>

View File

@ -89,5 +89,5 @@ Register a provider with a component when you must limit a service instance to a
You may also be interested in: You may also be interested in:
* [Singleton Services](guide/singleton-services), which elaborates on the concepts covered on this page. * [Singleton Services](guide/singleton-services), which elaborates on the concepts covered on this page.
* [Lazy Loading Modules](guide/lazy-loading-ngmodules). * [Lazy Loading Modules](guide/lazy-loading-ngmodules).
* [Tree-shakable Providers](guide/dependency-injection-providers#tree-shakable-providers). * [Dependency providers](guide/dependency-injection-providers).
* [NgModule FAQ](guide/ngmodule-faq). * [NgModule FAQ](guide/ngmodule-faq).