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:
commit
4400deeef8
17
.travis.yml
17
.travis.yml
|
@ -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
|
||||
|
|
115
gulpfile.js
115
gulpfile.js
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
`,
|
||||
|
|
|
@ -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>
|
||||
`,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
`,
|
||||
|
|
|
@ -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>
|
||||
`,
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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 { }
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = [
|
||||
{
|
||||
|
|
|
@ -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 = [
|
||||
{
|
||||
|
|
|
@ -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 = [
|
||||
{
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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: `
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h2>Page Not Found</h2>
|
||||
`
|
||||
})
|
||||
export class PageNotFoundComponent {}
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<script>
|
||||
System.import('app').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'))
|
||||
};
|
||||
}
|
|
@ -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">
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'))
|
||||
};
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'))
|
||||
};
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -9,3 +9,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hero-search></hero-search>
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 — and why they are arrays —
|
||||
We'll learn to write link expressions — and why they are arrays —
|
||||
[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 — we can imagine other features that require authenticated users —
|
||||
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 — 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 — 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 — 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 — 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!
|
||||
|
||||
|
|
|
@ -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(文档对象模型)的值。
|
||||
|
||||
Let’s turn to the first form of data binding — interpolation — 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 element’s `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 we’ve 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** — for example, `(click)` —
|
||||
identifies the target event. In the following example, the target is the button’s 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).
|
||||
|
|
|
@ -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类。
|
||||
|
|
|
@ -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`
|
||||
)
|
||||
|
|
|
@ -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 |
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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*
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue