docs: recommend best practice for providers (#22934)

PR Close #22934
This commit is contained in:
Judy Bogart 2018-03-22 10:37:50 -07:00 committed by Igor Minar
parent 33f8120164
commit 1fac5f4eb1
5 changed files with 39 additions and 46 deletions

View File

@ -1,7 +1,6 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component'; import { HeroesComponent } from './heroes/heroes.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component'; import { HeroDetailComponent } from './hero-detail/hero-detail.component';
@ -19,13 +18,9 @@ import { MessagesComponent } from './messages/messages.component';
FormsModule FormsModule
], ],
// #docregion providers // #docregion providers
// #docregion providers-heroservice
providers: [ providers: [
// #enddocregion providers-heroservice
// no need to place any providers due to the `providedIn` flag... // no need to place any providers due to the `providedIn` flag...
// #docregion providers-heroservice
], ],
// #enddocregion providers-heroservice
// #enddocregion providers // #enddocregion providers
bootstrap: [ AppComponent ] bootstrap: [ AppComponent ]
}) })

View File

@ -8,7 +8,9 @@ import { Hero } from './hero';
import { HEROES } from './mock-heroes'; import { HEROES } from './mock-heroes';
// #docregion new // #docregion new
@Injectable({providedIn: 'root'}) @Injectable({
providedIn: 'root',
})
export class HeroService { export class HeroService {
constructor() { } constructor() { }

View File

@ -14,7 +14,9 @@ import { HEROES } from './mock-heroes';
import { MessageService } from './message.service'; import { MessageService } from './message.service';
// #enddocregion import-message-service // #enddocregion import-message-service
@Injectable({ providedIn: 'root' }) @Injectable({
providedIn: 'root',
})
export class HeroService { export class HeroService {
// #docregion ctor // #docregion ctor

View File

@ -1,6 +1,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' }) @Injectable({
providedIn: 'root',
})
export class MessageService { export class MessageService {
messages: string[] = []; messages: string[] = [];

View File

@ -31,7 +31,7 @@ Using the Angular CLI, create a service called `hero`.
</code-example> </code-example>
The command generates skeleton `HeroService` class in `src/app/hero.service.ts` The command generates skeleton `HeroService` class in `src/app/hero.service.ts`
The `HeroService` class should look like the below. The `HeroService` class should look like the following example.
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="new" <code-example path="toh-pt4/src/app/hero.service.1.ts" region="new"
title="src/app/hero.service.ts (new service)" linenums="false"> title="src/app/hero.service.ts (new service)" linenums="false">
@ -40,19 +40,10 @@ The `HeroService` class should look like the below.
### _@Injectable()_ services ### _@Injectable()_ services
Notice that the new service imports the Angular `Injectable` symbol and annotates Notice that the new service imports the Angular `Injectable` symbol and annotates
the class with the `@Injectable()` decorator. the class with the `@Injectable()` decorator. This marks the class as one that participates in the _dependency injection system_. The `HeroService` class is going to provide an injectable service, and it can also have its own injected dependencies.
It doesn't have any dependencies yet, but [it will soon](#inject-message-service).
The `@Injectable()` decorator tells Angular that this service _might_ itself The `@Injectable()` decorator accepts a metadata object for the service, the same way the `@Component()` decorator did for your component classes.
have injected dependencies.
It doesn't have dependencies now but [it will soon](#inject-message-service).
Whether it does or it doesn't, it's good practice to keep the decorator.
<div class="l-sub-section">
The Angular [style guidelines](guide/styleguide#style-07-04) strongly recommend keeping it
and the linter enforces this rule.
</div>
### Get hero data ### Get hero data
@ -76,32 +67,39 @@ Add a `getHeroes` method to return the _mock heroes_.
{@a provide} {@a provide}
## Provide the `HeroService` ## Provide the `HeroService`
You must _provide_ the `HeroService` in the _dependency injection system_ You must make the `HeroService` available to the dependency injection system
before Angular can _inject_ it into the `HeroesComponent`, before Angular can _inject_ it into the `HeroesComponent`,
as you will do [below](#inject). as you will do [below](#inject). You do this by registering a _provider_. A provider is something that can create or deliver a service; in this case, it instantiates the `HeroService` class to provide the service.
There are several ways to provide the `HeroService`: Now, you need to make sure that the `HeroService` is registered as the provider of this service.
in the `HeroesComponent`, in the `AppComponent`, in the `AppModule`. You are registering it with an _injector_, which is the object that is responsible for choosing and injecting the provider where it is required.
Each option has pros and cons.
This tutorial chooses to provide it in the `AppModule`. By default, the Angular CLI command `ng generate service` registers a provider with the _root injector_ for your service by including provider metadata in the `@Injectable` decorator.
That's such a popular choice that you could have told the CLI to provide it there automatically If you look at the `@Injectable()` statement right before the `HeroService` class definition, you can see that the `providedIn` metadata value is 'root':
by appending `--module=app`.
```
@Injectable({
providedIn: 'root',
})
```
When you provide the service at the root level, Angular creates a single, shared instance of `HeroService` and injects into any class that asks for it.
Registering the provider in the `@Injectable` metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
<div class="l-sub-section">
If you need to, you can register providers at different levels:
in the `HeroesComponent`, in the `AppComponent`, in the `AppModule`.
For instance, you could have told the CLI to provide the service at the module level automatically by appending `--module=app`.
<code-example language="sh" class="code-shell"> <code-example language="sh" class="code-shell">
ng generate service hero --module=app ng generate service hero --module=app
</code-example> </code-example>
Since you did not, you'll have to provide it yourself. To learn more about providers and injectors, see the [Dependency Injection guide](guide/dependency-injection).
Open the `AppModule` class, import the `HeroService`, and add it to the `@NgModule.providers` array. </div>
<code-example path="toh-pt4/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (providers)" region="providers-heroservice">
</code-example>
The `providers` array tells Angular to create a single, shared instance of `HeroService`
and inject into any class that asks for it.
The `HeroService` is now ready to plug into the `HeroesComponent`. The `HeroService` is now ready to plug into the `HeroesComponent`.
@ -111,17 +109,12 @@ This is a interim code sample that will allow you to provide and use the `HeroSe
</div> </div>
<div class="alert is-helpful">
Learn more about _providers_ in the [Providers](guide/providers) guide.
</div>
## Update `HeroesComponent` ## Update `HeroesComponent`
Open the `HeroesComponent` class file. Open the `HeroesComponent` class file.
Delete the `HEROES` import as you won't need that anymore. Delete the `HEROES` import, because you won't need that anymore.
Import the `HeroService` instead. Import the `HeroService` instead.
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" title="src/app/heroes/heroes.component.ts (import HeroService)" region="hero-service-import"> <code-example path="toh-pt4/src/app/heroes/heroes.component.ts" title="src/app/heroes/heroes.component.ts (import HeroService)" region="hero-service-import">
@ -295,10 +288,9 @@ You should see the default paragraph from `MessagesComponent` at the bottom of t
### Create the _MessageService_ ### Create the _MessageService_
Use the CLI to create the `MessageService` in `src/app`. Use the CLI to create the `MessageService` in `src/app`.
The `--module=app` option tells the CLI to [_provide_ this service](#provide) in the `AppModule`,
<code-example language="sh" class="code-shell"> <code-example language="sh" class="code-shell">
ng generate service message --module=app ng generate service message
</code-example> </code-example>
Open `MessageService` and replace its contents with the following. Open `MessageService` and replace its contents with the following.
@ -442,7 +434,7 @@ Here are the code files discussed on this page and your app should look like thi
## Summary ## Summary
* You refactored data access to the `HeroService` class. * You refactored data access to the `HeroService` class.
* You _provided_ the `HeroService` in the root `AppModule` so that it can be injected anywhere. * You registered the `HeroService` as the _provider_ of its service at the root level so that it can be injected anywhere in the app.
* You used [Angular Dependency Injection](guide/dependency-injection) to inject it into a component. * You used [Angular Dependency Injection](guide/dependency-injection) to inject it into a component.
* You gave the `HeroService` _get data_ method an asynchronous signature. * You gave the `HeroService` _get data_ method an asynchronous signature.
* You discovered `Observable` and the RxJS _Observable_ library. * You discovered `Observable` and the RxJS _Observable_ library.