docs(DI-cookbook): copy edits and update TOCs
This commit is contained in:
parent
c9db9b2648
commit
f38620d882
|
@ -2,54 +2,44 @@ include ../_util-fns
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Dependency Injection is a powerful pattern for managing code dependencies.
|
Dependency Injection is a powerful pattern for managing code dependencies.
|
||||||
In this cookbook we will explore many of the features of Dependency Injection (DI) in Angular.
|
This cookbook explores many of the features of Dependency Injection (DI) in Angular.
|
||||||
|
|
||||||
<a id="toc"></a>
|
<a id="toc"></a>
|
||||||
:marked
|
:marked
|
||||||
## Table of contents
|
# Contents
|
||||||
|
|
||||||
[Application-wide dependencies](#app-wide-dependencies)
|
- [Application-wide dependencies](#app-wide-dependencies)
|
||||||
|
- [External module configuration](#external-module-configuration)
|
||||||
[External module configuration](#external-module-configuration)
|
- [`@Injectable()` and nested service dependencies](#nested-dependencies)
|
||||||
|
- [`@Injectable()`](#injectable-1)
|
||||||
[*@Injectable* and nested service dependencies](#nested-dependencies)
|
- [Limit service scope to a component subtree](#service-scope)
|
||||||
|
- [Multiple service instances (sandboxing)](#multiple-service-instances)
|
||||||
[Limit service scope to a component subtree](#service-scope)
|
- [Qualify dependency lookup with `@Optional()` and `@Host()`](#qualify-dependency-lookup)
|
||||||
|
- [Demonstration](#demonstration)
|
||||||
[Multiple service instances (sandboxing)](#multiple-service-instances)
|
- [Inject the component's DOM element](#component-element)
|
||||||
|
- [Define dependencies with providers](#providers)
|
||||||
[Qualify dependency lookup with *@Optional* and *@Host*](#qualify-dependency-lookup)
|
- [Defining providers](#defining-providers)
|
||||||
|
- [The *provide* object literal](#provide)
|
||||||
[Inject the component's DOM element](#component-element)
|
- [`useValue`—the *value provider*](#usevalue)
|
||||||
|
- [`useClass`—the *class provider*](#useclass)
|
||||||
[Define dependencies with providers](#providers)
|
- [`useExisting`—the *alias provider*](#useexisting)
|
||||||
|
- [`useFactory`—the *factory provider*](#usefactory)
|
||||||
* [The *provide* object literal](#provide)
|
- [Provider token alternatives: the class-interface and `OpaqueToken`](#tokens)
|
||||||
* [useValue - the *value provider*](#usevalue)
|
- [class-interface](#class-interface)
|
||||||
* [useClass - the *class provider*](#useclass)
|
- [`OpaqueToken`](#opaque-token)
|
||||||
* [useExisting - the *alias provider*](#useexisting)
|
- [Inject into a derived class](#di-inheritance)
|
||||||
* [useFactory - the *factory provider*](#usefactory)
|
- [Find a parent component by injection](#find-parent)
|
||||||
|
- [Find parent with a known component type](#known-parent)
|
||||||
[Provider token alternatives](#tokens)
|
- [Cannot find a parent by its base class](#base-parent)
|
||||||
|
- [Find a parent by its class-interface](#class-interface-parent)
|
||||||
* [class-interface](#class-interface)
|
- [Find a parent in a tree of parents with `@SkipSelf()`](#parent-tree)
|
||||||
* [OpaqueToken](#opaque-token)
|
- [The `Parent` class-interface](#parent-token)
|
||||||
|
- [A `provideParent()` helper function](#provideparent)
|
||||||
[Inject into a derived class](#di-inheritance)
|
- [Break circularities with a forward class reference (*forwardRef*)](#forwardref)
|
||||||
|
|
||||||
[Find a parent component by injection](#find-parent)
|
|
||||||
|
|
||||||
* [Find parent with a known component type](#known-parent)
|
|
||||||
* [Cannot find a parent by its base class](#base-parent)
|
|
||||||
* [Find a parent by its class-interface](#class-interface-parent)
|
|
||||||
* [Find a parent in a tree of parents (*@SkipSelf*)](#parent-tree)
|
|
||||||
* [A *provideParent* helper function](#provideparent)
|
|
||||||
|
|
||||||
[Break circularities with a forward class reference (*forwardRef*)](#forwardref)
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
**See the <live-example name="cb-dependency-injection"></live-example>**
|
See the <live-example name="cb-dependency-injection"></live-example>
|
||||||
of the code supporting this cookbook.
|
of the code in this cookbook.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
|
|
||||||
|
@ -58,22 +48,23 @@ include ../_util-fns
|
||||||
## Application-wide dependencies
|
## Application-wide dependencies
|
||||||
Register providers for dependencies used throughout the application in the root application component, `AppComponent`.
|
Register providers for dependencies used throughout the application in the root application component, `AppComponent`.
|
||||||
|
|
||||||
In the following example, we import and register several services
|
The following example shows importing and registering
|
||||||
(the `LoggerService`, `UserContext`, and the `UserService`)
|
the `LoggerService`, `UserContext`, and the `UserService`
|
||||||
in the `@Component` metadata `providers` array.
|
in the `@Component` metadata `providers` array.
|
||||||
|
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/app.component.ts','import-services','src/app/app.component.ts (excerpt)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/app.component.ts','import-services','src/app/app.component.ts (excerpt)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
All of these services are implemented as classes.
|
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
|
Service classes can act as their own providers which is why listing them in the `providers` array
|
||||||
is all the registration we need.
|
is all the registration you need.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
A *provider* is something that can create or deliver a service.
|
A *provider* is something that can create or deliver a service.
|
||||||
Angular creates a service instance from a class provider by "new-ing" it.
|
Angular creates a service instance from a class provider by using `new`.
|
||||||
Learn more about providers [below](#providers).
|
Read more about providers in the [Dependency Injection](../guide/dependency-injection.html#!#injector-providers)
|
||||||
|
guide.
|
||||||
:marked
|
:marked
|
||||||
Now that we've registered these services,
|
Now that you've registered these services,
|
||||||
Angular can inject them into the constructor of *any* component or service, *anywhere* in the application.
|
Angular can inject them into the constructor of *any* component or service, *anywhere* in the application.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-bios.component.ts','ctor','src/app/hero-bios.component.ts (component constructor injection)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-bios.component.ts','ctor','src/app/hero-bios.component.ts (component constructor injection)')(format='.')
|
||||||
|
|
||||||
|
@ -83,12 +74,12 @@ include ../_util-fns
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## External module configuration
|
## External module configuration
|
||||||
We often register providers in the `NgModule` rather than in the root application component.
|
Generally, register providers in the `NgModule` rather than in the root application component.
|
||||||
|
|
||||||
We do this when (a) we expect the service to be injectable everywhere
|
Do this when you expect the service to be injectable everywhere,
|
||||||
or (b) we must configure another application global service _before it starts_.
|
or you are configuring another application global service _before the application starts_.
|
||||||
|
|
||||||
We see an example of the second case here, where we configure the Component Router with a non-default
|
Here is an example of the second case, where the component router configuration includes a non-default
|
||||||
[location strategy](../guide/router.html#location-strategy) by listing its provider
|
[location strategy](../guide/router.html#location-strategy) by listing its provider
|
||||||
in the `providers` list of the `AppModule`.
|
in the `providers` list of the `AppModule`.
|
||||||
|
|
||||||
|
@ -98,37 +89,42 @@ a(id="injectable")
|
||||||
a(id="nested-dependencies")
|
a(id="nested-dependencies")
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## *@Injectable* and nested service dependencies
|
## _@Injectable()_ and nested service dependencies
|
||||||
The consumer of an injected service does not know how to create that service.
|
The consumer of an injected service does not know how to create that service.
|
||||||
It shouldn't care.
|
It shouldn't care.
|
||||||
It's the dependency injection's job to create and cache that service.
|
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.
|
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.
|
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.
|
At each step, the consumer of dependencies simply declares what it requires in its
|
||||||
|
constructor and the framework takes over.
|
||||||
|
|
||||||
For example, we inject both the `LoggerService` and the `UserContext` in the `AppComponent`.
|
The following example shows injecting both the `LoggerService` and the `UserContext` in the `AppComponent`.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/app.component.ts','ctor','src/app/app.component.ts')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/app.component.ts','ctor','src/app/app.component.ts')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `UserContext` in turn has dependencies on both the `LoggerService` (again) and
|
The `UserContext` in turn has its own dependencies on both the `LoggerService` and
|
||||||
a `UserService` that gathers information about a particular user.
|
a `UserService` that gathers information about a particular user.
|
||||||
|
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/user-context.service.ts','injectables','user-context.service.ts (injection)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/user-context.service.ts','injectables','user-context.service.ts (injection)')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
When Angular creates an`AppComponent`, the dependency injection framework creates an instance of the `LoggerService` and
|
When Angular creates the `AppComponent`, the dependency injection framework creates an instance of the `LoggerService` and
|
||||||
starts to create the `UserContextService`.
|
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 `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 just `new` one into existence.
|
The `UserService` has no dependencies so the dependency injection framework can just
|
||||||
|
use `new` to instantiate one.
|
||||||
|
|
||||||
The beauty of dependency injection is that the author of `AppComponent` didn't care about any of this.
|
The beauty of dependency injection is that `AppComponent` doesn't care about any of this.
|
||||||
The author simply declared what was needed in the constructor (`LoggerService` and `UserContextService`) and the framework did the rest.
|
You simply declare what is needed in the constructor (`LoggerService` and `UserContextService`)
|
||||||
|
and the framework does the rest.
|
||||||
|
|
||||||
Once all the dependencies are in place, the `AppComponent` displays the user information:
|
Once all the dependencies are in place, the `AppComponent` displays the user information:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/cookbooks/dependency-injection/logged-in-user.png" alt="Logged In User")
|
img(src="/resources/images/cookbooks/dependency-injection/logged-in-user.png" alt="Logged In User")
|
||||||
|
|
||||||
|
a#injectable-1
|
||||||
:marked
|
:marked
|
||||||
### *@Injectable()*
|
### *@Injectable()*
|
||||||
Notice the `@Injectable()`decorator on the `UserContextService` class.
|
Notice the `@Injectable()`decorator on the `UserContextService` class.
|
||||||
|
@ -136,23 +132,24 @@ figure.image-display
|
||||||
:marked
|
:marked
|
||||||
That decorator makes it possible for Angular to identify the types of its two dependencies, `LoggerService` and `UserService`.
|
That decorator makes it possible for Angular to identify the types of its two dependencies, `LoggerService` and `UserService`.
|
||||||
|
|
||||||
Technically, the `@Injectable()`decorator is only _required_ for a service class that has _its own dependencies_.
|
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 we omitted `@Injectable()`
|
The `LoggerService` doesn't depend on anything. The logger would work if you omitted `@Injectable()`
|
||||||
and the generated code would be slightly smaller.
|
and the generated code would be slightly smaller.
|
||||||
|
|
||||||
But the service would break the moment we gave it a dependency and we'd have to go back
|
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. We add `@Injectable()` from the start for the sake of consistency and to avoid future pain.
|
and add `@Injectable()` to fix it. Add `@Injectable()` from the start for the sake
|
||||||
|
of consistency and to avoid future pain.
|
||||||
|
|
||||||
.alert.is-helpful
|
.alert.is-helpful
|
||||||
:marked
|
:marked
|
||||||
Although we recommend applying `@Injectable` to all service classes, do not feel bound by it.
|
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.
|
Some developers prefer to add it only where needed and that's a reasonable policy too.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The `AppComponent` class had two dependencies as well but no `@Injectable()`.
|
The `AppComponent` class had two dependencies as well but no `@Injectable()`.
|
||||||
It didn't need `@Injectable()` because that component class has the `@Component` decorator.
|
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.
|
In Angular with TypeScript, a *single* decorator—*any* decorator—is sufficient to identify dependency types.
|
||||||
|
|
||||||
|
|
||||||
<a id="service-scope"></a>
|
<a id="service-scope"></a>
|
||||||
|
@ -161,10 +158,10 @@ figure.image-display
|
||||||
## Limit service scope to a component subtree
|
## Limit service scope to a component subtree
|
||||||
|
|
||||||
All injected service dependencies are singletons meaning that,
|
All injected service dependencies are singletons meaning that,
|
||||||
for a given dependency injector ("injector"), there is only one instance of service.
|
for a given dependency injector, there is only one instance of service.
|
||||||
|
|
||||||
But an Angular application has multiple dependency injectors, arranged in a tree hierarchy that parallels the component tree.
|
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
|
So a particular service can be *provided* and created at any component level and multiple times
|
||||||
if provided in multiple components.
|
if provided in multiple components.
|
||||||
|
|
||||||
By default, a service dependency provided in one component is visible to all of its child components and
|
By default, a service dependency provided in one component is visible to all of its child components and
|
||||||
|
@ -173,21 +170,23 @@ figure.image-display
|
||||||
Accordingly, dependencies provided in the root `AppComponent` can be injected into *any* component *anywhere* in the application.
|
Accordingly, dependencies provided in the root `AppComponent` can be injected into *any* component *anywhere* in the application.
|
||||||
|
|
||||||
That isn't always desirable.
|
That isn't always desirable.
|
||||||
Sometimes we want to restrict service availability to a particular region of the application.
|
Sometimes you want to restrict service availability to a particular region of the application.
|
||||||
|
|
||||||
We can limit the scope of an injected service to a *branch* of the application hierarchy
|
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*.
|
by providing that service *at the sub-root component for that branch*.
|
||||||
Here we provide the `HeroService` to the `HeroesBaseComponent` by listing it in the `providers` array:
|
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:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/sorted-heroes.component.ts','injection','src/app/sorted-heroes.component.ts (HeroesBaseComponent excerpt)')
|
+makeExample('cb-dependency-injection/ts/src/app/sorted-heroes.component.ts','injection','src/app/sorted-heroes.component.ts (HeroesBaseComponent excerpt)')
|
||||||
:marked
|
:marked
|
||||||
When Angular creates the `HeroesBaseComponent`, it also creates a new instance of `HeroService`
|
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).
|
that is visible only to the component and its children, if any.
|
||||||
|
|
||||||
We could also provide the `HeroService` to a *different* component elsewhere in the application.
|
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.
|
That would result in a *different* instance of the service, living in a *different* injector.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
We examples of such scoped `HeroService` singletons appear throughout the accompanying sample code,
|
Examples of such scoped `HeroService` singletons appear throughout the accompanying sample code,
|
||||||
including the `HeroBiosComponent`, `HeroOfTheMonthComponent`, and `HeroesBaseComponent`.
|
including the `HeroBiosComponent`, `HeroOfTheMonthComponent`, and `HeroesBaseComponent`.
|
||||||
Each of these components has its own `HeroService` instance managing its own independent collection of heroes.
|
Each of these components has its own `HeroService` instance managing its own independent collection of heroes.
|
||||||
|
|
||||||
|
@ -203,12 +202,12 @@ figure.image-display
|
||||||
:marked
|
:marked
|
||||||
## Multiple service instances (sandboxing)
|
## Multiple service instances (sandboxing)
|
||||||
|
|
||||||
Sometimes we want multiple instances of a service at *the same level of the component hierarchy*.
|
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.
|
A good example is a service that holds state for its companion component instance.
|
||||||
We need a separate instance of the service for each component.
|
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.
|
Each service has its own work-state, isolated from the service-and-state of a different component.
|
||||||
We call this *sandboxing* because each service and component instance has its own sandbox to play in.
|
This is called *sandboxing* because each service and component instance has its own sandbox to play in.
|
||||||
|
|
||||||
<a id="hero-bios-component"></a>
|
<a id="hero-bios-component"></a>
|
||||||
Imagine a `HeroBiosComponent` that presents three instances of the `HeroBioComponent`.
|
Imagine a `HeroBiosComponent` that presents three instances of the `HeroBioComponent`.
|
||||||
|
@ -226,7 +225,7 @@ figure.image-display
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-bio.component.ts','component','src/app/hero-bio.component.ts')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-bio.component.ts','component','src/app/hero-bio.component.ts')
|
||||||
:marked
|
:marked
|
||||||
The parent `HeroBiosComponent` binds a value to the `heroId`.
|
The parent `HeroBiosComponent` binds a value to the `heroId`.
|
||||||
The `ngOnInit` pass that `id` to the service which fetches and caches the hero.
|
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.
|
The getter for the `hero` property pulls the cached hero from the service.
|
||||||
And the template displays this data-bound property.
|
And the template displays this data-bound property.
|
||||||
|
|
||||||
|
@ -239,15 +238,15 @@ a(id="optional")
|
||||||
a(id="qualify-dependency-lookup")
|
a(id="qualify-dependency-lookup")
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Qualify dependency lookup with *@Optional* and *@Host*
|
## Qualify dependency lookup with _@Optional()_ and `@Host()`
|
||||||
We learned that dependencies can be registered at any level in the component hierarchy.
|
As you now know, dependencies can be registered at any level in the component hierarchy.
|
||||||
|
|
||||||
When a component requests a dependency, Angular starts with that component's injector and walks up the injector tree
|
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.
|
until it finds the first suitable provider. Angular throws an error if it can't find the dependency during that walk.
|
||||||
|
|
||||||
We *want* this behavior most of the time.
|
You *want* this behavior most of the time.
|
||||||
But sometimes we need to limit the search and/or accommodate a missing dependency.
|
But sometimes you need to limit the search and/or accommodate a missing dependency.
|
||||||
We can modify Angular's search behavior with the `@Host` and `@Optional` qualifying decorators,
|
You can modify Angular's search behavior with the `@Host` and `@Optional` qualifying decorators,
|
||||||
used individually or together.
|
used individually or together.
|
||||||
|
|
||||||
The `@Optional` decorator tells Angular to continue when it can't find the dependency.
|
The `@Optional` decorator tells Angular to continue when it can't find the dependency.
|
||||||
|
@ -257,17 +256,19 @@ a(id="qualify-dependency-lookup")
|
||||||
|
|
||||||
The host component is typically the component requesting the dependency.
|
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.
|
But when this component is projected into a *parent* component, that parent component becomes the host.
|
||||||
We look at this second, more interesting case in our next example.
|
The next example covers this second case.
|
||||||
|
|
||||||
|
a#demonstration
|
||||||
|
:marked
|
||||||
### Demonstration
|
### Demonstration
|
||||||
The `HeroBiosAndContactsComponent` is a revision of the `HeroBiosComponent` that we looked at [above](#hero-bios-component).
|
The `HeroBiosAndContactsComponent` is a revision of the `HeroBiosComponent` that you looked at [above](#hero-bios-component).
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-bios.component.ts','hero-bios-and-contacts','src/app/hero-bios.component.ts (HeroBiosAndContactsComponent)')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-bios.component.ts','hero-bios-and-contacts','src/app/hero-bios.component.ts (HeroBiosAndContactsComponent)')
|
||||||
:marked
|
:marked
|
||||||
Focus on the template:
|
Focus on the template:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-bios.component.ts','template')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-bios.component.ts','template')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We've inserted a `<hero-contact>` element between the `<hero-bio>` tags.
|
Now there is a new `<hero-contact>` element between the `<hero-bio>` tags.
|
||||||
Angular *projects* (*transcludes*) the corresponding `HeroContactComponent` into the `HeroBioComponent` view,
|
Angular *projects*, or *transcludes*, the corresponding `HeroContactComponent` into the `HeroBioComponent` view,
|
||||||
placing it in the `<ng-content>` slot of the `HeroBioComponent` template:
|
placing it in the `<ng-content>` slot of the `HeroBioComponent` template:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-bio.component.ts','template','src/app/hero-bio.component.ts (template)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-bio.component.ts','template','src/app/hero-bio.component.ts (template)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
|
@ -275,58 +276,56 @@ a(id="qualify-dependency-lookup")
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/cookbooks/dependency-injection/hero-bio-and-content.png" alt="bio and contact")
|
img(src="/resources/images/cookbooks/dependency-injection/hero-bio-and-content.png" alt="bio and contact")
|
||||||
:marked
|
:marked
|
||||||
Here's the `HeroContactComponent` which demonstrates the qualifying decorators that we're talking about in this section:
|
Here's the `HeroContactComponent` which demonstrates the qualifying decorators:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-contact.component.ts','component','src/app/hero-contact.component.ts')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-contact.component.ts','component','src/app/hero-contact.component.ts')
|
||||||
:marked
|
:marked
|
||||||
Focus on the constructor parameters
|
Focus on the constructor parameters:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-contact.component.ts','ctor-params','src/app/hero-contact.component.ts')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-contact.component.ts','ctor-params','src/app/hero-contact.component.ts')(format='.')
|
||||||
:marked
|
:marked
|
||||||
The `@Host()` function decorating the `heroCache` property ensures that
|
The `@Host()` function decorating the `heroCache` property ensures that
|
||||||
we get a reference to the cache service from the parent `HeroBioComponent`.
|
you get a reference to the cache service from the parent `HeroBioComponent`.
|
||||||
Angular throws if the parent lacks that service, even if a component higher in the component tree happens to have that service.
|
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.
|
A second `@Host()` function decorates the `loggerService` property.
|
||||||
We know the only `LoggerService` instance in the app is provided at the `AppComponent` level.
|
The only `LoggerService` instance in the app is provided at the `AppComponent` level.
|
||||||
The host `HeroBioComponent` doesn't have its own `LoggerService` provider.
|
The host `HeroBioComponent` doesn't have its own `LoggerService` provider.
|
||||||
|
|
||||||
Angular would throw an error if we hadn't also decorated the property with the `@Optional()` function.
|
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.
|
Thanks to `@Optional()`, Angular sets the `loggerService` to null and the rest of the component adapts.
|
||||||
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
|
||||||
We'll come back to the `elementRef` property shortly.
|
|
||||||
:marked
|
:marked
|
||||||
Here's the `HeroBiosAndContactsComponent` in action.
|
Here's the `HeroBiosAndContactsComponent` in action.
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/cookbooks/dependency-injection/hero-bios-and-contacts.png" alt="Bios with contact into")
|
img(src="/resources/images/cookbooks/dependency-injection/hero-bios-and-contacts.png" alt="Bios with contact into")
|
||||||
:marked
|
:marked
|
||||||
If we comment out the `@Host()` decorator, Angular now walks up the injector ancestor tree
|
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
|
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.
|
with the gratuitous "!!!", indicating that the logger was found.
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/cookbooks/dependency-injection/hero-bio-contact-no-host.png" alt="Without @Host")
|
img(src="/resources/images/cookbooks/dependency-injection/hero-bio-contact-no-host.png" alt="Without @Host")
|
||||||
:marked
|
:marked
|
||||||
On the other hand, if we restore the `@Host()` decorator and comment out `@Optional`,
|
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.
|
the application fails for lack of the required logger at the host component level.
|
||||||
<br>
|
<br>
|
||||||
`EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)`
|
`EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)`
|
||||||
|
|
||||||
<a id="component-element"></a>
|
<a id="component-element"></a>
|
||||||
:marked
|
:marked
|
||||||
## Inject the component's element
|
## Inject the component's DOM element
|
||||||
|
|
||||||
On occasion we might need to access a component's corresponding DOM element.
|
On occasion you might need to access a component's corresponding DOM element.
|
||||||
Although we strive to avoid it, many visual effects and 3rd party tools (such as jQuery)
|
Although developers strive to avoid it, many visual effects and 3rd party tools, such as jQuery,
|
||||||
require DOM access.
|
require DOM access.
|
||||||
|
|
||||||
To illustrate, we've written a simplified version of the `HighlightDirective` from
|
To illustrate, here's a simplified version of the `HighlightDirective` from
|
||||||
the [Attribute Directives](../guide/attribute-directives.html) chapter.
|
the [Attribute Directives](../guide/attribute-directives.html) page.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/highlight.directive.ts','','src/app/highlight.directive.ts')
|
+makeExample('cb-dependency-injection/ts/src/app/highlight.directive.ts','','src/app/highlight.directive.ts')
|
||||||
:marked
|
:marked
|
||||||
The directive sets the background to a highlight color when the user mouses over the
|
The directive sets the background to a highlight color when the user mouses over the
|
||||||
DOM element to which it is applied.
|
DOM element to which it is applied.
|
||||||
|
|
||||||
Angular set the constructor's `el` parameter to the injected `ElementRef` which is
|
Angular sets the constructor's `el` parameter to the injected `ElementRef`, which is
|
||||||
a wrapper around that DOM element.
|
a wrapper around that DOM element.
|
||||||
Its `nativeElement` property exposes the DOM element for the directive to manipulate.
|
Its `nativeElement` property exposes the DOM element for the directive to manipulate.
|
||||||
|
|
||||||
|
@ -343,12 +342,11 @@ figure.image-display
|
||||||
:marked
|
:marked
|
||||||
## Define dependencies with providers
|
## Define dependencies with providers
|
||||||
|
|
||||||
In this section we learn to write providers that deliver dependent services.
|
This section demonstrates how to write providers that deliver dependent services.
|
||||||
|
|
||||||
### Background
|
Get a service from a dependency injector by giving it a ***token***.
|
||||||
We get a service from a dependency injector by giving it a ***token***.
|
|
||||||
|
|
||||||
We usually let Angular handle this transaction for us by specifying a constructor parameter and its type.
|
You usually let Angular handle this transaction by specifying a constructor parameter and its type.
|
||||||
The parameter type serves as the injector lookup *token*.
|
The parameter type serves as the injector lookup *token*.
|
||||||
Angular passes this token to the injector and assigns the result to the parameter.
|
Angular passes this token to the injector and assigns the result to the parameter.
|
||||||
Here's a typical example:
|
Here's a typical example:
|
||||||
|
@ -366,32 +364,32 @@ figure.image-display
|
||||||
:marked
|
:marked
|
||||||
If the injector doesn't have a provider for the requested *token*, it delegates the request
|
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.
|
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](#optional).
|
If the search is futile, the injector throws an error—unless the request was [optional](#optional).
|
||||||
|
|
||||||
Let's return our attention to providers themselves.
|
|
||||||
:marked
|
:marked
|
||||||
A new injector has no providers.
|
A new injector has no providers.
|
||||||
Angular initializes the injectors it creates with some providers it cares about.
|
Angular initializes the injectors it creates with some providers it cares about.
|
||||||
We have to register our _own_ application providers manually,
|
You have to register your _own_ application providers manually,
|
||||||
usually in the `providers` array of the `Component` or `Directive` metadata:
|
usually in the `providers` array of the `Component` or `Directive` metadata:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/app.component.ts','providers','src/app/app.component.ts (providers)')
|
+makeExample('cb-dependency-injection/ts/src/app/app.component.ts','providers','src/app/app.component.ts (providers)')
|
||||||
|
a#defining-providers
|
||||||
:marked
|
:marked
|
||||||
### Defining providers
|
### Defining providers
|
||||||
|
|
||||||
The simple class provider is the most typical by far.
|
The simple class provider is the most typical by far.
|
||||||
We mention the class in the `providers` array and we're done.
|
You mention the class in the `providers` array and you're done.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-bios.component.ts','class-provider','src/app/hero-bios.component.ts (class provider)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-bios.component.ts','class-provider','src/app/hero-bios.component.ts (class provider)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
It's that simple because the most common injected service is an instance of a class.
|
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.
|
But not every dependency can be satisfied by creating a new instance of a class.
|
||||||
We need other ways to deliver dependency values and that means we need other ways to specify a provider.
|
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 we need them.
|
The `HeroOfTheMonthComponent` example demonstrates many of the alternatives and why you need them.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/cookbooks/dependency-injection/hero-of-month.png" alt="Hero of the month" width="300px")
|
img(src="/resources/images/cookbooks/dependency-injection/hero-of-month.png" alt="Hero of the month" width="300px")
|
||||||
:marked
|
:marked
|
||||||
It's visually simple: a few properties and the output of a logger. The code behind it gives us plenty to talk about.
|
It's visually simple: a few properties and the output of a logger. The code behind it gives you plenty to think about.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','hero-of-the-month','hero-of-the-month.component.ts')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','hero-of-the-month','hero-of-the-month.component.ts')
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
|
@ -402,18 +400,18 @@ a(id='provide')
|
||||||
The `provide` object literal takes a *token* and a *definition object*.
|
The `provide` object literal takes a *token* and a *definition object*.
|
||||||
The *token* is usually a class but [it doesn't have to be](#tokens).
|
The *token* is usually a class but [it doesn't have to be](#tokens).
|
||||||
|
|
||||||
The *definition* object has one main property, (e.g. `useValue`) that indicates how the provider
|
The *definition* object has one main property, `useValue`, that indicates how the provider
|
||||||
should create or return the provided value.
|
should create or return the provided value.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
a(id='usevalue')
|
a(id='usevalue')
|
||||||
:marked
|
:marked
|
||||||
#### useValue - the *value provider*
|
#### useValue—the *value provider*
|
||||||
|
|
||||||
Set the `useValue` property to a ***fixed value*** that the provider can return as the dependency object.
|
Set the `useValue` property to a ***fixed value*** that the provider can return as the dependency object.
|
||||||
|
|
||||||
Use this technique to provide *runtime configuration constants* such as web-site base addresses and feature flags.
|
Use this technique to provide *runtime configuration constants* such as website base addresses and feature flags.
|
||||||
We often use a *value provider* in a unit test to replace a production service with a fake or mock.
|
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 `HeroOfTheMonthComponent` example has two *value providers*.
|
||||||
The first provides an instance of the `Hero` class;
|
The first provides an instance of the `Hero` class;
|
||||||
|
@ -425,9 +423,9 @@ a(id='usevalue')
|
||||||
|
|
||||||
The `TITLE` provider token is *not a class*.
|
The `TITLE` provider token is *not a class*.
|
||||||
It's a special kind of provider lookup key called an [OpaqueToken](#opaquetoken).
|
It's a special kind of provider lookup key called an [OpaqueToken](#opaquetoken).
|
||||||
We often use an `OpaqueToken` when the dependency is a simple value like a string, a number, or a function.
|
You can use an `OpaqueToken` 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*. We can't create the value later.
|
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.
|
Obviously the title string literal is immediately available.
|
||||||
The `someHero` variable in this example was set earlier in the file:
|
The `someHero` variable in this example was set earlier in the file:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','some-hero')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','some-hero')
|
||||||
|
@ -437,7 +435,7 @@ a(id='usevalue')
|
||||||
.l-main-section
|
.l-main-section
|
||||||
a(id='useclass')
|
a(id='useclass')
|
||||||
:marked
|
:marked
|
||||||
#### useClass - the *class provider*
|
#### useClass—the *class provider*
|
||||||
|
|
||||||
The `useClass` provider creates and returns new instance of the specified class.
|
The `useClass` provider creates and returns new instance of the specified class.
|
||||||
|
|
||||||
|
@ -445,12 +443,12 @@ a(id='useclass')
|
||||||
The alternative could implement a different strategy, extend the 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.
|
or fake the behavior of the real class in a test case.
|
||||||
|
|
||||||
We see two examples in the `HeroOfTheMonthComponent`:
|
Here are two examples in the `HeroOfTheMonthComponent`:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-class')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-class')(format='.')
|
||||||
:marked
|
:marked
|
||||||
The first provider is the *de-sugared*, expanded form of the most typical case in which the
|
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 injection token.
|
class to be created (`HeroService`) is also the provider's injection token.
|
||||||
We wrote it in this long form to de-mystify the preferred short form.
|
It's in this long form to de-mystify the preferred short form.
|
||||||
|
|
||||||
The second provider substitutes the `DateLoggerService` for the `LoggerService`.
|
The second provider substitutes the `DateLoggerService` for the `LoggerService`.
|
||||||
The `LoggerService` is already registered at the `AppComponent` level.
|
The `LoggerService` is already registered at the `AppComponent` level.
|
||||||
|
@ -466,17 +464,17 @@ a(id='useclass')
|
||||||
.l-main-section
|
.l-main-section
|
||||||
a(id='useexisting')
|
a(id='useexisting')
|
||||||
:marked
|
:marked
|
||||||
#### useExisting - the *alias provider*
|
#### _useExisting_—the *alias provider*
|
||||||
|
|
||||||
The `useExisting` provider maps one token to another.
|
The `useExisting` provider maps one token to another.
|
||||||
In effect, the first token is an ***alias*** for the service associated with second token,
|
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***.
|
creating ***two ways to access the same service object***.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-existing')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-existing')
|
||||||
:marked
|
:marked
|
||||||
Narrowing an API through an aliasing interface is _one_ important use case for this technique.
|
Narrowing an API through an aliasing interface is _one_ important use case for this technique.
|
||||||
We're aliasing for that very purpose here.
|
This example shows aliasing for that very purpose here.
|
||||||
Imagine that the `LoggerService` had a large API (it's actually only three methods and a property).
|
Imagine that the `LoggerService` had a large API; it's actually only three methods and a property.
|
||||||
We want to shrink that API surface to just the two members exposed by the `MinimalLogger` [*class-interface*](#class-interface):
|
You'd want to shrink that API surface to just the two members exposed by the `MinimalLogger` [*class-interface*](#class-interface):
|
||||||
|
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','minimal-logger','src/app/date-logger.service.ts (MinimalLogger)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','minimal-logger','src/app/date-logger.service.ts (MinimalLogger)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
|
@ -485,7 +483,8 @@ figure.image-display
|
||||||
img(src="/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png" alt="MinimalLogger restricted API")
|
img(src="/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png" alt="MinimalLogger restricted API")
|
||||||
:marked
|
:marked
|
||||||
Angular actually sets the `logger` parameter to the injector's full version of the `LoggerService`
|
Angular actually sets the `logger` parameter to the injector's full version of the `LoggerService`
|
||||||
which happens to be the `DateLoggerService` thanks to the override provider registered previously via `useClass`.
|
which happens to be the `DateLoggerService`. This is because of the override provider
|
||||||
|
registered previously via `useClass`.
|
||||||
The following image, which displays the logging date, confirms the point:
|
The following image, which displays the logging date, confirms the point:
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/cookbooks/dependency-injection/date-logger-entry.png" alt="DateLoggerService entry" width="300px")
|
img(src="/resources/images/cookbooks/dependency-injection/date-logger-entry.png" alt="DateLoggerService entry" width="300px")
|
||||||
|
@ -493,10 +492,10 @@ figure.image-display
|
||||||
.l-main-section
|
.l-main-section
|
||||||
a(id='usefactory')
|
a(id='usefactory')
|
||||||
:marked
|
:marked
|
||||||
#### useFactory - the *factory provider*
|
#### _useFactory_—the *factory provider*
|
||||||
|
|
||||||
The `useFactory` provider creates a dependency object by calling a factory function
|
The `useFactory` provider creates a dependency object by calling a factory function
|
||||||
as seen in this example.
|
as in this example.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-factory')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-factory')
|
||||||
:marked
|
:marked
|
||||||
Use this technique to ***create a dependency object***
|
Use this technique to ***create a dependency object***
|
||||||
|
@ -507,7 +506,7 @@ a(id='usefactory')
|
||||||
to the "Hero of the Month" contest.
|
to the "Hero of the Month" contest.
|
||||||
|
|
||||||
The local state is the number `2`, the number of runners-up this component should show.
|
The local state is the number `2`, the number of runners-up this component should show.
|
||||||
We execute `runnersUpFactory` immediately with `2`.
|
It executes `runnersUpFactory` immediately with `2`.
|
||||||
|
|
||||||
The `runnersUpFactory` itself isn't the provider factory function.
|
The `runnersUpFactory` itself isn't the provider factory function.
|
||||||
The true provider factory function is the function that `runnersUpFactory` returns.
|
The true provider factory function is the function that `runnersUpFactory` returns.
|
||||||
|
@ -537,34 +536,35 @@ a(id="tokens")
|
||||||
## Provider token alternatives: the *class-interface* and *OpaqueToken*
|
## Provider token alternatives: the *class-interface* and *OpaqueToken*
|
||||||
|
|
||||||
Angular dependency injection is easiest when the provider *token* is a class
|
Angular dependency injection is easiest when the provider *token* is a class
|
||||||
that is also the type of the returned dependency object (what we usually call the *service*).
|
that is also the type of the returned dependency object, or what you usually call the *service*.
|
||||||
|
|
||||||
But the token doesn't have to be a class and even when it is a class,
|
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.
|
it doesn't have to be the same type as the returned object.
|
||||||
That's the subject of our next section.
|
That's the subject of the next section.
|
||||||
|
|
||||||
<a id="class-interface"></a>
|
<a id="class-interface"></a>
|
||||||
|
:marked
|
||||||
### class-interface
|
### class-interface
|
||||||
In the previous *Hero of the Month* example, we used the `MinimalLogger` class
|
The previous *Hero of the Month* example used the `MinimalLogger` class
|
||||||
as the token for a provider of a `LoggerService`.
|
as the token for a provider of a `LoggerService`.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-existing')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','use-existing')
|
||||||
:marked
|
:marked
|
||||||
The `MinimalLogger` is an abstract class.
|
The `MinimalLogger` is an abstract class.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','minimal-logger')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','minimal-logger')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We usually inherit from an abstract class.
|
You usually inherit from an abstract class.
|
||||||
But `LoggerService` doesn't inherit from `MinimalLogger`. *No class* inherits from it.
|
But `LoggerService` doesn't inherit from `MinimalLogger`. *No class* inherits from it.
|
||||||
Instead, we use it like an interface.
|
Instead, you use it like an interface.
|
||||||
|
|
||||||
Look again at the declaration for `DateLoggerService`
|
Look again at the declaration for `DateLoggerService`:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','date-logger-service-signature')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','date-logger-service-signature')(format='.')
|
||||||
:marked
|
:marked
|
||||||
`DateLoggerService` inherits (extends) from `LoggerService`, not `MinimalLogger`.
|
`DateLoggerService` inherits (extends) from `LoggerService`, not `MinimalLogger`.
|
||||||
The `DateLoggerService` *implements* `MinimalLogger` as if `MinimalLogger` were an *interface*.
|
The `DateLoggerService` *implements* `MinimalLogger` as if `MinimalLogger` were an *interface*.
|
||||||
|
|
||||||
We call a class used in this way a ***class-interface***.
|
When you use a class this way, it's called a ***class-interface***.
|
||||||
The key benefit of a *class-interface* is that we can get the strong-typing of an interface
|
The key benefit of a *class-interface* is that you can get the strong-typing of an interface
|
||||||
and we can ***use it as a provider token*** in the same manner as a normal class.
|
and you can ***use it as a provider token*** in the same manner as a normal class.
|
||||||
|
|
||||||
A ***class-interface*** should define *only* the members that its consumers are allowed to call.
|
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.
|
Such a narrowing interface helps decouple the concrete class from its consumers.
|
||||||
|
@ -573,27 +573,27 @@ a(id="tokens")
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
#### Why *MinimalLogger* is a class and not an interface
|
#### Why *MinimalLogger* is a class and not an interface
|
||||||
We can't use an interface as a provider token because
|
You can't use an interface as a provider token because
|
||||||
interfaces are not JavaScript objects.
|
interfaces are not JavaScript objects.
|
||||||
They exist only in the TypeScript design space.
|
They exist only in the TypeScript design space.
|
||||||
They disappear after the code is transpiled to JavaScript.
|
They disappear after the code is transpiled to JavaScript.
|
||||||
|
|
||||||
A provider token must be a real JavaScript object of some kind:
|
A provider token must be a real JavaScript object of some kind:
|
||||||
a function, an object, a string ... a class.
|
such as a function, an object, a string, or a class.
|
||||||
|
|
||||||
Using a class as an interface gives us the characteristics of an interface in a JavaScript object.
|
Using a class as an interface gives you the characteristics of an interface in a JavaScript object.
|
||||||
|
|
||||||
The minimize memory cost, the class should have *no implementation*.
|
To minimize memory cost, the class should have *no implementation*.
|
||||||
The `MinimalLogger` transpiles to this unoptimized, pre-minified JavaScript:
|
The `MinimalLogger` transpiles to this unoptimized, pre-minified JavaScript:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','minimal-logger-transpiled')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/date-logger.service.ts','minimal-logger-transpiled')(format='.')
|
||||||
:marked
|
:marked
|
||||||
It never grows larger no matter how many members we add *as long as they are typed but not implemented*.
|
It never grows larger no matter how many members you add *as long as they are typed but not implemented*.
|
||||||
|
|
||||||
a(id='opaque-token')
|
a(id='opaque-token')
|
||||||
:marked
|
:marked
|
||||||
### OpaqueToken
|
### _OpaqueToken_
|
||||||
|
|
||||||
Dependency objects can be simple values like dates, numbers and strings or
|
Dependency objects can be simple values like dates, numbers and strings, or
|
||||||
shapeless objects like arrays and functions.
|
shapeless objects like arrays and functions.
|
||||||
|
|
||||||
Such objects don't have application interfaces and therefore aren't well represented by a class.
|
Such objects don't have application interfaces and therefore aren't well represented by a class.
|
||||||
|
@ -602,11 +602,11 @@ a(id='opaque-token')
|
||||||
another token that happens to have the same name.
|
another token that happens to have the same name.
|
||||||
|
|
||||||
The `OpaqueToken` has these characteristics.
|
The `OpaqueToken` has these characteristics.
|
||||||
We encountered them twice in the *Hero of the Month* example,
|
You encountered them twice in the *Hero of the Month* example,
|
||||||
in the *title* value provider and in the *runnersUp* factory provider.
|
in the *title* value provider and in the *runnersUp* factory provider.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','provide-opaque-token')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','provide-opaque-token')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We created the `TITLE` token like this:
|
You created the `TITLE` token like this:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','opaque-token')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts','opaque-token')(format='.')
|
||||||
|
|
||||||
|
|
||||||
|
@ -614,9 +614,9 @@ a(id="di-inheritance")
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Inject into a derived class
|
## Inject into a derived class
|
||||||
We must take care when writing a component that inherits from another component.
|
Take care when writing a component that inherits from another component.
|
||||||
If the base component has injected dependencies,
|
If the base component has injected dependencies,
|
||||||
we must re-provide and re-inject them in the derived class
|
you must re-provide and re-inject them in the derived class
|
||||||
and then pass them down to the base class through the constructor.
|
and then pass them down to the base class through the constructor.
|
||||||
|
|
||||||
In this contrived example, `SortedHeroesComponent` inherits from `HeroesBaseComponent`
|
In this contrived example, `SortedHeroesComponent` inherits from `HeroesBaseComponent`
|
||||||
|
@ -632,30 +632,28 @@ figure.image-display
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/sorted-heroes.component.ts','heroes-base','src/app/sorted-heroes.component.ts (HeroesBaseComponent)')
|
+makeExample('cb-dependency-injection/ts/src/app/sorted-heroes.component.ts','heroes-base','src/app/sorted-heroes.component.ts (HeroesBaseComponent)')
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
We strongly prefer simple constructors. They should do little more than initialize variables.
|
***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.
|
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 we call the `HeroService` from within the `ngOnInit` rather than the constructor.
|
That's why you call the `HeroService` from within the `ngOnInit` rather than the constructor.
|
||||||
|
|
||||||
We explain the mysterious `afterGetHeroes` below.
|
|
||||||
:marked
|
:marked
|
||||||
Users want to see the heroes in alphabetical order.
|
Users want to see the heroes in alphabetical order.
|
||||||
Rather than modify the original component, we sub-class it and create a
|
Rather than modify the original component, sub-class it and create a
|
||||||
`SortedHeroesComponent` that sorts the heroes before presenting them.
|
`SortedHeroesComponent` that sorts the heroes before presenting them.
|
||||||
The `SortedHeroesComponent` lets the base class fetch the heroes.
|
The `SortedHeroesComponent` lets the base class fetch the heroes.
|
||||||
(we said it was contrived).
|
|
||||||
|
|
||||||
Unfortunately, Angular cannot inject the `HeroService` directly into the base class.
|
Unfortunately, Angular cannot inject the `HeroService` directly into the base class.
|
||||||
We must provide the `HeroService` again for *this* component,
|
You must provide the `HeroService` again for *this* component,
|
||||||
then pass it down to the base class inside the constructor.
|
then pass it down to the base class inside the constructor.
|
||||||
|
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/sorted-heroes.component.ts','sorted-heroes','src/app/sorted-heroes.component.ts (SortedHeroesComponent)')
|
+makeExample('cb-dependency-injection/ts/src/app/sorted-heroes.component.ts','sorted-heroes','src/app/sorted-heroes.component.ts (SortedHeroesComponent)')
|
||||||
:marked
|
:marked
|
||||||
Now take note of the `afterGetHeroes` method.
|
Now take note of the `afterGetHeroes()` method.
|
||||||
Our first instinct was to create an `ngOnInit` method in `SortedHeroesComponent` and do the sorting there.
|
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 `ngOnInit` *before* calling the base class's `ngOnInit`
|
But Angular calls the *derived* class's `ngOnInit` *before* calling the base class's `ngOnInit`
|
||||||
so we'd be sorting the heroes array *before they arrived*. That produces a nasty error.
|
so 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
|
Overriding the base class's `afterGetHeroes()` method solves the problem.
|
||||||
|
|
||||||
These complications argue for *avoiding component inheritance*.
|
These complications argue for *avoiding component inheritance*.
|
||||||
|
|
||||||
|
@ -665,27 +663,29 @@ a(id="find-parent")
|
||||||
## Find a parent component by injection
|
## Find a parent component by injection
|
||||||
|
|
||||||
Application components often need to share information.
|
Application components often need to share information.
|
||||||
We prefer the more loosely coupled techniques such as data binding and service sharing.
|
More loosely coupled techniques such as data binding and service sharing
|
||||||
But sometimes it makes sense for one component to have a direct reference to another component
|
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.
|
perhaps to access values or call methods on that component.
|
||||||
|
|
||||||
Obtaining a component reference is a bit tricky in Angular.
|
Obtaining a component reference is a bit tricky in Angular.
|
||||||
Although an Angular application is a tree of components,
|
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 inspecting and traversing that tree.
|
||||||
|
|
||||||
There is an API for acquiring a child reference
|
There is an API for acquiring a child reference.
|
||||||
(checkout `Query`, `QueryList`, `ViewChildren`, and `ContentChildren`).
|
Check out `Query`, `QueryList`, `ViewChildren`, and `ContentChildren`
|
||||||
|
in the [API Reference](../api/).
|
||||||
|
|
||||||
There is no public API for acquiring a parent reference.
|
There is no public API for acquiring a parent reference.
|
||||||
But because every component instance is added to an injector's container,
|
But because every component instance is added to an injector's container,
|
||||||
we can use Angular dependency injection to reach a parent component.
|
you can use Angular dependency injection to reach a parent component.
|
||||||
|
|
||||||
This section describes some techniques for doing that.
|
This section describes some techniques for doing that.
|
||||||
|
|
||||||
<a id="known-parent"></a>
|
<a id="known-parent"></a>
|
||||||
### Find a parent component of known type
|
### Find a parent component of known type
|
||||||
|
|
||||||
We use standard class injection to acquire a parent component whose type we know.
|
You use standard class injection to acquire a parent component whose type you know.
|
||||||
|
|
||||||
In the following example, the parent `AlexComponent` has several children including a `CathyComponent`:
|
In the following example, the parent `AlexComponent` has several children including a `CathyComponent`:
|
||||||
a(id='alex')
|
a(id='alex')
|
||||||
|
@ -695,35 +695,38 @@ a(id='alex')
|
||||||
after injecting an `AlexComponent` into her constructor:
|
after injecting an `AlexComponent` into her constructor:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','cathy','parent-finder.component.ts (CathyComponent)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','cathy','parent-finder.component.ts (CathyComponent)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We added the [@Optional](#optional) qualifier for safety but
|
Notice that even though the [@Optional](#optional) qualifier
|
||||||
|
is there for safety,
|
||||||
the <live-example name="cb-dependency-injection"></live-example>
|
the <live-example name="cb-dependency-injection"></live-example>
|
||||||
confirms that the `alex` parameter is set.
|
confirms that the `alex` parameter is set.
|
||||||
|
|
||||||
<a id="base-parent"></a>
|
<a id="base-parent"></a>
|
||||||
### Cannot find a parent by its base class
|
### Cannot find a parent by its base class
|
||||||
|
|
||||||
What if we do *not* know the concrete parent component class?
|
What if you *don't* know the concrete parent component class?
|
||||||
|
|
||||||
A re-usable component might be a child of multiple components.
|
A re-usable component might be a child of multiple components.
|
||||||
Imagine a component for rendering breaking news about a financial instrument.
|
Imagine a component for rendering breaking news about a financial instrument.
|
||||||
For sound (cough) business reasons, this news component makes frequent calls
|
For business reasons, this news component makes frequent calls
|
||||||
directly into its parent instrument as changing market data stream by.
|
directly into its parent instrument as changing market data streams by.
|
||||||
|
|
||||||
The app probably defines more than a dozen financial instrument components.
|
The app probably defines more than a dozen financial instrument components.
|
||||||
If we're lucky, they all implement the same base class
|
If you're lucky, they all implement the same base class
|
||||||
whose API our `NewsComponent` understands.
|
whose API your `NewsComponent` understands.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Looking for components that implement an interface would be better.
|
Looking for components that implement an interface would be better.
|
||||||
That's not possible because TypeScript interfaces disappear from the transpiled JavaScript
|
That's not possible because TypeScript interfaces disappear
|
||||||
which doesn't support interfaces. There's no artifact we could look for.
|
from the transpiled JavaScript, which doesn't support interfaces.
|
||||||
|
There's no artifact to look for.
|
||||||
:marked
|
:marked
|
||||||
We're not claiming this is good design.
|
This isn't necessarily good design.
|
||||||
We are asking *can a component inject its parent via the parent's base class*?
|
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](#alex)
|
The sample's `CraigComponent` explores this question. [Looking back](#alex),
|
||||||
we see that the `Alex` component *extends* (*inherits*) from a class named `Base`.
|
you see that the `Alex` component *extends* (*inherits*) from a class named `Base`.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-class-signature','parent-finder.component.ts (Alex class signature)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-class-signature','parent-finder.component.ts (Alex class signature)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
The `CraigComponent` tries to inject `Base` into its `alex` constructor parameter and reports if it succeeded.
|
The `CraigComponent` tries to inject `Base` into its `alex` constructor parameter and reports if it succeeded.
|
||||||
|
@ -732,28 +735,29 @@ a(id='alex')
|
||||||
Unfortunately, this does not work.
|
Unfortunately, this does not work.
|
||||||
The <live-example name="cb-dependency-injection"></live-example>
|
The <live-example name="cb-dependency-injection"></live-example>
|
||||||
confirms that the `alex` parameter is null.
|
confirms that the `alex` parameter is null.
|
||||||
*We cannot inject a parent by its base class.*
|
*You cannot inject a parent by its base class.*
|
||||||
|
|
||||||
<a id="class-interface-parent"></a>
|
<a id="class-interface-parent"></a>
|
||||||
### Find a parent by its class-interface
|
### Find a parent by its class-interface
|
||||||
|
|
||||||
We can find a parent component with a [class-interface](#class-interface).
|
You can find a parent component with a [class-interface](#class-interface).
|
||||||
|
|
||||||
The parent must cooperate by providing an *alias* to itself in the name of a *class-interface* token.
|
The parent must cooperate by providing an *alias* to itself in the name of a *class-interface* token.
|
||||||
|
|
||||||
Recall that Angular always adds a component instance to its own injector;
|
Recall that Angular always adds a component instance to its own injector;
|
||||||
that's why we could inject *Alex* into *Cathy* [earlier](#known-parent).
|
that's why you could inject *Alex* into *Cathy* [earlier](#known-parent).
|
||||||
|
|
||||||
We write an [*alias provider*](#useexisting) — a `provide` object literal with a `useExisting` definition —
|
Write an [*alias provider*](#useexisting)—a `provide` object literal with a `useExisting`
|
||||||
that creates an *alternative* way to inject the same component instance
|
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`:
|
and add that provider to the `providers` array of the `@Component` metadata for the `AlexComponent`:
|
||||||
a(id="alex-providers")
|
a(id="alex-providers")
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-providers','parent-finder.component.ts (AlexComponent providers)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-providers','parent-finder.component.ts (AlexComponent providers)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
[Parent](#parent-token) is the provider's *class-interface* token.
|
[Parent](#parent-token) is the provider's *class-interface* token.
|
||||||
The [*forwardRef*](#forwardref) breaks the circular reference we just created by having the `AlexComponent` refer to itself.
|
The [*forwardRef*](#forwardref) breaks the circular reference you just created by having the `AlexComponent` refer to itself.
|
||||||
|
|
||||||
*Carol*, the third of *Alex*'s child components, injects the parent into its `parent` parameter, the same way we've done it before:
|
*Carol*, the third of *Alex*'s child components, injects the parent into its `parent` parameter,
|
||||||
|
the same way you've done it before:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','carol-class','parent-finder.component.ts (CarolComponent class)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','carol-class','parent-finder.component.ts (CarolComponent class)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
Here's *Alex* and family in action:
|
Here's *Alex* and family in action:
|
||||||
|
@ -762,7 +766,7 @@ figure.image-display
|
||||||
|
|
||||||
a(id="parent-tree")
|
a(id="parent-tree")
|
||||||
:marked
|
:marked
|
||||||
### Find the parent in a tree of parents
|
### Find the parent in a tree of parents with _@SkipSelf()_
|
||||||
|
|
||||||
Imagine one branch of a component hierarchy: *Alice* -> *Barry* -> *Carol*.
|
Imagine one branch of a component hierarchy: *Alice* -> *Barry* -> *Carol*.
|
||||||
Both *Alice* and *Barry* implement the `Parent` *class-interface*.
|
Both *Alice* and *Barry* implement the `Parent` *class-interface*.
|
||||||
|
@ -775,7 +779,7 @@ a(id="parent-tree")
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','barry','parent-finder.component.ts (BarryComponent)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','barry','parent-finder.component.ts (BarryComponent)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
*Barry*'s `providers` array looks just like [*Alex*'s](#alex-providers).
|
*Barry*'s `providers` array looks just like [*Alex*'s](#alex-providers).
|
||||||
If we're going to keep writing [*alias providers*](#useexisting) like this we should create a [helper function](#provideparent).
|
If you're going to keep writing [*alias providers*](#useexisting) like this you should create a [helper function](#provideparent).
|
||||||
|
|
||||||
For now, focus on *Barry*'s constructor:
|
For now, focus on *Barry*'s constructor:
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
|
@ -791,7 +795,7 @@ a(id="parent-tree")
|
||||||
1. It tells the injector to start its search for a `Parent` dependency in a component *above* itself,
|
1. It tells the injector to start its search for a `Parent` dependency in a component *above* itself,
|
||||||
which *is* what parent means.
|
which *is* what parent means.
|
||||||
|
|
||||||
2. Angular throws a cyclic dependency error if we omit the `@SkipSelf` decorator.
|
2. Angular throws a cyclic dependency error if you omit the `@SkipSelf` decorator.
|
||||||
|
|
||||||
`Cannot instantiate cyclic dependency! (BethComponent -> Parent -> BethComponent)`
|
`Cannot instantiate cyclic dependency! (BethComponent -> Parent -> BethComponent)`
|
||||||
|
|
||||||
|
@ -803,20 +807,20 @@ figure.image-display
|
||||||
a(id="parent-token")
|
a(id="parent-token")
|
||||||
:marked
|
:marked
|
||||||
### The *Parent* class-interface
|
### The *Parent* class-interface
|
||||||
We [learned earlier](#class-interface) that a *class-interface* is an abstract class used as an interface rather than as a base class.
|
You [learned earlier](#class-interface) that a *class-interface* is an abstract class used as an interface rather than as a base class.
|
||||||
|
|
||||||
Our example defines a `Parent` *class-interface* .
|
The example defines a `Parent` *class-interface*.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','parent','parent-finder.component.ts (Parent class-interface)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','parent','parent-finder.component.ts (Parent class-interface)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
The `Parent` *class-interface* defines a `name` property with a type declaration but *no implementation*.,
|
The `Parent` *class-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.
|
The `name` property is the only member of a parent component that a child component can call.
|
||||||
Such a narrowing interface helps decouple the child component class from its parent components.
|
Such a narrow interface helps decouple the child component class from its parent components.
|
||||||
|
|
||||||
A component that could serve as a parent *should* implement the *class-interface* as the `AliceComponent` does:
|
A component that could serve as a parent *should* implement the *class-interface* as the `AliceComponent` does:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alice-class-signature','parent-finder.component.ts (AliceComponent class signature)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alice-class-signature','parent-finder.component.ts (AliceComponent class signature)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
Doing so adds clarity to the code. But it's not technically necessary.
|
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)
|
Although the `AlexComponent` has a `name` property, as required by its `Base` class,
|
||||||
its class signature doesn't mention `Parent`:
|
its class signature doesn't mention `Parent`:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-class-signature','parent-finder.component.ts (AlexComponent class signature)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-class-signature','parent-finder.component.ts (AlexComponent class signature)')(format='.')
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
|
@ -826,25 +830,25 @@ a(id="parent-token")
|
||||||
|
|
||||||
a(id="provideparent")
|
a(id="provideparent")
|
||||||
:marked
|
:marked
|
||||||
### A *provideParent* helper function
|
### A _provideParent()_ helper function
|
||||||
|
|
||||||
Writing variations of the same parent *alias provider* gets old quickly,
|
Writing variations of the same parent *alias provider* gets old quickly,
|
||||||
especially this awful mouthful with a [*forwardRef*](#forwardref):
|
especially this awful mouthful with a [*forwardRef*](#forwardref):
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-providers')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-providers')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We can extract that logic into a helper function like this:
|
You can extract that logic into a helper function like this:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','provide-the-parent')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','provide-the-parent')(format='.')
|
||||||
:marked
|
:marked
|
||||||
Now we can add a simpler, more meaningful parent provider to our components:
|
Now you can add a simpler, more meaningful parent provider to your components:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alice-providers')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alice-providers')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We can do better. The current version of the helper function can only alias the `Parent` *class-interface*.
|
You can do better. The current version of the helper function can only alias the `Parent` *class-interface*.
|
||||||
Our application might have a variety of parent types, each with its own *class-interface* token.
|
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*.
|
Here's a revised version that defaults to `parent` but also accepts an optional second parameter for a different parent *class-interface*.
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','provide-parent')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','provide-parent')(format='.')
|
||||||
:marked
|
:marked
|
||||||
And here's how we could use it with a different parent type:
|
And here's how you could use it with a different parent type:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','beth-providers')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','beth-providers')(format='.')
|
||||||
|
|
||||||
a(id="forwardref")
|
a(id="forwardref")
|
||||||
|
@ -853,23 +857,23 @@ a(id="forwardref")
|
||||||
## Break circularities with a forward class reference (*forwardRef*)
|
## Break circularities with a forward class reference (*forwardRef*)
|
||||||
|
|
||||||
The order of class declaration matters in TypeScript.
|
The order of class declaration matters in TypeScript.
|
||||||
We can't refer directly to a class until it's been defined.
|
You can't refer directly to a class until it's been defined.
|
||||||
|
|
||||||
This isn't usually a problem, especially if we adhere to the recommended *one class per file* rule.
|
This isn't usually a problem, especially if you adhere to the recommended *one class per file* rule.
|
||||||
But sometimes circular references are unavoidable.
|
But sometimes circular references are unavoidable.
|
||||||
We're in a bind when class 'A refers to class 'B' and 'B' refers to 'A'.
|
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.
|
One of them has to be defined first.
|
||||||
|
|
||||||
The Angular `forwardRef` function creates an *indirect* reference that Angular can resolve later.
|
The 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.
|
The *Parent Finder* sample is full of circular class references that are impossible to break.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
We face this dilemma when a class makes *a reference to itself*
|
You face this dilemma when a class makes *a reference to itself*
|
||||||
as does the `AlexComponent` in its `providers` array.
|
as does the `AlexComponent` in its `providers` array.
|
||||||
The `providers` array is a property of the `@Component` decorator function which must
|
The `providers` array is a property of the `@Component` decorator function which must
|
||||||
appear *above* the class definition.
|
appear *above* the class definition.
|
||||||
|
|
||||||
We break the circularity with `forwardRef`:
|
Break the circularity with `forwardRef`:
|
||||||
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-providers','parent-finder.component.ts (AlexComponent providers)')(format='.')
|
+makeExample('cb-dependency-injection/ts/src/app/parent-finder.component.ts','alex-providers','parent-finder.component.ts (AlexComponent providers)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
|
|
Loading…
Reference in New Issue