@ -13,7 +13,7 @@ include ../_util-fns
It also makes it easier to unit test the component with a mock service.
It also makes it easier to unit test the component with a mock service.
Because data services are invariably asynchronous,
Because data services are invariably asynchronous,
we'll finish the chapter with a promise -based version of the data service.
we'll finish the chapter with a **!{_Promise}** -based version of the data service.
p Run the #[+liveExampleLink2('', 'toh-4')] for this part.
p Run the #[+liveExampleLink2('', 'toh-4')] for this part.
@ -35,6 +35,7 @@ p Run the #[+liveExampleLink2('', 'toh-4')] for this part.
.children
.children
.file index.html
.file index.html
.file main.dart
.file main.dart
.file styles.css
.file pubspec.yaml
.file pubspec.yaml
:marked
:marked
### Keep the app compiling and running
### Keep the app compiling and running
@ -59,7 +60,7 @@ code-example(language="bash").
First, defining heroes is not the component's job.
First, defining heroes is not the component's job.
Second, we can't easily share that list of heroes with other components and views.
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
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.
share that service with all components that need heroes.
### Create the HeroService
### Create the HeroService
@ -67,12 +68,12 @@ code-example(language="bash").
.l-sub-section
.l-sub-section
:marked
:marked
We've adopted a convention in which we spell the name of a service in lowercase followed by `_service`.
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" ).
If the service name were multi-word, we'd spell the base filename in lower underscore case (also called [snake_case](../guide/glossary.html#!#snake_case) ).
The `SpecialSuperHeroService` would be defined in the `special_super_hero_service.dart` file.
The `SpecialSuperHeroService` would be defined in the `special_super_hero_service.dart` file.
:marked
:marked
We name the class `HeroService`.
We name the class `HeroService`.
+makeExample('toh-4/dart/lib/hero_service_1.dart', 'empty-class', 'hero_service.dart (class )')(format=".")
+makeExample('toh-4/dart/lib/hero_service_1.dart', 'empty-class', 'lib/hero_service.dart (starting point )')(format=".")
:marked
:marked
### Injectable Services
### Injectable Services
@ -91,7 +92,7 @@ code-example(language="bash").
:marked
:marked
### Getting Heroes
### Getting Heroes
Add a `getHeroes` method stub.
Add a `getHeroes` method stub.
+makeExample('toh-4/dart/lib/hero_service_1.dart', 'getHeroes-stub', 'hero_service.dart (getHeroes stub)')(format=".")
+makeExample('toh-4/dart/lib/hero_service_1.dart', 'getHeroes-stub', 'lib/ hero_service.dart (getHeroes stub)')(format=".")
:marked
:marked
We're holding back on the implementation for a moment to make an important point.
We're holding back on the implementation for a moment to make an important point.
@ -109,31 +110,31 @@ code-example(language="bash").
We already have mock `Hero` data sitting in the `AppComponent`. It doesn't belong there. It doesn't belong *here* either.
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.
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`.
Cut 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.
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) ')
+makeExample('toh-4/dart/lib/mock_heroes.dart', null, 'lib/ mock_heroes.dart')
:marked
:marked
Meanwhile, back in `app_component.dart` where we cut away the `mockHeroes` list,
Meanwhile, back in `app_component.dart` where we cut away the `mockHeroes` list,
we leave behind an uninitialized `heroes` property:
we leave behind an uninitialized `heroes` property:
+makeExample('toh-4/dart/lib/app_component_1.dart', 'heroes-prop', 'app_component.dart (heroes property)')(format=".")
+makeExample('toh-4/dart/lib/app_component_1.dart', 'heroes-prop', 'lib/ app_component.dart (heroes property)')(format=".")
:marked
:marked
### Return Mocked Heroes
### Return Mocked Heroes
Back in the `HeroService` we import the mock `mockHeroes` and return it from the `getHeroes` method.
Back in the `HeroService` we import the mock `mockHeroes` and return it from the `getHeroes` method.
Our `HeroService` looks like this:
Our `HeroService` looks like this:
+makeExample('toh-4/dart/lib/hero_service_1.dart', 'final', 'hero_service.dart')(format=".")
+makeExample('toh-4/dart/lib/hero_service_1.dart', 'final', 'lib/ hero_service.dart')(format=".")
:marked
:marked
### Use the Hero Service
### Use the Hero Service
We're ready to use the `HeroService` in other components starting with our `AppComponent`.
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`.
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="." )
+makeExcerpt ('toh-4/dart/lib/app_component.dart', 'hero-service-import')
:marked
:marked
Importing the service allows us to *reference* it in our code.
Importing the service allows us to *reference* it in our code.
How should the `AppComponent` acquire a runtime concrete `HeroService` instance?
How should the `AppComponent` acquire a runtime concrete `HeroService` instance?
### Do we *new* the *HeroService*? No way!
### Do we *new* the *HeroService*? No way!
We could create a new instance of the `HeroService` with "new" like this:
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=".")
+makeExample('toh-4/dart/lib/app_component_1.dart', 'new-service')(format=".")
:marked
:marked
That's a bad idea for several reasons including
That's a bad idea for several reasons including
@ -143,7 +144,7 @@ code-example(language="bash").
we'll have to find every place we create the service and fix it.
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.
Running around patching code is error prone and adds to the test burden.
* We create a new service each time we use "new" .
* We create a new service each time we use `new` .
What if the service should cache heroes and share that cache with others?
What if the service should cache heroes and share that cache with others?
We couldn't do that.
We couldn't do that.
@ -166,7 +167,7 @@ code-example(language="bash").
1. We add to the component's `providers` metadata.
1. We add to the component's `providers` metadata.
Here are the property and the constructor:
Here are the property and the constructor:
+makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'app_component.dart (constructor)')(format='.')
+makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'lib/ app_component.dart (constructor)')(format='.')
:marked
:marked
The constructor does nothing except set the `_heroService`
The constructor does nothing except set the `_heroService`
property. The `HeroService` type of `_heroService`
property. The `HeroService` type of `_heroService`
@ -186,14 +187,14 @@ code-example(language="bash").
:marked
:marked
The *injector* does not know yet how to create a `HeroService`.
The *injector* does not know yet how to create a `HeroService`.
If we ran our code now, Angular would fail with an error:
If we ran our code now, Angular would fail with an error:
code-example(format="." language="html ").
code-example(format="nocode ").
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
:marked
:marked
We have to teach the *injector* how to make a `HeroService` by registering a `HeroService` **provider**.
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
Do that by adding the following `providers` parameter to the bottom of the component metadata
in the `@Component` annotation.
in the `@Component` annotation.
+makeExample ('toh-4/dart/lib/app_component_1.dart', 'providers', 'app_component.dart (providing HeroService)')(format="." )
+makeExcerpt ('toh-4/dart/lib/app_component_1.dart', 'providers')
:marked
:marked
The `providers` parameter tells Angular to create a fresh instance of the `HeroService` when it creates a new `AppComponent`.
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.
The `AppComponent` can use that service to get heroes and so can every child component of its component tree.
@ -207,7 +208,7 @@ code-example(format="." language="html").
If the `HeroDetailComponent` needed its parent component's `HeroService`,
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`:
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=".")
+makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'lib/ hero_detail_component.dart (constructor)')(format=".")
:marked
:marked
The `HeroDetailComponent` must *not* repeat its parent's `providers` list! Guess [why](#shadow-provider).
The `HeroDetailComponent` must *not* repeat its parent's `providers` list! Guess [why](#shadow-provider).
@ -221,7 +222,7 @@ code-example(format="." language="html").
+makeExample('toh-4/dart/lib/app_component_1.dart', 'get-heroes')(format=".")
+makeExample('toh-4/dart/lib/app_component_1.dart', 'get-heroes')(format=".")
:marked
:marked
We don't really need a dedicated method to wrap one line. We write it anyway:
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="." )
+makeExcerpt ('toh-4/dart/lib/app_component_1.dart', 'getHeroes')
<a id="oninit"></a>
<a id="oninit"></a>
:marked
:marked
@ -248,18 +249,19 @@ code-example(format="." language="html").
Learn more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) chapter.
Learn more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) chapter.
:marked
:marked
Here's the essential outline for the `OnInit` interface:
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=".")
+makeExample('toh-4/dart/lib/app_component_1.dart', 'on-init', 'lib/app_component.dart (ngOnInit stub )')(format=".")
:marked
:marked
We write an `ngOnInit` method with our initialization logic inside and leave it to Angular to call it
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`.
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="." )
+makeExcerpt ('toh-4/dart/lib/app_component_1.dart', 'ng-on-init')
:marked
:marked
Our application should be running as expected, showing a list of heroes and a hero detail view
Our application should be running as expected, showing a list of heroes and a hero detail view
when we click on a hero name.
when we click on a hero name.
We're getting closer. But something isn't quite right.
We're getting closer. But something isn't quite right.
## Async Services and Futures
<a id="async"></a>
## Async Services and !{_Promise}
Our `HeroService` returns a list of mock heroes immediately.
Our `HeroService` returns a list of mock heroes immediately.
Its `getHeroes` signature is synchronous
Its `getHeroes` signature is synchronous
+makeExample('toh-4/dart/lib/app_component_1.dart', 'get-heroes')(format=".")
+makeExample('toh-4/dart/lib/app_component_1.dart', 'get-heroes')(format=".")
@ -273,42 +275,42 @@ code-example(format="." language="html").
We'll have to use some kind of asynchronous technique and that will change the signature of our `getHeroes` method.
We'll have to use some kind of asynchronous technique and that will change the signature of our `getHeroes` method.
We'll use *future s*.
We'll use *!{_Promise} s*.
### The Hero Service returns a future
### The Hero Service returns a !{_Promise}
We ask an asynchronous service to do some work and give us the result in the future .
We ask an asynchronous service to do some work and give us the result in the !{_Promise} .
The service does that work (somewhere) and eventually it updates the future with the results of the work or an error.
The service does that work (somewhere) and eventually it updates the !{_Promise} with the results of the work or an error.
.l-sub-section
.l-sub-section
:marked
:marked
We are simplifying. Learn about Future s in the tutorial
We are simplifying. Learn about !{_Promise} s in the tutorial
[Asynchronous Programming: Futures](https://www.dartlang.org/docs/tutorials/futures/).
[Asynchronous Programming: Futures](https://www.dartlang.org/docs/tutorials/futures/).
:marked
:marked
Update the `HeroService` with this future -returning `getHeroes` method:
Update the `HeroService` with this !{_Promise} -returning `getHeroes` method:
+makeExample('toh-4/dart/lib/hero_service.dart', 'get-heroes', 'hero_service.dart (getHeroes )')(format=".")
+makeExample('toh-4/dart/lib/hero_service.dart', 'get-heroes', 'lib/hero_service.dart (excerpt )')(format=".")
:marked
:marked
We're still mocking the data. We're simulating the behavior of an ultra-fast, zero-latency server,
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.
by returning a !{_Promise} that will quickly resolve with our mock heroes as the result.
.l-sub-section
.l-sub-section
:marked
:marked
Marking the method's body with `async` makes the method immediately return a `Future` object.
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.
That !{_Promise} later completes with the method's return value.
For more information on async functions, see
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.
[Declaring async functions](https://www.dartlang.org/docs/dart-up-and-running/ch02.html#async) in the Dart language tour.
:marked
:marked
### Act on the Futures
### Act on the !{_Promise}
Returning to the `AppComponent` and its `getHeroes` method, we see that it still looks like this:
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=".")
+makeExample('toh-4/dart/lib/app_component_1.dart', 'getHeroes', 'lib/ app_component.dart (getHeroes - old)')(format=".")
:marked
:marked
As a result of our change to `HeroService`, we're now setting `heroes` to a future rather than a list of heroes.
As a result of our change to `HeroService`, we're now setting `heroes` to a !{_Promise} rather than a list of heroes.
We have to change our implementation to *act on the future when it resolves*.
We have to change our implementation to *act on the !{_Promise} when it resolves*.
We can *await* the future to resolve, and then display the heroes:
We can *await* for the !{_Promise} to resolve, and then display the heroes:
+makeExample('toh-4/dart/lib/app_component.dart', 'get-heroes', 'app_component.dart (getHeroes - revised)')(format=".")
+makeExample('toh-4/dart/lib/app_component.dart', 'get-heroes', 'lib/ app_component.dart (getHeroes - revised)')(format=".")
:marked
:marked
Our code waits until the future completes, and then
Our code waits until the !{_Promise} 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!
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
Our app should still be running, still showing a list of heroes, and still
@ -334,6 +336,7 @@ code-example(format="." language="html").
.children
.children
.file index.html
.file index.html
.file main.dart
.file main.dart
.file styles.css
.file pubspec.yaml
.file pubspec.yaml
:marked
:marked
Here are the code files we discussed in this chapter.
Here are the code files we discussed in this chapter.
@ -351,11 +354,11 @@ code-example(format="." language="html").
## The Road We’ ve Travelled
## The Road We’ ve Travelled
Let’ s take stock of what we’ ve built.
Let’ s take stock of what we’ ve built.
* We created a service class that can be shared by many components
* 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 used the `ngOnInit` Lifecycle Hook to get our heroes when our `AppComponent` activates.
* We defined our `HeroService` as a provider for our `AppComponent`
* We defined our `HeroService` as a provider for our `AppComponent`.
* We created mock hero data and imported them into our service
* 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
* We designed our service to return a !{_Promise} and our component to get our data from the !{_Promise}.
### The Road Ahead
### The Road Ahead
@ -373,10 +376,10 @@ code-example(format="." language="html").
We can simulate a slow connection.
We can simulate a slow connection.
Add the following `getHeroesSlowly` method to the `HeroService`:
Add the following `getHeroesSlowly` method to the `HeroService`:
+makeExample('toh-4/dart/lib/hero_service.dart', 'get-heroes-slowly', 'hero_service.dart (getHeroesSlowly)')(format=".")
+makeExample('toh-4/dart/lib/hero_service.dart', 'get-heroes-slowly', 'lib/ hero_service.dart (getHeroesSlowly)')(format=".")
:marked
:marked
Like `getHeroes`, it also returns a future .
Like `getHeroes`, it also returns a !{_Promise} .
But this future waits 2 seconds before resolving the future with mock heroes.
But this !{_Promise} waits 2 seconds before resolving the !{_Promise} with mock heroes.
Back in the `AppComponent`, replace
Back in the `AppComponent`, replace
`_heroService.getHeroes` with `_heroService.getHeroesSlowly`
`_heroService.getHeroes` with `_heroService.getHeroesSlowly`