Merge remote-tracking branch 'origin/master'

# Conflicts:
#	public/contribute.jade
#	public/docs/js/latest/cookbook/ts-to-js.jade
#	public/docs/ts/latest/cookbook/_data.json
#	public/docs/ts/latest/cookbook/component-communication.jade
#	public/docs/ts/latest/cookbook/component-relative-paths.jade
#	public/docs/ts/latest/cookbook/dependency-injection.jade
#	public/docs/ts/latest/cookbook/dynamic-form.jade
#	public/docs/ts/latest/cookbook/set-document-title.jade
#	public/docs/ts/latest/glossary.jade
#	public/docs/ts/latest/guide/forms.jade
#	public/docs/ts/latest/guide/pipes.jade
#	public/docs/ts/latest/guide/router.jade
#	public/docs/ts/latest/guide/user-input.jade
#	public/docs/ts/latest/tutorial/toh-pt6.jade
This commit is contained in:
Zhicheng Wang 2016-08-06 19:52:08 +08:00
commit d034bc33c8
65 changed files with 599 additions and 397 deletions

View File

@ -108,20 +108,21 @@ var _exampleConfigFilename = 'example-config.json';
// Gulp flags:
//
// --lang=[all | ts | js | dart | (ts|js) | (ts|js|dart) | ...]
// --lang=[all | ts | js | dart | 'ts|js' | 'ts|js|dart' | ...]
//
// This affects which language API docs and E2E tests are run. Can be 'all',
// or a regex pattern to match any one of 'ts', 'js', or 'dart'.
// Default: '(ts|js)' except for check-deploy for which it is 'all'.
// Default: 'ts|js' except for the "full site build" tasks (see below),
// for which it is 'all'.
//
var lang, langs, buildDartApiDocs = false;
function configLangs(langOption) {
const fullSiteBuildTasks = ['build-compile', 'check-serve', 'check-deploy'];
const buildAllDocs = argv['_'] &&
fullSiteBuildTasks.some((task) => argv['_'].indexOf(task) >= 0);
const langDefault = buildAllDocs ? 'all' : '(ts|js)';
const langDefault = buildAllDocs ? 'all' : 'ts|js';
lang = (langOption || langDefault).toLowerCase();
if (lang === 'all') lang = '(ts|js|dart)';
if (lang === 'all') lang = 'ts|js|dart';
langs = lang.match(/\w+/g); // the languages in `lang` as an array
gutil.log('Building docs for: ' + lang);
if (langs.indexOf('dart') >= 0) {
@ -161,10 +162,8 @@ gulp.task('run-e2e-tests', runE2e);
* Use it for repeated test runs (but not the FIRST run)
* e.g. gulp e2e --fast
*
* --lang to filter by code language
* --lang to filter by code language (see above for details)
* e.g. gulp e2e --lang=ts // only TypeScript apps
* default is (ts|js)
* all means (ts|js|dart)
*/
function runE2e() {
var promise;
@ -596,8 +595,16 @@ gulp.task('dartdoc', ['pub upgrade'], function() {
return true;
}
checkAngularProjectPath(ngRepoPath);
const topLevelLibFilePath = path.resolve(ngRepoPath, 'lib', 'angular2.dart');
const tmpPath = topLevelLibFilePath + '.disabled';
if (!fs.existsSync(topLevelLibFilePath)) throw new Error(`Missing file: ${topLevelLibFilePath}`);
fs.renameSync(topLevelLibFilePath, tmpPath);
gutil.log(`Hiding top-level angular2 library: ${topLevelLibFilePath}`);
const dartdoc = spawnExt('dartdoc', ['--output', 'doc/api', '--add-crossdart'], { cwd: ngRepoPath});
return dartdoc.promise;
return dartdoc.promise.finally(() => {
gutil.log(`Restoring top-level angular2 library: ${topLevelLibFilePath}`);
fs.renameSync(tmpPath, topLevelLibFilePath);
})
});
gulp.task('pub upgrade', [], function() {

View File

@ -6,7 +6,7 @@ for page, slug in data
// CHECK IF CURRENT PAGE IS SET, THEN SET NEXT PAGE
if currentPage
if !nextPage && page.nextable
if !nextPage && page.nextable && !page.hide
.l-sub-section
h3 下一步
a.translated-cn(href="/docs/#{current.path[1]}/#{current.path[2]}/#{current.path[3]}/#{slug}.html") #{page.title}

View File

@ -7,8 +7,9 @@
.l-sub-section
h3 Angular 2
p Angular 2, now in beta, is a next generation mobile and desktop application development platform.
p Angular 2, 目前正在beta阶段是下一代移动和桌面应用开发平台。
p Angular 2 is a next generation mobile and desktop application development platform.
p Angular 2是下一代移动与桌面应用开发平台。
a(href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md" class="button" md-button) Contribute to Angular 2
a(href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md" class="button" md-button) 为Angular 2做贡献

View File

@ -7,11 +7,11 @@ import 'package:angular2/core.dart';
import 'hero_list_component_2.dart';
import 'hero_service_1.dart';
/*
// #docregion full
// #docregion full, v1
import 'hero_list_component.dart';
// #docregion v1
// #enddocregion v1
import 'hero_service.dart';
// #enddocregion full, v1
// #enddocregion full
*/
// #docregion full, v1

View File

@ -0,0 +1,2 @@
### Angular 2 Documentation Example

View File

@ -50,9 +50,7 @@ class HeroService {
// #docregion extract-data
dynamic _extractData(Response res) {
var body = JSON.decode(res.body);
// TODO: https://github.com/adaojunior/http-in-memory-web-api/issues/1
// Once #1 is fixed, drop the `?? body` term:
return body['data'] ?? body;
return body['data'];
}
// #enddocregion extract-data
// #docregion error-handling

View File

@ -11,7 +11,7 @@ dependencies:
http: ^0.11.3+3
jsonpadding: ^0.1.0
stream_transformers: ^0.3.0+3
http_in_memory_web_api: ^0.0.1
http_in_memory_web_api: ^0.2.0
# #docregion transformers
transformers:
- angular2:

View File

@ -1,41 +1,31 @@
// #docplaster
// #docregion
// #docregion , search
import 'dart:async';
import 'package:angular2/core.dart';
// #docregion import-router
import 'package:angular2/router.dart';
// #enddocregion import-router
import 'hero.dart';
import 'hero_service.dart';
import 'hero_search_component.dart';
@Component(
selector: 'my-dashboard',
// #docregion template-url
templateUrl: 'dashboard_component.html',
// #enddocregion template-url
// #docregion css
styleUrls: const ['dashboard_component.css']
// #enddocregion css
)
// #docregion component
styleUrls: const ['dashboard_component.css'],
directives: const [HeroSearchComponent])
// #enddocregion search
class DashboardComponent implements OnInit {
List<Hero> heroes;
// #docregion ctor
final Router _router;
final HeroService _heroService;
DashboardComponent(this._heroService, this._router);
// #enddocregion ctor
Future<Null> ngOnInit() async {
heroes = (await _heroService.getHeroes()).skip(1).take(4).toList();
}
// #docregion goto-detail
void gotoDetail(Hero hero) {
var link = [
'HeroDetail',
@ -43,5 +33,4 @@ class DashboardComponent implements OnInit {
];
_router.navigate(link);
}
// #enddocregion goto-detail
}

View File

@ -1,11 +1,10 @@
<!-- #docregion -->
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<!-- #docregion click -->
<div *ngFor="let hero of heroes" (click)="gotoDetail(hero)" class="col-1-4" >
<!-- #enddocregion click -->
<div *ngFor="let hero of heroes" (click)="gotoDetail(hero)" class="col-1-4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</div>
</div>
<hero-search></hero-search>

View File

@ -0,0 +1,15 @@
/* #docregion */
.search-result {
border-bottom: 1px solid gray;
border-left: 1px solid gray;
border-right: 1px solid gray;
width:195px;
height: 20px;
padding: 5px;
background-color: white;
cursor: pointer;
}
#search-box {
width: 200px;
height: 20px;
}

View File

@ -0,0 +1,57 @@
// #docplaster
// #docregion
import 'dart:async';
import 'package:angular2/core.dart';
import 'package:angular2/router.dart';
import 'package:stream_transformers/stream_transformers.dart';
import 'hero_search_service.dart';
import 'hero.dart';
@Component(
selector: 'hero-search',
templateUrl: 'hero_search_component.html',
styleUrls: const ['hero_search_component.css'],
providers: const [HeroSearchService])
class HeroSearchComponent implements OnInit {
HeroSearchService _heroSearchService;
Router _router;
// #docregion search
Stream<List<Hero>> heroes;
// #enddocregion search
// #docregion searchTerms
StreamController<String> _searchTerms =
new StreamController<String>.broadcast();
// #enddocregion searchTerms
HeroSearchComponent(this._heroSearchService, this._router) {}
// #docregion searchTerms
// Push a search term into the stream.
void search(String term) => _searchTerms.add(term);
// #enddocregion searchTerms
// #docregion search
Future<Null> ngOnInit() async {
heroes = _searchTerms.stream
.transform(new Debounce(new Duration(milliseconds: 300)))
.distinct()
.transform(new FlatMapLatest((term) => term.isEmpty
? new Stream<List<Hero>>.fromIterable([<Hero>[]])
: _heroSearchService.search(term).asStream()))
.handleError((e) {
print(e); // for demo purposes only
});
}
// #enddocregion search
void gotoDetail(Hero hero) {
var link = [
'HeroDetail',
{'id': hero.id.toString()}
];
_router.navigate(link);
}
}

View File

@ -0,0 +1,11 @@
<!-- #docregion -->
<div id="search-component">
<h4>Hero Search</h4>
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<div>
<div *ngFor="let hero of heroes | async"
(click)="gotoDetail(hero)" class="search-result" >
{{hero.name}}
</div>
</div>
</div>

View File

@ -0,0 +1,33 @@
// #docregion
import 'dart:async';
import 'dart:convert';
import 'package:angular2/core.dart';
import 'package:http/http.dart';
import 'hero.dart';
@Injectable()
class HeroSearchService {
final Client _http;
HeroSearchService(this._http);
Future<List<Hero>> search(String term) async {
try {
final response = await _http.get('app/heroes/?name=$term');
return _extractData(response)
.map((json) => new Hero.fromJson(json))
.toList();
} catch (e) {
throw _handleError(e);
}
}
dynamic _extractData(Response resp) => JSON.decode(resp.body)['data'];
Exception _handleError(dynamic e) {
print(e); // for demo purposes only
return new Exception('Server error; cause: $e');
}
}

View File

@ -1,3 +1,4 @@
/* #docregion */
.selected {
background-color: #CFD8DC !important;
color: white;
@ -57,3 +58,10 @@ button {
button:hover {
background-color: #cfd8dc;
}
/* #docregion additions */
.error {color:red;}
button.delete-button {
float:right;
background-color: gray !important;
color:white;
}

View File

@ -1,6 +1,7 @@
// #docregion
import 'dart:async';
import 'dart:convert';
import 'dart:math';
// #docregion init
import 'package:angular2/core.dart';
@ -23,17 +24,18 @@ class InMemoryDataService extends MockClient {
{'id': 19, 'name': 'Magma'},
{'id': 20, 'name': 'Tornado'}
];
// #enddocregion init
static final List<Hero> _heroesDb =
_initialHeroes.map((json) => new Hero.fromJson(json)).toList();
static int _nextId = 21;
// #enddocregion init
static int _nextId = _heroesDb.map((hero) => hero.id).reduce(max) + 1;
static Future<Response> _handler(Request request) async {
var data;
switch (request.method) {
case 'GET':
data = _heroesDb;
String prefix = request.url.queryParameters['name'] ?? '';
final regExp = new RegExp(prefix, caseSensitive: false);
data = _heroesDb.where((hero) => hero.name.contains(regExp)).toList();
break;
case 'POST':
var name = JSON.decode(request.body)['name'];

View File

@ -13,6 +13,7 @@ dependencies:
dart_to_js_script_rewriter: ^1.0.1
# #docregion additions
http: ^0.11.0
stream_transformers: ^0.3.0
transformers:
- angular2:
# #enddocregion additions

View File

@ -7,7 +7,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="sample.css">
<script defer src="main.dart" type="application/dart"></script>
<script defer src="packages/browser/dart.js"></script>

View File

@ -1,7 +0,0 @@
/* #docregion */
.error {color:red;}
button.delete-button {
float:right;
background-color: gray !important;
color:white;
}

View File

@ -1,13 +1,10 @@
<!-- #docregion -->
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<!-- #docregion click -->
<div *ngFor="let hero of heroes" (click)="gotoDetail(hero)" class="col-1-4">
<!-- #enddocregion click -->
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</div>
</div>
<hero-search></hero-search>

View File

@ -1,6 +1,4 @@
// #docplaster
// #docregion
// #docregion hero-search-component
// #docregion , search
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@ -14,9 +12,8 @@ import { HeroSearchComponent } from './hero-search.component';
styleUrls: ['app/dashboard.component.css'],
directives: [HeroSearchComponent]
})
// #enddocregion hero-search-component
// #enddocregion search
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(

View File

@ -1,11 +1,4 @@
/* #docregion */
.error {color:red;}
button.delete-button{
float:right;
background-color: gray !important;
color:white;
}
.search-result{
border-bottom: 1px solid gray;
border-left: 1px solid gray;
@ -21,4 +14,3 @@ button.delete-button{
width: 200px;
height: 20px;
}

View File

@ -11,29 +11,29 @@ import { Hero } from './hero';
@Component({
selector: 'hero-search',
templateUrl: 'app/hero-search.component.html',
styleUrls: ['app/hero-search.component.css'],
providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
// #docregion search
heroes: Observable<Hero[]>;
// #enddocregion search
// #docregion searchSubject
searchSubject = new Subject<string>();
// #enddocregion searchSubject
// #docregion searchTerms
private searchTerms = new Subject<string>();
// #enddocregion searchTerms
constructor(
private heroSearchService: HeroSearchService,
private router: Router) {}
// #docregion searchSubject
// #docregion searchTerms
// Push a search term into the observable stream.
search(term: string) { this.searchSubject.next(term); }
// #enddocregion searchSubject
search(term: string) { this.searchTerms.next(term); }
// #enddocregion searchTerms
// #docregion search
ngOnInit() {
this.heroes = this.searchSubject
.asObservable() // cast as Observable
this.heroes = this.searchTerms
.debounceTime(300) // wait for 300ms pause in events
.distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => term // switch to new observable each time
@ -41,9 +41,8 @@ export class HeroSearchComponent implements OnInit {
? this.heroSearchService.search(term)
// or the observable of empty heroes if no search term
: Observable.of<Hero[]>([]))
.catch(error => {
// Todo: real error handling
// TODO: real error handling
console.log(error);
return Observable.of<Hero[]>([]);
});

View File

@ -9,11 +9,9 @@ export class HeroSearchService {
constructor(private http: Http) {}
// #docregion observable-search
search(term: string) {
return this.http
.get(`app/heroes/?name=${term}+`)
.get(`app/heroes/?name=${term}`)
.map((r: Response) => r.json().data as Hero[]);
}
// #enddocregion observable-search
}

View File

@ -1,3 +1,4 @@
/* #docregion */
.selected {
background-color: #CFD8DC !important;
color: white;
@ -57,3 +58,10 @@ button {
button:hover {
background-color: #cfd8dc;
}
/* #docregion additions */
.error {color:red;}
button.delete-button{
float:right;
background-color: gray !important;
color:white;
}

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="sample.css">
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>

View File

@ -3,7 +3,7 @@
"icon": "home",
"title": "Angular Docs",
"menuTitle": "Docs Home",
"banner": "Welcome to <b>angular.io/dart</b>! The current Angular 2 Dart release is <b>beta.18</b>. Consult the <a href='https://github.com/angular/angular/blob/master/CHANGELOG.md' target='_blank'>Change Log</a> about recent enhancements, fixes, and breaking changes."
"banner": "Welcome to <b>angular.io/dart</b>! The current Angular 2 Dart release is <b>beta.18</b>. Consult the <a href='https://github.com/dart-lang/angular2/blob/master/CHANGELOG.md' target='_blank'>Change Log</a> about recent enhancements, fixes, and breaking changes."
},
"quickstart": {

View File

@ -1,45 +1,35 @@
include _util-fns
// From ts/glossary.jade, the folder ts/latest/_fragments is generated which contains a bunch of partial files.
// These partials comprise the glossary,a subset of these partials should be used to generate the glossary for
// __dart__ under BASICS.
!=partial('../../ts/latest/_fragments/glossary-intro')
!=partial('../../ts/latest/_fragments/glossary-a-2')
!=partial('../../ts/latest/_fragments/glossary-b-c')
!=partial('../../ts/latest/_fragments/glossary-d1')
!=partial('../../ts/latest/_fragments/glossary-d2')
!=partial('../../ts/latest/_fragments/glossary-e1')
!=partial('../../ts/latest/_fragments/glossary-e2')
!=partial('../../ts/latest/_fragments/glossary-f-l')
!=partial('../../ts/latest/_fragments/glossary-m1')
//!=partial('../../ts/latest/_fragments/glossary-m2') not needed in dart
!=partial('../../ts/latest/_fragments/glossary-n-s-1')
block var-def
- var fragPath='../../ts/latest/_fragments/';
!=partial(fragPath + 'glossary-intro')
!=partial(fragPath + 'glossary-a1')
!=partial(fragPath + 'glossary-a-2')
!=partial(fragPath + 'glossary-b-c')
!=partial(fragPath + 'glossary-d1')
!=partial(fragPath + 'glossary-d2')
!=partial(fragPath + 'glossary-e1')
!=partial(fragPath + 'glossary-e2')
!=partial(fragPath + 'glossary-f-l')
!=partial(fragPath + 'glossary-m1')
//partial(fragPath + 'glossary-m2') not needed in dart
!=partial(fragPath + 'glossary-n-s-1')
:marked
## snake_case
.l-sub-section
:marked
The practice of writing compound words or phrases such that each word is separated by an underscore (`_`).
The practice of writing compound words or phrases such that each word is separated by an
underscore (`_`).
Library and file names are often spelled in snake_case. Examples include: `angular2_tour_of_heroes` and `app_component.dart`.
Library and file names are often spelled in snake_case. Examples include:
`angular2_tour_of_heroes` and `app_component.dart`.
This form is also known as **underscore case**.
!=partial('../../ts/latest/_fragments/glossary-n-s-2')
!=partial('../../ts/latest/_fragments/glossary-t1')
//!=partial('../../ts/latest/_fragments/glossary-t2') notneeded in dart
!=partial('../../ts/latest/_fragments/glossary-u-z')
// NOTE: (ericjim): I am almost certain these lines are doing nothing,
// so instead I use `!=partial` to include the glossary fragments.
//+includeShared('{ts}', 'intro')
//+includeShared('{ts}', 'a2')
//+includeShared('{ts}', 'b-c')
//+includeShared('{ts}', 'd1')
//+includeShared('{ts}', 'd2')
//+includeShared('{ts}', 'e1')
//+includeShared('{ts}', 'e2')
//+includeShared('{ts}', 'f-l')
//+includeShared('{ts}', 'm1')
//+includeShared('{ts}', 'n-s')
//+includeShared('{ts}', 't1')
//+includeShared('{ts}', 'u-z')
!=partial(fragPath + 'glossary-n-s-2')
!=partial(fragPath + 'glossary-t1')
//partial(fragPath + 'glossary-t2') notneeded in dart
!=partial(fragPath + 'glossary-u-z')

View File

@ -36,6 +36,13 @@
"basics": true
},
"forms-deprecated": {
"title": "Forms",
"intro": "A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors.",
"basics": true,
"hide": true
},
"dependency-injection": {
"title": "Dependency Injection",
"intro": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\".",
@ -63,6 +70,11 @@
"basics": true
},
"animations": {
"title": "Animations",
"intro": "A guide to Angular's animation system."
},
"attribute-directives": {
"title": "Attribute Directives",
"intro": "Attribute directives attach behavior to elements."
@ -79,11 +91,6 @@
"basics": true
},
"security": {
"title": "Security",
"intro": "Prevent security vulnerabilities"
},
"hierarchical-dependency-injection": {
"title": "Hierarchical Dependency Injectors",
"navTitle": "Hierarchical Injectors",
@ -112,14 +119,19 @@
},
"router-deprecated": {
"title": "Router (Deprecated Beta)",
"intro": "The deprecated Beta Router."
"title": "Routing & Navigation",
"intro": "Discover the basics of screen navigation with the Angular 2 Component Router.",
"hide": true
},
"router": {
"title": "Routing & Navigation",
"intro": "Discover the basics of screen navigation with the Angular 2 router.",
"hide": true
"intro": "Discover the basics of screen navigation with the Angular 2 Component Router."
},
"security": {
"title": "Security",
"intro": "Developing for content security in Angular applications"
},
"structural-directives": {
@ -149,11 +161,5 @@
"title": "Webpack: an introduction",
"intro": "Create your Angular 2 applications with a Webpack based tooling",
"hide": true
},
"glossary": {
"title": "Glossary",
"intro": "Brief definitions of the most important words in the Angular 2 vocabulary",
"basics": true
}
}

View File

@ -0,0 +1,4 @@
include ../_util-fns
:marked
This page has no Dart equivalent. Instead, see the [forms guide](forms.html).

View File

@ -1 +1,4 @@
include ../glossary
extends ../glossary
block var-def
- var fragPath='../../../ts/latest/_fragments/';

View File

@ -1 +1,4 @@
!= partial("../../../_includes/_ts-temp")
include ../_util-fns
:marked
This page has no Dart equivalent. Instead, see the [router guide](router.html).

View File

@ -1,6 +1 @@
include ../_util-fns
:marked
We're working on the Dart version of this chapter.
In the meantime, please read the
[TypeScript version of this chapter](/docs/ts/latest/guide/router.html).
!= partial("../../../_includes/_ts-temp")

View File

@ -25,7 +25,8 @@ block http-library
### Pubspec updates
We need to add a package dependency for the !{_Angular_http_library}.
We need to add package dependencies for the
`stream_transformers` and !{_Angular_http_library}s.
We also need to add a `resolved_identifiers` entry, to inform the [angular2
transformer][ng2x] that we'll be using `BrowserClient`. (For an explanation of why
@ -37,7 +38,7 @@ block http-library
[guide-http]: ../guide/server-communication.html#!#http-providers
[ng2x]: https://github.com/angular/angular/wiki/Angular-2-Dart-Transformer
- var stylePattern = { pnk: /(http.*|resolved_identifiers:|Browser.*|Client.*)/gm };
- var stylePattern = { pnk: /(http.*|stream.*|resolved_identifiers:|Browser.*|Client.*)/gm };
+makeExcerpt('pubspec.yaml', 'additions', null, stylePattern)
block http-providers
@ -55,7 +56,13 @@ block backend
implementations.
block dont-be-distracted-by-backend-subst
//- N/A
//- No backend substitution but we do need to comment on the changes to Hero.
:marked
As is common for web API services, our mock in-memory service will be
encoding and decoding heroes in JSON format, so we enhance the `Hero`
class with these capabilities:
+makeExample('lib/hero.dart')
block get-heroes-details
:marked
@ -89,8 +96,42 @@ block heroes-comp-add
block review
//- Not showing animated gif due to differences between TS and Dart implementations.
block observables-section
//- TBC
block observables-section-intro
:marked
Recall that `HeroService.getHeroes()` awaits for an `http.get()`
response and yields a _Future_ `List<Hero>`, which is fine when we are only
interested in a single result.
block search-criteria-intro
:marked
A [StreamController][], as its name implies, is a controller for a [Stream][] that allows us to
manipulate the underlying stream by adding data to it, for example.
In our sample, the underlying stream of strings (`_searchTerms.stream`) represents the hero
name search patterns entered by the user. Each call to `search` puts a new string into
the stream by calling `add` over the controller.
[Stream]: https://api.dartlang.org/stable/dart-async/Stream-class.html
[StreamController]: https://api.dartlang.org/stable/dart-async/StreamController-class.html
block observable-transformers
:marked
Fortunately, there are stream transformers that will help us reduce the request flow.
We'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
* `transform(new Debounce(... 300)))` waits until the flow of search terms pauses for 300
milliseconds before passing along the latest string. We'll never make requests more frequently
than 300ms.
* `distinct()` ensures that we only send a request if a search term has changed.
There's no point in repeating a request for the same search term.
* `transform(new FlatMapLatest(...))` applies a map-like transformer that (1) calls our search
service for each search term that makes it through the debounce and distinct gauntlet and (2)
returns only the most recent search service result, discarding any previous results.
* `handleError()` handles errors. Our simple example prints the error to the console; a real
life application should do better.
block filetree
.filetree
@ -98,47 +139,65 @@ block filetree
.children
.file lib
.children
.file app_component.dart
.file app_component.css
.file app_component.dart
.file dashboard_component.css
.file dashboard_component.html
.file dashboard_component.dart
.file dashboard_component.html
.file hero.dart
.file hero_detail_component.css
.file hero_detail_component.html
.file hero_detail_component.dart
.file hero_detail_component.html
.file hero_search_component.css (new)
.file hero_search_component.dart (new)
.file hero_search_component.html (new)
.file hero_search_service.dart (new)
.file hero_service.dart
.file heroes_component.css
.file heroes_component.html
.file heroes_component.dart
.file main.dart
.file heroes_component.html
.file in_memory_data_service.dart (new)
.file web
.children
.file main.dart
.file index.html
.file sample.css (new)
.file styles.css
.file pubspec.yaml
block file-summary
+makeTabs(
`toh-6/dart/lib/hero.dart,
`toh-6/dart/lib/dashboard_component.dart,
toh-6/dart/lib/dashboard_component.html,
toh-6/dart/lib/hero.dart,
toh-6/dart/lib/hero_detail_component.dart,
toh-6/dart/lib/hero_detail_component.html,
toh-6/dart/lib/hero_service.dart,
toh-6/dart/lib/heroes_component.dart,
toh-6/dart/web/index.html,
toh-6/dart/web/main.dart,
toh-6/dart/web/sample.css`,
`,,,,,,final,`,
`lib/hero.dart,
toh-6/dart/lib/heroes_component.css,
toh-6/dart/lib/heroes_component.dart`,
null,
`lib/dashboard_component.dart,
lib/dashboard_component.html,
lib/hero.dart,
lib/hero_detail_component.dart,
lib/hero_detail_component.html,
lib/hero_service.dart,
lib/heroes_component.dart,
web/index.html,
web/main.dart,
web/sample.css`)
lib/heroes_component.css,
lib/heroes_component.dart`)
+makeExample('pubspec.yaml')
+makeTabs(
`toh-6/dart/lib/hero_search_component.css,
toh-6/dart/lib/hero_search_component.dart,
toh-6/dart/lib/hero_search_component.html,
toh-6/dart/lib/hero_search_service.dart`,
null,
`lib/hero_search_component.css,
lib/hero_search_component.dart,
lib/hero_search_component.html,
lib/hero_search_service.dart`)
+makeTabs(
`toh-6/dart/pubspec.yaml,
toh-6/dart/web/main.dart`,
`,final`,
`pubspec.yaml,
web/main.dart`)

View File

@ -27,7 +27,7 @@
},
"dynamic-form": {
"title": "Dynamic Form",
"title": "Dynamic Forms",
"intro": "Render dynamic forms with NgFormModel"
},

View File

@ -1 +0,0 @@
!= partial("../../../_includes/_ts-temp")

View File

@ -45,10 +45,10 @@ include ../../../../_includes/_util-fns
[`Host`(宿主)与`Query`(查询)元数据](#other-property-metadata)
**Run and compare the live [TypeScript](/resources/live-examples/cb-ts-to-js/ts/plnkr.html) and
[JavaScript](/resources/live-examples/cb-ts-to-js/js/plnkr.html) code shown in this cookbook.**
**Run and compare the live <live-example name="cb-ts-to-js">TypeScript</live-example> and <live-example name="cb-ts-to-js" lang="js">JavaScript</live-example>
code shown in this cookbook.**
**运行并比较本烹饪宝典里的在线[TypeScript](/resources/live-examples/cb-ts-to-js/ts/plnkr.html)和[JavaScript](/resources/live-examples/cb-ts-to-js/js/plnkr.html)代码**
**运行并比较本烹饪宝典里的在线<live-example name="cb-ts-to-js">TypeScript</live-example>和<live-example name="cb-ts-to-js" lang="js">JavaScript</live-example>代码**
a(id="modularity")
.l-main-section

View File

@ -35,7 +35,8 @@ include ../_util-fns
- How to share information across controls with template local variables
[Live Example](/resources/live-examples/forms-deprecated/js/plnkr.html)
<live-example>Live Example</live-example>
.l-main-section
:marked
## Template-Driven Forms

View File

@ -35,7 +35,7 @@ include ../_util-fns
- How to share information across controls with template local variables
[Live Example](/resources/live-examples/forms/js/plnkr.html)
<live-example>Live Example</live-example>
.l-main-section
:marked

View File

@ -13,7 +13,7 @@ include _util-fns
:marked
## See It Run!
Running the [live example](/resources/live-examples/quickstart/js/plnkr.html)
Running the <live-example></live-example>
is the quickest way to see an Angular 2 app come to life.
Clicking that link fires up a browser, loads the sample in [plunker](http://plnkr.co/ "Plunker"),

View File

@ -26,14 +26,9 @@
"intro": "依赖注入技术"
},
"dynamic-form": {
"title": "动态表单",
"intro": "通过FormGroup渲染动态表单",
"basics": true
},
"dynamic-form-deprecated": {
"title": "动态表单(已废弃)",
"intro": "通过NgFormModel渲染动态表单",
"hide": true
"title": "Dynamic Forms",
"intro": "Render dynamic forms with FormGroup",
"basics": true,
},
"set-document-title": {

View File

@ -9,7 +9,7 @@ a(id="top")
本章提供了一个快速的参考指南指出一些常用的Angular 1语法及其在Angular 2中的等价物。
:marked
**See the Angular 2 syntax in this [live example](/resources/live-examples/cb-a1-a2-quick-reference/ts/plnkr.html)**.
**See the Angular 2 syntax in this <live-example name="cb-a1-a2-quick-reference"></live-example>**.
**可到[在线范例](/resources/live-examples/cb-a1-a2-quick-reference/ts/plnkr.html)中查看Angular 2语法**。

View File

@ -48,9 +48,9 @@ include ../_util-fns
[父组件和子组件通过服务来通讯](#bidirectional-service)
:marked
**See the [live example](/resources/live-examples/cb-component-communication/ts/plnkr.html)**.
**See the <live-example name="cb-component-communication"></live-example>**.
**参见[在线例子](/resources/live-examples/cb-component-communication/ts/plnkr.html)**
**参见<live-example name="cb-component-communication"></live-example>**。
.l-main-section
<a id="parent-to-child"></a>

View File

@ -118,14 +118,14 @@ include ../_util-fns
.l-main-section
:marked
## Source
## 源码
**We can see the [live example](/resources/live-examples/cb-component-relative-paths/ts/plnkr.html)**
**We can see the <live-example name="cb-component-relative-paths"></live-example>**
and download the source code from there
or simply read the pertinent source here.
**参见[在线范例](/resources/live-examples/cb-component-relative-paths/ts/plnkr.html)**,并从中下载源码或只在这里阅读相关源码。
**参见<live-example name="cb-component-relative-paths"></live-example>**,并从中下载源码或只在这里阅读相关源码。
+makeTabs(
`cb-component-relative-paths/ts/app/some.component.ts,

View File

@ -113,7 +113,7 @@ include ../_util-fns
[使用类的前向引用(*forwardRef*)打破循环依赖](#forwardref)
:marked
**See the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)**
**See the <live-example name="cb-dependency-injection"></live-example>**
of the code supporting this cookbook.
要获取本“烹饪宝典”的代码,**参见[在线例子](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)**。
@ -419,13 +419,13 @@ figure.image-display
The `ngOnInit` pass that `id` to the service which fetches and caches the hero.
The getter for the `hero` property pulls the cached hero from the service.
And the template displays this data-bound property.
父组件`HeroBiosComponent`把一个值绑定到`heroId`。`ngOnInit`把该`id`传递到服务,然后服务获取和缓存英雄。`hero`属性的getter从服务里面获取缓存的英雄并在模板里显示它绑定到属性值。
Find this example in [live code](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
Find this example in <live-example name="cb-dependency-injection">live code</live-example>
and confirm that the three `HeroBioComponent` instances have their own cached hero data.
[在线代码](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)找到这个例子,确认三个`HeroBioComponent`实例拥有自己独立的英雄数据缓存。
<live-example name="cb-dependency-injection">在线例子</live-example>中找到这个例子,确认三个`HeroBioComponent`实例拥有自己独立的英雄数据缓存。
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/hero-bios.png" alt="Bios")
@ -924,7 +924,7 @@ a(id='usefactory')
:marked
The function retrieves candidate heroes from the `HeroService`,
takes `2` of them to be the runners-up, and returns their concatenated names.
Look at the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
Look at the <live-example name="cb-dependency-injection"></live-example>
for the full source code.
该函数从`HeroService`获取英雄参赛者,从中取`2`个作为亚军,并把他们的名字拼接起来。请到[在线例子](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)查看全部原代码。
@ -1197,7 +1197,7 @@ a(id='alex')
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','cathy','parent-finder.component.ts (CathyComponent)')(format='.')
:marked
We added the [@Optional](#optional) qualifier for safety but
the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
the <live-example name="cb-dependency-injection"></live-example>
confirms that the `alex` parameter is set.
安全起见,我们添加了[@Optional](#optional)装饰器,但是[在线例子](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)显示`alex`参数确实被设置了。
@ -1253,7 +1253,7 @@ a(id='alex')
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','craig','parent-finder.component.ts (CraigComponent)')(format='.')
:marked
Unfortunately, this does not work.
The [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
The <live-example name="cb-dependency-injection"></live-example>
confirms that the `alex` parameter is null.
*We cannot inject a parent by its base class.*

View File

@ -35,7 +35,7 @@ include ../_util-fns
[Dynamic Template](#dynamic-template)
:marked
**See the [live example](/resources/live-examples/cb-dynamic-form-deprecated/ts/plnkr.html)**.
**See the <live-example name="cb-dynamic-form-deprecated"></live-example>**.
.l-main-section
<a id="object-model"></a>

View File

@ -60,9 +60,9 @@ include ../_util-fns
[动态模板](#dynamic-template)
:marked
**See the [live example](/resources/live-examples/cb-dynamic-form/ts/plnkr.html)**.
**See the <live-example name="cb-dynamic-form"></live-example>**.
**参见[在线例子](/resources/live-examples/cb-dynamic-form/ts/plnkr.html)**.
**参见<live-example name="cb-dynamic-form"></live-example>**。
.l-main-section

View File

@ -7,9 +7,9 @@ a(id='top')
应用程序应该能让浏览器标题栏显示我们想让它显示的内容。本*烹饪宝典*解释怎么做。
:marked
**See the [live example](/resources/live-examples/cb-set-document-title/ts/plnkr.html)**.
**See the <live-example name="cb-set-document-title"></live-example>**.
**参见[在线例子](/resources/live-examples/cb-set-document-title/ts/plnkr.html)**.
**参见<live-example name="cb-set-document-title"></live-example>**。
.l-sub-section
img(src='/resources/images/devguide/plunker-separate-window-button.png' alt="pop out the window" align="right" style="margin-right:-20px")

View File

@ -369,10 +369,10 @@ include _util-fns
Angular提供并使用自己精心设计的[依赖注入(Dependency Injection)](dependency-injection.html)系统来组装和运行应用程序:在要用时,将一些部件“注入”到另一些部件里面。
At the core is an [`Injector`](#injector) that returns dependency values on request.
At the core there is an [`Injector`](#injector) that returns dependency values on request.
The expression `injector.get(token)` returns the value associated with the given token.
依赖注入的核心是一个[注入器(Injector)](#injector),这个注入器根据需要返回被依赖部件。`injector.get(token)`方法返回与该token(令牌)参数相关的依赖部件。
在Angular内核中有一个[注入器(Injector)](#injector),这个注入器根据需要返回被依赖部件。`injector.get(token)`方法返回与该token(令牌)参数相关的依赖部件。
A token is an Angular type (`OpaqueToken`). We rarely deal with tokens directly; most
methods accept a class name (`Foo`) or a string ("foo") and Angular converts it
@ -383,7 +383,7 @@ include _util-fns
当调用`injector.get(Foo)`时,注入器返回用`Foo`类生成的令牌所对应的依赖值,该依赖值通常是`Foo`类的实例。
Angular makes similar requests internally during many of its operations
as when it creates a [`Component`](#AppComponent) for display.
as when it creates a [`Component`](#component) for display.
Angular在创建[组件(Component)](#AppComponent)以供显示的过程中,会在内部执行很多类似的依赖注入请求。

View File

@ -77,7 +77,7 @@ include ../_util-fns
.l-sub-section
:marked
The examples referenced in this chapter are available as a [live example](/resources/live-examples/animations/ts/plnkr.html).
The examples referenced in this chapter are available as a <live-example></live-example>.
本章中引用的这个例子可以到[在线例子](/resources/live-examples/animations/ts/plnkr.html)去体验。

View File

@ -208,7 +208,8 @@ block angular-library-modules
<br clear="all">
There are other important Angular libraries too, such as `!{_at_angular}/common`, `!{_at_angular}/router`, and `!{_at_angular}/http`.
There are other important Angular libraries too, such as `!{_at_angular}/common`<span if-docs="ts">,
`!{_at_angular}/http`</span> and `!{_at_angular}/router`.
We import what we need from an Angular !{_library_module}.
还有另一些重要的Angular模块库比如`@angular/common`、`@angular/router` 和 `@angular/http`。我们从一个Angular的!{_library_module}导入我们需要的模块。

View File

@ -35,7 +35,7 @@ include ../_util-fns
- sharing information among controls with template reference variables
[Live Example](/resources/live-examples/forms-deprecated/ts/plnkr.html)
<live-example>Live Example</live-example>
.l-main-section
:marked
## Template-Driven Forms

View File

@ -65,10 +65,10 @@ include ../_util-fns
- sharing information among controls with template reference variables
- 通过模板引用变量,在控件之间共享信息
[Live Example](/resources/live-examples/forms/ts/plnkr.html)
[在线例子](/resources/live-examples/forms/ts/plnkr.html)
<live-example>Live Example</live-example>
<live-example>在线例子</live-example>
.l-main-section
:marked

View File

@ -188,7 +188,7 @@ table.vertical-table(width="100%")
block example-links
:marked
Look for a link to a running version of that sample near the top of each page,
such as this [live example](/resources/live-examples/architecture/ts/plnkr.html) from the [Architecture](architecture.html) chapter.
such as this <live-example></live-example> from the [Architecture](architecture.html) chapter.
在每页靠近顶部的地方都可以看到一个链接,指向这个范例的可执行版本,比如[架构](architecture.html)一章中的[在线例子](/resources/live-examples/architecture/ts/plnkr.html)。

View File

@ -61,6 +61,16 @@ block includes
在这个插值表达式中,我们让组件的`birthday`值通过[管道操作符](./template-syntax.html#pipe)( | )流动到
右侧的[Date管道](../api/common/index/DatePipe-class.html)函数中。所有管道都会用这种方式工作。
.l-sub-section
:marked
The `Date` and `Currency` pipes need the **ECMAScript Internationalization API**.
Safari and other older browsers don't support it. We can add support with a polyfill.
`Date`和`Currency`管道需要**ECMAScript国际化I18nAPI**但Safari和其它老式浏览器不支持它该问题可以用垫片Polyfill解决。
code-example(language="html").
&lt;script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"&gt;&lt;/script&gt;
.l-main-section
:marked
## Built-in pipes
@ -74,7 +84,7 @@ block includes
.l-sub-section
:marked
Learn more about these and many other built-in pipes in the the [API Reference](../api/#!?apiFilter=pipe);
Learn more about these and many other built-in pipes in the [API Reference](../api/#!?apiFilter=pipe);
filter for entries that include the word "pipe".
要学习更多内建管道的知识,参见[API参考手册](../api/#!?apiFilter=pipe)并用“pipe”为关键词对结果进行过滤。

View File

@ -10,7 +10,7 @@ include ../_util-fns
as users perform application tasks.
We cover the router's primary features in this chapter, illustrating them through the evolution
of a small application that we can [run live](/resources/live-examples/router-deprecated/ts/plnkr.html).
of a small application that we can <live-example>run live</live-example>.
.l-sub-section
img(src='/resources/images/devguide/plunker-separate-window-button.png' alt="pop out the window" align="right" style="margin-right:-20px")
:marked
@ -207,7 +207,7 @@ table
We discuss code and design decisions pertinent to routing and application design.
We gloss over everything in between.
The full source is available in the [live example](/resources/live-examples/router-deprecated/ts/plnkr.html).
The full source is available in the <live-example></live-example>
:marked
Our client is the Hero Employment Agency.
Heroes need work and The Agency finds Crises for them to solve.
@ -216,7 +216,7 @@ table
1. A *Crisis Center* where we maintain the list of crises for assignment to heroes.
1. A *Heroes* area where we maintain the list of heroes employed by The Agency.
Run the [live example](/resources/live-examples/router-deprecated/ts/plnkr.html).
Run the <live-example></live-example>.
It opens in the *Crisis Center*. We'll come back to that.
Click the *Heroes* link. We're presented with a list of Heroes.
@ -1110,7 +1110,7 @@ code-example(format="." language="bash").
.l-sub-section
:marked
The [live example](/resources/live-examples/router-deprecated/ts/plnkr.html) *does* highlight the selected
The <live-example></live-example> *does* highlight the selected
row because it demonstrates the final state of the application which includes the steps we're *about* to cover.
At the moment we're describing the state of affairs *prior* to those steps.
:marked
@ -1202,7 +1202,7 @@ code-example(format="." language="bash").
As we end our chapter, we take a parting look at
the entire application.
We can always try the [live example](/resources/live-examples/router-deprecated/ts/plnkr.html) and download the source code from there.
We can always try the <live-example></live-example> and download the source code from there.
Our final project folder structure looks like this:
.filetree

View File

@ -15,9 +15,9 @@ include ../_util-fns
在用户使用应用程序时Angular的***组件路由器***能让用户从一个[视图](./glossary.html#view)导航到另一个视图。
We cover the router's primary features in this chapter, illustrating them through the evolution
of a small application that we can [run live](/resources/live-examples/router/ts/plnkr.html).
of a small application that we can <live-example>run live</live-example>.
本章覆盖了该路由器的主要特性。我们通过一个小型应用的成长演进来讲解它。参见[在线例子](/resources/live-examples/router/ts/plnkr.html)
本章覆盖了该路由器的主要特性。我们通过一个小型应用的成长演进来讲解它。参见<live-example>在线例子</live-example>
.l-sub-section
@ -339,9 +339,6 @@ code-example(format="", language="html").
:marked
### Router State
### 路由器状态
After the end of each successful navigation lifecycle, the router builds a tree of `ActivatedRoute`s,
which make up the current state of the router. We can access the current `RouterState` from anywhere in our
application using the `Router` service and the `routerState` property.
@ -530,13 +527,7 @@ table
We discuss code and design decisions pertinent to routing and application design.
We gloss over everything in between.
虽然我们会渐进式的前进到最终的范例应用,但本章并不是一个教程。
我们讨论路由和应用设计有关的代码和设计决策,并在这期间,处理遇到的所有问题。
The full source is available in the [live example](/resources/live-examples/router/ts/plnkr.html).
完整代码可以在[在线例子](/resources/live-examples/router/ts/plnkr.html)中找到。
:marked
Our client is the Hero Employment Agency.
Heroes need work and The Agency finds Crises for them to solve.
@ -554,8 +545,6 @@ table
1. A *Heroes* area where we maintain the list of heroes employed by The Agency.
1. 一个*英雄*区,用来维护该中心雇佣英雄的列表。
Run the [live example](/resources/live-examples/router/ts/plnkr.html).
It opens in the *Crisis Center*. We'll come back to that.
@ -2753,7 +2742,7 @@ code-example(format="." language="bash").
.l-sub-section
:marked
The [live example](/resources/live-examples/router/ts/plnkr.html) *does* highlight the selected
The <live-example></live-example> *does* highlight the selected
row because it demonstrates the final state of the application which includes the steps we're *about* to cover.
At the moment we're describing the state of affairs *prior* to those steps.
@ -2912,7 +2901,7 @@ figure.image-display
## 总结
We've covered a lot of ground in this chapter and the application is too big to reprint here.
Please visit the [live example](/resources/live-examples/router/ts/plnkr.html) and
Please visit the <live-example></live-example> and
where you can download the final source code.
本章中涉及到了很多背景知识,而且本应用程序也太大了,所以没法在这里显示。请访问[在线例子](/resources/live-examples/router/ts/plnkr.html),在那里你可以下载最终的源码。

View File

@ -9,9 +9,9 @@ include ../_util-fns
当用户点击链接、按下按钮或者输入文字时我们得知道发生了什么。这些用户动作都会产生DOM事件。
本章中我们将学习如何使用Angular事件绑定语法来绑定这些事件。
[Run the live example](/resources/live-examples/user-input/ts/plnkr.html)
<live-example>Run the live example</live-example>
[运行在线例子](/resources/live-examples/user-input/ts/plnkr.html)
<live-example>运行在线例子</live-example>
:marked
## Binding to user input events

View File

@ -179,7 +179,7 @@ block dont-be-distracted-by-backend-subst
来看看我们目前的`HeroService`的实现
+makeExample('toh-4/ts/app/hero.service.ts', 'get-heroes', 'app/hero.service.ts (old getHeroes)')(format=".")
+makeExcerpt('toh-4/ts/app/hero.service.ts (old getHeroes)', 'get-heroes')
:marked
We returned a !{_Promise} resolved with mock heroes.
@ -219,7 +219,7 @@ block get-heroes-details
*现在*,我们先利用`toPromise`操作符把`Observable`直接转换成`Promise`对象,回到已经熟悉的地盘。
+makeExample('toh-6/ts/app/hero.service.ts', 'to-promise')(format=".")
+makeExcerpt('app/hero.service.ts', 'to-promise', '')
:marked
Unfortunately, the Angular `Observable` doesn't have a `toPromise` operator ... not out of the box.
@ -236,7 +236,7 @@ block get-heroes-details
如果我们希望得到那些能力,就得自己添加那些操作符。
那很容易只要从RxJS库中导入它们就可以了就像这样
+makeExample('toh-6/ts/app/hero.service.ts', 'rxjs')(format=".")
+makeExcerpt('app/hero.service.ts', 'rxjs', '')
:marked
### Extracting the data in the *then* callback
@ -248,7 +248,7 @@ block get-heroes-details
在*promise*的`then`回调中我们调用http的`Reponse`对象的`json`方法,以提取出其中的数据。
+makeExample('toh-6/ts/app/hero.service.ts', 'to-data')(format=".")
+makeExcerpt('app/hero.service.ts', 'to-data', '')
:marked
That response JSON has a single `data` property.
@ -292,7 +292,7 @@ block get-heroes-details
在`getHeroes()`的最后,我们`catch`了服务器的失败信息,并把它们传给了错误处理器:
+makeExcerpt('app/hero.service.ts', 'catch')
+makeExcerpt('app/hero.service.ts', 'catch', '')
:marked
This is a critical step!
@ -301,7 +301,8 @@ block get-heroes-details
这是一个关键的步骤!
我们必须预料到http请求会失败因为有太多我们无法控制的原因可能导致它们频繁出现各种错误。
+makeExcerpt('app/hero.service.ts', 'handleError')
+makeExcerpt('app/hero.service.ts', 'handleError', '')
- var rejected_promise = _docsFor == 'dart' ? 'propagated exception' : 'rejected promise';
:marked
In this demo service we log the error to the console; we should do better in real life.
@ -357,9 +358,9 @@ block get-heroes-details
### Put
Put will be used to update an individual hero. Its structure is very similar to Post requests. The only difference is that we have to change the url slightly by appending the id of the hero we want to update.
Put will be used to update an individual hero. Its structure is very similar to Post requests. The only difference is that we have to change the URL slightly by appending the id of the hero we want to update.
Put用来编辑一个指定的英雄但是代码结构和POST请求很相似。唯一的不同是我们要修改url为它附加上我们想要编辑的那位英雄的id。
Put用来编辑一个指定的英雄但是代码结构和POST请求很相似。唯一的不同是我们要修改URL为它附加上我们想要编辑的那位英雄的id。
+makeExcerpt('app/hero.service.ts', 'put')
@ -437,7 +438,7 @@ block hero-detail-comp-updates
+makeExcerpt('app/hero-detail.component.ts', 'ngOnInit')
:marked
In order to differentiate between add and edit we are adding a check to see if an id is passed in the url. If the id is absent we bind `HeroDetailComponent` to an empty `Hero` object. In either case, any edits made through the UI will be bound back to the same `hero` property.
In order to differentiate between add and edit we are adding a check to see if an id is passed in the URL. If the id is absent we bind `HeroDetailComponent` to an empty `Hero` object. In either case, any edits made through the UI will be bound back to the same `hero` property.
为了区分添加和编辑操作我们增加了一步检查看看url中是否传入了id。如果没有id我们就把`HeroDetailComponent`绑定到一个空的`Hero`对象。
无论是哪种情况通过UI进行的任何编辑操作都会被绑定回同一个`hero`属性。
@ -527,7 +528,13 @@ block add-new-hero-via-detail-comp
用户可以通过点击英雄名后面的删除按钮来*删除*一个现存的英雄。
往`heroes.component.html`中往`<li>`标签中名字的紧后面添加下列HTML代码
+makeExample('app/heroes.component.html', 'delete')
+makeExcerpt('app/heroes.component.html', 'delete')
:marked
Add the following to the bottom of the `HeroesComponent` CSS file:
把下列代码添加到`HeroesComponent`的CSS文件底部
+makeExcerpt('app/heroes.component.css', 'additions')
:marked
Now let's fix-up the `HeroesComponent` to support the *add* and *delete* actions used in the template.
@ -599,14 +606,16 @@ block review
下面是这些劳动成果的操作演示:
figure.image-display
img(src='/resources/images/devguide/toh/toh-http.anim.gif' alt="Heroes List Editting w/ HTTP")
img(src='/resources/images/devguide/toh/toh-http.anim.gif' alt="Heroes List Editing w/ HTTP")
block observables-section
:marked
## Observables
## !{_Observable}s
## 可观察对象Observable
block observables-section-intro
:marked
Each `Http` method returns an `Observable` of HTTP `Response` objects.
每个`Http`方法都返回一个Http `Response`对象的`Observable`实例。
@ -646,117 +655,137 @@ block observables-section
转换成承诺通常是更好地选择,我们通常要求`http`获取单块数据。只要接收到数据,就算完成。
使用承诺这种形式的结果是让调用方更容易写并且承诺已经在JavaScript程序员中被广泛接受了。
But requests aren't always "one and done". We may start one request,
then cancel it, and make a different request ... before the server has responded to the first request.
Such a _request-cancel-new-request_ sequence is difficult to implement with *promises*.
It's easy with *observables* as we'll see.
但是请求并非总是“一次性”的。我们可以开始一个请求,并且取消它,再开始另一个不同的请求 —— 在服务器对第一个请求作出回应之前。
像这样一个_请求-取消-新请求_的序列用*承诺*是很难实现的,但接下来我们会看到,它对于*可观察对象*却很简单。
### Search-by-name
### 按名搜索
We're going to add a *hero search* feature to the Tour of Heroes.
As the user types a name into a search box, we'll make repeated http requests for heroes filtered by that name.
我们要为《英雄指南》添加一个*英雄搜索*特性。
当用户在搜索框中输入一个名字时我们将不断发起http请求以获得按名字过滤的英雄。
We start by creating `HeroSearchService` that sends search queries to our server's web api.
我们先创建`HeroSearchService`服务它会把搜索请求发送到我们服务器上的Web API。
+makeExample('toh-6/ts/app/hero-search.service.ts', null, 'app/hero-search.service.ts')(format=".")
:marked
The `http.get` call in `HeroSearchService` is similar to the `http.get` call in the `HeroService`.
The notable difference: we no longer call `toPromise`.
We simply return the *observable* instead.
`HeroSearchService`中的`http.get`调用和`HeroService`中的很相似。
显著的不同是:我们不再调用`toPromise`,而是直接返回*可观察对象*。
### HeroSearchComponent
### HeroSearchComponent
Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
我们再创建一个新的`HeroSearchComponent`来调用这个新的`HeroSearchService`。
The component template is simple - just a textbox and a list of matching search results.
组件模板很简单,就是一个输入框和一个相匹配的搜索结果列表。
+makeExample('toh-6/ts/app/hero-search.component.html', null,'hero-search.component.html')
:marked
As the user types in the search box, a *keyup* event binding calls the component's `search` with the new search box value.
当用户在搜索框中输入时,一个*keyup*事件绑定会调用该组件的`search`方法,并传入新的搜索框的值。
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
`*ngFor`为该组件的`heroes`属性重复*hero*对象。这也没啥特别的。
But, as we'll soon see, the `heroes` property returns an `Observable` of heroes, not an array of heroes.
The `*ngFor` can't do anything with an observable until we flow it through the `AsyncPipe` (`heroes | async`).
The `AsyncPipe` subscribes to the observable and produces the array of heroes to `*ngFor`.
但是,接下来我们看到`heroes`属性返回了一个英雄们的`Observable`对象,不是英雄们的数组。
`*ngFor`不能利用可观察对象做任何事,除非我们在它后面跟一个`AsyncPipe``heroes | async`)。
`AsyncPipe`会订阅到这个可观察对象,并且为`*ngFor`生产一个英雄们的数组。
Time to create the `HeroSearchComponent` class and metadata.
该创建`HeroSearchComponent`类及其元数据了。
+makeExample('toh-6/ts/app/hero-search.component.ts', null,'hero-search.component.ts')
:marked
But requests aren't always "one and done". We may start one request,
then cancel it, and make a different request before the server has responded to the first request.
Such a _request-cancel-new-request_ sequence is difficult to implement with *!{_Promise}s*.
It's easy with *!{_Observable}s* as we'll see.
但是请求并非总是“一次性”的。我们可以开始一个请求,并且取消它,再开始另一个不同的请求 —— 在服务器对第一个请求作出回应之前。
像这样一个_请求-取消-新请求_的序列用*承诺*是很难实现的,但接下来我们会看到,它对于*可观察对象*却很简单。
### Search-by-name
### 按名搜索
We're going to add a *hero search* feature to the Tour of Heroes.
As the user types a name into a search box, we'll make repeated HTTP requests for heroes filtered by that name.
我们要为《英雄指南》添加一个*英雄搜索*特性。
当用户在搜索框中输入一个名字时我们将不断发起http请求以获得按名字过滤的英雄。
We start by creating `HeroSearchService` that sends search queries to our server's web api.
我们先创建`HeroSearchService`服务它会把搜索请求发送到我们服务器上的Web API。
+makeExample('app/hero-search.service.ts')
:marked
The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one
in the `HeroService`, although the URL now has a query string.
<span if-docs="ts">Another notable difference: we no longer call `toPromise`,
we simply return the *observable* instead.</span>
`HeroSearchService`中的`http.get()`调用和`HeroService`中的很相似,只是这次带了查询字符串。
<span if-docs="ts">显著的不同是:我们不再调用`toPromise`,而是直接返回*可观察对象*。</span>
### HeroSearchComponent
### HeroSearchComponent
Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
我们再创建一个新的`HeroSearchComponent`来调用这个新的`HeroSearchService`。
The component template is simple &mdash; just a text box and a list of matching search results.
组件模板很简单,就是一个输入框和一个相匹配的搜索结果列表。
+makeExample('app/hero-search.component.html')
:marked
We'll also want to add styles for the new component.
我们还要往这个新组件中添加样式。
+makeExample('app/hero-search.component.css')
:marked
As the user types in the search box, a *keyup* event binding calls the component's `search` method with the new search box value.
当用户在搜索框中输入时,一个*keyup*事件绑定会调用该组件的`search`方法,并传入新的搜索框的值。
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
`*ngFor`为该组件的`heroes`属性重复*hero*对象。这也没啥特别的。
But, as we'll soon see, the `heroes` property is now !{_an} *!{_Observable}* of hero !{_array}s, rather than just a hero !{_array}.
The `*ngFor` can't do anything with !{_an} `!{_Observable}` until we flow it through the `async` pipe (`AsyncPipe`).
The `async` pipe subscribes to the `!{_Observable}` and produces the !{_array} of heroes to `*ngFor`.
但是,接下来我们看到`heroes`属性现在是英雄列表的`Observable`对象,而不再只是英雄数组。
`*ngFor`不能利用可观察对象做任何事,除非我们在它后面跟一个`async` pipe (`AsyncPipe`)。
这个`async`管道会订阅到这个可观察对象,并且为`*ngFor`生成一个英雄数组。
Time to create the `HeroSearchComponent` class and metadata.
该创建`HeroSearchComponent`类及其元数据了。
+makeExample('app/hero-search.component.ts')
:marked
#### Search terms
#### 搜索词
Let's focus on the `!{_priv}searchTerms`:
仔细看下这个`searchTerms`
+makeExcerpt('app/hero-search.component.ts', 'searchTerms', '')
block search-criteria-intro
:marked
Focus on the `searchSubject`.
仔细看`searchSubject`。
+makeExample('toh-6/ts/app/hero-search.component.ts', 'searchSubject')(format=".")
:marked
A `Subject` is a producer of an _observable_ event stream.
This `searchSubject` produces an `Observable` of strings, the filter criteria for the name search.
A `Subject` is a producer of an _observable_ event stream;
`searchTerms` produces an `Observable` of strings, the filter criteria for the name search.
`Subject`主体是一个_可观察的_事件流中的生产者。
这里的`searchSubject`生产一些字符串的`Observable`,用于作为按名搜索时的过滤条件。
`searchTerms`生成一些字符串的`Observable`,用于作为按名搜索时的过滤条件。
Each call to `search` puts a new string into this subject's _observable_ stream by calling `next`.
每次到`search`的调用都会调用`next`来把新的字符串放进该主体的_可观察_流中。
A `Subject` is also an `Observable`.
We're going to access that `Observable` and turn the stream
of strings into a stream of `Hero[]` arrays, the `heroes` property.
每当调用`search`时都会调用`next`来把新的字符串放进该主体的_可观察_流中。
`Subject`也是一个`Observable`对象。
我们将访问`Observable`并且把字符串数组组成的流转换成`Hero[]`数组组成的流,也就是`heroes`属性。
:marked
<a id="ngoninit"></a>
#### Initialize the _**heroes**_ property (_**ngOnInit**_)
#### 初始化_**heroes**_属性(_**ngOnInit**_)
<span if-docs="ts">A `Subject` is also an `Observable`.</span>
We're going to turn the stream
of search terms into a stream of `Hero` !{_array}s and assign the result to the `heroes` property.
<span if-docs="ts">`Subject`也是一个`Observable`对象。</span>
我们要把字符串数组的流转换成`Hero`数组的流,并把结果赋值给`heroes`属性。
+makeExample('toh-6/ts/app/hero-search.component.ts', 'search')(format=".")
+makeExcerpt('app/hero-search.component.ts', 'search', '')
:marked
If we passed every user keystroke directly to the `HeroSearchService`, we'd unleash a storm of HTTP requests.
Bad idea. We don't want to tax our server resources and burn through our cellular network data plan.
如果我们直接把每一次用户按键都直接传给`HeroSearchService`就会发起一场HTTP请求风暴。
这可不好玩。我们不希望占用服务器资源,也不想耗尽网络带宽。
block observable-transformers
:marked
If we passed every user keystroke directly to the `HeroSearchService`, we'd unleash a storm of http requests.
Bad idea. We don't want to tax our server resources and burn through our cellular network data plan.
如果我们直接把每一次用户按键都直接传给`HeroSearchService`就会发起一场Http请求风暴。
这可不好玩。我们不希望占用服务器资源,也不想耗尽网络带宽。
Fortunately we can chain `Observable` operators to the string `Observable` that reduce the request flow.
Fortunately, we can chain `Observable` operators to the string `Observable` that reduce the request flow.
We'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
幸运的是,我们可以在字符串的`Observable`后面串联一个`Observable`操作符,来归并这些请求。
我们将对`HeroSearchService`发起更少的调用,并且仍然获得足够及时的响应。做法如下:
* The `asObservable` operator casts the `Subject` as an `Observable` of filter strings.
* `asObservable`操作符把`Subject`转换成过滤字符串组成的`Observable`。
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
before passing along the latest string. We'll never make requests more frequently than 300ms.
@ -840,42 +869,42 @@ block observables-section
在这个例子中,我们使用一些不同的方法。
我们把整个应用中要用的那些RxJS `Observable`扩展组合在一起放在一个单独的RxJS导入文件中。
+makeExample('toh-6/ts/app/rxjs-extensions.ts', null, 'app/rxjs-extensions.ts')(format=".")
+makeExample('app/rxjs-extensions.ts')
:marked
We load them all at once by importing `rxjs-extensions` in `AppComponent`.
我们在`AppComponent`中导入`rxjs-extensions`就可以一次性加载它们。
+makeExample('toh-6/ts/app/app.component.ts', 'rxjs-extensions', 'app/app.component.ts')(format=".")
+makeExcerpt('app/app.component.ts', 'rxjs-extensions')
:marked
### Adding the search component to the dashboard
### 为仪表盘添加搜索组件
:marked
### Add the search component to the dashboard
### 为仪表盘添加搜索组件
We add the `HeroSearchComponent` to the bottom of the `DashboardComponent` template.
将`HeroSearchComponent`添加到`DashboardComponent`的模版的最后面。
We add the hero search HTML element to the bottom of the `DashboardComponent` template.
将表示“英雄搜索”组件的HTML元素添加到`DashboardComponent`模版的最后面。
+makeExample('toh-6/ts/app/dashboard.component.html', null, 'dashboard.component.html')
+makeExample('app/dashboard.component.html')
:marked
And finally, we import the `HeroSearchComponent` and add it to the `directives` array.
最后,导入`HeroSearchComponent`并将其添加到`directives`数组中。
:marked
And finally, we import the `HeroSearchComponent` and add it to the `directives` !{_array}.
最后,导入`HeroSearchComponent`并将其添加到`directives`数组中。
+makeExcerpt('app/dashboard.component.ts', 'hero-search-component')
+makeExcerpt('app/dashboard.component.ts', 'search')
:marked
Run the app again, go to the *Dashboard*, and enter some text in the search box below the hero tiles.
At some point it might look like this.
再次运行该应用,跳转到*Dashboard*,并在英雄下方的搜索框里输入一些文本。
看起来就像这样:
:marked
Run the app again, go to the *Dashboard*, and enter some text in the search box below the hero tiles.
At some point it might look like this.
再次运行该应用,跳转到*Dashboard*,并在英雄下方的搜索框里输入一些文本。
看起来就像这样:
figure.image-display
img(src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component")
figure.image-display
img(src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component")
.l-main-section
:marked
@ -905,9 +934,10 @@ block filetree
.file hero-detail.component.css
.file hero-detail.component.html
.file hero-detail.component.ts
.file hero-search.component.html
.file hero-search.component.ts
.file hero-search.service.ts
.file hero-search.component.html (new)
.file hero-search.component.css (new)
.file hero-search.component.ts (new)
.file hero-search.service.ts (new)
.file rxjs-operators.ts
.file hero.service.ts
.file heroes.component.css
@ -919,7 +949,6 @@ block filetree
.file typings ...
.file index.html
.file package.json
.file sample.css (new)
.file styles.css
.file systemjs.config.json
.file tsconfig.json
@ -935,17 +964,17 @@ block filetree
旅程即将结束,不过我们已经收获颇丰。
- We added the necessary dependencies to use Http in our application.
- We added the necessary dependencies to use HTTP in our application.
- 我们添加了在应用程序中使用Http的必备依赖。
- 我们添加了在应用程序中使用HTTP的必备依赖。
- We refactored HeroService to load heroes from an API.
- We refactored `HeroService` to load heroes from a web API.
- 我们重构了HeroService以通过api来加载英雄数据。
- 我们重构了`HeroService`以通过web API来加载英雄数据。
- We extended HeroService to support post, put and delete calls.
- We extended `HeroService` to support post, put and delete methods.
- 我们扩展了HeroService来支持post、put和delete调用
- 我们扩展了`HeroService`来支持post、put和delete方法
- We updated our components to allow adding, editing and deleting of heroes.
@ -955,44 +984,45 @@ block filetree
- 我们配置了一个内存Web API。
<li if-docs="ts"> We learned how to use Observables.</li>
- We learned how to use !{_Observable}s.
<li if-docs="ts">我们学会了如何使用可观察对象。</li>
- 我们学会了如何使用“可观察对象”。
Below is a summary of the files we changed and added.
下面是我们添加之后的文件汇总。
Here are the files we added or changed in this chapter.
下面是我们添加或修改之后的文件汇总。
block file-summary
+makeTabs(
`toh-6/ts/app/app.component.ts,
toh-6/ts/app/heroes.component.ts,
toh-6/ts/app/heroes.component.html,
toh-6/ts/app/heroes.component.css,
toh-6/ts/app/hero-detail.component.ts,
toh-6/ts/app/hero-detail.component.html,
toh-6/ts/app/hero.service.ts,
toh-6/ts/app/in-memory-data.service.ts,
toh-6/ts/sample.css`,
toh-6/ts/app/in-memory-data.service.ts`,
null,
`app.comp...ts,
heroes.comp...ts,
heroes.comp...html,
heroes.comp...css,
hero-detail.comp...ts,
hero-detail.comp...html,
hero.service.ts,
in-memory-data.service.ts,
sample.css`
in-memory-data.service.ts`
)
+makeTabs(
`toh-6/ts/app/hero-search.service.ts,
toh-6/ts/app/hero-search.component.ts,
toh-6/ts/app/hero-search.component.html,
toh-6/ts/app/hero-search.component.css,
toh-6/ts/app/rxjs-operators.ts`,
null,
`hero-search.service.ts,
hero-search.component.ts,
hero-search.service.html,
hero-search.component.css,
rxjs-operators.ts`
)
)

View File

@ -17,7 +17,7 @@ angularIO.directive('ifDocs', ['ngIfDirective', '$location', function (ngIfDirec
terminal: ngIf.terminal,
restrict: ngIf.restrict,
link: function (scope, element, attrs) {
var ngIfCond = (attrs.ifDocs === 'dart') == !NgIoUtil.isDartDoc($location);
var ngIfCond = (attrs.ifDocs === 'dart') == !NgIoUtil.isDoc($location, 'dart');
attrs.ngIf = function () { return !ngIfCond; }
ngIf.link.apply(ngIf, arguments);
}

View File

@ -28,10 +28,12 @@ angularIO.directive('liveExample', ['$location', function ($location) {
var ex = attrs.name || NgIoUtil.getExampleName($location);
var href, template;
var isForDart = attrs.lang === 'dart' || NgIoUtil.isDartDoc($location);
var isForDart = attrs.lang === 'dart' || NgIoUtil.isDoc($location, 'dart');
var isForJs = attrs.lang === 'js' || NgIoUtil.isDoc($location, 'js');
var exLang = isForDart ? 'dart' : isForJs ? 'js' : 'ts';
var href = isForDart
? 'http://angular-examples.github.io/' + ex
: '/resources/live-examples/' + ex + '/ts/plnkr.html';
: '/resources/live-examples/' + ex + '/' + exLang + '/plnkr.html';
// Link to live example.
var template = a(text, { href: href, target: '_blank' });

View File

@ -23,7 +23,7 @@ angularIO.directive('ngioEx', ['$location', function ($location) {
compile: function (tElement, attrs) {
var examplePath = attrs.path || tElement.text();
if (NgIoUtil.isDartDoc($location) || attrs.lang === 'dart') {
if (NgIoUtil.isDoc($location, 'dart') || attrs.lang === 'dart') {
examplePath = NgIoUtil.adjustTsExamplePathForDart(examplePath);
}
var template = '<code>' + examplePath + '</code>';

View File

@ -4,9 +4,9 @@ var NgIoUtil = (function () {
function NgIoUtil() { }
NgIoUtil.isDartDoc = function ($location) {
NgIoUtil.isDoc = function ($location, lang) {
var loc = $location.absUrl();
return loc.includes('/docs/dart/');
return loc.includes('/docs/' + lang + '/');
};
// The following util functions are adapted from _utils-fn.jade.

View File

@ -10,10 +10,10 @@ var mkdirp = require('mkdirp');
var indexHtmlTranslator = require('./indexHtmlTranslator');
var regionExtractor = require('../doc-shredder/regionExtractor');
var COPYRIGHT, COPYRIGHT_JS_CSS, COPYRIGHT_HTML;
var README; // content of plunker.README.md for plunkers
var SYSTEMJS_CONFIG; // content of systemjs.config.js for plunkers that use systemjs
var TSCONFIG; // content of tsconfig.json for plunkers that use systemjs
module.exports = {
buildPlunkers: buildPlunkers
};
@ -30,7 +30,7 @@ function buildCopyrightStrings() {
}
function buildPlunkers(basePath, destPath, options) {
getSystemJsConfigPlunker(basePath);
getPlunkerFiles(basePath);
var errFn = options.errFn || function(e) { console.log(e); };
var plunkerPaths = path.join(basePath, '**/*plnkr.json');
var fileNames = globby.sync(plunkerPaths, { ignore: "**/node_modules/**"});
@ -59,7 +59,7 @@ function buildPlunkerFrom(configFileName, basePath, destPath) {
try {
var config = initConfigAndCollectFileNames(configFileName);
var postData = createPostData(config);
addSystemJsConfig(config, postData);
addPlunkerFiles(config, postData);
var html = createPlunkerHtml(postData);
fs.writeFileSync(outputFileName, html, 'utf-8');
if (altFileName) {
@ -81,10 +81,8 @@ function buildPlunkerFrom(configFileName, basePath, destPath) {
}
}
/**
* Add plunker versions of systemjs.config and tsconfig.json
*/
function addSystemJsConfig(config, postData){
function addPlunkerFiles(config, postData) {
addReadme(config, postData);
if (config.basePath.indexOf('/ts') > -1) {
// uses systemjs.config.js so add plunker version
postData['files[systemjs.config.js]'] = SYSTEMJS_CONFIG;
@ -92,8 +90,20 @@ function addSystemJsConfig(config, postData){
}
}
function getSystemJsConfigPlunker(basePath) {
function addReadme(config, postData) {
var existingFiles = config.fileNames.map(function(file) {
return file.substr(file.lastIndexOf('/') + 1);
});
if (existingFiles.indexOf('README.md') === -1) {
var plunkerReadme = README + config.description;
postData['files[README.md]'] = plunkerReadme;
}
}
function getPlunkerFiles(basePath) {
// Assume plunker version is sibling of node_modules version
README = fs.readFileSync(basePath + '/plunker.README.md', 'utf-8');
SYSTEMJS_CONFIG = fs.readFileSync(basePath + '/systemjs.config.plunker.js', 'utf-8');
SYSTEMJS_CONFIG += COPYRIGHT_JS_CSS;
TSCONFIG = fs.readFileSync(basePath + '/tsconfig.json', 'utf-8');