Merge remote-tracking branch 'origin/master'

# Conflicts:
#	gulpfile.js
#	public/docs/ts/latest/guide/router.jade
#	public/docs/ts/latest/guide/template-syntax.jade
#	public/docs/ts/latest/tutorial/toh-pt6.jade
#	public/resources/js/controllers/resources-controller.js
This commit is contained in:
Zhicheng Wang 2016-07-24 17:27:48 +08:00
commit 4400deeef8
101 changed files with 1741 additions and 423 deletions

View File

@ -11,23 +11,26 @@ env:
- DISPLAY=:99.0
- CHROME_BIN=chromium-browser
- LATEST_RELEASE=2.0.0-rc.4
- TASK_FLAGS="--dgeni-log=warn"
# - TASK_FLAGS=""
matrix:
- TASK=lint
- TASK="run-e2e-tests --fast" SCRIPT=examples-install.sh
- TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh
- TASK=build-compile SCRIPT=deploy-install.sh
- TASK=build-compile SCRIPT=deploy-install-preview.sh
- TASK=build-compile SCRIPT=deploy-install.sh WAIT="travis_wait 50"
- TASK=build-compile SCRIPT=deploy-install-preview.sh WAIT="travis_wait 50"
matrix:
fast_finish: true
allow_failures:
- env: "TASK=\"run-e2e-tests --fast\" SCRIPT=examples-install-preview.sh"
- env: "TASK=build-compile SCRIPT=deploy-install-preview.sh"
- env: TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh
- env: TASK=build-compile SCRIPT=deploy-install-preview.sh WAIT="travis_wait 50"
before_install:
- npm install -g gulp --no-optional
- source ./scripts/env-set.sh
- ./scripts/before-install.sh
install:
- npm install --no-optional
- if [[ $SCRIPT ]]; then ./scripts/$SCRIPT; fi
- if [[ -n "$SCRIPT" ]]; then echo "EXTRA INSTALL $SCRIPT"; ./scripts/$SCRIPT; fi
before_script:
- sh -e /etc/init.d/xvfb start
script:
- gulp $TASK
- $WAIT gulp $TASK $TASK_FLAGS

View File

@ -106,11 +106,29 @@ var _exampleProtractorBoilerplateFiles = [
var _exampleConfigFilename = 'example-config.json';
var lang, langs;
// Gulp flags:
//
// --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'.
//
var lang, langs, buildDartApiDocs = false;
function configLangs(langOption) {
lang = (langOption || 'all').toLowerCase();
if (lang === 'all') { lang = '(ts|js|dart)'; }
const fullSiteBuildTasks = ['build-compile', 'check-serve', 'check-deploy'];
const buildAllDocs = argv['_'] &&
fullSiteBuildTasks.some((task) => argv['_'].indexOf(task) >= 0);
const langDefault = buildAllDocs ? 'all' : '(ts|js)';
lang = (langOption || langDefault).toLowerCase();
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) {
buildDartApiDocs = true;
// For Dart, be proactive about checking for the repo
checkAngularProjectPath(ngPathFor('dart'));
}
}
configLangs(argv.lang);
@ -149,7 +167,6 @@ gulp.task('run-e2e-tests', runE2e);
* all means (ts|js|dart)
*/
function runE2e() {
if (!argv.lang) configLangs('ts|js'); // Exclude dart by default
var promise;
if (argv.fast) {
// fast; skip all setup
@ -545,9 +562,8 @@ gulp.task('build-docs', ['build-devguide-docs', 'build-api-docs', 'build-plunker
// Stop zipping examples Feb 28, 2016
//gulp.task('build-docs', ['build-devguide-docs', 'build-api-docs', 'build-plunkers', '_zip-examples']);
gulp.task('build-api-docs', ['build-js-api-docs', 'build-ts-api-docs', 'build-dart-cheatsheet']
// On TRAVIS? Skip building the Dart API docs for now.
.concat(process.env.TRAVIS ? [] : ['build-dart-api-docs']));
gulp.task('build-api-docs', ['build-js-api-docs', 'build-ts-api-docs']
.concat(buildDartApiDocs ? ['build-dart-api-docs', 'build-dart-cheatsheet'] : []));
gulp.task('build-devguide-docs', ['_shred-devguide-examples', '_shred-devguide-shared-jade'], function() {
return buildShredMaps(true);
@ -562,41 +578,37 @@ gulp.task('build-js-api-docs', ['_shred-api-examples'], function() {
});
gulp.task('build-dart-api-docs', ['_shred-api-examples', 'dartdoc'], function() {
// TODO(chalin): also build build-dart-cheatsheet
// return buildApiDocsForDart();
return buildApiDocsForDart();
});
gulp.task('build-plunkers', ['_copy-example-boilerplate'], function() {
return plunkerBuilder.buildPlunkers(EXAMPLES_PATH, LIVE_EXAMPLES_PATH, { errFn: gutil.log });
});
gulp.task('build-dart-cheatsheet', ['build-ts-api-docs'], function() {
// gutil.log('build-dart-cheatsheet - NOT IMPLEMENTED YET - copying TS cheatsheet data');
// const src = './public/docs/ts/latest/guide/cheatsheet.json';
// fs.copy(src, './public/docs/dart/latest/guide/cheatsheet.json', {clobber: true},
// (err) => { if(err) throw err });
gulp.task('build-dart-cheatsheet', [], function() {
return buildDartCheatsheet();
});
gulp.task('dartdoc', ['pub upgrade'], function() {
// const ngRepoPath = ngPathFor('dart');
// if (argv.fast && fs.existsSync(path.resolve(ngRepoPath, 'doc'))) {
// gutil.log('Skipping dartdoc: --fast flag enabled and "doc" dir exists');
// return true;
// }
// checkAngularProjectPath(ngRepoPath);
// const dartdoc = spawnExt('dartdoc', ['--output', 'doc/api', '--add-crossdart'], { cwd: ngRepoPath});
// return dartdoc.promise;
const ngRepoPath = ngPathFor('dart');
if (argv.fast && fs.existsSync(path.resolve(ngRepoPath, 'doc'))) {
gutil.log('Skipping dartdoc: --fast flag enabled and "doc" dir exists');
return true;
}
checkAngularProjectPath(ngRepoPath);
const dartdoc = spawnExt('dartdoc', ['--output', 'doc/api', '--add-crossdart'], { cwd: ngRepoPath});
return dartdoc.promise;
});
gulp.task('pub upgrade', [], function() {
// const ngRepoPath = ngPathFor('dart');
// if (argv.fast && fs.existsSync(path.resolve(ngRepoPath, 'packages'))) {
// gutil.log('Skipping pub upgrade: --fast flag enabled and "packages" dir exists');
// return true;
// }
// checkAngularProjectPath(ngRepoPath);
// const pubUpgrade = spawnExt('pub', ['upgrade'], { cwd: ngRepoPath});
// return pubUpgrade.promise;
const ngRepoPath = ngPathFor('dart');
if (argv.fast && fs.existsSync(path.resolve(ngRepoPath, 'packages'))) {
gutil.log('Skipping pub upgrade: --fast flag enabled and "packages" dir exists');
return true;
}
checkAngularProjectPath(ngRepoPath);
const pubUpgrade = spawnExt('pub', ['upgrade'], { cwd: ngRepoPath});
return pubUpgrade.promise;
});
gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){
@ -1034,9 +1046,7 @@ function watchAndSync(options, cb) {
execCommands(['npm run harp -- server .'], {}, cb);
var browserSync = require('browser-sync').create();
browserSync.init({
proxy: 'localhost:9000',
scrollRestoreTechnique: 'cookie'});
browserSync.init({proxy: 'localhost:9000'});
if (options.devGuide) {
devGuideExamplesWatch(_devguideShredOptions, browserSync.reload);
@ -1177,6 +1187,32 @@ function buildApiDocs(targetLanguage) {
}
function buildDartCheatsheet() {
'use strict';
const ALLOWED_LANGUAGES = ['ts', 'js', 'dart'];
const lang = 'dart';
const vers = 'latest';
checkAngularProjectPath(ngPathFor(lang));
try {
const pkg = new Package('dartApiDocs', [require(path.resolve(TOOLS_PATH, 'dart-api-builder'))]);
pkg.config(function(log, targetEnvironments, writeFilesProcessor) {
log.level = _dgeniLogLevel;
ALLOWED_LANGUAGES.forEach(function(target) { targetEnvironments.addAllowed(target); });
targetEnvironments.activate(lang);
const outputPath = path.join(lang, vers, 'can-be-any-name-read-comment-below');
// Note: cheatsheet data gets written to: outputPath + '/../guide';
writeFilesProcessor.outputFolder = outputPath;
});
var dgeni = new Dgeni([pkg]);
return dgeni.generate();
} catch(err) {
console.error(err);
console.error(err.stack);
throw err;
}
}
function buildApiDocsForDart() {
const apiDir = 'api';
const vers = 'latest';
@ -1192,7 +1228,7 @@ function buildApiDocsForDart() {
dabInfo.excludeLibRegExp = new RegExp(/^(?!angular2)|\.testing|_|codegen|^angular2$/);
try {
checkAngularProjectPath('dart');
checkAngularProjectPath(ngPathFor('dart'));
var destPath = dabInfo.ngIoDartApiDocPath;
var sourceDirs = fs.readdirSync(dabInfo.ngDartDocPath)
.filter((name) => !name.match(/^index/))
@ -1208,7 +1244,7 @@ function buildApiDocsForDart() {
dab.createApiDataAndJadeFiles(apiEntries);
}).catch((err) => {
console.log(err);
console.log(err);
});
} catch(err) {
@ -1393,9 +1429,8 @@ function ngPathFor(lang) {
return ANGULAR_PROJECT_PATH + (lang === 'dart' ? '-dart' : '');
}
function checkAngularProjectPath(lang) {
var ngPath = path.resolve(ngPathFor(lang || 'ts'));
if (!fs.existsSync(ngPath)) {
throw new Error('API related tasks require the angular2 repo to be at ' + ngPath);
}
function checkAngularProjectPath(_ngPath) {
var ngPath = path.resolve(_ngPath || ngPathFor('ts'));
if (fs.existsSync(ngPath)) return;
throw new Error('API related tasks require the angular2 repo to be at ' + ngPath);
}

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -73,6 +73,7 @@
"style-loader": "^0.13.1",
"ts-loader": "^0.8.2",
"ts-node": "^0.7.3",
"tslint": "^3.13.0",
"typescript": "^1.8.10",
"typings": "^1.0.4",
"webpack": "^1.13.0",

View File

@ -13,13 +13,5 @@ import 'package:angular2/angular2.dart';
*/
@Pipe(name: 'exponentialStrength')
class ExponentialStrengthPipe extends PipeTransform {
num transform(dynamic _value, [List<dynamic> args]) {
var exponent = args.isEmpty
? 1
: args.first is num
? args.first
: num.parse(args.first.toString(), (_) => 1);
var value = _value is num ? _value : num.parse(_value.toString(), (_) => 0);
return math.pow(value, exponent);
}
num transform(num value, num exponent) => math.pow(value, exponent);
}

View File

@ -11,7 +11,7 @@ class FetchJsonPipe extends PipeTransform {
dynamic _fetchedJson;
String _prevUrl;
dynamic transform(dynamic url, [List<dynamic> args]) {
dynamic transform(String url) {
if (url != _prevUrl) {
_prevUrl = url;
_fetchedJson = null;

View File

@ -20,7 +20,7 @@ New hero:
<h4>Heroes who fly (piped)</h4>
<div id="flyers">
<!-- #docregion template-flying-heroes -->
<div *ngFor="#hero of (heroes | flyingHeroes)">
<div *ngFor="let hero of (heroes | flyingHeroes)">
{{hero.name}}
</div>
<!-- #enddocregion template-flying-heroes -->
@ -30,7 +30,7 @@ New hero:
<div id="all">
<!-- #docregion template-1 -->
<!-- #docregion template-all-heroes -->
<div *ngFor="#hero of heroes">
<div *ngFor="let hero of heroes">
{{hero.name}}
</div>
<!-- #enddocregion template-all-heroes -->

View File

@ -6,7 +6,7 @@ import 'heroes.dart';
@Pipe(name: 'flyingHeroes')
class FlyingHeroesPipe extends PipeTransform {
// #docregion filter
List<Hero> transform(dynamic value, [List<dynamic> args]) =>
List<Hero> transform(List<Hero> value) =>
value.where((hero) => hero.canFly).toList();
// #enddocregion filter
}

View File

@ -9,7 +9,7 @@ import 'fetch_json_pipe.dart';
template: '''
<h4>Heroes from JSON File</h4>
<div *ngFor="#hero of ('heroes.json' | fetch) ">
<div *ngFor="let hero of ('heroes.json' | fetch) ">
{{hero['name']}}
</div>

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.15
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -12,8 +12,8 @@ import { ROUTER_DIRECTIVES } from '@angular/router';
template: `
<h1>Component Router</h1>
<nav>
<a [routerLink]="['/crisis-center']">Crisis Center</a>
<a [routerLink]="['/heroes']">Heroes</a>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>
`,

View File

@ -25,8 +25,8 @@ import { HeroService } from './heroes/hero.service';
template: `
<h1>Component Router</h1>
<nav>
<a [routerLink]="['/crisis-center']">Crisis Center</a>
<a [routerLink]="['/heroes']">Heroes</a>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>
`,

View File

@ -28,15 +28,20 @@ import { HeroService } from './heroes/hero.service';
*/
/* Crisis Center Detail link
// #docregion Dragon-anchor
<a [routerLink]="['/crisis-center/1']">Dragon Crisis</a>
<a [routerLink]="['/crisis-center', 1]">Dragon Crisis</a>
// #enddocregion Dragon-anchor
*/
/* Crisis Center link with optional query params
// #docregion cc-query-params
<a [routerLink]="['/crisis-center', { foo: 'foo' }]">Crisis Center</a>
// #enddocregion cc-query-params
*/
// #docregion template
template: `
<h1 class="title">Component Router</h1>
<nav>
<a [routerLink]="['/crisis-center']">Crisis Center</a>
<a [routerLink]="['/crisis-center/1']">Dragon Crisis</a>
<a [routerLink]="['/crisis-center/1', { foo: 'foo' }]">Dragon Crisis</a>
<a [routerLink]="['/crisis-center/2']">Shark Crisis</a>
</nav>
<router-outlet></router-outlet>

View File

@ -11,9 +11,10 @@ import { HeroService } from './heroes/hero.service';
template: `
<h1 class="title">Component Router</h1>
<nav>
<a [routerLink]="['/crisis-center']">Crisis Center</a>
<a [routerLink]="['/heroes']">Heroes</a>
<a [routerLink]="['/crisis-center/admin']">Crisis Admin</a>
<a routerLink="/crisis-center" routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
<a routerLink="/crisis-center/admin" routerLinkActive="active">Crisis Admin</a>
</nav>
<router-outlet></router-outlet>
`,

View File

@ -12,10 +12,11 @@ import { HeroService } from './heroes/hero.service';
template: `
<h1 class="title">Component Router</h1>
<nav>
<a [routerLink]="['/crisis-center']">Crisis Center</a>
<a [routerLink]="['/heroes']">Heroes</a>
<a [routerLink]="['/crisis-center/admin']">Crisis Admin</a>
<a [routerLink]="['/login']">Login</a>
<a routerLink="/crisis-center" routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
<a routerLink="/crisis-center/admin" routerLinkActive="active">Crisis Admin</a>
<a routerLink="/login" routerLinkActive="active">Login</a>
</nav>
<router-outlet></router-outlet>
`,

View File

@ -10,6 +10,7 @@ import { provideRouter, RouterConfig } from '@angular/router';
import { HeroListComponent } from './hero-list.component';
import { CrisisCenterComponent } from './crisis-center/crisis-center.component';
import { HeroDetailComponent } from './heroes/hero-detail.component';
import { PageNotFoundComponent } from './not-found.component';
// #enddocregion base-routes
// #docregion
@ -20,8 +21,9 @@ const routes: RouterConfig = [
{ path: 'heroes', component: HeroListComponent },
// #enddocregion route-defs
// #docregion hero-detail-route
{ path: 'hero/:id', component: HeroDetailComponent }
{ path: 'hero/:id', component: HeroDetailComponent },
// #enddocregion hero-detail-route
{ path: '**', component: PageNotFoundComponent }
];
export const appRouterProviders = [

View File

@ -7,7 +7,7 @@ import { heroesRoutes } from './heroes/heroes.routes';
import { loginRoutes,
authProviders } from './login.routes';
import { CanDeactivateGuard } from './interfaces';
import { CanDeactivateGuard } from './can-deactivate-guard.service';
export const routes: RouterConfig = [
...heroesRoutes,

View File

@ -1,6 +1,8 @@
// #docregion
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate() {
console.log('AuthGuard#canActivate called');

View File

@ -0,0 +1,22 @@
// #docregion
import { Injectable } from '@angular/core';
import { CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = state.url;
// Navigate to the login page
this.router.navigate(['/login']);
return false;
}
}

View File

@ -0,0 +1,32 @@
// #docregion
import { Injectable } from '@angular/core';
import { CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = state.url;
// Create a dummy session id
let sessionId = 123456789;
// Set our navigation extras object
// that contains our global query params and fragment
let navigationExtras = {
queryParams: { 'session_id': sessionId },
fragment: 'anchor'
};
// Navigate to the login page with extras
this.router.navigate(['/login'], navigationExtras);
return false;
}
}

View File

@ -1,15 +0,0 @@
// #docregion
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate() {
if (this.authService.isLoggedIn) { return true; }
this.router.navigate(['/login']);
return false;
}
}

View File

@ -10,6 +10,9 @@ import 'rxjs/add/operator/delay';
export class AuthService {
isLoggedIn: boolean = false;
// store the URL so we can redirect after logging in
redirectUrl: string;
login() {
return Observable.of(true).delay(1000).do(val => this.isLoggedIn = true);
}

View File

@ -1,4 +1,5 @@
// #docregion
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs/Observable';
@ -6,6 +7,7 @@ export interface CanComponentDeactivate {
canDeactivate: () => boolean | Observable<boolean>;
}
@Injectable()
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate): Observable<boolean> | boolean {
return component.canDeactivate ? component.canDeactivate() : true;

View File

@ -0,0 +1,11 @@
// #docregion
import { Component } from '@angular/core';
@Component({
template: `
<h3>CRISIS ADMINISTRATION</h3>
<p>Manage your crises here</p>
`,
directives: []
})
export class CrisisAdminComponent { }

View File

@ -1,13 +1,37 @@
// #docregion
import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
@Component({
template: `
<h3>CRISIS ADMINISTRATION</h3>
<p>Manage your crises here</p>
`,
directives: [ROUTER_DIRECTIVES]
})
export class CrisisAdminComponent { }
<p>Session ID: {{ sessionId | async }}</p>
<a id="anchor"></a>
<p>Token: {{ token | async }}</p>
`,
directives: []
})
export class CrisisAdminComponent implements OnInit {
sessionId: Observable<string>;
token: Observable<string>;
constructor(private router: Router) {}
ngOnInit() {
// Capture the session ID if available
this.sessionId = this.router
.routerState
.queryParams
.map(params => params['session_id'] || 'None');
// Capture the fragment if available
this.token = this.router
.routerState
.fragment
.map(fragment => fragment || 'None');
}
}

View File

@ -6,7 +6,7 @@ import { CrisisListComponent } from './crisis-list.component';
import { CrisisCenterComponent } from './crisis-center.component';
import { CrisisAdminComponent } from './crisis-admin.component';
import { CanDeactivateGuard } from '../interfaces';
import { CanDeactivateGuard } from '../can-deactivate-guard.service';
export const crisisCenterRoutes: RouterConfig = [
{

View File

@ -6,8 +6,8 @@ import { CrisisListComponent } from './crisis-list.component';
import { CrisisCenterComponent } from './crisis-center.component';
import { CrisisAdminComponent } from './crisis-admin.component';
import { CanDeactivateGuard } from '../interfaces';
import { AuthGuard } from '../auth.guard';
import { CanDeactivateGuard } from '../can-deactivate-guard.service';
import { AuthGuard } from '../auth-guard.service';
export const crisisCenterRoutes: RouterConfig = [
{

View File

@ -5,8 +5,8 @@ import { CrisisListComponent } from './crisis-list.component';
import { CrisisCenterComponent } from './crisis-center.component';
import { CrisisAdminComponent } from './crisis-admin.component';
import { CanDeactivateGuard } from '../interfaces';
import { AuthGuard } from '../auth.guard';
import { CanDeactivateGuard } from '../can-deactivate-guard.service';
import { AuthGuard } from '../auth-guard.service';
export const crisisCenterRoutes: RouterConfig = [
{

View File

@ -5,8 +5,8 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromPromise';
import { Crisis, CrisisService } from './crisis.service';
import { DialogService } from '../dialog.service';
import { Crisis, CrisisService } from './crisis.service';
import { DialogService } from '../dialog.service';

View File

@ -5,8 +5,8 @@ import { Router, ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromPromise';
import { Crisis, CrisisService } from './crisis.service';
import { DialogService } from '../dialog.service';
import { Crisis, CrisisService } from './crisis.service';
import { DialogService } from '../dialog.service';
@Component({
template: `

View File

@ -1,7 +1,7 @@
// #docplaster
// #docregion
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Crisis, CrisisService } from './crisis.service';

View File

@ -1,7 +1,7 @@
// #docplaster
// #docregion
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Crisis, CrisisService } from './crisis.service';

View File

@ -56,7 +56,7 @@ export class HeroDetailComponent implements OnInit, OnDestroy {
let heroId = this.hero ? this.hero.id : null;
// Pass along the hero id if available
// so that the HeroList component can select that hero.
this.router.navigate(['/heroes'], { queryParams: { id: heroId, foo: 'foo' } });
this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);
}
// #enddocregion gotoHeroes-navigate
}

View File

@ -1,64 +0,0 @@
// #docplaster
// #docregion
// TODO SOMEDAY: Feature Componetized like CrisisCenter
import { Component, OnInit, OnDestroy } from '@angular/core';
// #docregion import-router
import { Router } from '@angular/router';
// #enddocregion import-router
import { Hero, HeroService } from './hero.service';
@Component({
// #docregion template
template: `
<h2>HEROES</h2>
<ul class="items">
<li *ngFor="let hero of heroes"
[class.selected]="isSelected(hero)"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
`
// #enddocregion template
})
export class HeroListComponent implements OnInit, OnDestroy {
heroes: Hero[];
// #docregion ctor
private selectedId: number;
private sub: any;
constructor(
private service: HeroService,
private router: Router) {}
// #enddocregion ctor
ngOnInit() {
this.sub = this.router
.routerState
.queryParams
.subscribe(params => {
this.selectedId = +params['id'];
this.service.getHeroes()
.then(heroes => this.heroes = heroes);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
// #enddocregion ctor
// #docregion isSelected
isSelected(hero: Hero) { return hero.id === this.selectedId; }
// #enddocregion isSelected
// #docregion select
onSelect(hero: Hero) {
this.router.navigate(['/hero', hero.id]);
}
// #enddocregion select
}
// #enddocregion

View File

@ -3,7 +3,7 @@
// TODO SOMEDAY: Feature Componetized like CrisisCenter
import { Component, OnInit, OnDestroy } from '@angular/core';
// #docregion import-router
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
// #enddocregion import-router
import { Hero, HeroService } from './hero.service';
@ -31,13 +31,13 @@ export class HeroListComponent implements OnInit, OnDestroy {
constructor(
private service: HeroService,
private route: ActivatedRoute,
private router: Router) {}
// #enddocregion ctor
ngOnInit() {
this.sub = this.router
.routerState
.queryParams
this.sub = this.route
.params
.subscribe(params => {
this.selectedId = +params['id'];
this.service.getHeroes()

View File

@ -1,7 +1,7 @@
// #docregion
import { RouterConfig } from '@angular/router';
import { HeroListComponent } from './hero-list.component';
import { HeroDetailComponent } from './hero-detail.component';
import { RouterConfig } from '@angular/router';
import { HeroListComponent } from './hero-list.component';
import { HeroDetailComponent } from './hero-detail.component';
export const heroesRoutes: RouterConfig = [
{ path: 'heroes', component: HeroListComponent },

View File

@ -29,9 +29,12 @@ export class LoginComponent {
this.authService.login().subscribe(() => {
this.setMessage();
if (this.authService.isLoggedIn) {
// Todo: capture where the user was going and nav there.
// Meanwhile redirect the user to the crisis admin
this.router.navigate(['/crisis-center/admin']);
// Get the redirect URL from our auth service
// If no redirect has been set, use the default
let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/crisis-center/admin';
// Redirect the user
this.router.navigate([redirect]);
}
});
}

View File

@ -1,6 +1,6 @@
// #docregion
import { RouterConfig } from '@angular/router';
import { AuthGuard } from './auth.guard';
import { AuthGuard } from './auth-guard.service';
import { AuthService } from './auth.service';
import { LoginComponent } from './login.component';

View File

@ -0,0 +1,9 @@
// #docregion
import { Component } from '@angular/core';
@Component({
template: `
<h2>Page Not Found</h2>
`
})
export class PageNotFoundComponent {}

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
http: ^0.11.3+3

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -6,7 +6,7 @@
(function(global) {
var ngVer = '@2.0.0-rc.4'; // lock in the angular package version; do not let it float to current!
var routerVer = '@3.0.0-beta.1'; // lock router version
var routerVer = '@3.0.0-beta.2'; // lock router version
var formsVer = '@0.2.0'; // lock forms version
var routerDeprecatedVer = '@2.0.0-rc.2'; // temporarily until we update all the guides

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -1,27 +1,70 @@
/// <reference path='../_protractor/e2e.d.ts' />
'use strict';
type WPromise<T> = webdriver.promise.Promise<T>;
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
class Hero {
id: number;
name: string;
// Factory method
// Get hero id and name from the given detail element.
static async fromDetail(detail: protractor.ElementFinder): Promise<Hero> {
// Get hero id from the first <div>
let _id = await detail.all(by.css('div')).first().getText();
// Get name from the h2
let _name = await detail.element(by.css('h2')).getText();
return {
id: +_id.substr(_id.indexOf(' ') + 1),
name: _name.substr(0, _name.indexOf(' '))
};
}
}
const nameSuffix = 'X';
function addToHeroName(text: string): WPromise<void> {
let input = element(by.css('input'));
return sendKeys(input, text);
}
describe('Tutorial part 1', () => {
let expectedH1 = 'Tour of Heroes';
let expectedTitle = `Angular 2 ${expectedH1}`;
let hero = { id: 1, name: 'Windstorm' };
let expectedH2 = `${hero.name} details!`;
const expectedHero = { id: 1, name: 'Windstorm' };
beforeEach(() => {
return browser.get('');
});
beforeAll(() => browser.get(''));
it(`should have title '${expectedTitle}'`, () => {
it(`has title '${expectedTitle}'`, () => {
expect(browser.getTitle()).toEqual(expectedTitle);
});
it(`should have '${expectedH2}'`, () => {
let text = element(by.css('h2')).getText();
expect(text).toEqual(expectedH2);
it(`has h1 '${expectedH1}'`, () => {
let hText = element(by.css('h1')).getText();
expect(hText).toEqual(expectedH1, 'h1');
});
it(`should have input name '${hero.name}'`, () => {
let name = element(by.css('input')).getAttribute('value');
expect(name).toEqual(hero.name);
it(`shows initial hero details`, async () => {
let page = getPageElts();
let hero = await Hero.fromDetail(page.heroDetail);
expect(hero.id).toEqual(expectedHero.id);
expect(hero.name).toEqual(expectedHero.name);
});
it(`shows updated hero name`, async () => {
addToHeroName(nameSuffix);
let page = getPageElts();
let hero = await Hero.fromDetail(page.heroDetail);
let newName = expectedHero.name + nameSuffix;
expect(hero.id).toEqual(expectedHero.id);
expect(hero.name).toEqual(newName);
});
});
function getPageElts() {
return {
heroDetail: element(by.css('my-app'))
};
}

View File

@ -17,6 +17,7 @@
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-app>

View File

@ -1,5 +1,5 @@
// #docregion ng-for
<li *ngFor="#hero of heroes">
<li *ngFor="let hero of heroes">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
// #enddocregion ng-for
@ -7,14 +7,14 @@
// #docregion heroes-styled
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="#hero of heroes">
<li *ngFor="let hero of heroes">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
// #enddocregion heroes-styled
// #docregion selectedHero-click
<li *ngFor="#hero of heroes" (click)="onSelect(hero)">
<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
// #enddocregion selectedHero-click
@ -53,7 +53,7 @@ final List<Hero> heroes = mockHeroes;
// #enddocregion heroes-template-1
// #docregion heroes-ngfor-1
<li *ngFor="#hero of heroes">
<li *ngFor="let hero of heroes">
// #enddocregion heroes-ngfor-1
// #docregion class-selected-1
@ -61,7 +61,7 @@ final List<Hero> heroes = mockHeroes;
// #enddocregion class-selected-1
// #docregion class-selected-2
<li *ngFor="#hero of heroes"
<li *ngFor="let hero of heroes"
[class.selected]="hero == selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -0,0 +1,133 @@
/// <reference path='../_protractor/e2e.d.ts' />
'use strict';
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
const expectedH2 = 'My Heroes';
const targetHero = { id: 16, name: 'RubberMan' };
const nameSuffix = 'X';
type WPromise<T> = webdriver.promise.Promise<T>;
class Hero {
id: number;
name: string;
// Factory methods
// Get hero from s formatted as '<id> <name>'.
static fromString(s: string): Hero {
return {
id: +s.substr(0, s.indexOf(' ')),
name: s.substr(s.indexOf(' ') + 1),
};
}
// Get hero id and name from the given detail element.
static async fromDetail(detail: protractor.ElementFinder): Promise<Hero> {
// Get hero id from the first <div>
let _id = await detail.all(by.css('div')).first().getText();
// Get name from the h2
let _name = await detail.element(by.css('h2')).getText();
return {
id: +_id.substr(_id.indexOf(' ') + 1),
name: _name.substr(0, _name.indexOf(' '))
};
}
}
describe('Tutorial part 2', () => {
beforeAll(() => browser.get(''));
describe('Initial page', initialPageTests);
describe('Select hero', selectHeroTests);
describe('Update hero', updateHeroTests);
});
function initialPageTests() {
it(`has title '${expectedTitle}'`, () => {
expect(browser.getTitle()).toEqual(expectedTitle);
});
it(`has h1 '${expectedH1}'`, () => {
expectHeading(1, expectedH1);
});
it(`has h2 '${expectedH2}'`, () => {
expectHeading(2, expectedH2);
});
it('has the right number of heroes', () => {
let page = getPageElts();
expect(page.heroes.count()).toEqual(10);
});
it('has no selected hero and no hero details', function () {
let page = getPageElts();
expect(page.selected.isPresent()).toBeFalsy('selected hero');
expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail');
});
}
function selectHeroTests() {
it(`selects ${targetHero.name} from hero list`, function () {
let hero = element(by.cssContainingText('li span.badge', targetHero.id.toString()));
hero.click();
// Nothing specific to expect other than lack of exceptions.
});
it(`has selected ${targetHero.name}`, function () {
let page = getPageElts();
let expectedText = `${targetHero.id} ${targetHero.name}`;
expect(page.selected.getText()).toBe(expectedText);
});
it('shows selected hero details', async () => {
let page = getPageElts();
let hero = await Hero.fromDetail(page.heroDetail);
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(targetHero.name);
});
}
function updateHeroTests() {
it(`can update hero name`, () => {
addToHeroName(nameSuffix);
// Nothing specific to expect other than lack of exceptions.
});
it(`shows updated hero name in details`, async () => {
let page = getPageElts();
let hero = await Hero.fromDetail(page.heroDetail);
let newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName);
});
it(`shows updated hero name in list`, async () => {
let page = getPageElts();
let hero = Hero.fromString(await page.selected.getText());
let newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName);
});
}
function addToHeroName(text: string): WPromise<void> {
let input = element(by.css('input'));
return sendKeys(input, text);
}
function expectHeading(hLevel: number, expectedText: string): void {
let hTag = `h${hLevel}`;
let hText = element(by.css(hTag)).getText();
expect(hText).toEqual(expectedText, hTag);
};
function getPageElts() {
return {
heroes: element.all(by.css('my-app li')),
selected: element(by.css('my-app li.selected')),
heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div'))
};
}

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Tour of Heros</title>
<title>Angular 2 Tour of Heroes</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -0,0 +1,133 @@
/// <reference path='../_protractor/e2e.d.ts' />
'use strict';
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
const expectedH2 = 'My Heroes';
const targetHero = { id: 16, name: 'RubberMan' };
const nameSuffix = 'X';
type WPromise<T> = webdriver.promise.Promise<T>;
class Hero {
id: number;
name: string;
// Factory methods
// Get hero from s formatted as '<id> <name>'.
static fromString(s: string): Hero {
return {
id: +s.substr(0, s.indexOf(' ')),
name: s.substr(s.indexOf(' ') + 1),
};
}
// Get hero id and name from the given detail element.
static async fromDetail(detail: protractor.ElementFinder): Promise<Hero> {
// Get hero id from the first <div>
let _id = await detail.all(by.css('div')).first().getText();
// Get name from the h2
let _name = await detail.element(by.css('h2')).getText();
return {
id: +_id.substr(_id.indexOf(' ') + 1),
name: _name.substr(0, _name.indexOf(' '))
};
}
}
describe('Tutorial part 3', () => {
beforeAll(() => browser.get(''));
describe('Initial page', initialPageTests);
describe('Select hero', selectHeroTests);
describe('Update hero', updateHeroTests);
});
function initialPageTests() {
it(`has title '${expectedTitle}'`, () => {
expect(browser.getTitle()).toEqual(expectedTitle);
});
it(`has h1 '${expectedH1}'`, () => {
expectHeading(1, expectedH1);
});
it(`has h2 '${expectedH2}'`, () => {
expectHeading(2, expectedH2);
});
it('has the right number of heroes', () => {
let page = getPageElts();
expect(page.heroes.count()).toEqual(10);
});
it('has no selected hero and no hero details', function () {
let page = getPageElts();
expect(page.selected.isPresent()).toBeFalsy('selected hero');
expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail');
});
}
function selectHeroTests() {
it(`selects ${targetHero.name} from hero list`, function () {
let hero = element(by.cssContainingText('li span.badge', targetHero.id.toString()));
hero.click();
// Nothing specific to expect other than lack of exceptions.
});
it(`has selected ${targetHero.name}`, function () {
let page = getPageElts();
let expectedText = `${targetHero.id} ${targetHero.name}`;
expect(page.selected.getText()).toBe(expectedText);
});
it('shows selected hero details', async () => {
let page = getPageElts();
let hero = await Hero.fromDetail(page.heroDetail);
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(targetHero.name);
});
}
function updateHeroTests() {
it(`can update hero name`, () => {
addToHeroName(nameSuffix);
// Nothing specific to expect other than lack of exceptions.
});
it(`shows updated hero name in details`, async () => {
let page = getPageElts();
let hero = await Hero.fromDetail(page.heroDetail);
let newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName);
});
it(`shows updated hero name in list`, async () => {
let page = getPageElts();
let hero = Hero.fromString(await page.selected.getText());
let newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName);
});
}
function addToHeroName(text: string): WPromise<void> {
let input = element(by.css('input'));
return sendKeys(input, text);
}
function expectHeading(hLevel: number, expectedText: string): void {
let hTag = `h${hLevel}`;
let hText = element(by.css(hTag)).getText();
expect(hText).toEqual(expectedText, hTag);
};
function getPageElts() {
return {
heroes: element.all(by.css('my-app li')),
selected: element(by.css('my-app li.selected')),
heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div'))
};
}

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -1,6 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Tour of Heroes</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<script defer src="main.dart" type="application/dart"></script>
<script defer src="packages/browser/dart.js"></script>
</head>

View File

@ -0,0 +1,133 @@
/// <reference path='../_protractor/e2e.d.ts' />
'use strict';
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
const expectedH2 = 'My Heroes';
const targetHero = { id: 16, name: 'RubberMan' };
const nameSuffix = 'X';
type WPromise<T> = webdriver.promise.Promise<T>;
class Hero {
id: number;
name: string;
// Factory methods
// Get hero from s formatted as '<id> <name>'.
static fromString(s: string): Hero {
return {
id: +s.substr(0, s.indexOf(' ')),
name: s.substr(s.indexOf(' ') + 1),
};
}
// Get hero id and name from the given detail element.
static async fromDetail(detail: protractor.ElementFinder): Promise<Hero> {
// Get hero id from the first <div>
let _id = await detail.all(by.css('div')).first().getText();
// Get name from the h2
let _name = await detail.element(by.css('h2')).getText();
return {
id: +_id.substr(_id.indexOf(' ') + 1),
name: _name.substr(0, _name.indexOf(' '))
};
}
}
describe('Tutorial part 4', () => {
beforeAll(() => browser.get(''));
describe('Initial page', initialPageTests);
describe('Select hero', selectHeroTests);
describe('Update hero', updateHeroTests);
});
function initialPageTests() {
it(`has title '${expectedTitle}'`, () => {
expect(browser.getTitle()).toEqual(expectedTitle);
});
it(`has h1 '${expectedH1}'`, () => {
expectHeading(1, expectedH1);
});
it(`has h2 '${expectedH2}'`, () => {
expectHeading(2, expectedH2);
});
it('has the right number of heroes', () => {
let page = getPageElts();
expect(page.heroes.count()).toEqual(10);
});
it('has no selected hero and no hero details', function () {
let page = getPageElts();
expect(page.selected.isPresent()).toBeFalsy('selected hero');
expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail');
});
}
function selectHeroTests() {
it(`selects ${targetHero.name} from hero list`, function () {
let hero = element(by.cssContainingText('li span.badge', targetHero.id.toString()));
hero.click();
// Nothing specific to expect other than lack of exceptions.
});
it(`has selected ${targetHero.name}`, function () {
let page = getPageElts();
let expectedText = `${targetHero.id} ${targetHero.name}`;
expect(page.selected.getText()).toBe(expectedText);
});
it('shows selected hero details', async () => {
let page = getPageElts();
let hero = await Hero.fromDetail(page.heroDetail);
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(targetHero.name);
});
}
function updateHeroTests() {
it(`can update hero name`, () => {
addToHeroName(nameSuffix);
// Nothing specific to expect other than lack of exceptions.
});
it(`shows updated hero name in details`, async () => {
let page = getPageElts();
let hero = await Hero.fromDetail(page.heroDetail);
let newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName);
});
it(`shows updated hero name in list`, async () => {
let page = getPageElts();
let hero = Hero.fromString(await page.selected.getText());
let newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName);
});
}
function addToHeroName(text: string): WPromise<void> {
let input = element(by.css('input'));
return sendKeys(input, text);
}
function expectHeading(hLevel: number, expectedText: string): void {
let hTag = `h${hLevel}`;
let hText = element(by.css(hTag)).getText();
expect(hText).toEqual(expectedText, hTag);
};
function getPageElts() {
return {
heroes: element.all(by.css('my-app li')),
selected: element(by.css('my-app li.selected')),
heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div'))
};
}

View File

@ -34,9 +34,11 @@ export class AppComponent implements OnInit {
// #enddocregion heroes-prop
selectedHero: Hero;
/*
// #docregion new-service
heroService = new HeroService(); // don't do this
// #enddocregion new-service
*/
// #docregion ctor
constructor(private heroService: HeroService) { }
// #enddocregion ctor

View File

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

View File

@ -7,7 +7,7 @@ environment:
sdk: '>=1.13.0 <2.0.0'
# #docregion additions
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
# #enddocregion additions
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1

View File

@ -23,10 +23,31 @@ describe('TOH Http Chapter', function () {
addButton: element.all(by.buttonText('Add New Hero')).get(0),
heroDetail: element(by.css('my-app my-hero-detail'))
heroDetail: element(by.css('my-app my-hero-detail')),
searchBox: element(by.css('#search-box')),
searchResults: element.all(by.css('.search-result'))
};
}
it('should search for hero and navigate to details view', function() {
let page = getPageStruct();
return sendKeys(page.searchBox, 'Magneta').then(function () {
expect(page.searchResults.count()).toBe(1);
let hero = page.searchResults.get(0);
return hero.click();
})
.then(function() {
browser.waitForAngular();
let inputEle = page.heroDetail.element(by.css('input'));
return inputEle.getAttribute('value');
})
.then(function(value) {
expect(value).toBe('Magneta');
});
});
it('should be able to add a hero from the "Heroes" view', function(){
let page = getPageStruct();
let heroCount: webdriver.promise.Promise<number>;

View File

@ -4,6 +4,9 @@ import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES } from '@angular/router';
import { HeroService } from './hero.service';
// #docregion rxjs-extensions
import './rxjs-extensions';
// #enddocregion rxjs-extensions
@Component({
selector: 'my-app',

View File

@ -9,3 +9,5 @@
</div>
</div>
</div>
<hero-search></hero-search>

View File

@ -5,11 +5,13 @@ import { Router } from '@angular/router';
import { Hero } from './hero';
import { HeroService } from './hero.service';
import { HeroSearchComponent } from './hero-search.component';
@Component({
selector: 'my-dashboard',
templateUrl: 'app/dashboard.component.html',
styleUrls: ['app/dashboard.component.css']
styleUrls: ['app/dashboard.component.css'],
directives: [HeroSearchComponent]
})
export class DashboardComponent implements OnInit {

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,57 @@
// #docplaster
// #docregion
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';
@Component({
selector: 'hero-search',
templateUrl: 'app/hero-search.component.html',
providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
// #docregion search
heroes: Observable<Hero[]>;
// #enddocregion search
// #docregion searchSubject
searchSubject = new Subject<string>();
// #enddocregion searchSubject
constructor(
private heroSearchService: HeroSearchService,
private router: Router) {}
// #docregion searchSubject
// Push a search term into the observable stream.
search(term: string) { this.searchSubject.next(term); }
// #enddocregion searchSubject
// #docregion search
ngOnInit() {
this.heroes = this.searchSubject
.asObservable() // cast as Observable
.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
// return the http search observable
? this.heroSearchService.search(term)
// or the observable of empty heroes if no search term
: Observable.of<Hero[]>([]))
.catch(error => {
// Todo: real error handling
console.log(error);
return Observable.of<Hero[]>([]);
});
}
// #enddocregion search
gotoDetail(hero: Hero) {
let link = ['/detail', hero.id];
this.router.navigate(link);
}
}

View File

@ -0,0 +1,19 @@
// #docregion
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Hero } from './hero';
@Injectable()
export class HeroSearchService {
constructor(private http: Http) {}
// #docregion observable-search
search(term: string) {
return this.http
.get(`app/heroes/?name=${term}+`)
.map((r: Response) => r.json().data as Hero[]);
}
// #enddocregion observable-search
}

View File

@ -17,13 +17,13 @@ export class HeroService {
constructor(private http: Http) { }
getHeroes(): Promise<Hero[]> {
getHeroes() {
return this.http.get(this.heroesUrl)
// #docregion to-promise
.toPromise()
// #enddocregion to-promise
// #docregion to-data
.then(response => response.json().data)
.then(response => response.json().data as Hero[])
// #enddocregion to-data
// #docregion catch
.catch(this.handleError);

View File

@ -0,0 +1,13 @@
// #docregion
// Observable class extensions
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';

View File

@ -5,3 +5,20 @@ button.delete-button{
background-color: gray !important;
color:white;
}
.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

@ -5,7 +5,7 @@ version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.17
angular2: 2.0.0-beta.18
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
transformers:

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.17</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/angular/angular/blob/master/CHANGELOG.md' target='_blank'>Change Log</a> about recent enhancements, fixes, and breaking changes."
},
"quickstart": {

View File

@ -2,7 +2,7 @@
.banner.grid-fluid
.alert.is-important
:marked
**Known issue:** Some cheat sheet entries are currently inaccurate, reflecting TypeScript instead of Dart.
This cheat sheet is provisional and subject to change.
article(class="l-content-small grid-fluid docs-content")
.cheatsheet

View File

@ -31,7 +31,7 @@ block package-and-config-files
packages as dependencies, as well as the `angular2` transformer.
It can also specify other packages and transformers for the app to use,
such as [dart_to_js_script_rewriter](https://pub.dartlang.org/packages/dart_to_js_script_rewriter).
Angular 2 is still changing, so provide an exact version: **2.0.0-beta.17**.
Angular 2 is still changing, so provide an exact version: **2.0.0-beta.18**.
[pubspec]: https://www.dartlang.org/tools/pub/pubspec.html

View File

@ -89,6 +89,9 @@ block heroes-comp-add
block review
//- Not showing animated gif due to differences between TS and Dart implementations.
block observables-section
//- TBC
block filetree
.filetree
.file angular2_tour_of_heroes

View File

@ -91,6 +91,10 @@ include ../_util-fns
* 在[程序的控制下](#navigate)进行导航
* toggling css classes for the [active router link](#router-link-active)
* 利用[`router-link-active`指令]切换CSS类(#router-link-active)
* embedding critical information in the URL with [route parameters](#route-parameters)
* 用[路由参数](#route-parameters)把重要信息嵌入URL
@ -111,14 +115,22 @@ include ../_util-fns
* 用[CanActivate](#can-activate-guard)阻止进入某路由的导航
* [CanDeactivate](#can-deactivate-deactivate) to prevent navigation away from the current route
* [CanDeactivate](#can-deactivate-guard) to prevent navigation away from the current route
* 用[CanDeactivate](#can-deactivate-deactivate)阻止离开当前路由的导航
* 用[CanDeactivate](#can-deactivate-guard)阻止离开当前路由的导航
* passing optional information in [query parameters](#query-parameters)
* 用[查询参数](#query-parameters)传入可选信息
* persisting information across routes with [global query parameters](#global-query-parameters)
* 使用[全局查询参数](#global-query-parameters)在各路由之间持久化信息
* jumping to anchor elements using a [fragment](#fragment)
* 使用[fragment](#fragment)跳转到其它元素
* choosing the "HTML5" or "hash" [URL style](#browser-url-styles)
* 选择"HTML5"或"hash"[URL风格](#browser-url-styles)
@ -199,9 +211,9 @@ include ../_util-fns
需要先配置路由器,才会有路由信息。
首选方案是用带有“路由数组”的**`provideRouter`**工厂函数(`[provideRouter(routes)]`)来启动此应用。
In the following example, we configure our application with three route definitions.
In the following example, we configure our application with four route definitions.
在下面的例子中,我们用个路由定义配置了本应用的路由器。
在下面的例子中,我们用个路由定义配置了本应用的路由器。
+makeExample('router/ts/app/app.routes.1.ts','route-config','app/app.routes.ts')(format='.')
@ -226,6 +238,12 @@ include ../_util-fns
第三个路由中的`:id`是一个路由参数的令牌(Token)。比如`/hero/42`这个URL中“42”就是`id`参数的值。此URL对应的`HeroDetailComponent`组件将据此查找和展现`id`为42的英雄。在本章中稍后的部分我们将会学习关于路由参数的更多知识。
The `**` in the fourth route denotes a **wildcard** path for our route. The router will match this route
if the URL requested doesn't match any paths for routes defined in our configuration. This is useful for
displaying a 404 page or redirecting to another route.
第四个路由中的`**`代表该路由是一个**通配符**路径。如果当前URL无法匹配上我们配置过的任何一个路由中的路径路由器就会匹配上这一个。当需要显示404页面或者重定向到其它路由时该特性非常有用。
We pass the configuration array to the `provideRouter()` function which returns
(among other things) a configured *Router* [service provider](dependency-injection.html#!#injector-providers).
@ -272,13 +290,25 @@ code-example(format="", language="html").
现在我们已经有了配置好的一些路由还找到了渲染它们的地方但又该如何导航到它呢固然从浏览器的地址栏直接输入URL也能做到但是大多数情况下导航是某些用户操作的结果比如点击一个A标签。
We add a **`RouterLink`** directive to the anchor tag and bind it to a template expression that
We add a **`RouterLink`** directive to the anchor tag. Since
we know our link doesn't contain any dynamic information, we can use a one-time binding to our route *path*.
我们往A标签上添加了**`RouterLink`**指令。由于我们知道链接中不包含任何动态信息,因此我们使用一次性绑定的方式把它绑定到我们路由中的*path*值。
If our `RouterLink` needed to be more dynamic we could bind to a template expression that
returns an array of route link parameters (the **link parameters array**). The router ultimately resolves that array
into a URL and a component view.
我们把一个**`RouterLink`**指令添加到这个A标签上并把该指令绑定到一个能返回路由链接数组**链接参数数组**)的模板表达式上。
如果`RouterLink`需要动态信息,我们就可以把它绑定到一个能返回路由链接数组(**链接参数数组**)的模板表达式上。
路由器最终会把此数组解析成一个URL和一个组件视图。
We also add a **`RouterLinkActive`** directive to each anchor tag to add or remove CSS classes to the
element when the associated *RouterLink* becomes active. The directive can be added directly on the element
or on its parent element.
我们还往每个A标签上添加了一个**`RouterLinkActive`**指令,用于在相关的*RouterLink*被激活时为所在元素添加或移除CSS类。
该指令可以直接添加到该元素上,也可以添加到其父元素上。
We see such bindings in the following `AppComponent` template:
我们会在下面的`AppComponent`模板中看到类似这样的绑定:
@ -288,17 +318,44 @@ code-example(format="", language="html").
.l-sub-section
:marked
We're adding two anchor tags with `RouterLink` directives.
We bind each `RouterLink` to an array containing the path of a route.
We're adding two anchor tags with `RouterLink` and `RouterLinkActive` directives.
We bind each `RouterLink` to a string containing the path of a route.
'/crisis-center' and '/heroes' are the paths of the `Routes` we configured above.
我们用`RouterLink`指令添加了两个A标签。每个`RouterLink`都绑定到了一个包含路由路径的数组上。
'/crisis-center'和'/heroes'都是我们前面配置过的`Routes`中的路径。
We'll learn to write more complex link expressions &mdash; and why they are arrays &mdash;
We'll learn to write link expressions &mdash; and why they are arrays &mdash;
[later](#link-parameters-array) in the chapter.
在本章的[后面](#link-parameters-array),我们还将学到如何写更复杂的链接表达式,以及了解它们为什么是数组。
在本章的[后面](#link-parameters-array),我们还将学到如何写链接表达式,以及了解它们为什么是数组。
We define `active` as the CSS class we want toggled to each `RouterLink` when they become
the current route using the `RouterLinkActive ` directive. We could add multiple classes to
the `RouterLink` if we so desired.
利用`RouterLinkActive`指令,我们把`active`作为当路由被激活时为`RouterLink`切换的CSS类。
必要时,还可以为`RouterLink`添加多个类。
: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.
在导航时的每个生命周期成功完成时,路由器会构建出一个`ActivatedRoute`组成的树,它表示路由器的当前状态。
我们可以在应用中的任何地方用`Router`服务及其`routerState`属性来访问当前的`RouterState`值。
The router state provides us with methods to traverse up and down the route tree from any activated route
to get information we may need from parent, child and sibling routes. It also contains the URL *fragment*
and *query parameters* which are **global** to all routes. We'll use the `RouterState` to access
[Query Parameters](#query-parameters).
路由器状态为我们提供了从任意激活路由开始向上或向下遍历路由树的一种方式以获得关于父、子、兄弟路由的信息。它还包含了URL*片段Fragment*和
*查询参数*等所有路由都能访问的**全局**信息。以后我们还将使用`RouterState`来访问[查询参数](#query-parameters)。
:marked
### Let's summarize
@ -390,8 +447,35 @@ table
a route. Clicking an anchor tag with a <code>routerLink</code> directive
that is bound to a <i>Link Parameters Array</i> triggers a navigation.
p 该指令用来把一个可点击的HTML元素绑定到路由。点击带有绑定了<i>链接参数数组</i>的<code>routerLink</code>指令的A标签就会触发一次导航。
p.
该指令用来把一个可点击的HTML元素绑定到路由。
点击带有绑定到<i>字符串</i>或<i>链接参数数组</i>的<code>routerLink</code>指令的A标签就会触发一次导航。
tr
td
p <code>RouterLinkActive</code>
p <code>RouterLinkActive</code>(活动路由链接)
td
p.
The directive for adding/removing classes from an HTML element when an associated
routerLink contained on or inside the element becomes active/inactive.
p.
TODO: 翻译完。当HTML元素的相关routerLink。
tr
td
p <code>RouterState</code>
p <code>RouterState</code>(路由器状态)
td
p.
The current state of the router including a tree of the currently activated
activated routes in our application along with the URL query params, fragment
and convenience methods for traversing the route tree.
p.
路由器的当前状态包含了一棵由程序中激活的路由构成的树。它包含URL查询参数、片段和用于遍历路由树的快捷方法。
tr
td
p <code><i>Link Parameters Array</i></code>
@ -877,35 +961,42 @@ h3#router-link <i>RouterLink</i>绑定
:marked
Above the outlet, within the anchor tags, we see [Property Bindings](template-syntax.html#property-binding) to
the `RouterLink` directive that look like `[routerLink]="[...]"`. We imported `RouterLink` from the router library.
the `RouterLink` directive that look like `routerLink="..."`. We imported `RouterLink` from the router library.
在插座上方的A标签中有一个绑定`RouterLink`指令的[属性绑定](template-syntax.html#property-binding),就像这样:`[routerLink]="[...]"`。我们从路由库中导入了`RouterLink`。
The template expression to the right of the equals (=) returns a *link parameters array*.
等号(=)右侧的模板表达式返回一个*链接参数数组*。
A link parameters array holds the ingredients for router navigation:
链接参数数组中存放导航时所需的那些要素:
* the *path* of the route to the destination component
* 指向目标组件的路由中的*path*属性
* optional route and query parameters that go into the route URL
* 可选的路由参数和查询参数它们会被编入到该路由的URL中
The arrays in this example each have a single string parameter, the path of a route that
The links in this example each have a string path, the path of a route that
we configured earlier. We don't have route parameters yet.
例子中的每个链接都有一个字符串型的路径,也就是我们以前配置过的路由路径,但还没有指定路由参数。
这个例子中的数组都只有一个字符串参数,也就是我们以前配置过的路由中的`path`部分。目前还没有用到路由参数。
We can also add more contextual information to our `RouterLink` by providing query string parameters
or a URL fragment for jumping to different areas on our page. Query string parameters
are provided through the `[queryParams]` binding which takes an object (e.g. `{ name: 'value' }`), while the URL fragment
takes a single value bound to the `[fragment]` input binding.
我们还可以通过提供查询字符串参数为`RouterLink`提供更多情境信息或提供一个URL片段Fragment或hash来跳转到本页面中的其它区域。
查询字符串可以由`[queryParams]`绑定来提供,它需要一个对象型参数(如`{ name: 'value' }`而URL片段需要一个绑定到`[fragment]`的单一值。
.l-sub-section
:marked
Learn more about the link parameters array in the [appendix below](#link-parameters-array).
Learn about the how we can also use the **link parameters array** in the [appendix below](#link-parameters-array).
a#router-link-active
h3#router-link <i>RouterLinkActive</i> binding
:marked
On each anchor tag, we also see [Property Bindings](template-syntax.html#property-binding) to
the `RouterLinkActive` directive that look like `routerLinkActive="..."`.
The template expression to the right of the equals (=) contains our space-delimited string of CSS classes.
We can also bind to the `RouterLinkActive` directive using an array of classes
such as `[routerLinkActive]="['...']"`.
The `RouterLinkActive` directive toggles css classes for active `RouterLink`s based on the current `RouterState`.
This cascades down through each level in our route tree, so parent and child router links can be active at the same time.
To override this behavior, we can bind to the `[routerLinkActiveOptions]` input binding with the `{ exact: true }` expression.
By using `{ exact: true }`, a given `RouterLink` will only be active if its URL is an exact match to the current URL.
要学习关于链接参数数组的更多知识,参见[下面的附录](#link-parameters-array)。
@ -914,7 +1005,7 @@ h3#router-directives <i>ROUTER_DIRECTIVES</i>
h3#router-directives <i>ROUTER_DIRECTIVES</i>(路由指令集)
:marked
`RouterLink` and `RouterOutlet` are directives in the `ROUTER_DIRECTIVES` collection.
`RouterLink`, `RouterLinkActive` and `RouterOutlet` are directives in the `ROUTER_DIRECTIVES` collection.
Remember to add them to the `directives` array of the `@Component` metadata.
`RouterLink`和`RouterOutlet`是`ROUTER_DIRECTIVES`集合中的指令。
@ -947,9 +1038,9 @@ h3#router-directives <i>ROUTER_DIRECTIVES</i>(路由指令集)
* 加载路由库
* add a nav bar to the shell template with anchor tags and `routerLink` directives
* add a nav bar to the shell template with anchor tags, `routerLink` and `routerLinkActive` directives
* 往壳组件的模板中添加一个导航条导航条中有一些A标签和`routerLink`指令
* 往壳组件的模板中添加一个导航条导航条中有一些A标签、`routerLink`指令和`routerLinkActive`指令
* added a `router-outlet` to the shell template where views will be displayed
@ -1285,9 +1376,8 @@ h3#navigate 命令式地导航到英雄详情
+makeExample('router/ts/app/heroes/hero-list.component.1.ts','select')(format=".")
:marked
It calls the router's **`navigate`** method with a **Link Parameters Array**.
This array is similar to the *link parameters array* we met [earlier](#shell-template) in an anchor tag while
binding to the `RouterLink` directive. This time we see it in code rather than in HTML.
It calls the router's **`navigate`** method with a **Link Parameters Array**. We can use this same syntax
with a `RouterLink` if we want to use it in HTML rather than code.
它用一个**链接参数数组**调用路由器的**`navigate`**方法。
该数组与我们[以前](#shell-template)在A标签中用来绑定到`RouterLink`指令的链接参数数组很相似。只是这次它出现在代码而不是HTML中。
@ -1489,7 +1579,7 @@ h3#nav-to-list 导航回列表组件
`HeroDetailComponent`组件有一个“Back”按钮关联到它的`gotoHeroes`方法,该方法会导航回`HeroListComponent`组件。
The router `navigate` method takes the same one-item *link parameters array*
that we bound to the application shell's *Heroes* `[routerLink]` directive.
that we can bind to a `[routerLink]` directive.
It holds the **path to the `HeroListComponent`**:
路由的`navigate`方法同样接受一个单条目的*链接参数数组*,我们曾把它绑定到应用壳中“英雄”区的`[routerLink]`指令上。
@ -1799,15 +1889,15 @@ h3#child-routing-component 子路由组件
它有自己的`RouterOutlet`和自己的子路由。
We create a `crisis-center.routes.ts` file as we did the `heroes.routes.ts` file.
But this time we define **child routes** *within* the parent `/crisis-center` route.
But this time we define **child routes** *within* the parent `crisis-center` route.
像`heroes.routes.ts`文件一样,我们也创建一个`crisis-center.routes.ts`。
但这次,我们要把**子路由**定义在父路由`/crisis-center`中。
但这次,我们要把**子路由**定义在父路由`crisis-center`中。
+makeExample('router/ts/app/crisis-center/crisis-center.routes.1.ts', 'routes', 'app/crisis-center/crisis-center.routes.ts (Routes)' )(format='.')
:marked
Notice that the parent `/crisis-center` route has a `children` property
Notice that the parent `crisis-center` route has a `children` property
with an array of two routes.
These two routes navigate to the two *Crisis Center* child components,
`CrisisListComponent` and `CrisisDetailComponent`.
@ -2094,7 +2184,7 @@ h3#can-activate-guard <i>CanActivate</i>: 要求认证
我们准备扩展“危机中心”,添加一些新的*管理类*特性。
这些特性还没有定义过,所以我们先只添加一个占位组件:
+makeExample('router/ts/app/crisis-center/crisis-admin.component.ts', '', 'crisis-admin.component.ts')(format=".")
+makeExample('router/ts/app/crisis-center/crisis-admin.component.1.ts', '', 'crisis-admin.component.ts')(format=".")
:marked
Next, we add a child route to the `crisis-center.routes` with the path, `/admin`.
@ -2110,6 +2200,17 @@ h3#can-activate-guard <i>CanActivate</i>: 要求认证
+makeExample('router/ts/app/app.component.4.ts', 'template', 'app/app.component.ts (template)')(format=".")
.l-sub-section
:marked
Since our admin `RouterLink` is a child route of our `Crisis Center`, we only want the `Crisis Center`
link to be active when we visit that route. We've added an additional binding to our `/crisis-center` routerLink,
`[routerLinkActiveOptions]="{ exact: true }"` which will only mark the `/crisis-center` link as active when
we navigate the to `/crisis-center` URL and not when we navigate to one its child routes.
由于管理区的`RouterLink`也都是`Crisis Center`的子路由,所以我们会希望`Crisis Center`路由只在访问该路由时才被激活。
于是我们往`/crisis-center`这个routerLink下添加了额外的绑定值`[routerLinkActiveOptions]="{ exact: true }"`
这样只有当所访问的URL是`/crisis-center`时,`/crisis-center`链接才会被标记为激活状态,而导航到它的某个子路由时却不会。
:marked
#### Guard the admin feature
@ -2129,7 +2230,7 @@ h3#can-activate-guard <i>CanActivate</i>: 要求认证
我们换种方式:写一个`CanActivate`守卫,当匿名用户尝试访问管理组件时,把他/她重定向到登录页。
This is a general purpose guard &mdash; we can imagine other features that require authenticated users &mdash;
so we create an `auth.guard.ts` in the application root folder.
so we create an `auth-guard.service.ts` in the application root folder.
这是一种具有通用性的守护目标(通常会有其它特性需要登录用户才能访问),所以我们在应用的根目录下创建一个`auth.guard.ts`文件。
@ -2138,8 +2239,7 @@ h3#can-activate-guard <i>CanActivate</i>: 要求认证
此刻,我们的兴趣在于看看守卫是如何工作的,所以我们第一个版本没做什么有用的事情。它只是往控制台写日志,并且立即返回`true`,让导航继续:
+makeExample('router/ts/app/auth.guard.1.ts', '', 'app/auth.guard.ts')(format=".")
+makeExample('router/ts/app/auth-guard.service.1.ts', '', 'app/auth-guard.service.ts')(format=".")
:marked
Next we open `crisis-center.routes.ts `, import the `AuthGuard` class, and
update the admin route with a `CanActivate` guard property that references it:
@ -2173,6 +2273,7 @@ h3#can-activate-guard <i>CanActivate</i>: 要求认证
Although it doesn't actually log in, it has what we need for this discussion.
It has an `isLoggedIn` flag to tell us whether the user is authenticated.
Its `login` method simulates an API call to an external service by returning an observable that resolves successfully after a short pause.
The `redirectUrl` property will store our attempted URL so we can navigate to it after authenticating.
虽然它不会真的进行登录,但足够让我们进行这个讨论了。
它有一个`isLoggedIn`标志,用来标识是否用户已经登录过了。
@ -2182,7 +2283,7 @@ h3#can-activate-guard <i>CanActivate</i>: 要求认证
我们这就修改`AuthGuard`来调用它。
+makeExample('router/ts/app/auth.guard.ts', '', 'app/auth.guard.ts (v.2)')(format=".")
+makeExample('router/ts/app/auth-guard.service.2.ts', '', 'app/auth-guard.service.ts (v.2)')(format=".")
:marked
Notice that we *inject* the `AuthService` and the `Router` in the constructor.
@ -2195,22 +2296,30 @@ h3#can-activate-guard <i>CanActivate</i>: 要求认证
If the user is logged in, it returns true and the navigation continues.
该守卫返回一个同步的布尔值。如果用户已经登录,它就返回`true`,导航会继续。
The `ActivatedRouteSnapshot` contains the _future_ route that will be activated and the `RouterStateSnapshot`
contains the _future_ `RouterState` of our application, should we pass through our guard check.
这个`ActivatedRouteSnapshot`包含了_即将_被激活的路由而`RouterStateSnapshot`包含了该应用_即将_到达的状态。
它们要通过我们的守卫进行检查。
If the user is not logged in, we tell the router to navigate to a login page &mdash; a page we haven't created yet.
This secondary navigation automatically cancels the current navigation; we return `false` just to be clear about that.
如果用户还没有登录,我们就告诉路由器导航到登录页(尚未创建该页)。
这间接导致路由器自动中止了这次导航,我们返回`false`并不是必须的,但这样可以更清楚的表达这一点。
If the user is not logged in, we store the attempted URL the user came from using the `RouterStateSnapshot.url` and
tell the router to navigate to a login page &mdash; a page we haven't created yet.
This secondary navigation automatically cancels the current navigation; we return `false` just to be clear about that.
如果用户还没有登录,我们会用`RouterStateSnapshot.url`保存用户来自的URL并让路由器导航到登录页我们尚未创建该页
这间接导致路由器自动中止了这次导航,我们返回`false`并不是必须的,但这样可以更清楚的表达意图。
#### Add the *LoginComponent*
#### 添加*LoginComponent*
We need a `LoginComponent` for the user to log in to the app.
We need a `LoginComponent` for the user to log in to the app. After logging in, we'll redirect
to our stored URL if available, or use the default URL.
There is nothing new about this component or the way we wire it into the router configuration.
Here is the pertinent code, offered without comment:
我们需要一个`LoginComponent`来让用户登录进这个应用。
我们需要一个`LoginComponent`来让用户登录进这个应用。在登录之后我们跳转到前面保存的URL如果没有就跳转到默认URL。
该组件没有什么新内容,我们把它放进路由配置的方式也没什么新意。
这里是相关代码,不带注释:
@ -2345,7 +2454,7 @@ h3#can-deactivate-guard <i>CanDeactivate</i>:处理未保存的更改
我们创建了一个`Guard`,它将检查这个组件中`canDeactivate`函数的工作现场,在这里,它就是`CrisisDetailComponent`。我们并不需要知道`CrisisDetailComponent`确认退出激活状态的详情。这让我们的守卫可以被复用,这是一次轻而易举的胜利。
+makeExample('router/ts/app/interfaces.ts', '', 'interfaces.ts')
+makeExample('router/ts/app/can-deactivate-guard.service.ts', '', 'can-deactivate-guard.service.ts')
:marked
Looking at our `CrisisDetailComponent`, we have implemented our confirmation workflow for unsaved changes.
@ -2401,23 +2510,23 @@ h3#can-deactivate-guard <i>CanDeactivate</i>:处理未保存的更改
+makeTabs(
`router/ts/app/app.component.ts,
router/ts/app/auth.guard.ts,
router/ts/app/auth-guard.service.2.ts,
router/ts/app/can-deactivate-guard.service.ts,
router/ts/app/crisis-center/crisis-center.component.ts,
router/ts/app/crisis-center/crisis-center.routes.ts,
router/ts/app/crisis-center/crisis-list.component.1.ts,
router/ts/app/crisis-center/crisis-detail.component.1.ts,
router/ts/app/crisis-center/crisis.service.ts,
router/ts/app/interfaces.ts
router/ts/app/crisis-center/crisis.service.ts
`,
null,
`app.component.ts,
auth.guard.ts,
auth-guard.service.ts,
can-deactivate-guard.service.ts,
crisis-center.component.ts,
crisis-center.routes.ts,
crisis-list.component.ts,
crisis-detail.component.ts,
crisis.service.ts,
interfaces.ts
crisis.service.ts
`)
@ -2473,7 +2582,7 @@ figure.image-display
**URL查询字符串**是在导航时表达复杂信息的理想工具。查询字符串不参与模式匹配,还能让表达式有巨大的弹性 —— 几乎任何可以序列化的东西都能出现在查询字符串中。
The Component Router supports navigation with query strings as well as route parameters.
We define query string parameters in the *route parameters object* just like we do with route parameters.
We define _optional_ query string parameters in an *object* after we define our required route parameters.
像路由参数一样,组件路由器也支持使用查询字符串进行导航。同样,我们也在*路由参数对象*中定义查询字符串参数。
@ -2550,11 +2659,13 @@ figure.image-display
现在我们“终于”找到了一个理由。我们希望让导航请求中带上当前英雄的id以便让`HeroListComponent`组件可以在它的列表中把该英雄高亮显示。
We do that with a `NavigationExtras` object with `queryParams`.
We do that with an object that contains our optional `id` parameter.
We also defined a junk parameter (`foo`) that the `HeroListComponent` should ignore.
Here's the revised navigation statement:
我们通过一个带`queryParams`的`NavigationExtras`对象来做到这一点。
我们通过一个包含可选`id`参数的对象来做到这一点。
我们还定义了一个假参数(`foo``HeroListComponent`会忽略它。
下面是修改后的导航语句:
+makeExample('router/ts/app/heroes/hero-detail.component.ts','gotoHeroes-navigate')(format=".")
@ -2581,21 +2692,39 @@ figure.image-display
你看到的东西应该类似这样,具体取决于你在哪里运行它:
code-example(format="." language="bash").
localhost:3000/heroes?id=15&foo=foo
localhost:3000/heroes;id=15;foo=foo
:marked
The `id` value appears in the query string (`?id=15&foo=foo`), not in the URL path.
The `id` value appears in the query string (`;id=15;foo=foo`), not in the URL path.
The path for the "Heroes" route doesn't have an `:id` token.
`id`值出现在查询字符串中(`?id=15&foo=foo`而不是URL路径中。“英雄”路由的路径中并没有出现`:id`令牌。
.alert.is-helpful
:marked
The query string parameters are not separated by "?" and "&".
They are **separated by semicolons (;)**
This is *matrix URL* notation &mdash; something we may not have seen before.
这些查询字符串参数不再使用“?”和“&”进行分隔了。
它们被**用分号(;)分隔开了**,这叫做*矩阵URL*标记法 —— 我们以前从未见过。
.l-sub-section
:marked
The router replaces route path tokens with corresponding values from the route parameters object.
**Every parameter _not_ consumed by a route path goes in the query string.**
路由器把路由的路径替换成了路由参数对象中相应的值。**路由参数对象中每个没有被路由路径用到的参数都会出现在查询字符串中。**
*Matrix URL* notation is an idea first floated
in a [1996 proposal](http://www.w3.org/DesignIssues/MatrixURIs.html) by the founder of the web, Tim Berners-Lee.
*矩阵URL*标记法的概念首先出现在[1996年的提案](http://www.w3.org/DesignIssues/MatrixURIs.html)中提出者是Web的奠基人Tim Berners-Lee。
Although matrix notation never made it into the HTML standard, it is legal and
it became popular among browser routing systems as a way to isolate parameters
belonging to parent and child routes. The Angular Component Router is such a system.
虽然矩阵标记法从未成为HTML标准的一部分但它是合法的并成了各种“浏览器路由系统”中用来隔离父子路由参数时的一种理想方式。Angular的组件路由器就是其中之一。
The syntax may seem strange to us but users are unlikely to notice or care
as long as the URL can be emailed and pasted into a browser address bar
as this one can.
对我们来说这种语法看起来可能有点奇怪。但是只要这种URL能被邮件出去或传到浏览器地址栏用户不大可能注意到或关心这一点这种方法恰巧可以。
:marked
### Query parameters in the *ActivatedRoute* service
@ -2628,7 +2757,6 @@ code-example(format="." language="bash").
当从`HeroListComponent`导航到`HeroDetailComponent`时,我们讲过`ActivatedRoute`服务中的路由参数`Observable`,并让它可以在`HeroDetailComponent`中使用。我们把该服务注入到了`HeroDetailComponent`的构造函数中。
This time we'll be navigating in the opposite direction, from the `HeroDetailComponent` to the `HeroListComponent`.
This time we'll inject the `Router` service in the constructor of the `HeroListComponent`.
现在就要进行反向导航了 —— 从`HeroDetailComponent`到`HeroListComponent`。
这次我们把`Router`服务注入到`HeroListComponent`的构造函数中。
@ -2640,7 +2768,7 @@ code-example(format="." language="bash").
+makeExample('router/ts/app/heroes/hero-list.component.ts','import-router', 'hero-list.component.ts (import)')(format=".")
:marked
Then we use the `routerState` to access the globally available query parameters `Observable` so we can subscribe
Then we use the `ActivatedRoute` to access the `params` _Observable_ so we can subscribe
and extract the `id` parameter as the `selectedId`:
然后,使用`routerState`来访问全局可用的查询参数`Observable`,以便我们能订阅,并把`id`参数提取为`selectedId`属性:
@ -2681,83 +2809,57 @@ figure.image-display
:marked
The `foo` query string parameter is harmless and continues to be ignored.
`foo`查询字符串参数没带来任何麻烦,并且仍然被忽略了。
`foo`查询字符串参数没带来任何麻烦,并且仍旧被忽略了。
### Child Routers and Query Parameters
### 子路由与查询参数
We can define query parameters for child routers too.
我们还能为子路由定义查询参数。
The technique is precisely the same.
In fact, we made exactly the same changes to the *Crisis Center* feature.
Confirm the similarities in these *Hero* and *CrisisCenter* components,
arranged side-by-side for easy comparison:
这项技巧和“英雄”特性区用过的完全一样。
事实上,我们对*危机中心*特性区所做的修改也完全一样。
为了确认这些*英雄*和*危机中心*组件的相似之处,我们把它们进行逐项对比:
+makeTabs(
`router/ts/app/heroes/hero-list.component.ts,
router/ts/app/crisis-center/crisis-list.component.ts,
router/ts/app/heroes/hero-detail.component.ts,
router/ts/app/crisis-center/crisis-detail.component.ts
`,
null,
`hero-list.component.ts,
crisis-list.component.ts,
hero-detail.component.ts,
crisis-detail.component.ts
`)
<a id="global-query-parameters"></a>
<a id="fragment"></a>
:marked
When we navigate back from a `CrisisDetailComponent` that is showing the *Asteroid* crisis,
we see that crisis properly selected in the list like this:
当我们从`CrisisDetailComponent`中导航回来时,它正在显示的是*小行星*危机,我们也确实看到在列表中此项危机被正确的选中了,就像这样:
figure.image-display
img(src='/resources/images/devguide/router/selected-crisis.png' alt="Selected crisis" )
### Global Query parameters and Fragments
### 全局查询参数与片段
:marked
**Look at the browser address bar again**. It's *different*. It looks something like this:
In our [query parameters](#query-parameters) example, we only dealt with parameters specific to
our route, but what if we wanted optional parameters available to all routes? This is where our
query parameters come into play and serve a special purpose in our application.
TODO: 翻译
**再次看看浏览器的地址栏**。它*不一样了*,它看上去是这样的:
Traditional query string parameters (?name=value) **persist** across route navigations. This means we can pass these query params
around without having to specify them in each navigation method whether it be declaratively or imperatively.
code-example(format="." language="bash").
localhost:3000/crisis-center/;id=3;foo=foo
[Fragments](https://en.wikipedia.org/wiki/Fragment_identifier) refer to certain elements on the page
identified with an `id` attribute.
We'll update our `AuthGuard` to provide a `session_id` query that will remain after navigating to another route.
We'll also provide an arbitrary `anchor` fragment, which we would use to jump to a certain point on our page.
We'll add the extra navigation object to our `router.navigate` method that navigates us to our `/login` route.
+makeExample('router/ts/app/auth-guard.service.ts','', 'auth-guard.service.ts (v.3)')
:marked
The query string parameters are no longer separated by "?" and "&".
They are **separated by semicolons (;)**
This is *matrix URL* notation &mdash; something we may not have seen before.
Since we'll be navigating to our *Crisis Admin* route after logging in, we'll update it to handle our global
query parameters and fragment.
这些查询字符串参数不再使用“?”和“&”进行分隔了。
它们被**用分号(;)分隔开了**,这叫做*矩阵URL*标记法 —— 我们以前从未见过。
+makeExample('router/ts/app/crisis-center/crisis-admin.component.ts','', 'crisis-admin.component.ts (v.2)')
:marked
*Query Parameters* and *Fragments* are available through the `routerState` property in our `Router` service.
Just like our *route parameters*, global query parameters and fragments are provided as an `Observable`.
For our updated *Crisis Admin* component we'll feed the `Observable` directly into our template using the `AsyncPipe`, which
will handle _unsubscribing_ from the `Observable` for us when the component is destroyed.
.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
*Matrix URL* notation is an idea first floated
in a [1996 proposal](http://www.w3.org/DesignIssues/MatrixURIs.html) by the founder of the web, Tim Berners-Lee.
When running in plunker, pop out the preview window by clicking the blue 'X' button in the upper right corner.
*矩阵URL*标记法的概念首先出现在[1996年的提案](http://www.w3.org/DesignIssues/MatrixURIs.html)中提出者是Web的奠基人Tim Berners-Lee。
Although matrix notation never made it into the HTML standard, it is legal and
it became popular among browser routing systems as a way to isolate parameters
belonging to parent and child routes. The Angular Component Router is such a system.
虽然矩阵标记法从未成为HTML标准的一部分但它是合法的并成了各种“浏览器路由系统”中用来隔离父子路由参数时的一种理想方式。Angular的组件路由器就是其中之一。
The syntax may seem strange to us but users are unlikely to notice or care
as long as the URL can be emailed and pasted into a browser address bar
as this one can.
对我们来说这种语法看起来可能有点奇怪。但是只要这种URL能被邮件出去或传到浏览器地址栏用户不大可能注意到或关心这一点这种方法恰巧可以。
:marked
Following the steps in this process, we can click on the *Crisis Admin* button, that takes us to the *Login*
page with our provided `query params` and `fragment`. After we click the login button, we notice that
we have been redirected to the `Crisis Admin` page with our `query params` and `fragment` still intact. We can use
these persistent bits of information for things that need to be provided with every page interaction like
authentication tokens or session ids.
<a id="final-app"></a>
@ -2802,12 +2904,11 @@ code-example(format="." language="bash").
We've mentioned the *Link Parameters Array* several times. We've used it several times.
我们曾多次提到*链接参数数组*,并多次用过。
We've bound the `RouterLink` directive to such an array like this:
我们把`RouterLink`指令绑定到这样一个数组,就像这样:
A link parameters array holds the ingredients for router navigation:
* the *path* of the route to the destination component
* required route parameters and optional query parameters that go into the route URL
We can bind the `RouterLink` directive to such an array like this:
+makeExample('router/ts/app/app.component.3.ts', 'h-anchor')(format=".")
:marked
@ -2818,7 +2919,10 @@ code-example(format="." language="bash").
+makeExample('router/ts/app/heroes/hero-list.component.1.ts', 'nav-to-detail')(format=".")
:marked
These two examples cover our needs for an app with one level routing.
We can provide optional query parameters in an object like this:
+makeExample('router/ts/app/app.component.3.ts', 'cc-query-params')(format=".")
:marked
These three examples cover our needs for an app with one level routing.
The moment we add a child router, such as the *Crisis Center*, we create new link array possibilities.
这两个例子覆盖了我们在单级路由的应用中所需的一切。在添加一个像*危机中心*一样的子路由时,我们创建新链接数组组合。
@ -2877,8 +2981,9 @@ code-example(format="." language="bash").
* 详细的子路由需要一个`id`路由参数。
* We add `id` of the *Dragon Crisis* as the third item in the array (`1`)
* We add `id` of the *Dragon Crisis* as the second item in the array (`1`)
* 我们把*巨龙危机*的`id`添加为该数组中的第个条目(`1`)。
* 我们把*巨龙危机*的`id`添加为该数组中的第个条目(`1`)。
It looks like this!

View File

@ -71,8 +71,10 @@ p.
.l-main-section
:marked
## HTML
## HTML
HTML is the language of the Angular template. Our [QuickStart](../quickstart.html) application had a template that was pure HTML:
HTML is the language of the Angular template. Our [QuickStart](../quickstart.html) application has a template that is pure HTML:
HTML是Angular模板的“语言”。我们的[“快速起步”](../quickstart.html)应用就有一个模板是纯HTML的
@ -88,9 +90,9 @@ code-example(language="html" escape="html").
有些合法的HTML被用在一个模板中是没有意义的。`<html>`、`<body>`和`<base>`元素在我们的舞台上中并没有扮演有用的角色。基本上所有其它的元素都被一样使用。
We can extend the HTML vocabulary of our templates with components and directives that appear as new elements and attributes. And we are about to learn how to get and set DOM values dynamically through data binding.
We can extend the HTML vocabulary of our templates with components and directives that appear as new elements and attributes. In the following sections we are going to learn how to get and set DOM (Document Object Model) values dynamically through data binding.
我们可以通过组件和指令来扩展模板中的HTML词汇。它们看上去就是新元素和属性。我们将学习如何通过数据绑定来动态获取/设置DOM的值。
我们可以通过组件和指令来扩展模板中的HTML词汇。它们看上去就是新元素和属性。接下来我们将学习如何通过数据绑定来动态获取/设置DOM(文档对象模型)的值。
Lets turn to the first form of data binding &mdash; interpolation &mdash; to see how much richer template HTML can be.
@ -112,8 +114,8 @@ code-example(language="html" escape="html").
+makeExample('template-syntax/ts/app/app.component.html', 'title+image')(format=".")
:marked
The material between the braces is often the name of a component property. Angular replaces that name with the
string value of the corresponding component property. In this example, Angular evaluates the `title` and `heroImageUrl` properties
and "fills in the blanks", displaying first a bold application title and then a heroic image.
string value of the corresponding component property. In the example above, Angular evaluates the `title` and `heroImageUrl` properties
and "fills in the blanks", first displaying a bold application title and then a heroic image.
在括号之间的“素材”通常是组件属性的名字。Angular会用组件中同名属性的字符串值替换这个名字。
在这个例子中Angular计算`title`和`heroImageUrl`属性的值,并把它们填在空白处。首先显示一个粗体的应用标题,然后显示英雄的图片。
@ -129,15 +131,15 @@ code-example(language="html" escape="html").
这个表达式可以调用所属组件的方法,就像下面用的`getVal()`
+makeExample('template-syntax/ts/app/app.component.html', 'sum-2')(format=".")
:marked
Angular evaluates all expressions in double curly braces, converts the expression results to strings, and concatenates them with neighboring literal strings. Finally,
Angular evaluates all expressions in double curly braces, converts the expression results to strings, and links them with neighboring literal strings. Finally,
it assigns this composite interpolated result to an **element or directive property**.
Angular对所有双花括号中的表达式求值把求值的结果转换成字符串并把它们跟相邻的字符串字面量连接起来。最后它把这个组合出来的插值结果赋给一个**元素或指令的属性**。
We appear to be inserting the result between element tags and assigning to attributes.
We appear to be inserting the result between element tags and assigning it to attributes.
It's convenient to think so, and we rarely suffer for this mistake.
But it is not literally true. Interpolation is a special syntax that Angular converts into a
[property binding](#property-binding), as we'll explain below.
Though this is not exactly true. Interpolation is a special syntax that Angular converts into a
[property binding](#property-binding), and is explained below.
表面上看,我们在元素标签之间插入了结果和对标签的属性进行了赋值。
这样思考起来很方便,并且这个错误很少给我们带来麻烦。
@ -1017,17 +1019,22 @@ figure.image-display
.l-main-section
:marked
<a id="other-bindings"></a>
## Attribute, class, and style bindings
## Attribute、class和style绑定
## Attribute, Class, and Style Bindings
## Attribute、Class和Style绑定
The template syntax provides specialized one-way bindings for scenarios less well suited to property binding.
模板语法为那些不太适合使用属性绑定的场景提供了专门的单向数据绑定形式。
### Attribute binding
### Attribute Binding
### Attribute绑定
We can set the value of an attribute directly with an **attribute binding**.
我们可以通过**Attribute绑定**来直接设置Attribute的值。
.l-sub-section
:marked
This is the only exception to the rule that a binding sets a target property. This is the only binding that creates and sets an attribute.
@ -1109,7 +1116,8 @@ code-example(format="nocode").
Attribute绑定的主要用例之一是设置ARIA Attribute(译注ARIA指可访问性用于给残障人士访问互联网提供便利),就像这个例子中一样:
+makeExample('template-syntax/ts/app/app.component.html', 'attrib-binding-aria')(format=".")
:marked
### Class binding
### Class Binding
### CSS类绑定
We can add and remove CSS class names from an elements `class` attribute with
@ -1155,7 +1163,8 @@ block dart-class-binding-bug
虽然这是一个切换单一类名的好办法,但我们通常更喜欢使用[NgClass指令](#ngClass)来同时管理多个类名。
:marked
### Style binding
### Style Binding
### 样式绑定
We can set inline styles with a **style binding**.
@ -1197,8 +1206,10 @@ block style-property-name-dart-diff
.l-main-section
:marked
## Event binding
## Event Binding
## 事件绑定
The bindings weve met so far flow data in one direction: *from the component to an element*.
我们前面遇到过的那些绑定的数据流都是单向的:*从组件到元素* 。
@ -1224,14 +1235,19 @@ block style-property-name-dart-diff
事件绑定语法由等号左侧带圆括号的**目标事件**,和右侧一个引号中的[模板语句](#template-statements)组成。
下列事件绑定监听按钮的点击事件。无论什么时候,发生点击时,都会调用组件的`onSave()`方法。
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".")
:marked
### Target event
### Target Event
### 目标事件
A **name between enclosing parentheses** &mdash; for example, `(click)` &mdash;
identifies the target event. In the following example, the target is the buttons click event.
**圆括号中的名称** —— 比如`(click)` —— 标记出了目标事件。在下面这个例子中目标是按钮的click事件。
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".")
:marked
Some people prefer the `on-` prefix alternative, known as the *canonical form*:
@ -1306,7 +1322,9 @@ block style-property-name-dart-diff
<a id="eventemitter"></a>
<a id="custom-event"></a>
### Custom events with EventEmitter
### Custom Events with EventEmitter
### 使用EventEmitter实现自定义事件
Directives typically raise custom events with an Angular [EventEmitter](../api/core/index/EventEmitter-class.html).

View File

@ -1264,7 +1264,7 @@ figure.image-display
***routerLinkActive*指令**
The Angular Router provides a `routerLinkActive` directive we can use to
to add a class to the HTML navigation element whose route matches the active route.
add a class to the HTML navigation element whose route matches the active route.
All we have to do is define the style for it. Sweet!
Angular路由器提供了`routerLinkActive`指令我们可以用它来为匹配了活动路由的HTML导航元素自动添加一个CSS类。

View File

@ -208,14 +208,15 @@ block get-heroes-details
:marked
The Angular `http.get` returns an RxJS `Observable`.
*Observables* are a powerful way to manage asynchronous data flows.
We'll learn about `Observables` *later*.
We'll learn about [Observables](#observables) later in this chapter.
Angular的`http.get`返回一个RxJS的`Observable`对象。
*Observable(可观察对象)*是一个管理异步数据流的强力方式。
后面我们还会进一步学习`Observable`。
后面我们还会进一步学习[可观察对象](#observables)。
For *now* we get back on familiar ground by immediately by
converting that `Observable` to a `Promise` using the `toPromise` operator.
For *now* we get back on familiar ground by immediately converting that `Observable` to a `Promise` using the `toPromise` operator.
*现在*,我们先利用`toPromise`操作符把`Observable`直接转换成`Promise`对象,回到已经熟悉的地盘。
+makeExample('toh-6/ts/app/hero.service.ts', 'to-promise')(format=".")
@ -600,6 +601,148 @@ block review
figure.image-display
img(src='/resources/images/devguide/toh/toh-http.anim.gif' alt="Heroes List Editting w/ HTTP")
block observables-section
:marked
## Observables
Each `Http` method returns an `Observable` of HTTP `Response` objects.
Our `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
In this section we learn to return the `Observable` directly and discuss when and why that might be
a good thing to do.
### Background
An *observable* is a stream of events that we can process with array-like operators.
Angular core has basic support for observables. We developers augment that support with
operators and extensions from the [RxJS Observables](http://reactivex.io/rxjs/) library.
We'll see how shortly.
Recall that our `HeroService` quickly chained the `toPromise` operator to the `Observable` result of `http.get`.
That operator converted the `Observable` into a `Promise` and we passed that promise back to the caller.
Converting to a promise is often a good choice. We typically ask `http` to fetch a single chunk of data.
When we receive the data, we're done.
A single result in the form of a promise is easy for the calling component to consume
and it helps that promises are widely understood by JavaScript programmers.
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.
We start by creating `HeroSearchService` that sends search queries to our server's 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.
### HeroSearchComponent
Let's create a new `HeroSearchComponent` that calls this new `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.
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
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`.
Time to create the `HeroSearchComponent` class and metadata.
+makeExample('toh-6/ts/app/hero-search.component.ts', null,'hero-search.component.ts')
:marked
Focus on the `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.
Each call to `search` puts a new string into this subject's _observable_ stream by calling `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.
+makeExample('toh-6/ts/app/hero-search.component.ts', 'search')(format=".")
: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.
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:
* The `asObservable` operator casts the `Subject` as an `Observable` of filter strings.
* `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.
* `distinctUntilChanged` ensures that we only send a request if the filter text changed.
There's no point in repeating a request for the same search term.
* `switchMap` calls our search service for each search term that makes it through the `debounce` and `distinctUntilChanged` gauntlet.
It cancels and discards previous search observables, returning only the latest search service observable.
.l-sub-section
:marked
The [switchMap operator](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)
(formerly known as "flatMapLatest") is very clever.
Every qualifying key event can trigger an http call.
Even with a 300ms pause between requests, we could have multiple http requests in flight
and they may not return in the order sent.
`switchMap` preserves the original request order while returning
only the observable from the most recent http call.
Results from prior calls are canceled and discarded.
We also short-circuit the http call and return an observable containing an empty array
if the search text is empty.
Note that _canceling_ the `HeroSearchService` observable won't actually abort a pending http request
until the service supports that feature, a topic for another day.
We are content for now to discard unwanted results.
:marked
* `catch` intercepts a failed observable.
Our simple example prints the error to the console; a real life application should do better.
Then we return an observable containing an empty array to clear the search result.
### Import RxJS operators
The RxJS operators are not available in Angular's base `Observable` implementation.
We have to extend `Observable` by *importing* them.
We could extend `Observable` with just the operators we need here by
including the pertinent `import` statements at the top of this file.
.l-sub-section
:marked
Many authorities say we should do just that.
:marked
We take a different approach in this example.
We combine all of the RxJS `Observable` extensions that _our entire app_ requires into a single RxJS imports file.
+makeExample('toh-6/ts/app/rxjs-extensions.ts', null, 'app/rxjs-extensions.ts')(format=".")
:marked
We load them all at once by importing `rxjs-extensions` in `AppComponent`.
+makeExample('toh-6/ts/app/app.component.ts', 'rxjs-extensions', 'app/app/app.component.ts')(format=".")
:marked
Finally, we add the `HeroSearchComponent` to the bottom of the `DashboardComponent`.
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.
figure.image-display
img(src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component")
.l-main-section
:marked
## Application structure and code
@ -628,6 +771,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 rxjs-operators.ts
.file hero.service.ts
.file heroes.component.css
.file heroes.component.html
@ -646,37 +793,42 @@ block filetree
.l-main-section
:marked
## Home Stretch
## 最后的坦途
## Home Stretch
## 最后的坦途
We are at the end of our journey for now, but we have accomplished a lot.
We are at the end of our journey for now, but we have accomplished a lot.
旅程即将结束,不过我们已经收获颇丰。
旅程即将结束,不过我们已经收获颇丰。
- 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 an API.
- 我们重构了HeroService以通过api来加载英雄数据。
- 我们重构了HeroService以通过api来加载英雄数据。
- We extended HeroService to support post, put and delete calls.
- We extended HeroService to support post, put and delete calls.
- 我们扩展了HeroService来支持post、put和delete调用。
- 我们扩展了HeroService来支持post、put和delete调用。
- We updated our components to allow adding, editing and deleting of heroes.
- We updated our components to allow adding, editing and deleting of heroes.
- 我们更新了组件,以允许用户添加、编辑和删除英雄。
- 我们更新了组件,以允许用户添加、编辑和删除英雄。
- We configured an in-memory web API.
- We configured an in-memory web API.
- 我们配置了一个内存Web API。
- 我们配置了一个内存Web API。
<li if-docs="ts"> We learned how to use Observables.</li>
<li if-docs="ts">我们学会了如何使用可观察对象。</li>
Below is a summary of the files we changed and added.
Below is a summary of the files we changed and added.
下面是我们添加之后的文件汇总。
下面是我们添加之后的文件汇总。
block file-summary
+makeTabs(
@ -698,3 +850,15 @@ block file-summary
in-memory-data.service.ts,
sample.css`
)
+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/rxjs-operators.ts`,
null,
`hero-search.service.ts,
hero-search.component.ts,
hero-search.service.html,
rxjs-operators.ts`
)

View File

@ -1,6 +1,6 @@
.home-rows {
// NOTE (ericjim): if a banner is placed on the homescreen, add this margin.
//margin-top: 112px;
margin-top: 112px;
}
.home-row {

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -13,7 +13,7 @@
<h3 class="text-uppercase subcategory-title">{{subCategory}}</h3>
<div ng-repeat="(section, sectionObj) in subcategoryObj">
<div ng-repeat="resource in sectionObj">
<div ng-repeat="resource in sectionObj | orderObjectByOfTypeString:'title':false">
<div class="c-resource" ng-if="resource.rev">
<a class="l-flex--column resource-row-link" target="_blank" href="{{resource.url}}">
<div>

17
pubspec.yaml Normal file
View File

@ -0,0 +1,17 @@
name: angular2_io
description: Angular 2 for Dart Website
version: 0.0.1
environment:
sdk: '>=1.13.0 <2.0.0'
dependencies:
angular2: 2.0.0-beta.18
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
- dart_to_js_script_rewriter

14
scripts/before-install.sh Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -e -o pipefail
./scripts/env-info-and-check.sh
if [[ 0 ]]; then
# Doesn't seem to be necessary. Disabling.
travis_fold start install.globals
set -x
npm install -g gulp --no-optional
set +x
travis_fold end install.globals
fi

View File

@ -1,5 +1,27 @@
#!/usr/bin/env bash
set -ex -o pipefail
set -e -o pipefail
(cd ../ && git clone https://github.com/angular/angular.git --branch $LATEST_RELEASE)
[[ -z "$NGIO_ENV_DEFS" ]] && . ./scripts/env-set.sh
if [[ -e "$NG2_REPO" ]]; then
echo Angular2 repo is already present at: $NG2_REPO
else
travis_fold start install.ng2
echo GETTING Angular2 from GitHub ...
set -x
git clone https://github.com/angular/angular.git --branch $LATEST_RELEASE $NG2_REPO
set +x
travis_fold end install.ng2
fi
if [[ -e "$NG2DART_REPO" ]]; then
echo Angular2 Dart repo is already present at: $NG2DART_REPO
elif [[ -n "$TRAVIS" ]]; then
./scripts/install-ng2dart.sh
# else
# echo WARNING: no Angular2 Dart repo found at: $NG2DART_REPO
fi
echo INSTALLED repos:
ls -ld ../a*

47
scripts/env-info-and-check.sh Executable file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -e -o pipefail
travis_fold start env_info
echo ENVIRONMENT INFO
travis_fold start env_info.path
echo Path:
echo $PATH | tr : '\n'
echo
travis_fold end env_info.path
travis_fold start env_info.home
echo Home: $HOME
ls ~ -la
echo
travis_fold end env_info.home
travis_fold start env_info.pwd
echo Pwd: `pwd`
ls -la
echo
travis_fold end env_info.pwd
if [[ 0 ]]; then
# Not needed anymore, but keeping it at least for the first commit for archival purposes.
travis_fold start env_info.bash_profile
echo Bash profile ------------------------------------------------------------
cat ~/.bash_profile
travis_fold end env_info.bash_profile
travis_fold start env_info.bashrc
echo Bashrc ------------------------------------------------------------------
cat ~/.bashrc
echo -------------------------------------------------------------------------
travis_fold end env_info.bashrc
travis_fold start env_info.build
echo build.sh ----------------------------------------------------------------
cat ~/build.sh
echo -------------------------------------------------------------------------
travis_fold end env_info.build
fi
travis_fold end env_info
echo ENVIRONMENT CONFIG CHECK:
if [[ -z "$NGIO_ENV_DEFS" ]]; then
echo Environment variables are not being set. Aborting.
exit 1;
else
echo Environment variables successfully set.
fi

39
scripts/env-set.sh Normal file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
if [[ -z "$NGIO_ENV_DEFS" ]]; then
export ANSI_YELLOW="\033[33;1m"
export ANSI_RESET="\033[0m"
echo -e "${ANSI_YELLOW}Setting environment variables from scripts/env.sh${ANSI_RESET}"
export NGIO_ENV_DEFS=1
export NG2_REPO=../angular
export NG2DART_REPO=$NG2_REPO-dart
if [ ! $(type -t travis_fold) ]; then
# In case this is being run locally. Turn travis_fold into a noop.
travis_fold () { return; }
# Alternative definition:
# travis_fold () { echo -en "travis_fold:${1}:${2}"; }
fi
export -f travis_fold
case "$(uname -a)" in
Darwin\ *) _OS_NAME=macos ;;
Linux\ *) _OS_NAME=linux ;;
*) _OS_NAME=linux ;;
esac
export _OS_NAME
: ${TMP:=$HOME/tmp}
: ${PKG:=$TMP/pkg}
export TMP
export PKG
if [[ -z "$(type -t dart)" && ! $PATH =~ */dart-sdk/* ]]; then
export DART_SDK="$PKG/dart-sdk"
# echo Updating PATH to include access to Dart bin.
export PATH="$DART_SDK/bin:$PATH"
export PATH="$HOME/.pub-cache/bin:$PATH"
fi
fi

67
scripts/install-dart-sdk.sh Executable file
View File

@ -0,0 +1,67 @@
#!/usr/bin/env bash
set -e -o pipefail
[[ -z "$NGIO_ENV_DEFS" ]] && . ./scripts/env-set.sh
if [[ -z "$(type -t dart)" ]]; then
travis_fold start install.dart
echo INSTALLING Dart SDK and Dartium ...
# URLs for sdk and dartium:
# https://storage.googleapis.com/dart-archive/channels/stable/release/latest/sdk/dartsdk-linux-x64-release.zip
# https://storage.googleapis.com/dart-archive/channels/stable/release/latest/dartium/dartium-macos-x64-release.zip
DART_ARCHIVE=https://storage.googleapis.com/dart-archive/channels
VERS=stable/release/latest
mkUrl() {
local dir=$1
local pkg=$2
local arch=$3
local zip=$pkg-$_OS_NAME-$arch-release.zip
echo "$DART_ARCHIVE/$VERS/$dir/$zip";
}
getAndInstall() {
local dir=$1
local pkg=${2:-$dir};
local arch=${3:-x64}
local URL=$(mkUrl $dir $pkg $arch)
local exitStatus=0;
local zip=$(basename $URL)
echo "Getting $pkg from:"
echo " $URL"
[[ ! -d "$TMP" ]] && mkdir "$TMP"
[[ ! -d "$PKG" ]] && mkdir "$PKG"
curl $URL > "$TMP/$zip" # 2> /dev/null
if [[ "1000" -lt "$(wc -c $TMP/$zip | awk '{print $1}')" ]]; then
unzip "$TMP/$zip" -d "$PKG" > /dev/null
rm -f "$TMP/$zip"
# PATH is set in ./scripts/env-set.sh
else
echo FAILED to download Dart $pkg. Check URL.
exitStatus=1;
fi
}
if getAndInstall sdk dartsdk; then
# Install Dartium
if [[ "$_OS_NAME" == "macos" ]]; then
getAndInstall dartium dartium ia32
else
getAndInstall dartium
fi
echo
dart --version
fi
travis_fold end install.dart
else
echo Dart SDK appears to be installed: `type dart`
# PATH is set in ./scripts/env-set.sh
dart --version
fi

29
scripts/install-ng2dart.sh Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -e -o pipefail
[[ -z "$NGIO_ENV_DEFS" ]] && . ./scripts/env-set.sh
./scripts/install-dart-sdk.sh
travis_fold start install.ng2dart
if [[ -z "$(type -t dart)" ]]; then
echo "No Dart SDK: aborting install of Angular2/Dart"
exit 1;
elif [[ -e "$NG2DART_REPO" ]]; then
echo Angular2/Dart found at: $NG2DART_REPO
else
echo GETTING Angular2/Dart from pub package ...
set -x
# Get ng2dart via pub on ng.io pubspec.yaml
pub upgrade > /dev/null
NG2DART_PUB=`find ~/.pub-cache/ -type d -name "angular2*" | xargs ls -dtr | tail -1`
cp -r $NG2DART_PUB $NG2DART_REPO
fi
# Run pub on ng2dart
(cd $NG2DART_REPO && pub get)
set +x
travis_fold end install.ng2dart

View File

@ -19,7 +19,7 @@ npm install --no-optional
echo "Patching ..."
source ./scripts/patch.sh
if [ "$TRAVIS" != "true" ]; then
if [ -z "$TRAVIS" ]; then
echo "Rebuilding node-sass, just in case ..."
npm rebuild node-sass;
fi

View File

@ -0,0 +1,92 @@
var fs = require('fs');
var path = require('canonical-path');
var Package = require('dgeni').Package;
var basePackage = require('../api-builder/docs-package');
var targetPackage = require('../api-builder/target-package');
var cheatsheetPackage = require('../api-builder/cheatsheet-package');
var PROJECT_PATH = path.resolve(__dirname, "../..");
var PUBLIC_PATH = path.resolve(PROJECT_PATH, 'public');
var DOCS_PATH = path.resolve(PUBLIC_PATH, 'docs');
var ANGULAR_REPO_PATH = path.resolve(__dirname, '../../../angular-dart');
var ANGULAR2_DOCS_PATH = path.resolve(ANGULAR_REPO_PATH, 'docs');
var NG_IO_PKG_PATH = path.resolve(__dirname, "../api-builder/angular.io-package");
function requireNgIoPkg(_path) { return require(path.resolve(NG_IO_PKG_PATH, _path)); }
module.exports = new Package('dart-api-and-cheatsheet-builder', [basePackage, targetPackage, cheatsheetPackage])
// overrides base packageInfo and returns the one for the Angular repo.
.factory(require('./services/packageInfo'))
// Configure rendering
.config(function (templateFinder, renderDocsProcessor) {
templateFinder.templateFolders
.unshift(path.resolve(NG_IO_PKG_PATH, 'templates'));
// helpers are made available to the nunjucks templates
renderDocsProcessor.helpers.relativePath = function (from, to) {
return path.relative(from, to);
};
})
.config(function (parseTagsProcessor, getInjectables) {
const tagDefs = requireNgIoPkg('./tag-defs');
parseTagsProcessor.tagDefinitions =
parseTagsProcessor.tagDefinitions.concat(getInjectables(tagDefs));
})
.config(function (readFilesProcessor) {
// confirm that the angular repo is actually there.
if (!fs.existsSync(ANGULAR_REPO_PATH)) {
throw new Error('dart-api-and-cheatsheet-builder task requires the angular2 repo to be at ' + ANGULAR_REPO_PATH);
}
readFilesProcessor.basePath = DOCS_PATH;
readFilesProcessor.sourceFiles = [{
basePath: ANGULAR2_DOCS_PATH,
include: path.resolve(ANGULAR2_DOCS_PATH, 'cheatsheet/*.md')
}];
})
.config(function (convertPrivateClassesToInterfacesProcessor,
createOverviewDump,
extractDirectiveClassesProcessor,
extractJSDocCommentsProcessor,
extractTitleFromGuides,
generateNavigationDoc,
mergeDecoratorDocs,
readTypeScriptModules
) {
// Clear out unwanted processors
createOverviewDump.$enabled = false;
convertPrivateClassesToInterfacesProcessor.$enabled = false;
extractDirectiveClassesProcessor.$enabled = false;
extractJSDocCommentsProcessor.$enabled = false;
extractTitleFromGuides.$enabled = false;
generateNavigationDoc.$enabled = false;
mergeDecoratorDocs.$enabled = false;
readTypeScriptModules.$enabled = false;
})
.config(function (computePathsProcessor) {
computePathsProcessor.pathTemplates.push({
docTypes: ['cheatsheet-data'],
pathTemplate: '../guide/cheatsheet.json',
outputPathTemplate: '${path}'
});
})
.config(function (getLinkInfo) {
getLinkInfo.relativeLinks = true;
})
.config(function (templateEngine, getInjectables) {
templateEngine.filters = templateEngine.filters.concat(getInjectables([
requireNgIoPkg('./rendering/trimBlankLines'),
requireNgIoPkg('./rendering/toId'),
requireNgIoPkg('./rendering/indentForMarkdown')
]));
})
;

Some files were not shown because too many files have changed in this diff Show More