From 0f0918bb6f73bfd9236923fc9b0a6609e932ec4c Mon Sep 17 00:00:00 2001 From: Kathy Walrath Date: Wed, 13 Apr 2016 16:47:37 -0700 Subject: [PATCH 1/2] Kasper's version from #700 --- .../toh-4/dart/lib/app_component.dart | 106 +++++ .../toh-4/dart/lib/app_component_1.dart | 64 +++ .../docs/_examples/toh-4/dart/lib/hero.dart | 6 + .../toh-4/dart/lib/hero_detail_component.dart | 19 + .../toh-4/dart/lib/hero_service.dart | 23 ++ .../toh-4/dart/lib/hero_service_1.dart | 21 + .../_examples/toh-4/dart/lib/mock_heroes.dart | 16 + public/docs/_examples/toh-4/dart/pubspec.yaml | 15 + .../docs/_examples/toh-4/dart/web/index.html | 10 + .../docs/_examples/toh-4/dart/web/main.dart | 9 + .../docs/_examples/toh-4/dart/web/main_1.dart | 9 + public/docs/dart/latest/tutorial/toh-pt4.jade | 390 +++++++++++++++++- 12 files changed, 679 insertions(+), 9 deletions(-) create mode 100644 public/docs/_examples/toh-4/dart/lib/app_component.dart create mode 100644 public/docs/_examples/toh-4/dart/lib/app_component_1.dart create mode 100644 public/docs/_examples/toh-4/dart/lib/hero.dart create mode 100644 public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart create mode 100644 public/docs/_examples/toh-4/dart/lib/hero_service.dart create mode 100644 public/docs/_examples/toh-4/dart/lib/hero_service_1.dart create mode 100644 public/docs/_examples/toh-4/dart/lib/mock_heroes.dart create mode 100644 public/docs/_examples/toh-4/dart/pubspec.yaml create mode 100644 public/docs/_examples/toh-4/dart/web/index.html create mode 100644 public/docs/_examples/toh-4/dart/web/main.dart create mode 100644 public/docs/_examples/toh-4/dart/web/main_1.dart diff --git a/public/docs/_examples/toh-4/dart/lib/app_component.dart b/public/docs/_examples/toh-4/dart/lib/app_component.dart new file mode 100644 index 0000000000..745d183d11 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/app_component.dart @@ -0,0 +1,106 @@ +// #docplaster +// #docregion +import 'dart:async'; + +import 'package:angular2/core.dart'; + +import 'hero.dart'; +import 'hero_detail_component.dart'; +// #docregion hero-service-import +import 'hero_service.dart'; + +// #enddocregion hero-service-import + +@Component( + selector: 'my-app', + template: ''' +

{{title}}

+

My Heroes

+ + + ''', + styles: const [ + ''' + .selected { + background-color: #CFD8DC !important; + color: white; + } + .heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 10em; + } + .heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0em; + height: 1.6em; + border-radius: 4px; + } + .heroes li.selected:hover { + color: white; + } + .heroes li:hover { + color: #607D8B; + background-color: #EEE; + left: .1em; + } + .heroes .text { + position: relative; + top: -3px; + } + .heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0em 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0px 0px 4px; + } + ''' + ], + directives: const [ + HeroDetailComponent + ], + providers: const [ + HeroService + ]) +class AppComponent implements OnInit { + String title = 'Tour of Heroes'; + List heroes; + Hero selectedHero; + + final HeroService _heroService; + + AppComponent(this._heroService); + +// #docregion get-heroes + getHeroes() async { + heroes = await _heroService.getHeroes(); + } +// #enddocregion get-heroes + + ngOnInit() { + getHeroes(); + } + + onSelect(Hero hero) { + selectedHero = hero; + } +} diff --git a/public/docs/_examples/toh-4/dart/lib/app_component_1.dart b/public/docs/_examples/toh-4/dart/lib/app_component_1.dart new file mode 100644 index 0000000000..2f2cbb0dd0 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/app_component_1.dart @@ -0,0 +1,64 @@ +// #docplaster +// #docregion on-init +import 'package:angular2/core.dart'; + +// #enddocregion on-init +import 'hero.dart'; +import 'hero_detail_component.dart'; +// #docregion hero-service-import +import 'hero_service_1.dart'; +// #enddocregion hero-service-import + +// Testable but never shown +@Component( + selector: 'my-app', + template: ''' +
+ {{hero.name}} +
+ + ''', + directives: const [HeroDetailComponent], + // #docregion providers + providers: const [HeroService]) +// #enddocregion providers +// #docregion on-init +class AppComponent implements OnInit { + // #enddocregion on-init + String title = 'Tour of Heroes'; + // #docregion heroes-prop + List heroes; + // #enddocregion heroes-prop + Hero selectedHero; + + // #docregion new-service + HeroService heroService = new HeroService(); // don't do this + // #enddocregion new-service + // #docregion ctor + final HeroService _heroService; + AppComponent(this._heroService); + // #enddocregion ctor + // #docregion getHeroes + getHeroes() { + //#docregion get-heroes + heroes = _heroService.getHeroes(); + // #enddocregion get-heroes + } + // #enddocregion getHeroes + + // #docregion ng-on-init + // #docregion on-init + ngOnInit() { + // #enddocregion on-init + getHeroes(); + // #docregion on-init + } + // #enddocregion on-init + // #enddocregion ng-on-init + + onSelect(Hero hero) { + selectedHero = hero; + } + // #docregion on-init +} +// #enddocregion on-init diff --git a/public/docs/_examples/toh-4/dart/lib/hero.dart b/public/docs/_examples/toh-4/dart/lib/hero.dart new file mode 100644 index 0000000000..828f8cebab --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/hero.dart @@ -0,0 +1,6 @@ +class Hero { + final int id; + String name; + + Hero(this.id, this.name); +} diff --git a/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart b/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart new file mode 100644 index 0000000000..5d1f76fac8 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart @@ -0,0 +1,19 @@ +import 'package:angular2/core.dart'; +import 'hero.dart'; + +@Component( + selector: 'my-hero-detail', + template: ''' +
+

{{hero.name}} details!

+
{{hero.id}}
+
+ + +
+
+ ''', + inputs: const ['hero']) +class HeroDetailComponent { + Hero hero; +} diff --git a/public/docs/_examples/toh-4/dart/lib/hero_service.dart b/public/docs/_examples/toh-4/dart/lib/hero_service.dart new file mode 100644 index 0000000000..4893495822 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/hero_service.dart @@ -0,0 +1,23 @@ +// #docplaster +// #docregion +import 'dart:async'; + +import 'package:angular2/core.dart'; + +import 'mock_heroes.dart'; +import 'hero.dart'; + +@Injectable() +class HeroService { + //#docregion get-heroes + Future> getHeroes() => new Future(() => mockHeroes); + //#enddocregion get-heroes + + // See the "Take it slow" appendix + //#docregion get-heroes-slowly + Future> getHeroesSlowly() { + return new Future.delayed(const Duration(seconds: 2), () => mockHeroes); + } + //#enddocregion get-heroes-slowly +} +// #enddocregion diff --git a/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart b/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart new file mode 100644 index 0000000000..76ecb2143f --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart @@ -0,0 +1,21 @@ +// #docplaster +// #docregion +import 'mock_heroes.dart'; +import 'hero.dart'; +// #docregion empty-class +import 'package:angular2/core.dart'; + +// #docregion getHeroes-stub +@Injectable() +class HeroService { +// #enddocregion empty-class + List getHeroes() { +// #enddocregion getHeroes-stub + return mockHeroes; +// #docregion getHeroes-stub + } +// #docregion empty-class +} +// #enddocregion getHeroes-stub +// #enddocregion empty-class +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/toh-4/dart/lib/mock_heroes.dart b/public/docs/_examples/toh-4/dart/lib/mock_heroes.dart new file mode 100644 index 0000000000..55434761a2 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/lib/mock_heroes.dart @@ -0,0 +1,16 @@ +// #docregion +import 'hero.dart'; + +final List mockHeroes = [ + new Hero(11, "Mr. Nice"), + new Hero(12, "Narco"), + new Hero(13, "Bombasto"), + new Hero(14, "Celeritas"), + new Hero(15, "Magneta"), + new Hero(16, "RubberMan"), + new Hero(17, "Dynama"), + new Hero(18, "Dr IQ"), + new Hero(19, "Magma"), + new Hero(20, "Tornado") +]; +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/toh-4/dart/pubspec.yaml b/public/docs/_examples/toh-4/dart/pubspec.yaml new file mode 100644 index 0000000000..ddfafde27c --- /dev/null +++ b/public/docs/_examples/toh-4/dart/pubspec.yaml @@ -0,0 +1,15 @@ +name: angular2_tour_of_heroes +version: 0.0.1 +environment: + sdk: '>=1.13.0 <2.0.0' +dependencies: + angular2: 2.0.0-beta.1 + browser: ^0.10.0 + dart_to_js_script_rewriter: ^0.1.0+4 +transformers: +- angular2: + platform_directives: + - package:angular2/common.dart#COMMON_DIRECTIVES + platform_pipes: + - package:angular2/common.dart#COMMON_PIPES + entry_points: web/main.dart diff --git a/public/docs/_examples/toh-4/dart/web/index.html b/public/docs/_examples/toh-4/dart/web/index.html new file mode 100644 index 0000000000..059daeed4c --- /dev/null +++ b/public/docs/_examples/toh-4/dart/web/index.html @@ -0,0 +1,10 @@ + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-4/dart/web/main.dart b/public/docs/_examples/toh-4/dart/web/main.dart new file mode 100644 index 0000000000..d5b50195c1 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/web/main.dart @@ -0,0 +1,9 @@ +// #docregion pt1 +import 'package:angular2/bootstrap.dart'; + +import 'package:angular2_tour_of_heroes/app_component.dart'; + +main() { + bootstrap(AppComponent); +} +// #enddocregion pt1 \ No newline at end of file diff --git a/public/docs/_examples/toh-4/dart/web/main_1.dart b/public/docs/_examples/toh-4/dart/web/main_1.dart new file mode 100644 index 0000000000..c8baa39ed2 --- /dev/null +++ b/public/docs/_examples/toh-4/dart/web/main_1.dart @@ -0,0 +1,9 @@ +// #docregion pt1 +import 'package:angular2/bootstrap.dart'; + +import 'package:angular2_tour_of_heroes/app_component_1.dart'; + +main() { + bootstrap(AppComponent); +} +// #enddocregion pt1 \ No newline at end of file diff --git a/public/docs/dart/latest/tutorial/toh-pt4.jade b/public/docs/dart/latest/tutorial/toh-pt4.jade index 7c488613dd..92a64d4697 100644 --- a/public/docs/dart/latest/tutorial/toh-pt4.jade +++ b/public/docs/dart/latest/tutorial/toh-pt4.jade @@ -1,14 +1,386 @@ -include ../_util-fns +include ../../../../_includes/_util-fns :marked - We're working on the Dart version of this case study. - In the meantime, please see these resources: + # 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. - * [Services](/docs/ts/latest/tutorial/toh-pt4.html): - The TypeScript version of this chapter + [Run the live example for part 4](https://tour-of-heroes.firebaseapp.com/toh4/) - * [Dart source code](https://github.com/angular/angular.io/tree/master/public/docs/_examples/toh-5/dart): - A preliminary Dart version of the Tour of Heroes app, - featuring the hero editor, a master/detail page, - multiple components, services, and routing +.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 transpiling 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. + 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 imported the Angular `Injectable` function and applied that function as an `@Injectable()` decorator. +.callout.is-helpful + :marked + **Don't forget the parentheses!** Neglecting them leads to an error that's difficult to diagnose. +:marked + TypeScript sees the `@Injectable()` decorator 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 decorator anyway. + It is a "best practice" to apply the `@Injectable()` decorator ​*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 array)') +: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', null, '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)') +: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* + + Two lines replace the one line of *new*: + 1. we add a constructor. + 1. we add to the component's `providers` metadata + + Here's the constructor: ++makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'app_component.dart (constructor)') +:marked + The constructor itself does nothing. The parameter simultaneously + defines a private `_heroService` property and identifies it as a `HeroService` injection site. +.l-sub-section + :marked + We prefix private variables with an underscore (_) to warn readers of our code + that this variable is not part of the component's public API. +:marked + 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` array property to the bottom of the component metadata + in the `@Component` call. + ++makeExample('toh-4/dart/lib/app_component_1.dart', 'providers', 'app_component.dart (providing HeroService)') +:marked + The `providers` array 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. + +.l-sub-section + :marked + ### Services and the component tree + + Recall that the `AppComponent` creates an instance of `HeroDetail` by virtue of the + `` 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)') + :marked + The `HeroDetailComponent` must *not* repeat it's parent's `providers` array! 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=".") +: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 it a callback function. + It does that work (somewhere) and eventually it calls our function with the results of the work or an error. +.l-sub-section + :marked + We are simplifying. Learn about Futures [here](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 an **immediately resolved future** with our mock heroes as the result. + + ### 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 an 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 callback 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 + Checkout 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 + + [Run the live example for part 4](https://tour-of-heroes.firebaseapp.com/toh4/) + + ### 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’ll learn more about these tasks in the coming chapters. + +.l-main-section + +: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 (getHeroesSlowy)')(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`, swap `_heroService.getHeroesSlowly` for `_heroService.getHeroes` + and see how the app behaves. + +.l-main-section + +: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 array* 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` array 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. From 7f33a25d08db5c78a2468ffb2517803716af1658 Mon Sep 17 00:00:00 2001 From: Kathy Walrath Date: Thu, 14 Apr 2016 14:26:25 -0700 Subject: [PATCH 2/2] Kathy's tweaks to code & text --- .../toh-4/dart/lib/hero_detail_component.dart | 1 + .../toh-4/dart/lib/hero_service.dart | 4 +- .../toh-4/dart/lib/hero_service_1.dart | 23 +- public/docs/_examples/toh-4/dart/pubspec.yaml | 4 +- .../docs/_examples/toh-4/dart/web/main.dart | 4 +- .../docs/_examples/toh-4/dart/web/main_1.dart | 4 +- public/docs/dart/latest/tutorial/toh-pt4.jade | 241 +++++++++--------- 7 files changed, 151 insertions(+), 130 deletions(-) diff --git a/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart b/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart index 5d1f76fac8..14a1382348 100644 --- a/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart +++ b/public/docs/_examples/toh-4/dart/lib/hero_detail_component.dart @@ -1,4 +1,5 @@ import 'package:angular2/core.dart'; + import 'hero.dart'; @Component( diff --git a/public/docs/_examples/toh-4/dart/lib/hero_service.dart b/public/docs/_examples/toh-4/dart/lib/hero_service.dart index 4893495822..4fa0b97e1c 100644 --- a/public/docs/_examples/toh-4/dart/lib/hero_service.dart +++ b/public/docs/_examples/toh-4/dart/lib/hero_service.dart @@ -4,13 +4,13 @@ import 'dart:async'; import 'package:angular2/core.dart'; -import 'mock_heroes.dart'; import 'hero.dart'; +import 'mock_heroes.dart'; @Injectable() class HeroService { //#docregion get-heroes - Future> getHeroes() => new Future(() => mockHeroes); + Future> getHeroes() async => mockHeroes; //#enddocregion get-heroes // See the "Take it slow" appendix diff --git a/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart b/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart index 76ecb2143f..c95aeea7f8 100644 --- a/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart +++ b/public/docs/_examples/toh-4/dart/lib/hero_service_1.dart @@ -1,21 +1,28 @@ // #docplaster -// #docregion -import 'mock_heroes.dart'; -import 'hero.dart'; +// #docregion final // #docregion empty-class import 'package:angular2/core.dart'; +// #enddocregion empty-class +import 'hero.dart'; +import 'mock_heroes.dart'; + // #docregion getHeroes-stub @Injectable() class HeroService { -// #enddocregion empty-class - List getHeroes() { // #enddocregion getHeroes-stub - return mockHeroes; +// #enddocregion empty-class +// #enddocregion final + /* // #docregion getHeroes-stub - } + List getHeroes() {} +// #enddocregion getHeroes-stub + */ +// #docregion final + List getHeroes() => mockHeroes; // #docregion empty-class +// #docregion getHeroes-stub } // #enddocregion getHeroes-stub // #enddocregion empty-class -// #enddocregion \ No newline at end of file +// #enddocregion final diff --git a/public/docs/_examples/toh-4/dart/pubspec.yaml b/public/docs/_examples/toh-4/dart/pubspec.yaml index ddfafde27c..f4d1c8caa0 100644 --- a/public/docs/_examples/toh-4/dart/pubspec.yaml +++ b/public/docs/_examples/toh-4/dart/pubspec.yaml @@ -3,9 +3,9 @@ version: 0.0.1 environment: sdk: '>=1.13.0 <2.0.0' dependencies: - angular2: 2.0.0-beta.1 + angular2: 2.0.0-beta.15 browser: ^0.10.0 - dart_to_js_script_rewriter: ^0.1.0+4 + dart_to_js_script_rewriter: ^1.0.1 transformers: - angular2: platform_directives: diff --git a/public/docs/_examples/toh-4/dart/web/main.dart b/public/docs/_examples/toh-4/dart/web/main.dart index d5b50195c1..81c22cd91d 100644 --- a/public/docs/_examples/toh-4/dart/web/main.dart +++ b/public/docs/_examples/toh-4/dart/web/main.dart @@ -1,9 +1,9 @@ // #docregion pt1 -import 'package:angular2/bootstrap.dart'; +import 'package:angular2/platform/browser.dart'; import 'package:angular2_tour_of_heroes/app_component.dart'; main() { bootstrap(AppComponent); } -// #enddocregion pt1 \ No newline at end of file +// #enddocregion pt1 diff --git a/public/docs/_examples/toh-4/dart/web/main_1.dart b/public/docs/_examples/toh-4/dart/web/main_1.dart index c8baa39ed2..7d3d59c77d 100644 --- a/public/docs/_examples/toh-4/dart/web/main_1.dart +++ b/public/docs/_examples/toh-4/dart/web/main_1.dart @@ -1,9 +1,9 @@ // #docregion pt1 -import 'package:angular2/bootstrap.dart'; +import 'package:angular2/platform/browser.dart'; import 'package:angular2_tour_of_heroes/app_component_1.dart'; main() { bootstrap(AppComponent); } -// #enddocregion pt1 \ No newline at end of file +// #enddocregion pt1 diff --git a/public/docs/dart/latest/tutorial/toh-pt4.jade b/public/docs/dart/latest/tutorial/toh-pt4.jade index 92a64d4697..238f23e06f 100644 --- a/public/docs/dart/latest/tutorial/toh-pt4.jade +++ b/public/docs/dart/latest/tutorial/toh-pt4.jade @@ -1,26 +1,27 @@ -include ../../../../_includes/_util-fns +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 + + 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, + + Because data services are invariably asynchronous, we'll finish the chapter with a promise-based version of the data service. - [Run the live example for part 4](https://tour-of-heroes.firebaseapp.com/toh4/) + 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. + 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 @@ -37,8 +38,8 @@ include ../../../../_includes/_util-fns .file main.dart .file pubspec.yaml :marked - ### Keep the app transpiling and running - Open a terminal/console window. + ### 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"). @@ -48,18 +49,18 @@ code-example(format="." language="bash"). 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. + 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. - + 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. + 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 + 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 @@ -67,7 +68,7 @@ code-example(format="." language="bash"). .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. + 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`. @@ -76,43 +77,43 @@ code-example(format="." language="bash"). :marked ### Injectable Services - Notice that we imported the Angular `Injectable` function and applied that function as an `@Injectable()` decorator. + 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 - TypeScript sees the `@Injectable()` decorator and emits metadata about our service, + 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 decorator anyway. - It is a "best practice" to apply the `@Injectable()` decorator ​*from the start*​ + 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=".") ++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. + + 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 + + + ### 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 array)') ++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: @@ -121,63 +122,62 @@ code-example(format="." language="bash"). ### 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', null, 'hero_service.dart')(format=".") ++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)') ++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. + 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, + + * 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". + + * 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? + 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. + + 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* - - Two lines replace the one line of *new*: - 1. we add a constructor. - 1. we add to the component's `providers` metadata - - Here's the constructor: -+makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'app_component.dart (constructor)') -:marked - The constructor itself does nothing. The parameter simultaneously - defines a private `_heroService` property and identifies it as a `HeroService` injection site. -.l-sub-section - :marked - We prefix private variables with an underscore (_) to warn readers of our code - that this variable is not part of the component's public API. + + 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 - Now Angular will know to supply an instance of the `HeroService` when it creates a new `AppComponent`. - + 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. + 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. @@ -185,62 +185,64 @@ code-example(format="." language="bash"). :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`. + 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` array property to the bottom of the component metadata - in the `@Component` call. + 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)') ++makeExample('toh-4/dart/lib/app_component_1.dart', 'providers', 'app_component.dart (providing HeroService)')(format=".") :marked - The `providers` array 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. - + .l-sub-section :marked ### Services and the component tree - + Recall that the `AppComponent` creates an instance of `HeroDetail` by virtue of the `` 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)') + +makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'hero_detail_component.dart (constructor)')(format=".") :marked - The `HeroDetailComponent` must *not* repeat it's parent's `providers` array! Guess [why](#shadow-provider). - - The `AppComponent` is the top level component of our application. + 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=".") + + :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 + 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 @@ -255,56 +257,66 @@ code-example(format="." language="html"). :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. + 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, + + 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 it a callback function. - It does that work (somewhere) and eventually it calls our function with the results of the work or an error. + 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 [here](https://www.dartlang.org/docs/tutorials/futures/). + 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 an **immediately resolved future** with our mock heroes as the result. - + 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 an list of heroes. - + 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 callback sets the component's `heroes` property to the list of heroes returned by the service. That's all there is to it! - + 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 - Checkout the "[Take it slow](#slow)" appendix to see what the app might be like with a poor connection. + 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: @@ -346,41 +358,42 @@ code-example(format="." language="html"). * 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 - [Run the live example for part 4](https://tour-of-heroes.firebaseapp.com/toh4/) ### 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’ll learn more about these tasks in the coming chapters. - + + We learn about Angular Component Router and navigation among the views in the [next tutorial](toh-pt5.html) chapter. + .l-main-section :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 (getHeroesSlowy)')(format=".") + + 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`, swap `_heroService.getHeroesSlowly` for `_heroService.getHeroes` + + Back in the `AppComponent`, replace + `_heroService.getHeroes` with `_heroService.getHeroesSlowly` and see how the app behaves. - + .l-main-section :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 array* to the `HeroDetailComponent` metadata. + 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` array creates a new service instance that shadows the parent instance. - - Think carefully about where and when to register a provider. + 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.