{ "id": "guide/dependency-injection-in-action", "title": "Dependency injection in action", "contents": "\n\n\n
This guide explores many of the features of dependency injection (DI) in Angular.
\nSee the
Sometimes you want multiple instances of a service at the same level of the component hierarchy.
\nA good example is a service that holds state for its companion component instance.\nYou need a separate instance of the service for each component.\nEach service has its own work-state, isolated from the service-and-state of a different component.\nThis is called sandboxing because each service and component instance has its own sandbox to play in.
\n\nIn this example, HeroBiosComponent
presents three instances of HeroBioComponent
.
Each HeroBioComponent
can edit a single hero's biography.\nHeroBioComponent
relies on HeroCacheService
to fetch, cache, and perform other persistence operations on that hero.
Three instances of HeroBioComponent
can't share the same instance of HeroCacheService
,\nas they'd be competing with each other to determine which hero to cache.
Instead, each HeroBioComponent
gets its own HeroCacheService
instance\nby listing HeroCacheService
in its metadata providers
array.
The parent HeroBiosComponent
binds a value to heroId
.\nngOnInit
passes that ID to the service, which fetches and caches the hero.\nThe getter for the hero
property pulls the cached hero from the service.\nThe template displays this data-bound property.
Find this example in HeroBioComponent
instances have their own cached hero data.
When a class requires a dependency, that dependency is added to the constructor as a parameter.\nWhen Angular needs to instantiate the class, it calls upon the DI framework to supply the dependency.\nBy default, the DI framework searches for a provider in the injector hierarchy,\nstarting at the component's local injector of the component, and if necessary bubbling up\nthrough the injector tree until it reaches the root injector.
\nThe first injector configured with a provider supplies the dependency (a service instance or value) to the constructor.
\nIf no provider is found in the root injector, the DI framework throws an error.
\nThere are a number of options for modifying the default search behavior, using parameter decorators\non the service-valued parameters of a class constructor.
\n\n@Optional
and limit search with @Host
linkDependencies can be registered at any level in the component hierarchy.\nWhen a component requests a dependency, Angular starts with that component's injector\nand walks up the injector tree until it finds the first suitable provider.\nAngular throws an error if it can't find the dependency during that walk.
\nIn some cases, you need to limit the search or accommodate a missing dependency.\nYou can modify Angular's search behavior with the @Host
and @Optional
qualifying\ndecorators on a service-valued parameter of the component's constructor.
The @Optional
property decorator tells Angular to return null when it can't find the dependency.
The @Host
property decorator stops the upward search at the host component.\nThe host component is typically the component requesting the dependency.\nHowever, when this component is projected into a parent component,\nthat parent component becomes the host. The following example covers this second case.
These decorators can be used individually or together, as shown in the example.\nThis HeroBiosAndContactsComponent
is a revision of HeroBiosComponent
which you looked at above.
Focus on the template:
\nNow there's a new <hero-contact>
element between the <hero-bio>
tags.\nAngular projects, or transcludes, the corresponding HeroContactComponent
into the HeroBioComponent
view,\nplacing it in the <ng-content>
slot of the HeroBioComponent
template.
The result is shown below, with the hero's telephone number from HeroContactComponent
projected above the hero description.
Here's HeroContactComponent
, which demonstrates the qualifying decorators.
Focus on the constructor parameters.
\nThe @Host()
function decorating the heroCache
constructor property ensures that\nyou get a reference to the cache service from the parent HeroBioComponent
.\nAngular throws an error if the parent lacks that service, even if a component higher\nin the component tree includes it.
A second @Host()
function decorates the loggerService
constructor property.\nThe only LoggerService
instance in the app is provided at the AppComponent
level.\nThe host HeroBioComponent
doesn't have its own LoggerService
provider.
Angular throws an error if you haven't also decorated the property with @Optional()
.\nWhen the property is marked as optional, Angular sets loggerService
to null and the rest of the component adapts.
Here's HeroBiosAndContactsComponent
in action.
If you comment out the @Host()
decorator, Angular walks up the injector ancestor tree\nuntil it finds the logger at the AppComponent
level.\nThe logger logic kicks in and the hero display updates\nwith the \"!!!\" marker to indicate that the logger was found.
If you restore the @Host()
decorator and comment out @Optional
,\nthe app throws an exception when it cannot find the required logger at the host component level.
EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)
@Inject
linkUsing a custom provider allows you to provide a concrete implementation for implicit dependencies, such as built-in browser APIs. The following example uses an InjectionToken
to provide the localStorage browser API as a dependency in the BrowserStorageService
.
The factory
function returns the localStorage
property that is attached to the browser window object. The Inject
decorator is a constructor parameter used to specify a custom provider of a dependency. This custom provider can now be overridden during testing with a mock API of localStorage
instead of interacting with real browser APIs.
@Self
and @SkipSelf
linkProviders can also be scoped by injector through constructor parameter decorators. The following example overrides the BROWSER_STORAGE
token in the Component
class providers
with the sessionStorage
browser API. The same BrowserStorageService
is injected twice in the constructor, decorated with @Self
and @SkipSelf
to define which injector handles the provider dependency.
Using the @Self
decorator, the injector only looks at the component's injector for its providers. The @SkipSelf
decorator allows you to skip the local injector and look up in the hierarchy to find a provider that satisfies this dependency. The sessionStorageService
instance interacts with the BrowserStorageService
using the sessionStorage
browser API, while the localStorageService
skips the local injector and uses the root BrowserStorageService
that uses the localStorage
browser API.
Although developers strive to avoid it, many visual effects and third-party tools, such as jQuery,\nrequire DOM access.\nAs a result, you might need to access a component's DOM element.
\nTo illustrate, here's a minimal version of HighlightDirective
from\nthe Attribute Directives page.
The directive sets the background to a highlight color when the user mouses over the\nDOM element to which the directive is applied.
\nAngular sets the constructor's el
parameter to the injected ElementRef
.\n(An ElementRef
is a wrapper around a DOM element,\nwhose nativeElement
property exposes the DOM element for the directive to manipulate.)
The sample code applies the directive's myHighlight
attribute to two <div>
tags,\nfirst without a value (yielding the default color) and then with an assigned color value.
The following image shows the effect of mousing over the <hero-bios-and-contacts>
tag.
A dependency can't always be created by the default method of instantiating a class.\nYou learned about some other methods in Dependency Providers.\nThe following HeroOfTheMonthComponent
example demonstrates many of the alternatives and why you need them.\nIt's visually simple: a few properties and the logs produced by a logger.
The code behind it customizes how and where the DI framework provides dependencies.\nThe use cases illustrate different ways to use the provide object literal to associate a definition object with a DI token.
\nThe providers
array shows how you might use the different provider-definition keys;\nuseValue
, useClass
, useExisting
, or useFactory
.
useValue
linkThe useValue
key lets you associate a fixed value with a DI token.\nUse this technique to provide runtime configuration constants such as website base addresses and feature flags.\nYou can also use a value provider in a unit test to provide mock data in place of a production data service.
The HeroOfTheMonthComponent
example has two value providers.
The first provides an existing instance of the Hero
class to use for the Hero
token, rather than\nrequiring the injector to create a new instance with new
or use its own cached instance.\nHere, the token is the class itself.
The second specifies a literal string resource to use for the TITLE
token.\nThe TITLE
provider token is not a class, but is instead a\nspecial kind of provider lookup key called an injection token, represented by\nan InjectionToken
instance.
You can use an injection token for any kind of provider but it's particularly\nhelpful when the dependency is a simple value like a string, a number, or a function.
\nThe value of a value provider must be defined before you specify it here.\nThe title string literal is immediately available.\nThe someHero
variable in this example was set earlier in the file as shown below.\nYou can't use a variable whose value will be defined later.
Other types of providers can create their values lazily; that is, when they're needed for injection.
\n\nuseClass
linkThe useClass
provider key lets you create and return a new instance of the specified class.
You can use this type of provider to substitute an alternative implementation\nfor a common or default class.\nThe alternative implementation could, for example, implement a different strategy,\nextend the default class, or emulate the behavior of the real class in a test case.
\nThe following code shows two examples in HeroOfTheMonthComponent
.
The first provider is the de-sugared, expanded form of the most typical case in which the\nclass to be created (HeroService
) is also the provider's dependency injection token.\nThe short form is generally preferred; this long form makes the details explicit.
The second provider substitutes DateLoggerService
for LoggerService
.\nLoggerService
is already registered at the AppComponent
level.\nWhen this child component requests LoggerService
, it receives a DateLoggerService
instance instead.
This component and its tree of child components receive DateLoggerService
instance.\nComponents outside the tree continue to receive the original LoggerService
instance.
DateLoggerService
inherits from LoggerService
; it appends the current date/time to each message:
useExisting
linkThe useExisting
provider key lets you map one token to another.\nIn effect, the first token is an alias for the service associated with the second token,\ncreating two ways to access the same service object.
You can use this technique to narrow an API through an aliasing interface.\nThe following example shows an alias introduced for that purpose.
\nImagine that LoggerService
had a large API, much larger than the actual three methods and a property.\nYou might want to shrink that API surface to just the members you actually need.\nIn this example, the MinimalLogger
class-interface reduces the API to two members:
The following example puts MinimalLogger
to use in a simplified version of 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 sets the logger
parameter to the full service registered under the LoggingService
token, which happens to be the DateLoggerService
instance that was provided above.
This is illustrated in the following image, which displays the logging date.
\nuseFactory
linkThe useFactory
provider key lets you create a dependency object by calling a factory function,\nas in the following example.
The injector provides the dependency value by invoking a factory function,\nthat you provide as the value of the useFactory
key.\nNotice that this form of provider has a third key, deps
, which specifies\ndependencies for the useFactory
function.
Use this technique to create a dependency object with a factory function\nwhose inputs are a combination of injected services and local state.
\nThe dependency object (returned by the factory function) is typically a class instance,\nbut can be other things as well.\nIn this example, the dependency object is a string of the names of the runners up\nto the \"Hero of the Month\" contest.
\nIn the example, the local state is the number 2
, the number of runners up that the component should show.\nThe state value is passed as an argument to runnersUpFactory()
.\nThe runnersUpFactory()
returns the provider factory function, which can use both\nthe passed-in state value and the injected services Hero
and HeroService
.
The provider factory function (returned by runnersUpFactory()
) returns the actual dependency object,\nthe string of names.
The function takes a winning Hero
and a HeroService
as arguments.\nAngular supplies these arguments from injected values identified by\nthe two tokens in the deps
array.
The function returns the string of names, which Angular than injects into\nthe runnersUp
parameter of HeroOfTheMonthComponent
.
The function retrieves candidate heroes from the HeroService
,\ntakes 2
of them to be the runners-up, and returns their concatenated names.\nLook at the
Angular dependency injection is easiest when the provider token is a class\nthat is also the type of the returned dependency object, or service.
\nHowever, a token doesn't have to be a class and even when it is a class,\nit doesn't have to be the same type as the returned object.\nThat's the subject of the next section.\n
\nThe previous Hero of the Month example used the MinimalLogger
class\nas the token for a provider of LoggerService
.
MinimalLogger
is an abstract class.
An abstract class is usually a base class that you can extend.\nIn this app, however there is no class that inherits from MinimalLogger
.\nThe LoggerService
and the DateLoggerService
could have inherited from MinimalLogger
,\nor they could have implemented it instead, in the manner of an interface.\nBut they did neither.\nMinimalLogger
is used only as a dependency injection token.
When you use a class this way, it's called a class interface.
\nAs mentioned in DI Providers,\nan interface is not a valid DI token because it is a TypeScript artifact that doesn't exist at run time.\nUse this abstract class interface to get the strong typing of an interface,\nand also use it as a provider token in the way you would a normal class.
\nA class interface should define only the members that its consumers are allowed to call.\nSuch a narrowing interface helps decouple the concrete class from its consumers.
\nUsing a class as an interface gives you the characteristics of an interface in a real JavaScript object.\nTo minimize memory cost, however, the class should have no implementation.\nThe MinimalLogger
transpiles to this unoptimized, pre-minified JavaScript for a constructor function.
Notice that it doesn't have any members. It never grows no matter how many members you add to the class,\nas long as those members are typed but not implemented.
\nLook again at the TypeScript MinimalLogger
class to confirm that it has no implementation.
Dependency objects can be simple values like dates, numbers and strings, or\nshapeless objects like arrays and functions.
\nSuch objects don't have application interfaces and therefore aren't well represented by a class.\nThey're better represented by a token that is both unique and symbolic,\na JavaScript object that has a friendly name but won't conflict with\nanother token that happens to have the same name.
\nInjectionToken
has these characteristics.\nYou encountered them twice in the Hero of the Month example,\nin the title value provider and in the runnersUp factory provider.
You created the TITLE
token like this:
The type parameter, while optional, conveys the dependency's type to developers and tooling.\nThe token description is another developer aid.
\n\nTake care when writing a component that inherits from another component.\nIf the base component has injected dependencies,\nyou must re-provide and re-inject them in the derived class\nand then pass them down to the base class through the constructor.
\nIn this contrived example, SortedHeroesComponent
inherits from HeroesBaseComponent
\nto display a sorted list of heroes.
The HeroesBaseComponent
can stand on its own.\nIt demands its own instance of HeroService
to get heroes\nand displays them in the order they arrive from the database.
Constructors should do little more than initialize variables.\nThis rule makes the component safe to construct under test without fear that it will do something dramatic like talk to the server.\nThat's why you call the HeroService
from within the ngOnInit
rather than the constructor.
Users want to see the heroes in alphabetical order.\nRather than modify the original component, sub-class it and create a\nSortedHeroesComponent
that sorts the heroes before presenting them.\nThe SortedHeroesComponent
lets the base class fetch the heroes.
Unfortunately, Angular cannot inject the HeroService
directly into the base class.\nYou must provide the HeroService
again for this component,\nthen pass it down to the base class inside the constructor.
Now take note of the afterGetHeroes()
method.\nYour first instinct might have been to create an ngOnInit
method in SortedHeroesComponent
and do the sorting there.\nBut Angular calls the derived class's ngOnInit
before calling the base class's ngOnInit
\nso you'd be sorting the heroes array before they arrived. That produces a nasty error.
Overriding the base class's afterGetHeroes()
method solves the problem.
These complications argue for avoiding component inheritance.
\n\nThe order of class declaration matters in TypeScript.\nYou can't refer directly to a class until it's been defined.
\nThis isn't usually a problem, especially if you adhere to the recommended one class per file rule.\nBut sometimes circular references are unavoidable.\nYou're in a bind when class 'A' refers to class 'B' and 'B' refers to 'A'.\nOne of them has to be defined first.
\nThe Angular forwardRef()
function creates an indirect reference that Angular can resolve later.
The Parent Finder sample is full of circular class references that are impossible to break.
\nYou face this dilemma when a class makes a reference to itself\nas does AlexComponent
in its providers
array.\nThe providers
array is a property of the @Component()
decorator function which must\nappear above the class definition.
Break the circularity with forwardRef
.