diff --git a/public/docs/_examples/dependency-injection/ts/app/heroes/hero.service.2.ts b/public/docs/_examples/dependency-injection/ts/app/heroes/hero.service.2.ts index 7060373fbf..d0737bb611 100644 --- a/public/docs/_examples/dependency-injection/ts/app/heroes/hero.service.2.ts +++ b/public/docs/_examples/dependency-injection/ts/app/heroes/hero.service.2.ts @@ -7,7 +7,9 @@ import {Logger} from '../logger.service'; @Injectable() export class HeroService { + //#docregion ctor constructor(private _logger: Logger) { } + //#enddocregion ctor getHeroes() { this._logger.log('Getting heroes ...') diff --git a/public/docs/_examples/dependency-injection/ts/app/providers.component.ts b/public/docs/_examples/dependency-injection/ts/app/providers.component.ts index ebc8bfe162..de698f70e9 100644 --- a/public/docs/_examples/dependency-injection/ts/app/providers.component.ts +++ b/public/docs/_examples/dependency-injection/ts/app/providers.component.ts @@ -1,4 +1,5 @@ // Examples of provider arrays +//#docplaster import { Component, Host, Inject, Injectable, provide, Provider} from 'angular2/core'; @@ -103,10 +104,8 @@ class EvenBetterLogger { template: template, providers: //#docregion providers-5 - [ - UserService, - provide(Logger, {useClass: EvenBetterLogger}) - ] + [ UserService, + provide(Logger, {useClass: EvenBetterLogger}) ] //#enddocregion providers-5 }) export class ProviderComponent5 { @@ -131,11 +130,9 @@ class OldLogger { template: template, providers: //#docregion providers-6a - [ - NewLogger, + [ NewLogger, // Not aliased! Creates two instances of `NewLogger` - provide(OldLogger, {useClass:NewLogger}) - ] + provide(OldLogger, {useClass:NewLogger}) ] //#enddocregion providers-6a }) export class ProviderComponent6a { @@ -156,11 +153,9 @@ export class ProviderComponent6a { template: template, providers: //#docregion providers-6b - [ - NewLogger, + [ NewLogger, // Alias OldLogger w/ reference to NewLogger - provide(OldLogger, {useExisting: NewLogger}) - ] + provide(OldLogger, {useExisting: NewLogger}) ] //#enddocregion providers-6b }) export class ProviderComponent6b { @@ -306,7 +301,9 @@ export class ProviderComponent10b { log: (msg:string)=> this._logger.logs.push(msg), logs: [] } + // #enddocregion provider-10-logger this._logger.log("Optional logger was not available.") + // #docregion provider-10-logger } // #enddocregion provider-10-logger else { diff --git a/public/docs/ts/latest/guide/dependency-injection.jade b/public/docs/ts/latest/guide/dependency-injection.jade index e180df003d..043eb12006 100644 --- a/public/docs/ts/latest/guide/dependency-injection.jade +++ b/public/docs/ts/latest/guide/dependency-injection.jade @@ -399,38 +399,41 @@ include ../../../../_includes/_util-fns We're likely to need the same logger service everywhere in our application so we put it at the root level of the application in the `app/` folder and we register it in the `providers` array of the metadata for our application root component, `AppComponent`. -+makeExample('dependency-injection/ts/app/providers.component.ts','providers-1', 'app/app.component.ts (providers)') ++makeExample('dependency-injection/ts/app/providers.component.ts','providers-logger', 'app/app.component.ts (providers)') :marked - If we forget to register it, Angular will throw an exception when it first needs the logger: + If we forget to register it, Angular throws an exception when it first looks for the logger: code-example(format). EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger) :marked - That's telling us that the dependency injector couldn't find the *provider* for the logger - when it first tried to call the logger — inside the `HeroService` when the `HeroListComponent` - was calling for heroes. - The *provider* is the subject of our next section. + That's Angular telling us that the dependency injector couldn't find the *provider* for the logger. + It needed that provider to create a `Logger` to inject into a new `HeroService` which it needed to + create and inject into a new `HeroListComponent`. + + The chain of creations started with the `Logger` provider. The *provider* is the subject of our next section. But wait! What if the logger is optional? ### Optional dependencies Our `HeroService` currently requires a `Logger`. What if we could get by without a logger? - We'd use it if we had it, ignore it if we didn't. + We'd use it if we had it, ignore it if we didn't. We can do that. - First we import the `@Optional` decorator. + First import the `@Optional()` decorator. +makeExample('dependency-injection/ts/app/providers.component.ts','import-optional')(format='.') :marked - Then rewrite the constructor with that decorator to make the logger optional. + Then rewrite the constructor with `@Optional()` decorator preceding the private `_logger` parameter. + That tells the injector that `_logger` is optional. +makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-ctor')(format='.') :marked Be prepared for a null logger. If we don't register one somewhere up the line, - the injector will inject `null`. We have a method that logs. What can we do? + the injector will inject `null`. We have a method that logs. + What can we do to avoid a null reference exception? - We could substitute a *do-nothing* logger stub so that our methods continue to work: + We could substitute a *do-nothing* logger stub so that calling methods continue to work: +makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-logger')(format='.') :marked Obviously we'd take a more sophisticated approach if the logger were optional - elsewhere as well. + in multiple locations. But enough about optional loggers. In our sample application, the `Logger` is required. We must register a `Logger` with the application injector using *providers* @@ -445,18 +448,15 @@ code-example(format). The injector relies on **providers** to create instances of the services that it injects into components and other services. - We must register *providers* with the injector or it won't know what to do. + We must register a service *provider* with the injector or it won't know how to create the service. Earlier we registered the `Logger` service in the `providers` array of the metadata for the `AppComponent` like this: +makeExample('dependency-injection/ts/app/providers.component.ts','providers-logger') :marked - The `providers` array appears to hold service classes (one service class in this example). - In reality it holds instances of the [Provider](../api/core/Provider-class.html) class. - - In our example, when the `HeroService` constructor specifies `logger:Logger`, it's expecting - something that has the shape and behavior of the `Logger` class. - - There are many ways to *provide* something that has the shape and behavior of a `Logger`. + The `providers` array appears to hold a service class. + In reality it holds an instance the [Provider](../api/core/Provider-class.html) class that can create that service. + + There are many ways to *provide* something that looks and behaves like a `Logger`. The `Logger` class itself is an obvious and natural provider - it has the right shape and it's designed to be created. But it's not the only way. @@ -511,7 +511,7 @@ code-example(format). ### Aliased Class Providers Suppose there is an old component that depends upon an `OldLogger` class. - `OldLogger` has the same interface as the `NewLogger` but, for some reason, + `OldLogger` has the same interface as the `NewLogger` but for some reason we can't update the old component to use it. When the *old* component logs a message with `OldLogger`, @@ -545,14 +545,15 @@ code-example(format). Sometimes we need to create the dependent value dynamically, based on information we won't have until the last possible moment. - Maybe the information can change. + Maybe the information keeps changes repeatedly in the course of the browser session.. Suppose also that the injectable service has no independent access to the source of this information. - This situation calls for a **factory provider** as we illustrate next. + This situation calls for a **factory provider**. - Our HeroService should hide *secret* heroes from normal users. - Only authorized users should see them. + Let's illustrate by adding a new business requirement: + the HeroService must hide *secret* heroes from normal users. + Only authorized users should see secret heroes. Like the `EvenBetterLogger`, the `HeroService` needs a fact about the user. It needs to know if the user is authorized to see secret heroes. @@ -560,6 +561,8 @@ code-example(format). as when we log in a different user. Unlike `EvenBetterLogger`, we can't inject the `UserService` into the `HeroService`. + The `HeroService` won't have direct access to the user information to decide + who is authoriazed and who is not. .l-sub-section :marked Why? We don't know either. Stuff like this happens. @@ -573,9 +576,9 @@ code-example(format). A factory provider needs a factory function: +makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','factory', 'app/heroes/hero.service.provider.ts (factory)')(format='.') :marked - Although our `HeroService` knows nothing about the `UserService`, our factory function does. + Although the `HeroService` has no access to the `UserService`, our factory function does. - We inject both the `Logger` and the `UserService` into the factory provider: + We inject both the `Logger` and the `UserService` into the factory provider and let the injector pass them along to the factory function: +makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','provider', 'app/heroes/hero.service.provider.ts (provider)')(format='.') :marked @@ -586,12 +589,15 @@ code-example(format). The `deps` property is an array of [provider tokens](#token). The `Logger` and `UserService` classes serve as tokens for their own class providers. + The injector resolves these tokens and injects the corresponding services into the matching factory function parameters. :marked - Notice that we've captured the factory provider in an exported variable, `heroServiceProvider`. - This extra step makes it easier for us to register our `HeroService` whereever we need it. + Notice that we captured the factory provider in an exported variable, `heroServiceProvider`. + This extra step makes the factory provider re-usable. + We can register our `HeroService` with this variable whereever we need it. In our sample, we need it only in the `HeroesComponent` - where it replaces the previous `HeroService` registration in the metadata `providers` array: + where it replaces the previous `HeroService` registration in the metadata `providers` array. + Here we see the new and the old implementation side-by-side: +makeTabs( `dependency-injection/ts/app/heroes/heroes.component.ts, dependency-injection/ts/app/heroes/heroes.component.1.ts`, @@ -606,11 +612,11 @@ code-example(format). When we register a provider with an injector we associate that provider with a dependency injection token. The injector maintains an internal *token/provider* map that it references when - asked for a dependency + asked for a dependency. The token is the key to the map. In all previous examples, the dependency value has been a class *instance* and - the class *type* served as its own lookup token. - Here we get a `HeroService` directly from the injector by supplying the `HeroService` type as the token. + the class *type* served as its own lookup key. + Here we get a `HeroService` directly from the injector by supplying the `HeroService` type as the key/token. +makeExample('dependency-injection/ts/app/injector.component.ts','get-hero-service')(format='.') :marked We have similar good fortune (in typescript) when we write a constructor that requires an injected class-based dependency. @@ -618,7 +624,7 @@ code-example(format). service associated with that `HeroService` class token: +makeExample('dependency-injection/ts/app/providers.component.ts','provider-8-ctor')(format=".") :marked - This is all especially convenient when we consider that most dependency values are provided by classes. + This is especially convenient when we consider that most dependency values are provided by classes. ### Non-class Dependencies @@ -626,7 +632,7 @@ code-example(format). Sometimes the thing we want to inject is a string, a function, or an object. Applications often define configuration objects with lots of small facts like the title of the application or the address of a web api endpoint. - These configuration objects aren't always instances of a class. They're just objects ... like this one: + These configuration objects aren't always instances of a class. They tend to be object hashes like this one: +makeExample('dependency-injection/ts/app/app.config.ts','config','app/app-config.ts')(format='.') :marked @@ -638,13 +644,19 @@ code-example(format). // begin Typescript only :marked + ### Interfaces aren't valid tokens + The `CONFIG` constant has an interface, `Config`. Unfortunately, we - **cannot use an interface as a token** + cannot use an interface as a token: +makeExample('dependency-injection/ts/app/providers.component.ts','providers-9a-interface')(format=".") +makeExample('dependency-injection/ts/app/providers.component.ts','provider-9a-ctor-interface')(format=".") :marked - It's not Angular's fault. An interface is a TypeScript design-time artifact. - It disappears from the generated JavaScript so there is no interface type information for Angular to find at runtime. + That seems strange if we're used to dependency injection in strongly-typed languages where + an interface is the preferred dependency lookup key. + + It's not Angular's fault. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces. + The TypeScript interface disappears from the generated JavaScript. + There is no interface type information left for Angular to find at runtime. // end Typescript only :marked @@ -684,7 +696,9 @@ code-example(format). .l-sub-section :marked - Angular uses `OpaqueTokens` to register all of its non-class dependencies. + Angular itself uses `OpaqueTokens` to register all of its own non-class dependencies. For example, + [HTTP_PROVIDERS](https://angular.io/docs/ts/latest/api/http/HTTP_PROVIDERS-let.html) + is the `OpaqueToken` associated with an array of providers for persisting data with the Angular `Http` client. Internally, the `Provider` turns both the string and the class type into an `OpaqueToken` and keys its *token/provider* map with that. @@ -709,8 +723,13 @@ code-example(format). +makeExample('dependency-injection/ts/app/injector.component.ts', 'injector', 'app/injector.component.ts') :marked - Angular injects the component's own `Injector` which the component uses to acquire services. - The services themselves are not injected. They're retrieved via the injector. + The `Injector` is itself an injectable service. + + In this example, Angular injects the component's own `Injector` into the component's constructor. + The component then asks the injected injector for the services it wants. + + Note that the services themselves are not injected into the component. + They are retrieved by calling `injector.get`. The `get` method throws an error if it can't resolve the requested service. We can call `getOptional` instead, which we do in one case @@ -718,14 +737,15 @@ code-example(format). .l-sub-section :marked - This technique is an example of the [Service Locator Pattern](https://en.wikipedia.org/wiki/Service_locator_pattern). + The technique we just described is an example of the + [Service Locator Pattern](https://en.wikipedia.org/wiki/Service_locator_pattern). We **avoid** this technique unless we genuinely need it. It encourages a careless grab-bag approach such as we see here. It's difficult to explain, understand, and test. We can't know by inspecting the constructor what this class requires or what it will do. - It could acquire services from any ancestor component, not just its own. - We're forced to spelunk the implementation and hope for the best. + It could acquire services from any ancestor component, not just its own. + We're forced to spelunk the implementation to discover what it does. Framework developers may take this approach when they must acquire services generically and dynamically.