400 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			400 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| include ../_util-fns
 | ||
| 
 | ||
| :marked
 | ||
|   # Services
 | ||
|   The Tour of Heroes is evolving and we anticipate adding more components in the near future.
 | ||
| 
 | ||
|   Multiple components will need access to hero data and we don't want to copy and
 | ||
|   paste the same code over and over.
 | ||
|   Instead, we'll create a single reusable data service and learn to
 | ||
|   inject it in the components that need it.
 | ||
| 
 | ||
|   Refactoring data access to a separate service keeps the component lean and focused on supporting the view.
 | ||
|   It also makes it easier to unit test the component with a mock service.
 | ||
| 
 | ||
|   Because data services are invariably asynchronous,
 | ||
|   we'll finish the chapter with a promise-based version of the data service.
 | ||
| 
 | ||
|   The complete source code for the example app in this chapter is
 | ||
|   [in GitHub](https://github.com/angular/angular.io/tree/master/public/docs/_examples/toh-4/dart).
 | ||
| 
 | ||
| .l-main-section
 | ||
| :marked
 | ||
|   ## Where We Left Off
 | ||
|   Before we continue with our Tour of Heroes, let’s verify we have the following structure.
 | ||
|   If not, we’ll need to go back and follow the previous chapters.
 | ||
| 
 | ||
| .filetree
 | ||
|   .file angular2_tour_of_heroes
 | ||
|   .children
 | ||
|     .file lib
 | ||
|     .children
 | ||
|       .file app_component.dart
 | ||
|       .file hero.dart
 | ||
|       .file hero_detail_component.dart
 | ||
|     .file web
 | ||
|     .children
 | ||
|       .file index.html
 | ||
|       .file main.dart
 | ||
|     .file pubspec.yaml
 | ||
| :marked
 | ||
|   ### Keep the app compiling and running
 | ||
|   Open a terminal/console window.
 | ||
|   Start the Dart compiler, watch for changes, and start our server by entering the command:
 | ||
| 
 | ||
| code-example(format="." language="bash").
 | ||
|   pub serve
 | ||
| 
 | ||
| :marked
 | ||
|   The application runs and updates automatically as we continue to build the Tour of Heroes.
 | ||
| 
 | ||
|   ## Creating a Hero Service
 | ||
|   Our stakeholders have shared their larger vision for our app.
 | ||
|   They tell us they want to show the heroes in various ways on different pages.
 | ||
|   We already can select a hero from a list.
 | ||
|   Soon we'll add a dashboard with the top performing heroes and create a separate view for editing hero details.
 | ||
|   All three views need hero data.
 | ||
| 
 | ||
|   At the moment the `AppComponent` defines mock heroes for display.
 | ||
|   We have at least two objections.
 | ||
|   First, defining heroes is not the component's job.
 | ||
|   Second, we can't easily share that list of heroes with other components and views.
 | ||
| 
 | ||
|   We can refactor this hero data acquisition business to a single service that provides heroes and
 | ||
|   share that service with all components that need heroes.
 | ||
| 
 | ||
|   ### Create the HeroService
 | ||
|   Create a file in the `lib` folder called `hero_service.dart`.
 | ||
| .l-sub-section
 | ||
|   :marked
 | ||
|     We've adopted a convention in which we spell the name of a service in lowercase followed by `_service`.
 | ||
|     If the service name were multi-word, we'd spell the base filename with lower underscore case (AKA "snake_case").
 | ||
|     The `SpecialSuperHeroService` would be defined in the `special_super_hero_service.dart` file.
 | ||
| :marked
 | ||
|   We name the class `HeroService`.
 | ||
| 
 | ||
| +makeExample('toh-4/dart/lib/hero_service_1.dart', 'empty-class', 'hero_service.dart (class)')(format=".")
 | ||
| 
 | ||
| :marked
 | ||
|   ### Injectable Services
 | ||
|   Notice that we used an `@Injectable()` annotation.
 | ||
| .callout.is-helpful
 | ||
|   :marked
 | ||
|     **Don't forget the parentheses!** Neglecting them leads to an error that's difficult to diagnose.
 | ||
| :marked
 | ||
|   Dart sees the `@Injectable()` annotation and emits metadata about our service,
 | ||
|   metadata that Angular may need to inject other dependencies into this service.
 | ||
| 
 | ||
|   The `HeroService` doesn't have any dependencies *at the moment*. Add the annotation anyway.
 | ||
|   It is a "best practice" to apply the `@Injectable()` annotation *from the start*
 | ||
|   both for consistency and for future-proofing.
 | ||
| 
 | ||
| :marked
 | ||
|   ### Getting Heroes
 | ||
|   Add a `getHeroes` method stub.
 | ||
| +makeExample('toh-4/dart/lib/hero_service_1.dart', 'getHeroes-stub', 'hero_service.dart (getHeroes stub)')(format=".")
 | ||
| :marked
 | ||
|   We're holding back on the implementation for a moment to make an important point.
 | ||
| 
 | ||
|   The consumer of our service doesn't know how the service gets the data.
 | ||
|   Our `HeroService` could get `Hero` data from anywhere.
 | ||
|   It could get the data from a web service or local storage
 | ||
|   or from a mock data source.
 | ||
| 
 | ||
|   That's the beauty of removing data access from the component.
 | ||
|   We can change our minds about the implementation as often as we like,
 | ||
|   for whatever reason, without touching any of the components that need heroes.
 | ||
| 
 | ||
| 
 | ||
|   ### Mock Heroes
 | ||
|   We already have mock `Hero` data sitting in the `AppComponent`.  It doesn't belong there. It doesn't belong *here* either.
 | ||
|   We'll move the mock data to its own file.
 | ||
| 
 | ||
|   Cut the the `mockHeroes` list from `app_component.dart` and paste it to a new file in the `lib` folder named `mock_heroes.dart`.
 | ||
|   We copy the `import 'hero.dart'` statement as well because the heroes list uses the `Hero` class.
 | ||
| 
 | ||
| +makeExample('toh-4/dart/lib/mock_heroes.dart', null, 'mock_heroes.dart (Heroes list)')
 | ||
| :marked
 | ||
|   Meanwhile, back in `app_component.dart` where we cut away the `mockHeroes` list,
 | ||
|   we leave behind an uninitialized `heroes` property:
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'heroes-prop', 'app_component.dart (heroes property)')(format=".")
 | ||
| :marked
 | ||
|   ### Return Mocked Heroes
 | ||
|   Back in the `HeroService`  we import the mock `mockHeroes` and return it from the `getHeroes` method.
 | ||
|   Our `HeroService` looks like this:
 | ||
| +makeExample('toh-4/dart/lib/hero_service_1.dart', 'final', 'hero_service.dart')(format=".")
 | ||
| :marked
 | ||
|   ### Use the Hero Service
 | ||
|   We're ready to use the `HeroService` in other components starting with our `AppComponent`.
 | ||
| 
 | ||
|   We begin, as usual, by importing the thing we want to use, the `HeroService`.
 | ||
| +makeExample('toh-4/dart/lib/app_component.dart', 'hero-service-import', 'app_component.dart (import HeroService)')(format=".")
 | ||
| :marked
 | ||
|   Importing the service allows us to *reference* it in our code.
 | ||
|   How should the `AppComponent` acquire a runtime concrete `HeroService` instance?
 | ||
| 
 | ||
|   ### Do we *new* the *HeroService*? No way!
 | ||
|   We could create a new instance  of the `HeroService` with "new" like this:
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'new-service')(format=".")
 | ||
| :marked
 | ||
|   That's a bad idea for several reasons including
 | ||
| 
 | ||
|   * Our component has to know how to create a `HeroService`.
 | ||
|   If we ever change the `HeroService` constructor,
 | ||
|   we'll have to find every place we create the service and fix it.
 | ||
|   Running around patching code is error prone and adds to the test burden.
 | ||
| 
 | ||
|   * We create a new service each time we use "new".
 | ||
|   What if the service should cache heroes and share that cache with others?
 | ||
|   We couldn't do that.
 | ||
| 
 | ||
|   * We're locking the `AppComponent` into a specific implementation of the `HeroService`.
 | ||
|   It will be hard to switch implementations for different scenarios.
 | ||
|   Can we operate offline?
 | ||
|   Will we need different mocked versions under test?
 | ||
|   Not easy.
 | ||
| 
 | ||
|   *What if ... what if ... Hey, we've got work to do!*
 | ||
| 
 | ||
|   We get it. Really we do.
 | ||
|   But it is so ridiculously easy to avoid these problems that there is no excuse for doing it wrong.
 | ||
| 
 | ||
|   ### Inject the *HeroService*
 | ||
| 
 | ||
|   Three lines replace the one line of *new*:
 | ||
|   1. We add a property.
 | ||
|   1. We add a constructor that sets the property.
 | ||
|   1. We add to the component's `providers` metadata.
 | ||
| 
 | ||
|   Here are the property and the constructor:
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'app_component.dart (constructor)')(format='.')
 | ||
| :marked
 | ||
|   The constructor does nothing except set the `_heroService`
 | ||
|   property. The `HeroService` type of `_heroService`
 | ||
|   identifies the constructor's parameter as
 | ||
|   a `HeroService` injection site.
 | ||
| 
 | ||
|   Now Angular will know to supply an instance of the `HeroService` when it creates a new `AppComponent`.
 | ||
| 
 | ||
|   Angular has to get that instance from somewhere. That's the role of the Angular *Dependency Injector*.
 | ||
|   The **Injector** has a **container** of previously created services.
 | ||
|   Either it finds and returns a pre-existing `HeroService` from its container or it creates a new instance, adds
 | ||
|   it to the container, and returns it to Angular.
 | ||
| 
 | ||
| .l-sub-section
 | ||
|   :marked
 | ||
|     Learn more about Dependency Injection in the [Dependency Injection](../guide/dependency-injection.html) chapter.
 | ||
| :marked
 | ||
|   The *injector* does not know yet how to create a `HeroService`.
 | ||
|   If we ran our code now, Angular would fail with an error:
 | ||
| code-example(format="." language="html").
 | ||
|     EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
 | ||
| :marked
 | ||
|   We have to teach the *injector* how to make a `HeroService` by registering a `HeroService` **provider**.
 | ||
|   Do that by adding the following `providers` parameter to the bottom of the component metadata
 | ||
|   in the `@Component` annotation.
 | ||
| 
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'providers', 'app_component.dart (providing HeroService)')(format=".")
 | ||
| :marked
 | ||
|   The `providers` parameter  tells Angular to create a fresh instance of the `HeroService` when it creates a new `AppComponent`.
 | ||
|   The `AppComponent` can use that service to get heroes and so can every child component of its component tree.
 | ||
| <a id="child-component"></a>
 | ||
| .l-sub-section
 | ||
|   :marked
 | ||
|     ### Services and the component tree
 | ||
| 
 | ||
|     Recall that the `AppComponent` creates an instance of `HeroDetail` by virtue of the
 | ||
|     `<my-hero-detail>` tag at the bottom of its template. That `HeroDetail` is a child of the `AppComponent`.
 | ||
| 
 | ||
|     If the `HeroDetailComponent` needed its parent component's `HeroService`,
 | ||
|     it would ask Angular to inject the service into its constructor which would look just like the one for `AppComponent`:
 | ||
|   +makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'hero_detail_component.dart (constructor)')(format=".")
 | ||
|   :marked
 | ||
|     The `HeroDetailComponent` must *not* repeat its parent's `providers` list! Guess [why](#shadow-provider).
 | ||
| 
 | ||
|     The `AppComponent` is the top level component of our application.
 | ||
|     There should be only one instance of that component and only one instance of the `HeroService` in our entire app.
 | ||
| :marked
 | ||
|   ### *getHeroes* in the *AppComponent*
 | ||
|   We've got the service in a `_heroService` private variable. Let's use it.
 | ||
| 
 | ||
|   We pause to think. We can call the service and get the data in one line.
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'get-heroes')(format=".")
 | ||
| :marked
 | ||
|   We don't really need a dedicated method to wrap one line.  We write it anyway:
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'getHeroes', 'app_component.dart (getHeroes)')(format=".")
 | ||
| 
 | ||
| <a id="oninit"></a>
 | ||
| :marked
 | ||
|   ### The *ngOnInit* Lifecycle Hook
 | ||
|   `AppComponent` should fetch and display heroes without a fuss.
 | ||
|   Where do we call the `getHeroes` method? In a constructor? We do *not*!
 | ||
| 
 | ||
|   Years of experience and bitter tears have taught us to keep complex logic out of the constructor,
 | ||
|   especially anything that might call a server as a data access method is sure to do.
 | ||
| 
 | ||
|   The constructor is for simple initializations like wiring constructor parameters to properties.
 | ||
|   It's not for heavy lifting. We should be able to create a component in a test and not worry that it
 | ||
|   might do real work — like calling a server!  — before we tell it to do so.
 | ||
| 
 | ||
|   If not the constructor, something has to call `getHeroes`.
 | ||
| 
 | ||
|   Angular will call it if we implement the Angular **ngOnInit** *Lifecycle Hook*.
 | ||
|   Angular offers a number of interfaces for tapping into critical moments in the component lifecycle:
 | ||
|   at creation, after each change, and at its eventual destruction.
 | ||
| 
 | ||
|   Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time.
 | ||
| .l-sub-section
 | ||
|   :marked
 | ||
|     Learn more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) chapter.
 | ||
| :marked
 | ||
|   Here's the essential outline for the `OnInit` interface:
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'on-init', 'app_component.dart (OnInit protocol)')(format=".")
 | ||
| :marked
 | ||
|   We write an `ngOnInit` method with our initialization logic inside and leave it to Angular to call it
 | ||
|   at the right time. In our case, we initialize by calling `getHeroes`.
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'ng-on-init', 'app_component.dart (OnInit protocol)')(format=".")
 | ||
| :marked
 | ||
|   Our application should be running as expected, showing a list of heroes and a hero detail view
 | ||
|   when we click on a hero name.
 | ||
| 
 | ||
|   We're getting closer. But something isn't quite right.
 | ||
| 
 | ||
|   ## Async Services and Futures
 | ||
|   Our `HeroService` returns a list of mock heroes immediately.
 | ||
|   Its `getHeroes` signature is synchronous
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'get-heroes')(format=".")
 | ||
| :marked
 | ||
|   Ask for heroes and they are there in the returned result.
 | ||
| 
 | ||
|   Someday we're going to get heroes from a remote server. We don’t call http yet, but we aspire to in later chapters.
 | ||
| 
 | ||
|   When we do, we'll have to wait for the server to respond and we won't be able to block the UI while we wait,
 | ||
|   even if we want to (which we shouldn't) because the browser won't block.
 | ||
| 
 | ||
|   We'll have to use some kind of asynchronous technique and that will change the signature of our `getHeroes` method.
 | ||
| 
 | ||
|   We'll use  *futures*.
 | ||
| 
 | ||
|   ### The Hero Service returns a future
 | ||
| 
 | ||
|   We ask an asynchronous service to do some work and give us the result in the future.
 | ||
|   The service does that work (somewhere) and eventually it updates the future with the results of the work or an error.
 | ||
| .l-sub-section
 | ||
|   :marked
 | ||
|     We are simplifying. Learn about Futures in the tutorial
 | ||
|     [Asynchronous Programming: Futures](https://www.dartlang.org/docs/tutorials/futures/).
 | ||
| :marked
 | ||
|   Update the `HeroService` with this future-returning `getHeroes` method:
 | ||
| +makeExample('toh-4/dart/lib/hero_service.dart', 'get-heroes', 'hero_service.dart (getHeroes)')(format=".")
 | ||
| :marked
 | ||
|   We're still mocking the data. We're simulating the behavior of an ultra-fast, zero-latency server,
 | ||
|   by returning a future that will quickly resolve with our mock heroes as the result.
 | ||
| 
 | ||
| .l-sub-section
 | ||
|   :marked
 | ||
|     Marking the method's body with `async` makes the method immediately return a `Future` object.
 | ||
|     That future later completes with the method's return value.
 | ||
|     For more information on async functions, see
 | ||
|     [Declaring async functions](https://www.dartlang.org/docs/dart-up-and-running/ch02.html#async) in the Dart language tour.
 | ||
| 
 | ||
| :marked
 | ||
|   ### Act on the Futures
 | ||
|   Returning to the `AppComponent` and its `getHeroes` method, we see that it still looks like this:
 | ||
| +makeExample('toh-4/dart/lib/app_component_1.dart', 'getHeroes', 'app_component.dart (getHeroes - old)')(format=".")
 | ||
| :marked
 | ||
|   As a result of our change to `HeroService`, we're now setting `heroes` to a future rather than a list of heroes.
 | ||
| 
 | ||
|   We have to change our implementation to *act on the future when it resolves*.
 | ||
|   We can *await* the future to resolve, and then display the heroes:
 | ||
| +makeExample('toh-4/dart/lib/app_component.dart', 'get-heroes', 'app_component.dart (getHeroes - revised)')(format=".")
 | ||
| :marked
 | ||
|   Our code waits until the future completes, and then
 | ||
|   sets the component's `heroes` property to the list of heroes returned by the service. That's all there is to it!
 | ||
| 
 | ||
|   Our app should still be running, still showing a list of heroes, and still
 | ||
|   responding to a name selection with a detail view.
 | ||
| .l-sub-section
 | ||
|   :marked
 | ||
|     Check out the "[Take it slow](#slow)" appendix to see what the app might be like with a poor connection.
 | ||
| :marked
 | ||
|   ### Review the App Structure
 | ||
|   Let’s verify that we have the following structure after all of our good refactoring in this chapter:
 | ||
| 
 | ||
| .filetree
 | ||
|   .file angular2_tour_of_heroes
 | ||
|   .children
 | ||
|     .file lib
 | ||
|     .children
 | ||
|       .file app_component.dart
 | ||
|       .file hero.dart
 | ||
|       .file hero_detail_component.dart
 | ||
|       .file hero_service.dart
 | ||
|       .file mock_heroes.dart
 | ||
|     .file web
 | ||
|     .children
 | ||
|       .file index.html
 | ||
|       .file main.dart
 | ||
|     .file pubspec.yaml
 | ||
| :marked
 | ||
|   Here are the code files we discussed in this chapter.
 | ||
| 
 | ||
| +makeTabs(`
 | ||
|   toh-4/dart/lib/hero_service.dart,
 | ||
|   toh-4/dart/lib/app_component.dart,
 | ||
|   toh-4/dart/lib/mock_heroes.dart
 | ||
|   `,'',`
 | ||
|   lib/hero_service.dart,
 | ||
|   lib/app_component.dart,
 | ||
|   lib/mock_heroes.dart
 | ||
|   `)
 | ||
| :marked
 | ||
|   ## The Road We’ve Travelled
 | ||
|   Let’s take stock of what we’ve built.
 | ||
| 
 | ||
|   * We created a service class that can be shared by many components
 | ||
|   * We used the `ngOnInit` Lifecycle Hook to get our heroes when our `AppComponent` activates
 | ||
|   * We defined our `HeroService` as a provider for our `AppComponent`
 | ||
|   * We created mock hero data and imported them into our service
 | ||
|   * We designed our service to return a future and our component to get our data from the future
 | ||
| 
 | ||
| 
 | ||
|   ### The Road Ahead
 | ||
|   Our Tour of Heroes has become more reusable using shared components and services.
 | ||
|   We want to create a dashboard, add menu links that route between the views, and format data in a template.
 | ||
|   As our app evolves, we’ll learn how to design it to make it easier to grow and maintain.
 | ||
| 
 | ||
|   We learn about Angular Component Router and navigation among the views in the [next tutorial](toh-pt5.html) chapter.
 | ||
| 
 | ||
| .l-main-section
 | ||
| <a id="slow"></a>
 | ||
| :marked
 | ||
|   ### Appendix: Take it slow
 | ||
| 
 | ||
|   We can simulate a slow connection.
 | ||
| 
 | ||
|   Add the following `getHeroesSlowly` method to the `HeroService`:
 | ||
| +makeExample('toh-4/dart/lib/hero_service.dart', 'get-heroes-slowly', 'hero_service.dart (getHeroesSlowly)')(format=".")
 | ||
| :marked
 | ||
|   Like `getHeroes`, it also returns a future.
 | ||
|   But this future waits 2 seconds before resolving the future with mock heroes.
 | ||
| 
 | ||
|   Back in the `AppComponent`, replace
 | ||
|   `_heroService.getHeroes` with `_heroService.getHeroesSlowly`
 | ||
|   and see how the app behaves.
 | ||
| 
 | ||
| .l-main-section
 | ||
| <a id="shadow-provider"></a>
 | ||
| :marked
 | ||
|   ### Appendix: Shadowing the parent's service
 | ||
| 
 | ||
|   We stated [earlier](#child-component) that if we injected the parent `AppComponent` `HeroService`
 | ||
|   into the `HeroDetailComponent`, *we must not add a providers list* to the `HeroDetailComponent` metadata.
 | ||
| 
 | ||
|   Why?  Because that tells Angular to create a new instance of the `HeroService` at the `HeroDetailComponent` level.
 | ||
|   The `HeroDetailComponent` doesn't want its *own*  service instance; it wants its *parent's* service instance.
 | ||
|   Adding the `providers` list creates a new service instance that shadows the parent instance.
 | ||
| 
 | ||
|   Think carefully about where and when to register a provider.
 | ||
|   Understand the scope of that registration. Be careful not to create a new service instance at the wrong level.
 |