diff --git a/.travis.yml b/.travis.yml index ef7145f388..755b29e6a7 100644 --- a/.travis.yml +++ b/.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 diff --git a/gulpfile.js b/gulpfile.js index aa2685d54d..b7be783ea8 100644 --- a/gulpfile.js +++ b/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); } diff --git a/public/docs/_examples/architecture/dart/pubspec.yaml b/public/docs/_examples/architecture/dart/pubspec.yaml index e57bde6ce6..4da6837c0b 100644 --- a/public/docs/_examples/architecture/dart/pubspec.yaml +++ b/public/docs/_examples/architecture/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/attribute-directives/dart/pubspec.yaml b/public/docs/_examples/attribute-directives/dart/pubspec.yaml index b6fc8fb99f..2afe4114d8 100644 --- a/public/docs/_examples/attribute-directives/dart/pubspec.yaml +++ b/public/docs/_examples/attribute-directives/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/component-styles/dart/pubspec.yaml b/public/docs/_examples/component-styles/dart/pubspec.yaml index 416256ef16..731e9e5d8e 100755 --- a/public/docs/_examples/component-styles/dart/pubspec.yaml +++ b/public/docs/_examples/component-styles/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/dependency-injection/dart/pubspec.yaml b/public/docs/_examples/dependency-injection/dart/pubspec.yaml index 749f4d97ea..b3b5a1cb42 100644 --- a/public/docs/_examples/dependency-injection/dart/pubspec.yaml +++ b/public/docs/_examples/dependency-injection/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/displaying-data/dart/pubspec.yaml b/public/docs/_examples/displaying-data/dart/pubspec.yaml index 2ca27306c6..8c57ff7300 100644 --- a/public/docs/_examples/displaying-data/dart/pubspec.yaml +++ b/public/docs/_examples/displaying-data/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/forms/dart/pubspec.yaml b/public/docs/_examples/forms/dart/pubspec.yaml index 78e526201f..89b203abf0 100644 --- a/public/docs/_examples/forms/dart/pubspec.yaml +++ b/public/docs/_examples/forms/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/hierarchical-dependency-injection/dart/pubspec.yaml b/public/docs/_examples/hierarchical-dependency-injection/dart/pubspec.yaml index f03e95c696..d81a211702 100644 --- a/public/docs/_examples/hierarchical-dependency-injection/dart/pubspec.yaml +++ b/public/docs/_examples/hierarchical-dependency-injection/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/lifecycle-hooks/dart/pubspec.yaml b/public/docs/_examples/lifecycle-hooks/dart/pubspec.yaml index e80ef83c72..0710ddbb86 100644 --- a/public/docs/_examples/lifecycle-hooks/dart/pubspec.yaml +++ b/public/docs/_examples/lifecycle-hooks/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/package.json b/public/docs/_examples/package.json index 552731818b..21a52a65e3 100644 --- a/public/docs/_examples/package.json +++ b/public/docs/_examples/package.json @@ -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", diff --git a/public/docs/_examples/pipes/dart/lib/exponential_strength_pipe.dart b/public/docs/_examples/pipes/dart/lib/exponential_strength_pipe.dart index 1e66b9c791..73bce794f7 100644 --- a/public/docs/_examples/pipes/dart/lib/exponential_strength_pipe.dart +++ b/public/docs/_examples/pipes/dart/lib/exponential_strength_pipe.dart @@ -13,13 +13,5 @@ import 'package:angular2/angular2.dart'; */ @Pipe(name: 'exponentialStrength') class ExponentialStrengthPipe extends PipeTransform { - num transform(dynamic _value, [List 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); } diff --git a/public/docs/_examples/pipes/dart/lib/fetch_json_pipe.dart b/public/docs/_examples/pipes/dart/lib/fetch_json_pipe.dart index 4ff5eab8d9..e5ff8d34d6 100644 --- a/public/docs/_examples/pipes/dart/lib/fetch_json_pipe.dart +++ b/public/docs/_examples/pipes/dart/lib/fetch_json_pipe.dart @@ -11,7 +11,7 @@ class FetchJsonPipe extends PipeTransform { dynamic _fetchedJson; String _prevUrl; - dynamic transform(dynamic url, [List args]) { + dynamic transform(String url) { if (url != _prevUrl) { _prevUrl = url; _fetchedJson = null; diff --git a/public/docs/_examples/pipes/dart/lib/flying_heroes_component.html b/public/docs/_examples/pipes/dart/lib/flying_heroes_component.html index 5d2a5d8a40..93e635b662 100644 --- a/public/docs/_examples/pipes/dart/lib/flying_heroes_component.html +++ b/public/docs/_examples/pipes/dart/lib/flying_heroes_component.html @@ -20,7 +20,7 @@ New hero:

Heroes who fly (piped)

-
+
{{hero.name}}
@@ -30,7 +30,7 @@ New hero:
-
+
{{hero.name}}
diff --git a/public/docs/_examples/pipes/dart/lib/flying_heroes_pipe.dart b/public/docs/_examples/pipes/dart/lib/flying_heroes_pipe.dart index fe20fe1ea3..0ece480d12 100644 --- a/public/docs/_examples/pipes/dart/lib/flying_heroes_pipe.dart +++ b/public/docs/_examples/pipes/dart/lib/flying_heroes_pipe.dart @@ -6,7 +6,7 @@ import 'heroes.dart'; @Pipe(name: 'flyingHeroes') class FlyingHeroesPipe extends PipeTransform { // #docregion filter - List transform(dynamic value, [List args]) => + List transform(List value) => value.where((hero) => hero.canFly).toList(); // #enddocregion filter } diff --git a/public/docs/_examples/pipes/dart/lib/hero_list_component.dart b/public/docs/_examples/pipes/dart/lib/hero_list_component.dart index 6088fa547f..fcbe8c6d1e 100644 --- a/public/docs/_examples/pipes/dart/lib/hero_list_component.dart +++ b/public/docs/_examples/pipes/dart/lib/hero_list_component.dart @@ -9,7 +9,7 @@ import 'fetch_json_pipe.dart'; template: '''

Heroes from JSON File

-
+
{{hero['name']}}
diff --git a/public/docs/_examples/pipes/dart/pubspec.yaml b/public/docs/_examples/pipes/dart/pubspec.yaml index e8f32d1fae..4afa2870c0 100644 --- a/public/docs/_examples/pipes/dart/pubspec.yaml +++ b/public/docs/_examples/pipes/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/quickstart/dart/pubspec.yaml b/public/docs/_examples/quickstart/dart/pubspec.yaml index 780bd7b93c..0c08200992 100644 --- a/public/docs/_examples/quickstart/dart/pubspec.yaml +++ b/public/docs/_examples/quickstart/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/router/ts/app/app.component.1.ts b/public/docs/_examples/router/ts/app/app.component.1.ts index f8a23d3f47..e124b6771a 100644 --- a/public/docs/_examples/router/ts/app/app.component.1.ts +++ b/public/docs/_examples/router/ts/app/app.component.1.ts @@ -12,8 +12,8 @@ import { ROUTER_DIRECTIVES } from '@angular/router'; template: `

Component Router

`, diff --git a/public/docs/_examples/router/ts/app/app.component.2.ts b/public/docs/_examples/router/ts/app/app.component.2.ts index f1580cf6f8..fcbde30967 100644 --- a/public/docs/_examples/router/ts/app/app.component.2.ts +++ b/public/docs/_examples/router/ts/app/app.component.2.ts @@ -25,8 +25,8 @@ import { HeroService } from './heroes/hero.service'; template: `

Component Router

`, diff --git a/public/docs/_examples/router/ts/app/app.component.3.ts b/public/docs/_examples/router/ts/app/app.component.3.ts index e20c980aef..d8bb30b1e2 100644 --- a/public/docs/_examples/router/ts/app/app.component.3.ts +++ b/public/docs/_examples/router/ts/app/app.component.3.ts @@ -28,15 +28,20 @@ import { HeroService } from './heroes/hero.service'; */ /* Crisis Center Detail link // #docregion Dragon-anchor - Dragon Crisis + Dragon Crisis // #enddocregion Dragon-anchor */ + /* Crisis Center link with optional query params + // #docregion cc-query-params + Crisis Center + // #enddocregion cc-query-params + */ // #docregion template template: `

Component Router

diff --git a/public/docs/_examples/router/ts/app/app.component.4.ts b/public/docs/_examples/router/ts/app/app.component.4.ts index f39d71f5e9..80cf4b0d17 100644 --- a/public/docs/_examples/router/ts/app/app.component.4.ts +++ b/public/docs/_examples/router/ts/app/app.component.4.ts @@ -11,9 +11,10 @@ import { HeroService } from './heroes/hero.service'; template: `

Component Router

`, diff --git a/public/docs/_examples/router/ts/app/app.component.ts b/public/docs/_examples/router/ts/app/app.component.ts index 9196c34084..a3ad280a45 100644 --- a/public/docs/_examples/router/ts/app/app.component.ts +++ b/public/docs/_examples/router/ts/app/app.component.ts @@ -12,10 +12,11 @@ import { HeroService } from './heroes/hero.service'; template: `

Component Router

`, diff --git a/public/docs/_examples/router/ts/app/app.routes.1.ts b/public/docs/_examples/router/ts/app/app.routes.1.ts index 14a0ebe946..7919f34aba 100644 --- a/public/docs/_examples/router/ts/app/app.routes.1.ts +++ b/public/docs/_examples/router/ts/app/app.routes.1.ts @@ -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 = [ diff --git a/public/docs/_examples/router/ts/app/app.routes.ts b/public/docs/_examples/router/ts/app/app.routes.ts index 2f003bab55..eb238680da 100644 --- a/public/docs/_examples/router/ts/app/app.routes.ts +++ b/public/docs/_examples/router/ts/app/app.routes.ts @@ -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, diff --git a/public/docs/_examples/router/ts/app/auth.guard.1.ts b/public/docs/_examples/router/ts/app/auth-guard.service.1.ts similarity index 76% rename from public/docs/_examples/router/ts/app/auth.guard.1.ts rename to public/docs/_examples/router/ts/app/auth-guard.service.1.ts index 1d93866a4b..c824bcb208 100644 --- a/public/docs/_examples/router/ts/app/auth.guard.1.ts +++ b/public/docs/_examples/router/ts/app/auth-guard.service.1.ts @@ -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'); diff --git a/public/docs/_examples/router/ts/app/auth-guard.service.2.ts b/public/docs/_examples/router/ts/app/auth-guard.service.2.ts new file mode 100644 index 0000000000..9d4f3afb3b --- /dev/null +++ b/public/docs/_examples/router/ts/app/auth-guard.service.2.ts @@ -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; + } +} diff --git a/public/docs/_examples/router/ts/app/auth-guard.service.ts b/public/docs/_examples/router/ts/app/auth-guard.service.ts new file mode 100755 index 0000000000..a70fadb5cf --- /dev/null +++ b/public/docs/_examples/router/ts/app/auth-guard.service.ts @@ -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; + } +} diff --git a/public/docs/_examples/router/ts/app/auth.guard.ts b/public/docs/_examples/router/ts/app/auth.guard.ts deleted file mode 100755 index 52317985c1..0000000000 --- a/public/docs/_examples/router/ts/app/auth.guard.ts +++ /dev/null @@ -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; - } -} diff --git a/public/docs/_examples/router/ts/app/auth.service.ts b/public/docs/_examples/router/ts/app/auth.service.ts index 8869bf69f3..d6f8414af1 100755 --- a/public/docs/_examples/router/ts/app/auth.service.ts +++ b/public/docs/_examples/router/ts/app/auth.service.ts @@ -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); } diff --git a/public/docs/_examples/router/ts/app/interfaces.ts b/public/docs/_examples/router/ts/app/can-deactivate-guard.service.ts similarity index 88% rename from public/docs/_examples/router/ts/app/interfaces.ts rename to public/docs/_examples/router/ts/app/can-deactivate-guard.service.ts index 5bd1344a53..e7774533bc 100644 --- a/public/docs/_examples/router/ts/app/interfaces.ts +++ b/public/docs/_examples/router/ts/app/can-deactivate-guard.service.ts @@ -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; } +@Injectable() export class CanDeactivateGuard implements CanDeactivate { canDeactivate(component: CanComponentDeactivate): Observable | boolean { return component.canDeactivate ? component.canDeactivate() : true; diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-admin.component.1.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-admin.component.1.ts new file mode 100644 index 0000000000..4f4173a4e5 --- /dev/null +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-admin.component.1.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

CRISIS ADMINISTRATION

+

Manage your crises here

+ `, + directives: [] +}) +export class CrisisAdminComponent { } diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-admin.component.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-admin.component.ts index efb758a391..9783466d4c 100755 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-admin.component.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-admin.component.ts @@ -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: `

CRISIS ADMINISTRATION

Manage your crises here

- `, - directives: [ROUTER_DIRECTIVES] -}) -export class CrisisAdminComponent { } +

Session ID: {{ sessionId | async }}

+ +

Token: {{ token | async }}

+ `, + directives: [] +}) +export class CrisisAdminComponent implements OnInit { + sessionId: Observable; + token: Observable; + + 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'); + } +} diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.3.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.3.ts index 9a307f6541..b0d0fc9fca 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.3.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.3.ts @@ -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 = [ { diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.4.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.4.ts index 538044ebba..a7e1612297 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.4.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.4.ts @@ -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 = [ { diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.ts index f8b87190f8..6712b35ced 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-center.routes.ts @@ -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 = [ { diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.1.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.1.ts index e226381108..fdb331dec7 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.1.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.1.ts @@ -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'; diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.ts index 2557936899..95df7d567f 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.ts @@ -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: ` diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.1.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.1.ts index 79ad5152fe..c962ee0134 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.1.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.1.ts @@ -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'; diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.ts index 5c964eaf58..df51b14ade 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.ts @@ -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'; diff --git a/public/docs/_examples/router/ts/app/heroes/hero-detail.component.ts b/public/docs/_examples/router/ts/app/heroes/hero-detail.component.ts index bfa9ca6d0f..7d5fb1cf96 100644 --- a/public/docs/_examples/router/ts/app/heroes/hero-detail.component.ts +++ b/public/docs/_examples/router/ts/app/heroes/hero-detail.component.ts @@ -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 } diff --git a/public/docs/_examples/router/ts/app/heroes/hero-list.component.2.ts b/public/docs/_examples/router/ts/app/heroes/hero-list.component.2.ts deleted file mode 100644 index 482b70c381..0000000000 --- a/public/docs/_examples/router/ts/app/heroes/hero-list.component.2.ts +++ /dev/null @@ -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: ` -

HEROES

-
    -
  • - {{hero.id}} {{hero.name}} -
  • -
- ` - // #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 diff --git a/public/docs/_examples/router/ts/app/heroes/hero-list.component.ts b/public/docs/_examples/router/ts/app/heroes/hero-list.component.ts index 482b70c381..72dd3e0b25 100644 --- a/public/docs/_examples/router/ts/app/heroes/hero-list.component.ts +++ b/public/docs/_examples/router/ts/app/heroes/hero-list.component.ts @@ -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() diff --git a/public/docs/_examples/router/ts/app/heroes/heroes.routes.ts b/public/docs/_examples/router/ts/app/heroes/heroes.routes.ts index d5d092c016..f6c2b19c75 100644 --- a/public/docs/_examples/router/ts/app/heroes/heroes.routes.ts +++ b/public/docs/_examples/router/ts/app/heroes/heroes.routes.ts @@ -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 }, diff --git a/public/docs/_examples/router/ts/app/login.component.ts b/public/docs/_examples/router/ts/app/login.component.ts index 2790bfc79d..ddee339011 100755 --- a/public/docs/_examples/router/ts/app/login.component.ts +++ b/public/docs/_examples/router/ts/app/login.component.ts @@ -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]); } }); } diff --git a/public/docs/_examples/router/ts/app/login.routes.ts b/public/docs/_examples/router/ts/app/login.routes.ts index f50d2d7264..8bfc1ff10d 100644 --- a/public/docs/_examples/router/ts/app/login.routes.ts +++ b/public/docs/_examples/router/ts/app/login.routes.ts @@ -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'; diff --git a/public/docs/_examples/router/ts/app/not-found.component.ts b/public/docs/_examples/router/ts/app/not-found.component.ts new file mode 100644 index 0000000000..4a9f60cc32 --- /dev/null +++ b/public/docs/_examples/router/ts/app/not-found.component.ts @@ -0,0 +1,9 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

Page Not Found

+ ` +}) +export class PageNotFoundComponent {} diff --git a/public/docs/_examples/server-communication/dart/pubspec.yaml b/public/docs/_examples/server-communication/dart/pubspec.yaml index 12b1164c46..217cb4edcc 100644 --- a/public/docs/_examples/server-communication/dart/pubspec.yaml +++ b/public/docs/_examples/server-communication/dart/pubspec.yaml @@ -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 diff --git a/public/docs/_examples/structural-directives/dart/pubspec.yaml b/public/docs/_examples/structural-directives/dart/pubspec.yaml index ef3a8caa2c..daba4aa174 100644 --- a/public/docs/_examples/structural-directives/dart/pubspec.yaml +++ b/public/docs/_examples/structural-directives/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/systemjs.config.plunker.js b/public/docs/_examples/systemjs.config.plunker.js index fbf84a69ba..5ff363b229 100644 --- a/public/docs/_examples/systemjs.config.plunker.js +++ b/public/docs/_examples/systemjs.config.plunker.js @@ -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 diff --git a/public/docs/_examples/template-syntax/dart/pubspec.yaml b/public/docs/_examples/template-syntax/dart/pubspec.yaml index b3ca2e0c56..92574e0a45 100644 --- a/public/docs/_examples/template-syntax/dart/pubspec.yaml +++ b/public/docs/_examples/template-syntax/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/toh-1/dart/pubspec.yaml b/public/docs/_examples/toh-1/dart/pubspec.yaml index cb7ef1566c..9657ed659d 100644 --- a/public/docs/_examples/toh-1/dart/pubspec.yaml +++ b/public/docs/_examples/toh-1/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/toh-1/e2e-spec.ts b/public/docs/_examples/toh-1/e2e-spec.ts index 9746d080c8..15acf6e434 100644 --- a/public/docs/_examples/toh-1/e2e-spec.ts +++ b/public/docs/_examples/toh-1/e2e-spec.ts @@ -1,27 +1,70 @@ /// 'use strict'; + +type WPromise = webdriver.promise.Promise; + +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 { + // Get hero id from the first
+ 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 { + 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')) + }; +} diff --git a/public/docs/_examples/toh-1/ts/index.html b/public/docs/_examples/toh-1/ts/index.html index f784483eaa..e9c099696b 100644 --- a/public/docs/_examples/toh-1/ts/index.html +++ b/public/docs/_examples/toh-1/ts/index.html @@ -17,6 +17,7 @@ + Loading... diff --git a/public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart b/public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart index 2e3e2438fd..9f805c5bc5 100644 --- a/public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart +++ b/public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart @@ -1,5 +1,5 @@ // #docregion ng-for -
  • +
  • {{hero.id}} {{hero.name}}
  • // #enddocregion ng-for @@ -7,14 +7,14 @@ // #docregion heroes-styled

    My Heroes

      -
    • +
    • {{hero.id}} {{hero.name}}
    // #enddocregion heroes-styled // #docregion selectedHero-click -
  • +
  • {{hero.id}} {{hero.name}}
  • // #enddocregion selectedHero-click @@ -53,7 +53,7 @@ final List heroes = mockHeroes; // #enddocregion heroes-template-1 // #docregion heroes-ngfor-1 -
  • +
  • // #enddocregion heroes-ngfor-1 // #docregion class-selected-1 @@ -61,7 +61,7 @@ final List heroes = mockHeroes; // #enddocregion class-selected-1 // #docregion class-selected-2 -
  • {{hero.id}} {{hero.name}} diff --git a/public/docs/_examples/toh-2/dart/example-config.json b/public/docs/_examples/toh-2/dart/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-2/dart/pubspec.yaml b/public/docs/_examples/toh-2/dart/pubspec.yaml index cb7ef1566c..9657ed659d 100644 --- a/public/docs/_examples/toh-2/dart/pubspec.yaml +++ b/public/docs/_examples/toh-2/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/toh-2/e2e-spec.ts b/public/docs/_examples/toh-2/e2e-spec.ts new file mode 100644 index 0000000000..256ffbb5d7 --- /dev/null +++ b/public/docs/_examples/toh-2/e2e-spec.ts @@ -0,0 +1,133 @@ +/// +'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 = webdriver.promise.Promise; + +class Hero { + id: number; + name: string; + + // Factory methods + + // Get hero from s formatted as ' '. + 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 { + // Get hero id from the first
    + 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 { + 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')) + }; +} diff --git a/public/docs/_examples/toh-2/ts/index.html b/public/docs/_examples/toh-2/ts/index.html index 485409815c..e9c099696b 100644 --- a/public/docs/_examples/toh-2/ts/index.html +++ b/public/docs/_examples/toh-2/ts/index.html @@ -1,7 +1,7 @@ - Angular 2 Tour of Heros + Angular 2 Tour of Heroes diff --git a/public/docs/_examples/toh-3/dart/example-config.json b/public/docs/_examples/toh-3/dart/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-3/dart/pubspec.yaml b/public/docs/_examples/toh-3/dart/pubspec.yaml index cb7ef1566c..9657ed659d 100644 --- a/public/docs/_examples/toh-3/dart/pubspec.yaml +++ b/public/docs/_examples/toh-3/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/toh-3/e2e-spec.ts b/public/docs/_examples/toh-3/e2e-spec.ts new file mode 100644 index 0000000000..070dd69ebd --- /dev/null +++ b/public/docs/_examples/toh-3/e2e-spec.ts @@ -0,0 +1,133 @@ +/// +'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 = webdriver.promise.Promise; + +class Hero { + id: number; + name: string; + + // Factory methods + + // Get hero from s formatted as ' '. + 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 { + // Get hero id from the first
    + 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 { + 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')) + }; +} diff --git a/public/docs/_examples/toh-4/dart/example-config.json b/public/docs/_examples/toh-4/dart/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-4/dart/pubspec.yaml b/public/docs/_examples/toh-4/dart/pubspec.yaml index cb7ef1566c..9657ed659d 100644 --- a/public/docs/_examples/toh-4/dart/pubspec.yaml +++ b/public/docs/_examples/toh-4/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/toh-4/dart/web/index.html b/public/docs/_examples/toh-4/dart/web/index.html index 059daeed4c..3c09e8af9d 100644 --- a/public/docs/_examples/toh-4/dart/web/index.html +++ b/public/docs/_examples/toh-4/dart/web/index.html @@ -1,6 +1,10 @@ + Angular 2 Tour of Heroes + + + diff --git a/public/docs/_examples/toh-4/e2e-spec.ts b/public/docs/_examples/toh-4/e2e-spec.ts new file mode 100644 index 0000000000..8f7852a3e3 --- /dev/null +++ b/public/docs/_examples/toh-4/e2e-spec.ts @@ -0,0 +1,133 @@ +/// +'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 = webdriver.promise.Promise; + +class Hero { + id: number; + name: string; + + // Factory methods + + // Get hero from s formatted as ' '. + 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 { + // Get hero id from the first
    + 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 { + 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')) + }; +} diff --git a/public/docs/_examples/toh-4/ts/app/app.component.1.ts b/public/docs/_examples/toh-4/ts/app/app.component.1.ts index 20ac18a660..9df27d44bd 100644 --- a/public/docs/_examples/toh-4/ts/app/app.component.1.ts +++ b/public/docs/_examples/toh-4/ts/app/app.component.1.ts @@ -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 diff --git a/public/docs/_examples/toh-5/dart/pubspec.yaml b/public/docs/_examples/toh-5/dart/pubspec.yaml index cb7ef1566c..9657ed659d 100644 --- a/public/docs/_examples/toh-5/dart/pubspec.yaml +++ b/public/docs/_examples/toh-5/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/toh-6/dart/pubspec.yaml b/public/docs/_examples/toh-6/dart/pubspec.yaml index 574758d902..b59118580c 100644 --- a/public/docs/_examples/toh-6/dart/pubspec.yaml +++ b/public/docs/_examples/toh-6/dart/pubspec.yaml @@ -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 diff --git a/public/docs/_examples/toh-6/e2e-spec.ts b/public/docs/_examples/toh-6/e2e-spec.ts index 96277a910a..e2ab2602dc 100644 --- a/public/docs/_examples/toh-6/e2e-spec.ts +++ b/public/docs/_examples/toh-6/e2e-spec.ts @@ -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; diff --git a/public/docs/_examples/toh-6/ts/app/app.component.ts b/public/docs/_examples/toh-6/ts/app/app.component.ts index 2a1ff50ba3..d49c87ccbf 100644 --- a/public/docs/_examples/toh-6/ts/app/app.component.ts +++ b/public/docs/_examples/toh-6/ts/app/app.component.ts @@ -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', diff --git a/public/docs/_examples/toh-6/ts/app/dashboard.component.html b/public/docs/_examples/toh-6/ts/app/dashboard.component.html index 028eab6eb3..e22a2a5ebb 100644 --- a/public/docs/_examples/toh-6/ts/app/dashboard.component.html +++ b/public/docs/_examples/toh-6/ts/app/dashboard.component.html @@ -9,3 +9,5 @@
    + + diff --git a/public/docs/_examples/toh-6/ts/app/dashboard.component.ts b/public/docs/_examples/toh-6/ts/app/dashboard.component.ts index 08ffecc0ea..f7b4100cee 100644 --- a/public/docs/_examples/toh-6/ts/app/dashboard.component.ts +++ b/public/docs/_examples/toh-6/ts/app/dashboard.component.ts @@ -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 { diff --git a/public/docs/_examples/toh-6/ts/app/hero-search.component.html b/public/docs/_examples/toh-6/ts/app/hero-search.component.html new file mode 100644 index 0000000000..08c0560c5b --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/hero-search.component.html @@ -0,0 +1,11 @@ + +
    +

    Hero Search

    + +
    +
    + {{hero.name}} +
    +
    +
    diff --git a/public/docs/_examples/toh-6/ts/app/hero-search.component.ts b/public/docs/_examples/toh-6/ts/app/hero-search.component.ts new file mode 100644 index 0000000000..9ef06ca47c --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/hero-search.component.ts @@ -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; + // #enddocregion search + // #docregion searchSubject + searchSubject = new Subject(); + // #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([])) + + .catch(error => { + // Todo: real error handling + console.log(error); + return Observable.of([]); + }); + } + // #enddocregion search + + gotoDetail(hero: Hero) { + let link = ['/detail', hero.id]; + this.router.navigate(link); + } +} diff --git a/public/docs/_examples/toh-6/ts/app/hero-search.service.ts b/public/docs/_examples/toh-6/ts/app/hero-search.service.ts new file mode 100644 index 0000000000..42018e3526 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/hero-search.service.ts @@ -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 +} diff --git a/public/docs/_examples/toh-6/ts/app/hero.service.ts b/public/docs/_examples/toh-6/ts/app/hero.service.ts index 8abbcc2778..04012768be 100644 --- a/public/docs/_examples/toh-6/ts/app/hero.service.ts +++ b/public/docs/_examples/toh-6/ts/app/hero.service.ts @@ -17,13 +17,13 @@ export class HeroService { constructor(private http: Http) { } - getHeroes(): Promise { + 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); diff --git a/public/docs/_examples/toh-6/ts/app/rxjs-extensions.ts b/public/docs/_examples/toh-6/ts/app/rxjs-extensions.ts new file mode 100644 index 0000000000..a0facfe03e --- /dev/null +++ b/public/docs/_examples/toh-6/ts/app/rxjs-extensions.ts @@ -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'; diff --git a/public/docs/_examples/toh-6/ts/sample.css b/public/docs/_examples/toh-6/ts/sample.css index 0c99008d2d..a5ac5b4d70 100644 --- a/public/docs/_examples/toh-6/ts/sample.css +++ b/public/docs/_examples/toh-6/ts/sample.css @@ -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; +} + diff --git a/public/docs/_examples/user-input/dart/pubspec.yaml b/public/docs/_examples/user-input/dart/pubspec.yaml index ea7da2d84f..fa28056488 100644 --- a/public/docs/_examples/user-input/dart/pubspec.yaml +++ b/public/docs/_examples/user-input/dart/pubspec.yaml @@ -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: diff --git a/public/docs/dart/latest/_data.json b/public/docs/dart/latest/_data.json index fe6a0eb5ed..ab9169d800 100644 --- a/public/docs/dart/latest/_data.json +++ b/public/docs/dart/latest/_data.json @@ -3,7 +3,7 @@ "icon": "home", "title": "Angular Docs", "menuTitle": "Docs Home", - "banner": "Welcome to angular.io/dart! The current Angular 2 Dart release is beta.17. Consult the Change Log about recent enhancements, fixes, and breaking changes." + "banner": "Welcome to angular.io/dart! The current Angular 2 Dart release is beta.18. Consult the Change Log about recent enhancements, fixes, and breaking changes." }, "quickstart": { diff --git a/public/docs/dart/latest/cheatsheet.jade b/public/docs/dart/latest/cheatsheet.jade index 739ad031ab..77c0f11d36 100644 --- a/public/docs/dart/latest/cheatsheet.jade +++ b/public/docs/dart/latest/cheatsheet.jade @@ -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 diff --git a/public/docs/dart/latest/quickstart.jade b/public/docs/dart/latest/quickstart.jade index e2a026d4f7..f3d81db102 100644 --- a/public/docs/dart/latest/quickstart.jade +++ b/public/docs/dart/latest/quickstart.jade @@ -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 diff --git a/public/docs/dart/latest/tutorial/toh-pt6.jade b/public/docs/dart/latest/tutorial/toh-pt6.jade index eb24dd0d9d..6536c1a7d3 100644 --- a/public/docs/dart/latest/tutorial/toh-pt6.jade +++ b/public/docs/dart/latest/tutorial/toh-pt6.jade @@ -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 diff --git a/public/docs/ts/latest/guide/router.jade b/public/docs/ts/latest/guide/router.jade index a97af2f608..7d4c758c89 100644 --- a/public/docs/ts/latest/guide/router.jade +++ b/public/docs/ts/latest/guide/router.jade @@ -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 routerLink directive that is bound to a Link Parameters Array triggers a navigation. - p 该指令用来把一个可点击的HTML元素绑定到路由。点击带有绑定了链接参数数组routerLink指令的A标签就会触发一次导航。 + p. + 该指令用来把一个可点击的HTML元素绑定到路由。 + 点击带有绑定到字符串链接参数数组routerLink指令的A标签就会触发一次导航。 + tr + td + p RouterLinkActive + + p RouterLinkActive(活动路由链接) + + 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 RouterState + + p RouterState(路由器状态) + + 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 Link Parameters Array @@ -877,35 +961,42 @@ h3#router-link RouterLink绑定 :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 RouterLinkActive 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 ROUTER_DIRECTIVES h3#router-directives ROUTER_DIRECTIVES(路由指令集) :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 ROUTER_DIRECTIVES(路由指令集) * 加载路由库 - * 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 CanActivate: 要求认证 我们准备扩展“危机中心”,添加一些新的*管理类*特性。 这些特性还没有定义过,所以我们先只添加一个占位组件: -+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 CanActivate: 要求认证 +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 CanActivate: 要求认证 我们换种方式:写一个`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 CanActivate: 要求认证 此刻,我们的兴趣在于看看守卫是如何工作的,所以我们第一个版本没做什么有用的事情。它只是往控制台写日志,并且立即返回`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 CanActivate: 要求认证 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 CanActivate: 要求认证 我们这就修改`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 CanActivate: 要求认证 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 CanDeactivate:处理未保存的更改 我们创建了一个`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 CanDeactivate:处理未保存的更改 +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 - `) - + + :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. @@ -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! diff --git a/public/docs/ts/latest/guide/template-syntax.jade b/public/docs/ts/latest/guide/template-syntax.jade index 066cced085..f0cc2588ec 100644 --- a/public/docs/ts/latest/guide/template-syntax.jade +++ b/public/docs/ts/latest/guide/template-syntax.jade @@ -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被用在一个模板中是没有意义的。``、``和``元素在我们的舞台上中并没有扮演有用的角色。基本上所有其它的元素都被一样使用。 - 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 - ## 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 - ### Custom events with EventEmitter + + ### Custom Events with EventEmitter + ### 使用EventEmitter实现自定义事件 Directives typically raise custom events with an Angular [EventEmitter](../api/core/index/EventEmitter-class.html). diff --git a/public/docs/ts/latest/tutorial/toh-pt5.jade b/public/docs/ts/latest/tutorial/toh-pt5.jade index ef0c85148b..2e11e97cc3 100644 --- a/public/docs/ts/latest/tutorial/toh-pt5.jade +++ b/public/docs/ts/latest/tutorial/toh-pt5.jade @@ -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类。 diff --git a/public/docs/ts/latest/tutorial/toh-pt6.jade b/public/docs/ts/latest/tutorial/toh-pt6.jade index 2906183157..0d86af36d6 100644 --- a/public/docs/ts/latest/tutorial/toh-pt6.jade +++ b/public/docs/ts/latest/tutorial/toh-pt6.jade @@ -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。 + +
  • We learned how to use Observables.
  • + +
  • 我们学会了如何使用可观察对象。
  • + - 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` +) diff --git a/public/resources/css/module/_home-promos.scss b/public/resources/css/module/_home-promos.scss index 6f37ecd0d9..6034b8fa8a 100644 --- a/public/resources/css/module/_home-promos.scss +++ b/public/resources/css/module/_home-promos.scss @@ -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 { diff --git a/public/resources/images/devguide/toh/toh-hero-search.png b/public/resources/images/devguide/toh/toh-hero-search.png new file mode 100644 index 0000000000..f09fd45d7e Binary files /dev/null and b/public/resources/images/devguide/toh/toh-hero-search.png differ diff --git a/public/resources/index.ejs b/public/resources/index.ejs index fa4f07bcb8..b0919d984c 100644 --- a/public/resources/index.ejs +++ b/public/resources/index.ejs @@ -13,7 +13,7 @@

    {{subCategory}}

    -
    +
    diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000000..ce4dd88f48 --- /dev/null +++ b/pubspec.yaml @@ -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 diff --git a/scripts/before-install.sh b/scripts/before-install.sh new file mode 100755 index 0000000000..b99455e910 --- /dev/null +++ b/scripts/before-install.sh @@ -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 diff --git a/scripts/deploy-install.sh b/scripts/deploy-install.sh index 38cf6c7d19..f34a158fbf 100755 --- a/scripts/deploy-install.sh +++ b/scripts/deploy-install.sh @@ -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) \ No newline at end of file +[[ -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* diff --git a/scripts/env-info-and-check.sh b/scripts/env-info-and-check.sh new file mode 100755 index 0000000000..c919602f10 --- /dev/null +++ b/scripts/env-info-and-check.sh @@ -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 diff --git a/scripts/env-set.sh b/scripts/env-set.sh new file mode 100644 index 0000000000..8341107a8c --- /dev/null +++ b/scripts/env-set.sh @@ -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 diff --git a/scripts/install-dart-sdk.sh b/scripts/install-dart-sdk.sh new file mode 100755 index 0000000000..688bc8b9da --- /dev/null +++ b/scripts/install-dart-sdk.sh @@ -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 diff --git a/scripts/install-ng2dart.sh b/scripts/install-ng2dart.sh new file mode 100755 index 0000000000..a0e0283073 --- /dev/null +++ b/scripts/install-ng2dart.sh @@ -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 diff --git a/scripts/install.sh b/scripts/install.sh index cc95b6726b..79242a0d16 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -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 diff --git a/tools/dart-api-builder/index.js b/tools/dart-api-builder/index.js new file mode 100644 index 0000000000..4d2a0618ef --- /dev/null +++ b/tools/dart-api-builder/index.js @@ -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') + ])); + }) + + ; diff --git a/tools/dart-api-builder/services/packageInfo.js b/tools/dart-api-builder/services/packageInfo.js new file mode 100644 index 0000000000..53db824168 --- /dev/null +++ b/tools/dart-api-builder/services/packageInfo.js @@ -0,0 +1,38 @@ +'use strict'; + +var fs = require('fs'); +var path = require('canonical-path'); + +/** + * Load information about this project from the pubspec.yaml + * @return {Object} The package information + */ +module.exports = function packageInfo() { + const ngPath = '../angular-dart'; + const angularPubspec = path.join(ngPath, 'pubspec.yaml'); + const pubspec = fs.readFileSync(angularPubspec, 'UTF-8').split('\n'); + + const info = { + version: _get(pubspec, 'version'), + repository: { + type: 'git', //? 'pub' @ 'https://pub.dartlang.org/packages/angular2' + // Not sure `url has a user visible impact on the generated cheatsheet. + url: 'https://github.com/angular/angular.git', + } + }; + return info; +} + +// Array.prototype.find doesn't seem to be working. +// console.error([1, 'a', 2].find((x) => x === 'a')); // --> -1 +function _find(arr, test) { + for (let x of arr) { + // console.error(`Looking at: ${x}`); + if (test(x)) return x; + } +} + +function _get(lines, tag) { + const line = _find(lines, (line) => line.startsWith(tag)); + return line.match(/^\w+: (.*)/)[1] || 'unknown'; +}