merge from origin
This commit is contained in:
commit
cb964960ab
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -7,7 +7,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
# #enddocregion no-rewriter
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -4,7 +4,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -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
|
||||
// #enddocregion pt1
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// #docregion pt2
|
||||
import 'package:angular2/bootstrap.dart';
|
||||
import 'package:angular2/platform/browser.dart';
|
||||
|
||||
import 'package:angular2_tour_of_heroes/app_component.dart';
|
||||
|
||||
main() {
|
||||
bootstrap(AppComponent);
|
||||
}
|
||||
// #enddocregion pt2
|
||||
// #enddocregion pt2
|
||||
|
|
|
@ -3,7 +3,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -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
|
||||
// #enddocregion pt1
|
||||
|
|
|
@ -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: '''
|
||||
<h1>{{title}}</h1>
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li *ngFor="#hero of heroes"
|
||||
[class.selected]="hero == selectedHero"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
''',
|
||||
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<Hero> 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;
|
||||
}
|
||||
}
|
|
@ -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: '''
|
||||
<div *ngFor="#hero of heroes" (click)="onSelect(hero)">
|
||||
{{hero.name}}
|
||||
</div>
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
''',
|
||||
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<Hero> 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
|
|
@ -0,0 +1,6 @@
|
|||
class Hero {
|
||||
final int id;
|
||||
String name;
|
||||
|
||||
Hero(this.id, this.name);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'hero.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'my-hero-detail',
|
||||
template: '''
|
||||
<div *ngIf="hero != null">
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
<div><label>id: </label>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ngModel)]="hero.name" placeholder="name"/>
|
||||
</div>
|
||||
</div>
|
||||
''',
|
||||
inputs: const ['hero'])
|
||||
class HeroDetailComponent {
|
||||
Hero hero;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'hero.dart';
|
||||
import 'mock_heroes.dart';
|
||||
|
||||
@Injectable()
|
||||
class HeroService {
|
||||
//#docregion get-heroes
|
||||
Future<List<Hero>> getHeroes() async => mockHeroes;
|
||||
//#enddocregion get-heroes
|
||||
|
||||
// See the "Take it slow" appendix
|
||||
//#docregion get-heroes-slowly
|
||||
Future<List<Hero>> getHeroesSlowly() {
|
||||
return new Future.delayed(const Duration(seconds: 2), () => mockHeroes);
|
||||
}
|
||||
//#enddocregion get-heroes-slowly
|
||||
}
|
||||
// #enddocregion
|
|
@ -0,0 +1,28 @@
|
|||
// #docplaster
|
||||
// #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 getHeroes-stub
|
||||
// #enddocregion empty-class
|
||||
// #enddocregion final
|
||||
/*
|
||||
// #docregion getHeroes-stub
|
||||
List<Hero> getHeroes() {}
|
||||
// #enddocregion getHeroes-stub
|
||||
*/
|
||||
// #docregion final
|
||||
List<Hero> getHeroes() => mockHeroes;
|
||||
// #docregion empty-class
|
||||
// #docregion getHeroes-stub
|
||||
}
|
||||
// #enddocregion getHeroes-stub
|
||||
// #enddocregion empty-class
|
||||
// #enddocregion final
|
|
@ -0,0 +1,16 @@
|
|||
// #docregion
|
||||
import 'hero.dart';
|
||||
|
||||
final List<Hero> 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
|
|
@ -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.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
- angular2:
|
||||
platform_directives:
|
||||
- package:angular2/common.dart#COMMON_DIRECTIVES
|
||||
platform_pipes:
|
||||
- package:angular2/common.dart#COMMON_PIPES
|
||||
entry_points: web/main.dart
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script defer src="main.dart" type="application/dart"></script>
|
||||
<script defer src="packages/browser/dart.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
// #docregion pt1
|
||||
import 'package:angular2/platform/browser.dart';
|
||||
|
||||
import 'package:angular2_tour_of_heroes/app_component.dart';
|
||||
|
||||
main() {
|
||||
bootstrap(AppComponent);
|
||||
}
|
||||
// #enddocregion pt1
|
|
@ -0,0 +1,9 @@
|
|||
// #docregion pt1
|
||||
import 'package:angular2/platform/browser.dart';
|
||||
|
||||
import 'package:angular2_tour_of_heroes/app_component_1.dart';
|
||||
|
||||
main() {
|
||||
bootstrap(AppComponent);
|
||||
}
|
||||
// #enddocregion pt1
|
|
@ -5,12 +5,14 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
- angular2:
|
||||
platform_directives: package:angular2/common.dart#COMMON_DIRECTIVES
|
||||
platform_pipes: package:angular2/common.dart#COMMON_PIPES
|
||||
platform_directives:
|
||||
- package:angular2/common.dart#COMMON_DIRECTIVES
|
||||
platform_pipes:
|
||||
- package:angular2/common.dart#COMMON_PIPES
|
||||
entry_points: web/main.dart
|
||||
- dart_to_js_script_rewriter
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<!-- #docregion head -->
|
||||
<!-- #docregion base-href -->
|
||||
<head>
|
||||
<!-- base href="/" --> <!-- For testing using pub serve directly -->
|
||||
<base href="/dart/web/"> <!-- For testing in WebStorm -->
|
||||
<base href="/"> <!-- For testing using pub serve directly -->
|
||||
<!-- base href="/dart/web/" --> <!-- For testing in WebStorm -->
|
||||
<!-- #enddocregion base-href -->
|
||||
<title>Angular 2 Tour of Heroes</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.14
|
||||
angular2: 2.0.0-beta.15
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^1.0.1
|
||||
transformers:
|
||||
|
|
|
@ -41,7 +41,7 @@ p.
|
|||
specify the angular2 and browser packages as dependencies,
|
||||
as well as the angular2 transformer.
|
||||
Angular 2 is still changing, so provide an exact version:
|
||||
<b>2.0.0-beta.14</b>.
|
||||
<b>2.0.0-beta.15</b>.
|
||||
|
||||
+makeExample('quickstart/dart/pubspec.yaml', 'no-rewriter', 'pubspec.yaml')
|
||||
|
||||
|
|
|
@ -1,14 +1,399 @@
|
|||
include ../_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.
|
||||
|
||||
* [Services](/docs/ts/latest/tutorial/toh-pt4.html):
|
||||
The TypeScript version of this chapter
|
||||
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.
|
||||
|
||||
* [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
|
||||
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.
|
||||
|
|
Loading…
Reference in New Issue