Dependency Injection is a powerful pattern for managing code dependencies.
This cookbook explores many of the features of Dependency Injection (DI) in Angular.
All of these services are implemented as classes.
Service classes can act as their own providers which is why listing them in the providers array
is all the registration you need.
A provider is something that can create or deliver a service.
Angular creates a service instance from a class provider by using new.
Read more about providers in the Dependency Injection
guide.
Generally, register providers in the NgModule rather than in the root application component.
如果你希望这个服务在应用中到处都可以被注入,或者必须在应用启动前注册一个全局服务,那就这么做。
Do this when you expect the service to be injectable everywhere,
or you are configuring another application global service before the application starts.
Here is an example of the second case, where the component router configuration includes a non-default
location strategy by listing its provider
in the providers list of the AppModule.
The consumer of an injected service does not know how to create that service.
It shouldn't care.
It's the dependency injection's job to create and cache that service.
Sometimes a service depends on other services , which may depend on yet other services.
Resolving these nested dependencies in the correct order is also the framework's job.
At each step, the consumer of dependencies simply declares what it requires in its
constructor and the framework takes over.
When Angular creates the AppComponent, the dependency injection framework creates an instance of the LoggerService and
starts to create the UserContextService.
The UserContextService needs the LoggerService, which the framework already has, and the UserService, which it has yet to create.
The UserService has no dependencies so the dependency injection framework can justuse new to instantiateone .
The beauty of dependency injection is that AppComponent doesn't care about any of this.
You simply declare what is needed in the constructor (LoggerService and UserContextService) and the framework does the rest.
一旦所有依赖都准备好了,AppComponent就会显示用户信息:
Once all the dependencies are in place, the AppComponent displays the user information:
@Injectable()
@Injectable()
注意在UserContextService类里面的@Injectable()装饰器。
Notice the @Injectable()decorator on the UserContextService class.
Technically, the @Injectable()decorator is only required for a service class that has its own dependencies.
The LoggerService doesn't depend on anything. The logger would work if you omitted @Injectable()
and the generated code would be slightly smaller.
But the service would break the moment you gave it a dependency and you'd have to go back
and add @Injectable() to fix it. Add @Injectable() from the start for the sake of consistency and to avoid future pain.
Although this site recommends applying @Injectable() to all service classes, don't feel bound by it.
Some developers prefer to add it only where needed and that's a reasonable policy too.
The AppComponent class had two dependencies as well but no @Injectable().
It didn't need @Injectable() because that component class has the @Component decorator.
In Angular with TypeScript, a single decorator—any decorator—is sufficient to identify dependency types.
But an Angular application has multiple dependency injectors, arranged in a tree hierarchy that parallels the component tree.
So a particular service can be provided and created at any component level and multiple times
if provided in multiple components.
By default, a service dependency provided in one component is visible to all of its child components and
Angular injects the same service instance into all child components that ask for that service.
所以,在根部的AppComponent提供的依赖单例就能被注入到应用程序中任何地方的任何组件。
Accordingly, dependencies provided in the root AppComponent can be injected into any component anywhere in the application.
但这不一定总是想要的。有时候我们想要把服务的有效性限制到应用程序的一个特定区域。
That isn't always desirable.
Sometimes you want to restrict service availability to a particular region of the application.
You can limit the scope of an injected service to a branch of the application hierarchy
by providing that service at the sub-root component for that branch.
This example shows how similar providing a service to a sub-root component is
to providing a service in the root AppComponent. The syntax is the same.
Here, the HeroService is availble to the HeroesBaseComponent because it is in the providers array:
When Angular creates the HeroesBaseComponent, it also creates a new instance of HeroService
that is visible only to the component and its children, if any.
You could also provide the HeroService to a different component elsewhere in the application.
That would result in a different instance of the service, living in a different injector.
Examples of such scoped HeroService singletons appear throughout the accompanying sample code,
including the HeroBiosComponent, HeroOfTheMonthComponent, and HeroesBaseComponent.
Each of these components has its own HeroService instance managing its own independent collection of heroes.
This much Dependency Injection knowledge may be all that many Angular developers
ever need to build their applications. It doesn't always have to be more complicated.
多个服务实例(sandboxing)
Multiple service instances (sandboxing)
在同一个级别的组件树里,有时需要一个服务的多个实例。
Sometimes you want multiple instances of a service at the same level of the component hierarchy.
A good example is a service that holds state for its companion component instance.
You need a separate instance of the service for each component.
Each service has its own work-state, isolated from the service-and-state of a different component.
This is called sandboxing because each service and component instance has its own sandbox to play in.
Each HeroBioComponent can edit a single hero's biography.
A HeroBioComponent relies on a HeroCacheService to fetch, cache, and perform other persistence operations on that hero.
Clearly the three instances of the HeroBioComponent can't share the same HeroCacheService.
They'd be competing with each other to determine which hero to cache.
The parent HeroBiosComponent binds a value to the heroId.
The ngOnInit passes that id to the service, which fetches and caches the hero.
The getter for the hero property pulls the cached hero from the service.
And the template displays this data-bound property.
When a component requests a dependency, Angular starts with that component's injector and walks up the injector tree
until it finds the first suitable provider. Angular throws an error if it can't find the dependency during that walk.
You want this behavior most of the time.
But sometimes you need to limit the search and/or accommodate a missing dependency.
You can modify Angular's search behavior with the @Host and @Optional qualifying decorators,
used individually or together.
The host component is typically the component requesting the dependency.
But when this component is projected into a parent component, that parent component becomes the host.
The next example covers this second case.
Now there is a new <hero-contact> element between the <hero-bio> tags.
Angular projects, or transcludes, the corresponding HeroContactComponent into the HeroBioComponent view,
placing it in the <ng-content> slot of the HeroBioComponent template:
Here's the HeroContactComponent which demonstrates the qualifying decorators:
src/app/hero-contact.component.ts
@Component({
selector:'hero-contact',
template:`
<div>Phone #: {{phoneNumber}}
<span *ngIf="hasLogger">!!!</span></div>`
})
exportclassHeroContactComponent{
hasLogger =false;
constructor(
@Host()// limit to the host component's instance of the HeroCacheService
private heroCache:HeroCacheService,
@Host()// limit search for logger; hides the application-wide logger
@Optional()// ok if the logger doesn't exist
private loggerService:LoggerService
){
if(loggerService){
this.hasLogger =true;
loggerService.logInfo('HeroContactComponent can log!');
}
}
get phoneNumber(){returnthis.heroCache.hero.phone;}
}
注意看构造函数的参数:
Focus on the constructor parameters:
src/app/hero-contact.component.ts
@Host()// limit to the host component's instance of the HeroCacheServiceprivate heroCache:HeroCacheService,@Host()// limit search for logger; hides the application-wide logger@Optional()// ok if the logger doesn't existprivate loggerService:LoggerService
The @Host() function decorating the heroCache property ensures that
you get a reference to the cache service from the parent HeroBioComponent.
Angular throws an error if the parent lacks that service, even if a component higher in the component tree happens to have it.
A second @Host() function decorates the loggerService property.
The only LoggerService instance in the app is provided at the AppComponent level.
The host HeroBioComponent doesn't have its own LoggerService provider.
Angular would throw an error if you hadn't also decorated the property with the @Optional() function.
Thanks to @Optional(), Angular sets the loggerService to null and the rest of the component adapts.
下面是HeroBiosAndContactsComponent的执行结果:
Here's the HeroBiosAndContactsComponent in action.
If you comment out the @Host() decorator, Angular now walks up the injector ancestor tree
until it finds the logger at the AppComponent level. The logger logic kicks in and the hero display updates
with the gratuitous "!!!", indicating that the logger was found.
另一方面,如果恢复@Host()装饰器,注释掉@Optional,应用程序就会运行失败,因为它在宿主组件级别找不到需要的日志服务。
EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)
On the other hand, if you restore the @Host() decorator and comment out @Optional,
the application fails for lack of the required logger at the host component level.
EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)
On occasion you might need to access a component's corresponding DOM element.
Although developers strive to avoid it, many visual effects and 3rd party tools, such as jQuery,
require DOM access.
Angular sets the constructor's el parameter to the injected ElementRef, which is
a wrapper around that DOM element.
Its nativeElement property exposes the DOM element for the directive to manipulate.
The sample code applies the directive's myHighlight attribute to two <div> tags,
first without a value (yielding the default color) and then with an assigned color value.
src/app/app.component.html (highlight)
<divid="highlight"class="di-component"myHighlight><h3>Hero Bios and Contacts</h3><divmyHighlight="yellow"><hero-bios-and-contacts></hero-bios-and-contacts></div></div>
下图显示了鼠标移到<hero-bios-and-contacts>标签的效果:
The following image shows the effect of mousing over the <hero-bios-and-contacts> tag.
使用提供商来定义依赖
Define dependencies with providers
在这个部分,我们将演示如何编写提供商来提供被依赖的服务。
This section demonstrates how to write providers that deliver dependent services.
我们给依赖注入器提供令牌来获取服务。
Get a service from a dependency injector by giving it a token.
You usually let Angular handle this transaction by specifying a constructor parameter and its type.
The parameter type serves as the injector lookup token.
Angular passes this token to the injector and assigns the result to the parameter.
Here's a typical example:
Where did the injector get that value?
It may already have that value in its internal container.
If it doesn't, it may be able to make one with the help of a provider.
A provider is a recipe for delivering a service associated with a token.
If the injector doesn't have a provider for the requested token, it delegates the request
to its parent injector, where the process repeats until there are no more injectors.
If the search is futile, the injector throws an error—unless the request was optional.
A new injector has no providers.
Angular initializes the injectors it creates with some providers it cares about.
You have to register your own application providers manually,
usually in the providers array of the Component or Directive metadata:
It's that simple because the most common injected service is an instance of a class.
But not every dependency can be satisfied by creating a new instance of a class.
You need other ways to deliver dependency values and that means you need other ways to specify a provider.
The HeroOfTheMonthComponent example demonstrates many of the alternatives and why you need them.
It's visually simple: a few properties and the logs produced by a logger.
这段代码的背后有很多值得深入思考的地方。
The code behind it gives you plenty to think about.
Use this technique to provide runtime configuration constants such as website base addresses and feature flags.
You can use a value provider in a unit test to replace a production service with a fake or mock.
The HeroOfTheMonthComponent example has two value providers.
The first provides an instance of the Hero class;
the second specifies a literal string resource:
{ provide:Hero, useValue: someHero },{ provide: TITLE, useValue:'Hero of the Month'},
The Hero provider token is a class which makes sense because the value is a Hero
and the consumer of the injected hero would want the type information.
TITLE 提供商的令牌不是一个类。它是一个特别类型的提供商查询键,名叫InjectionToken.
你可以把InjectionToken用作任何类型的提供商的令牌,但是它在依赖是简单类型(比如字符串、数字、函数)时会特别有帮助。
The TITLE provider token is not a class.
It's a special kind of provider lookup key called an InjectionToken.
You can use an InjectionToken for any kind of provider but it's particular
helpful when the dependency is a simple value like a string, a number, or a function.
The value of a value provider must be defined now. You can't create the value later.
Obviously the title string literal is immediately available.
The someHero variable in this example was set earlier in the file:
const someHero =newHero(42,'Magma','Had a great month!','555-555-5555');
其它提供商只在需要注入它们的时候才创建并惰性加载它们的值。
The other providers create their values lazily when they're needed for injection.
useClass - 类-提供商
useClass — the class provider
userClass提供商创建并返回一个指定类的新实例。
The useClass provider creates and returns new instance of the specified class.
Use this technique to substitute an alternative implementation for a common or default class.
The alternative could implement a different strategy, extend the default class,
or fake the behavior of the real class in a test case.
请看下面HeroOfTheMonthComponent里的两个例子:
Here are two examples in the HeroOfTheMonthComponent:
The first provider is the de-sugared, expanded form of the most typical case in which the
class to be created (HeroService) is also the provider's dependency injection token.
It's in this long form to de-mystify the preferred short form.
The second provider substitutes the DateLoggerService for the LoggerService.
The LoggerService is already registered at the AppComponent level.
When this component requests the LoggerService, it receives the DateLoggerService instead.
This component and its tree of child components receive the DateLoggerService instance.
Components outside the tree continue to receive the original LoggerService instance.
The useExisting provider maps one token to another.
In effect, the first token is an alias for the service associated with the second token,
creating two ways to access the same service object.
Imagine that the LoggerService had a large API, much larger than the actual three methods and a property.
You might want to shrink that API surface to just the members you actually need.
Here the MinimalLoggerclass-interface reduces the API to two members:
src/app/minimal-logger.service.ts
// Class used as a "narrowing" interface that exposes a minimal logger// Other members of the actual implementation are invisibleexportabstractclassMinimalLogger{
logs:string[];
logInfo:(msg:string)=>void;}
现在,在一个简化版的HeroOfTheMonthComponent中使用它。
Now put it to use in a simplified version of the HeroOfTheMonthComponent.
The HeroOfTheMonthComponent constructor's logger parameter is typed as MinimalLogger so only the logs and logInfo members are visible in a TypeScript-aware editor:
Behind the scenes,Angular actually sets the logger parameter to the full service registered under the LoggingService token
which happens to be the DateLoggerService that was provided above.
在下面的图片中,显示了日志日期,可以确认这一点:
The following image, which displays the logging date, confirms the point:
useFactory - 工厂-提供商
useFactory— the factory provider
useFactory 提供商通过调用工厂函数来新建一个依赖对象,如下例所示。
The useFactory provider creates a dependency object by calling a factory function
as in this example.
The dependency object doesn't have to be a class instance. It could be anything.
In this example, the dependency object is a string of the names of the runners-up
to the "Hero of the Month" contest.
本地状态是数字2,该组件应该显示的亚军的个数。我们立刻用2来执行runnersUpFactory。
The local state is the number 2, the number of runners-up this component should show.
It executes runnersUpFactory immediately with 2.
Angular supplies these arguments from injected values identified by
the two tokens in the deps array.
The two deps values are tokens that the injector uses
to provide these factory function dependencies.
After some undisclosed work, the function returns the string of names
and Angular injects it into the runnersUp parameter of the HeroOfTheMonthComponent.
The function retrieves candidate heroes from the HeroService,
takes 2 of them to be the runners-up, and returns their concatenated names.
Look at the live example / downloadable example
for the full source code.
备选提供商令牌:类-接口和InjectionToken
Provider token alternatives: the class-interface and InjectionToken
Angular dependency injection is easiest when the provider token is a class
that is also the type of the returned dependency object , orwhat you usually call the service.
但令牌不一定都是类,就算它是一个类,它也不一定都返回类型相同的对象。这是下一节的主题。
But the token doesn't have to be a class and even when it is a class,
it doesn't have to be the same type as the returned object.
That's the subject of the next section.
// Class used as a "narrowing" interface that exposes a minimal logger// Other members of the actual implementation are invisibleexportabstractclassMinimalLogger{
logs:string[];
logInfo:(msg:string)=>void;}
我们通常从一个抽象类继承。但这个应用中并没有类会继承MinimalLogger。
You usually inherit from an abstract class.
But no class in this application inherits from MinimalLogger.
The LoggerService and the DateLoggerServicecould have inherited from MinimalLogger.
They could have implemented it instead in the manner of an interface.
But they did neither.
The MinimalLogger is used exclusively as a dependency injection token.
When you use a class this way, it's called a class-interface.
The key benefit of a class-interface is that you can get the strong-typing of an interface
and you can use it as a provider token in the way you would a normal class.
类-接口应该只定义允许它的消费者调用的成员。窄的接口有助于解耦该类的具体实现和它的消费者。
A class-interface should define only the members that its consumers are allowed to call.
Such a narrowing interface helps decouple the concrete class from its consumers.
为什么MinimalLogger是一个类而不是一个TypeScript接口
Why MinimalLogger is a class and not a TypeScript interface
You can't use an interface as a provider token because
interfaces are not JavaScript objects.
They exist only in the TypeScript design space.
They disappear after the code is transpiled to JavaScript.
Of course a real object occupies memory. To minimize memory cost, the class should have no implementation.
The MinimalLogger transpiles to this unoptimized, pre-minified JavaScript for a constructor function:
Notice that it doesn't have a single member. It never grows no matter how many members you add to the class as long as those members are typed but not implemented. Look again at the TypeScript MinimalLogger class to confirm that it has no implementation.
InjectionToken
依赖对象可以是一个简单的值,比如日期,数字和字符串,或者一个无形的对象,比如数组和函数。
Dependency objects can be simple values like dates, numbers and strings, or
shapeless objects like arrays and functions.
Such objects don't have application interfaces and therefore aren't well represented by a class.
They're better represented by a token that is both unique and symbolic,
a JavaScript object that has a friendly name but won't conflict with
another token that happens to have the same name.
InjectionToken具有这些特征。在Hero of the Month例子中遇见它们两次,一个是title的值,一个是runnersUp 工厂提供商。
The InjectionToken has these characteristics.
You encountered them twice in the Hero of the Month example,
in the title value provider and in the runnersUp factory provider.
{ provide: TITLE, useValue:'Hero of the Month'},{ provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps:[Hero,HeroService]}
这样创建TITLE令牌:
You created the TITLE token like this:
import{InjectionToken}from'@angular/core';exportconst TITLE =newInjectionToken<string>('title');
Take care when writing a component that inherits from another component.
If the base component has injected dependencies,
you must re-provide and re-inject them in the derived class
and then pass them down to the base class through the constructor.
The HeroesBaseComponent could stand on its own.
It demands its own instance of the HeroService to get heroes
and displays them in the order they arrive from the database.
Keep constructors simple. They should do little more than initialize variables.
This rule makes the component safe to construct under test without fear that it will do something dramatic like talk to the server.
That's why you call the HeroService from within the ngOnInit rather than the constructor.
Users want to see the heroes in alphabetical order.
Rather than modify the original component, sub-class it and create a
SortedHeroesComponent that sorts the heroes before presenting them.
The SortedHeroesComponent lets the base class fetch the heroes.
Unfortunately, Angular cannot inject the HeroService directly into the base class.
You must provide the HeroService again for this component,
then pass it down to the base class inside the constructor.
Now take note of the afterGetHeroes() method.
Your first instinct might have been to create an ngOnInit method in SortedHeroesComponent and do the sorting there.
But Angular calls the derived class's ngOnInitbefore calling the base class's ngOnInit
so you'd be sorting the heroes array before they arrived. That produces a nasty error.
覆盖基类的afterGetHeroes()方法可以解决这个问题。
Overriding the base class's afterGetHeroes() method solves the problem.
分析上面的这些复杂性是为了强调避免使用组件继承这一点。
These complications argue for avoiding component inheritance.
Application components often need to share information.
More loosely coupled techniques such as data binding and service sharing
are preferable. But sometimes it makes sense for one component
to have a direct reference to another component
perhaps to access values or call methods on that component.
Obtaining a component reference is a bit tricky in Angular.
Although an Angular application is a tree of components,
there is no public API for inspecting and traversing that tree.
There is no public API for acquiring a parent reference.
But because every component instance is added to an injector's container,
you can use Angular dependency injection to reach a parent component.
本章节描述了这项技术。
This section describes some techniques for doing that.
Cathy reports whether or not she has access to Alex
after injecting an AlexComponent into her constructor:
parent-finder.component.ts (CathyComponent)
@Component({
selector:'cathy',template:`
<div class="c">
<h3>Cathy</h3>
{{alex ? 'Found' : 'Did not find'}} Alex via the component class.<br>
</div>`})exportclassCathyComponent{
constructor(@Optional()public alex:AlexComponent){}}
A re-usable component might be a child of multiple components.
Imagine a component for rendering breaking news about a financial instrument.
For business reasons, this news component makes frequent calls
directly into its parent instrument as changing market data streams by.
The app probably defines more than a dozen financial instrument components.
If you're lucky, they all implement the same base class
whose API your NewsComponent understands.
Looking for components that implement an interface would be better.
That's not possible because TypeScript interfaces disappear
from the transpiled JavaScript, which doesn't support interfaces.
There's no artifact to look for.
这并不是好的设计。问题是一个组件是否能通过它父组件的基类来注入它的父组件呢?
This isn't necessarily good design.
This example is examining whether a component can inject its parent via the parent's base class.
The sample's CraigComponent explores this question. Looking back ,
you see that the Alex component extends (inherits) from a class named Base.
parent-finder.component.ts (Alex class signature)
exportclassAlexComponentextendsBase
CraigComponent试图把Base注入到到它的alex构造函数参数,来报告是否成功。
The CraigComponent tries to inject Base into its alex constructor parameter and reports if it succeeded.
parent-finder.component.ts (CraigComponent)
@Component({
selector:'craig',template:`
<div class="c">
<h3>Craig</h3>
{{alex ? 'Found' : 'Did not find'}} Alex via the base class.
</div>`})exportclassCraigComponent{
constructor(@Optional()public alex:Base){}}
Unfortunately, this does not work.
The live example / downloadable example
confirms that the alex parameter is null.
You cannot inject a parent by its base class.
Write an alias provider—a provide object literal with a useExisting
definition—that creates an alternative way to inject the same component instance
and add that provider to the providers array of the @Component metadata for the AlexComponent:
Parent is the provider's class-interface token.
The forwardRef breaks the circular reference you just created by having the AlexComponent refer to itself.
Carol,Alex的第三个子组件,把父级注入到了自己的parent参数,和之前做的一样:
Carol, the third of Alex's child components, injects the parent into its parent parameter,
the same way you've done it before:
Barry is the problem. He needs to reach his parent, Alice, and also be a parent to Carol.
That means he must both inject the Parentclass-interface to get Alice and
provide a Parent to satisfy Carol.
The Parentclass-interface defines a name property with a type declaration but no implementation.
The name property is the only member of a parent component that a child component can call.
Such a narrow interface helps decouple the child component class from its parent components.
一个能用做父级的组件应该实现类-接口,和下面的AliceComponent的做法一样:
A component that could serve as a parent should implement the class-interface as the AliceComponent does:
parent-finder.component.ts (AliceComponent class signature)
Doing so adds clarity to the code. But it's not technically necessary.
Although the AlexComponent has a name property, as required by its Base class,
its class signature doesn't mention Parent:
parent-finder.component.ts (AlexComponent class signature)
The AlexComponentshould implement Parent as a matter of proper style.
It doesn't in this example only to demonstrate that the code will compile and run without the interface
You can extract that logic into a helper function like this:
// Helper method to provide the current component instance in the name of a `parentType`.const provideParent =(component: any)=>{return{ provide:Parent, useExisting: forwardRef(()=> component)};};
现在就可以为组件添加一个更简单、直观的父级提供商了:
Now you can add a simpler, more meaningful parent provider to your components:
You can do better. The current version of the helper function can only alias the Parentclass-interface.
The application might have a variety of parent types, each with its own class-interface token.
Here's a revised version that defaults to parent but also accepts an optional second parameter for a different parent class-interface.
// Helper method to provide the current component instance in the name of a `parentType`.// The `parentType` defaults to `Parent` when omitting the second parameter.const provideParent =(component: any, parentType?: any)=>{return{ provide: parentType ||Parent, useExisting: forwardRef(()=> component)};};
下面的代码演示了如何使它添加一个不同类型的父级:
And here's how you could use it with a different parent type:
This isn't usually a problem, especially if you adhere to the recommended one class per file rule.
But sometimes circular references are unavoidable.
You're in a bind when class 'A' refers to class 'B' and 'B' refers to 'A'.
One of them has to be defined first.
Angular的forwardRef()函数建立一个间接地引用,Angular可以随后解析。
The Angular forwardRef() function creates an indirect reference that Angular can resolve later.
Parent Finder是一个充满了无法解决的循环引用的例子
The Parent Finder sample is full of circular class references that are impossible to break.
You face this dilemma when a class makes a reference to itself
as does the AlexComponent in its providers array.
The providers array is a property of the @Component decorator function which must
appear above the class definition.