Merge remote-tracking branch 'angular.io/master'
# Conflicts: # README.md # public/_includes/_footer.jade # public/_includes/_hero-home.jade # public/docs/ts/latest/cookbook/a1-a2-quick-reference.jade # public/docs/ts/latest/glossary.jade # public/docs/ts/latest/guide/architecture.jade # public/docs/ts/latest/guide/dependency-injection.jade # public/docs/ts/latest/guide/forms.jade # public/docs/ts/latest/guide/router.jade # public/docs/ts/latest/guide/style-guide.jade # public/docs/ts/latest/testing/first-app-tests.jade # public/docs/ts/latest/testing/jasmine-testing-101.jade # public/docs/ts/latest/tutorial/toh-pt5.jade
This commit is contained in:
commit
0e14785cba
20
.travis.yml
20
.travis.yml
|
@ -10,20 +10,24 @@ env:
|
|||
- DBUS_SESSION_BUS_ADDRESS=/dev/null
|
||||
- DISPLAY=:99.0
|
||||
- CHROME_BIN=chromium-browser
|
||||
- LATEST_RELEASE=2.0.0-rc.4
|
||||
matrix:
|
||||
- SCRIPT=lint
|
||||
- SCRIPT="run-e2e-tests --fast"
|
||||
- SCRIPT="run-e2e-tests --fast" PREVIEW=true
|
||||
- TASK=lint
|
||||
- TASK="run-e2e-tests --fast" SCRIPT=examples-install.sh
|
||||
- TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh
|
||||
- TASK=harp-compile SCRIPT=deploy-install.sh
|
||||
- TASK=harp-compile SCRIPT=deploy-install-preview.sh
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: "SCRIPT=\"run-e2e-tests --fast\" PREVIEW=true"
|
||||
- env: "TASK=\"run-e2e-tests --fast\" SCRIPT=examples-install-preview.sh"
|
||||
- env: "TASK=harp-compile SCRIPT=deploy-install-preview.sh"
|
||||
before_install:
|
||||
- npm install -g gulp --no-optional
|
||||
install:
|
||||
- npm install --no-optional
|
||||
- if [[ $SCRIPT ]]; then ./scripts/$SCRIPT; fi
|
||||
before_script:
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
install:
|
||||
- ./scripts/install.sh
|
||||
- if [[ $PREVIEW == true ]]; then npm install --prefix public/docs/_examples angular/{core,common,compiler,platform-browser,platform-browser-dynamic,http,forms,router-deprecated,router,upgrade}-builds; fi
|
||||
script:
|
||||
- gulp $SCRIPT
|
||||
- gulp $TASK
|
||||
|
|
78
gulpfile.js
78
gulpfile.js
|
@ -403,7 +403,9 @@ gulp.task('add-example-boilerplate', function() {
|
|||
|
||||
// copies boilerplate files to locations
|
||||
// where an example app is found
|
||||
gulp.task('_copy-example-boilerplate', copyExampleBoilerplate);
|
||||
gulp.task('_copy-example-boilerplate', function () {
|
||||
if (!argv.fast) copyExampleBoilerplate();
|
||||
});
|
||||
|
||||
|
||||
// copies boilerplate files to locations
|
||||
|
@ -451,6 +453,36 @@ gulp.task('remove-example-boilerplate', function() {
|
|||
deleteExampleBoilerPlate();
|
||||
});
|
||||
|
||||
// Npm install Angular libraries into examples/node_modules,
|
||||
// either release or current build packages
|
||||
// Examples:
|
||||
// gulp install-example-angular --build // use current build packages
|
||||
// gulp install-example-angular // restore release packages
|
||||
gulp.task('install-example-angular', installExampleAngular);
|
||||
|
||||
function installExampleAngular() {
|
||||
var sources;
|
||||
var template;
|
||||
var libs = [
|
||||
'core', 'common', 'compiler',
|
||||
'platform-browser', 'platform-browser-dynamic',
|
||||
'forms', 'http', 'router', 'upgrade'];
|
||||
|
||||
// Like: "angular/core-builds" or "@angular/core"
|
||||
sources = libs.map( lib => argv.build ? `angular/${lib}-builds` : `@angular/${lib}`);
|
||||
|
||||
sources.push('@angular/router-deprecated');
|
||||
|
||||
gutil.log(`Installing Angular npm packages from ${argv.build ? 'BUILD' : 'RELEASE'}`);
|
||||
|
||||
var spawnInfo = spawnExt('rm', ['-rf', 'node_modules/@angular'], { cwd: EXAMPLES_PATH});
|
||||
return spawnInfo.promise
|
||||
.then(() => {
|
||||
spawnInfo = spawnExt('npm', ['install', ...sources], {cwd: EXAMPLES_PATH});
|
||||
return spawnInfo.promise
|
||||
});
|
||||
}
|
||||
|
||||
// deletes boilerplate files that were added by copyExampleBoilerplate
|
||||
// from locations where an example app is found
|
||||
gulp.task('_delete-example-boilerplate', deleteExampleBoilerPlate);
|
||||
|
@ -564,16 +596,16 @@ gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){
|
|||
});
|
||||
});
|
||||
|
||||
gulp.task('harp-compile', ['build-docs'], function() {
|
||||
return harpCompile();
|
||||
});
|
||||
|
||||
gulp.task('check-deploy', ['build-docs'], function() {
|
||||
return harpCompile().then(function() {
|
||||
gutil.log('compile ok');
|
||||
if(argv.dryRun) {
|
||||
return false;
|
||||
} else {
|
||||
gutil.log('running live server ...');
|
||||
execPromise('npm run live-server ./www');
|
||||
return askDeploy();
|
||||
}
|
||||
gutil.log('running live server ...');
|
||||
execPromise('npm run live-server ./www');
|
||||
return askDeploy();
|
||||
}).then(function(shouldDeploy) {
|
||||
if (shouldDeploy) {
|
||||
gutil.log('deploying...');
|
||||
|
@ -596,7 +628,18 @@ gulp.task('test-api-builder', function (cb) {
|
|||
// angular.io: gulp link-checker
|
||||
// local site: gulp link-checker --url=http://localhost:3000
|
||||
gulp.task('link-checker', function(done) {
|
||||
return linkChecker();
|
||||
var method = 'get'; // the default 'head' fails for some sites
|
||||
var exclude = [
|
||||
// Dart API docs aren't working yet; ignore them
|
||||
'*/dart/latest/api/*',
|
||||
// Somehow the link checker sees ng1 {{...}} in the resource page; ignore it
|
||||
'resources/%7B%7Bresource.url%7D%7D',
|
||||
// API docs have links directly into GitHub repo sources; these can
|
||||
// quickly become invalid, so ignore them for now:
|
||||
'*/angular/tree/*'
|
||||
];
|
||||
var blcOptions = { requestMethod: method, excludedKeywords: exclude};
|
||||
return linkChecker({ blcOptions: blcOptions });
|
||||
});
|
||||
|
||||
|
||||
|
@ -727,12 +770,8 @@ function linkChecker(options) {
|
|||
var blcOptions = options.blcOptions || {};
|
||||
var customData = options.customData || {};
|
||||
|
||||
var excludeBad; // don't bother reporting bad links matching this RegExp
|
||||
if (argv.excludeBad) {
|
||||
excludeBad = new RegExp(argv.excludeBad);
|
||||
} else {
|
||||
excludeBad = options.excludeBad === undefined ? /docs\/dart\/latest\/api/ : '';
|
||||
}
|
||||
// don't bother reporting bad links matching this RegExp
|
||||
var excludeBad = argv.excludeBad ? new RegExp(argv.excludeBad) : (options.excludeBad || '');
|
||||
|
||||
var previousPage;
|
||||
var siteUrl = argv.url || options.url || 'https://angular.io/';
|
||||
|
@ -779,7 +818,8 @@ function linkChecker(options) {
|
|||
var outputFile = path.join(process.cwd(), 'link-checker-results.txt');
|
||||
var header = 'Link checker results for: ' + siteUrl +
|
||||
'\nStarted: ' + (new Date()).toLocaleString() +
|
||||
'\nSkipping bad links matching regex: ' +excludeBad.toString() + '\n\n';
|
||||
'\nExcluded links (blc file globs): ' + blcOptions.excludedKeywords +
|
||||
'\nExcluded links (custom --exclude-bad regex): ' + excludeBad.toString() + '\n\n';
|
||||
gutil.log(header);
|
||||
fs.writeFileSync(outputFile, header);
|
||||
|
||||
|
@ -1030,7 +1070,7 @@ function buildApiDocs(targetLanguage) {
|
|||
try {
|
||||
// Build a specialized package to generate different versions of the API docs
|
||||
var package = new Package('apiDocs', [require(path.resolve(TOOLS_PATH, 'api-builder/angular.io-package'))]);
|
||||
package.config(function(log, targetEnvironments, writeFilesProcessor, readTypeScriptModules) {
|
||||
package.config(function(log, targetEnvironments, writeFilesProcessor, readTypeScriptModules, linkDocsInlineTagDef) {
|
||||
log.level = _dgeniLogLevel;
|
||||
ALLOWED_LANGUAGES.forEach(function(target) { targetEnvironments.addAllowed(target); });
|
||||
if (targetLanguage) {
|
||||
|
@ -1040,7 +1080,9 @@ function buildApiDocs(targetLanguage) {
|
|||
// Don't read TypeScript modules if we are not generating API docs - Dart I am looking at you!
|
||||
readTypeScriptModules.$enabled = false;
|
||||
}
|
||||
writeFilesProcessor.outputFolder = targetLanguage + '/latest/api';
|
||||
linkDocsInlineTagDef.lang = targetLanguage;
|
||||
linkDocsInlineTagDef.vers = 'latest';
|
||||
writeFilesProcessor.outputFolder = path.join(targetLanguage, linkDocsInlineTagDef.vers, 'api');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"codelyzer": "0.0.22",
|
||||
"del": "^2.2.0",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.13.0",
|
||||
"dgeni-packages": "^0.13.1",
|
||||
"diff": "^2.1.3",
|
||||
"fs-extra": "^0.30.0",
|
||||
"globby": "^4.0.0",
|
||||
|
|
|
@ -255,7 +255,7 @@ describe('Animation Tests', () => {
|
|||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
||||
expect(li.getCssValue('opacity')).toMatch('1');
|
||||
|
||||
removeHero();
|
||||
removeHero(700);
|
||||
expect(li.isPresent()).toBe(false);
|
||||
});
|
||||
|
||||
|
@ -289,19 +289,22 @@ describe('Animation Tests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
function addActiveHero() {
|
||||
function addActiveHero(sleep?: number) {
|
||||
sleep = sleep || 500;
|
||||
element(by.buttonText('Add active hero')).click();
|
||||
browser.driver.sleep(500);
|
||||
browser.driver.sleep(sleep);
|
||||
}
|
||||
|
||||
function addInactiveHero() {
|
||||
function addInactiveHero(sleep?: number) {
|
||||
sleep = sleep || 500;
|
||||
element(by.buttonText('Add inactive hero')).click();
|
||||
browser.driver.sleep(500);
|
||||
browser.driver.sleep(sleep);
|
||||
}
|
||||
|
||||
function removeHero() {
|
||||
function removeHero(sleep?: number) {
|
||||
sleep = sleep || 500;
|
||||
element(by.buttonText('Remove hero')).click();
|
||||
browser.driver.sleep(500);
|
||||
browser.driver.sleep(sleep);
|
||||
}
|
||||
|
||||
function getScaleX(el: protractor.ElementFinder) {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// #docregion import
|
||||
import 'package:angular2/core.dart';
|
||||
// #enddocregion import
|
||||
|
||||
import 'hero_list_component.dart';
|
||||
import 'sales_tax_component.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'my-app',
|
||||
template: '''
|
||||
<hero-list></hero-list>
|
||||
<sales-tax></sales-tax>''',
|
||||
directives: const [HeroListComponent, SalesTaxComponent])
|
||||
// #docregion export
|
||||
class AppComponent { }
|
|
@ -1,4 +1,5 @@
|
|||
// #docregion
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'hero.dart';
|
||||
|
@ -6,19 +7,22 @@ import 'logger_service.dart';
|
|||
|
||||
@Injectable()
|
||||
class BackendService {
|
||||
static final _mockHeroes = [
|
||||
new Hero('Windstorm', 'Weather mastery'),
|
||||
new Hero('Mr. Nice', 'Killing them with kindness'),
|
||||
new Hero('Magneta', 'Manipulates metalic objects')
|
||||
];
|
||||
|
||||
final Logger _logger;
|
||||
List getAll(type) {
|
||||
// TODO get from the database and return as a promise
|
||||
if (type == Hero) {
|
||||
return [
|
||||
new Hero('Windstorm', power: 'Weather mastery'),
|
||||
new Hero('Mr. Nice', power: 'Killing them with kindness'),
|
||||
new Hero('Magneta', power: 'Manipulates metalic objects')
|
||||
];
|
||||
}
|
||||
_logger.error('Cannot get object of this type');
|
||||
throw new ArgumentError("TODO: put log content here");
|
||||
}
|
||||
|
||||
BackendService(Logger this._logger);
|
||||
|
||||
Future<List> getAll(type) {
|
||||
// TODO get from the database
|
||||
if (type == Hero) return new Future.value(_mockHeroes);
|
||||
|
||||
var msg = 'Cannot get object of this type';
|
||||
_logger.error(msg);
|
||||
throw new Exception(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
// #docregion
|
||||
class Hero {
|
||||
static int _nextId = 1;
|
||||
int id;
|
||||
String name;
|
||||
String power;
|
||||
final int id;
|
||||
String name, power;
|
||||
|
||||
Hero(this.name, {this.power}) {
|
||||
id = _nextId++;
|
||||
}
|
||||
Hero(this.name, [this.power = '']) : id = _nextId++;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// #docregion
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'hero.dart';
|
||||
|
||||
@Component(selector: 'hero-detail', templateUrl: 'hero_detail_component.html')
|
||||
@Component(
|
||||
selector: 'hero-detail',
|
||||
templateUrl: 'hero_detail_component.html')
|
||||
class HeroDetailComponent {
|
||||
@Input() Hero hero;
|
||||
@Input()
|
||||
Hero hero;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<h4>{{hero.name}} Detail</h4>
|
||||
<div>Id: {{hero.id}}</div>
|
||||
<div>Name:
|
||||
<!-- #docregion ng-model -->
|
||||
<input [(ngModel)]="hero.name"></div>
|
||||
<!-- #enddocregion ng-model -->
|
||||
<div>Power:<input [(ngModel)]="hero.power">
|
||||
<!-- #docregion ngModel -->
|
||||
<input [(ngModel)]="hero.name">
|
||||
<!-- #enddocregion ngModel -->
|
||||
</div>
|
||||
<div>Power:<input [(ngModel)]="hero.power"></div>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// #docplaster
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'hero.dart';
|
||||
|
@ -6,32 +5,31 @@ import 'hero_detail_component.dart';
|
|||
import 'hero_service.dart';
|
||||
|
||||
// #docregion metadata
|
||||
// #docregion providers
|
||||
@Component(
|
||||
// #enddocregion providers
|
||||
selector: 'hero-list',
|
||||
templateUrl: 'hero_list_component.html',
|
||||
directives: const [HeroDetailComponent],
|
||||
// #docregion providers
|
||||
providers: const [HeroService])
|
||||
// #enddocregion providers
|
||||
// #enddocregion metadata
|
||||
/*
|
||||
// #docregion metadata, providers
|
||||
class HeroListComponent { ... }
|
||||
// #enddocregion metadata, providers
|
||||
*/
|
||||
// #docregion providers
|
||||
providers: const [HeroService]
|
||||
// #enddocregion providers
|
||||
)
|
||||
// #docregion class
|
||||
class HeroListComponent {
|
||||
class HeroListComponent implements OnInit {
|
||||
// #enddocregion metadata
|
||||
List<Hero> heroes;
|
||||
Hero selectedHero;
|
||||
// #docregion ctor
|
||||
HeroListComponent(HeroService heroService) {
|
||||
heroes = heroService.getHeroes();
|
||||
// #docregion ctor
|
||||
final HeroService _heroService;
|
||||
|
||||
HeroListComponent(this._heroService);
|
||||
// #enddocregion ctor
|
||||
|
||||
void ngOnInit() {
|
||||
heroes = _heroService.getHeroes();
|
||||
}
|
||||
// #enddocregion ctor
|
||||
selectHero(Hero hero) {
|
||||
|
||||
void selectHero(Hero hero) {
|
||||
selectedHero = hero;
|
||||
}
|
||||
// #docregion metadata
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<!-- #docregion -->
|
||||
<h2>Hero List</h2>
|
||||
|
||||
<div *ngFor="let hero of heroes" (click)="selectHero(hero)">
|
||||
{{hero.name}}
|
||||
</div>
|
||||
<p><i>Pick a hero from the list</i></p>
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes" (click)="selectHero(hero)">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hero-detail *ngIf="selectedHero != null" [hero]="selectedHero"></hero-detail>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<!--#docregion binding -->
|
||||
<div ... >{{hero.name}}</div>
|
||||
<hero-detail ... [hero]="selectedHero"></hero-detail>
|
||||
<div ... (click)="selectHero(hero)">...</div>
|
||||
<li>{{hero.name}}</li>
|
||||
<hero-detail [hero]="selectedHero"></hero-detail>
|
||||
<li (click)="selectHero(hero)"></li>
|
||||
<!--#enddocregion binding -->
|
||||
|
||||
<!--#docregion structural -->
|
||||
<div *ngFor="let hero of heroes" ...>...</div>
|
||||
<hero-detail *ngIf="selectedHero != null" ...></hero-detail>
|
||||
<!--#enddocregion structural -->
|
||||
<li *ngFor="let hero of heroes"></li>
|
||||
<hero-detail *ngIf="selectedHero != null"></hero-detail>
|
||||
|
|
|
@ -4,16 +4,20 @@ import 'backend_service.dart';
|
|||
import 'hero.dart';
|
||||
import 'logger_service.dart';
|
||||
|
||||
// #docregion class
|
||||
@Injectable()
|
||||
// #docregion class
|
||||
class HeroService {
|
||||
final BackendService _backendService;
|
||||
final Logger _logger;
|
||||
HeroService(Logger this._logger, BackendService this._backendService);
|
||||
final List<Hero> heroes = [];
|
||||
|
||||
HeroService(this._logger, this._backendService);
|
||||
|
||||
List<Hero> getHeroes() {
|
||||
List<Hero> heroes = _backendService.getAll(Hero);
|
||||
_logger.log('Got ${heroes.length} heroes from the server.');
|
||||
_backendService.getAll(Hero).then((heroes) {
|
||||
_logger.log('Fetched ${heroes.length} heroes.');
|
||||
this.heroes.addAll(heroes); // fill cache
|
||||
});
|
||||
return heroes;
|
||||
}
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
// #docregion
|
||||
import 'dart:html';
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
/// A service for logging messages of various types.
|
||||
///
|
||||
/// We could switch this implementation to use package:logging.
|
||||
@Injectable()
|
||||
// #docregion class
|
||||
class Logger {
|
||||
void log(Object msg) => window.console.log(msg);
|
||||
|
||||
void error(Object msg) => window.console.error(msg);
|
||||
|
||||
void warn(Object msg) => window.console.warn(msg);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'sales_tax_service.dart';
|
||||
import 'tax_rate_service.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'sales-tax',
|
||||
template: '''
|
||||
<h2>Sales Tax Calculator</h2>
|
||||
Amount: <input #amountBox (change)="0">
|
||||
|
||||
<div *ngIf="amountBox.value != ''">
|
||||
The sales tax is
|
||||
{{ getTax(amountBox.value) | currency:'USD':false:'1.2-2' }}
|
||||
<!-- would like to write currency:'USD':true:'1.2-2' but
|
||||
currency as symbol is not currently supported; see
|
||||
https://github.com/dart-lang/intl/issues/59 -->
|
||||
</div>
|
||||
''',
|
||||
providers: const [SalesTaxService, TaxRateService])
|
||||
class SalesTaxComponent {
|
||||
SalesTaxService _salesTaxService;
|
||||
|
||||
SalesTaxComponent(this._salesTaxService) {}
|
||||
|
||||
num getTax(dynamic /* String | num */ value) =>
|
||||
this._salesTaxService.getVAT(value);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'tax_rate_service.dart';
|
||||
|
||||
@Injectable()
|
||||
class SalesTaxService {
|
||||
TaxRateService rateService;
|
||||
|
||||
SalesTaxService(this.rateService);
|
||||
|
||||
num getVAT(dynamic /* String | num */ value) =>
|
||||
rateService.getRate('VAT') *
|
||||
(value is num ? value : num.parse(value, (_) => 0));
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import 'package:angular2/core.dart';
|
||||
|
||||
@Injectable()
|
||||
class TaxRateService {
|
||||
getRate(String rateName) => 0.10;
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Intro to Angular 2</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script defer src="main.dart" type="application/dart"></script>
|
||||
<script defer src="packages/browser/dart.js"></script>
|
||||
</head>
|
||||
<head>
|
||||
<title>Architecture of Angular 2</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<script defer src="main.dart" type="application/dart"></script>
|
||||
<script defer src="packages/browser/dart.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<hero-list>Loading...</hero-list>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
// #docregion
|
||||
import 'package:angular2/platform/browser.dart';
|
||||
|
||||
// #docregion import
|
||||
import 'package:developer_guide_intro/app_component.dart';
|
||||
// #enddocregion import
|
||||
import 'package:developer_guide_intro/backend_service.dart';
|
||||
import 'package:developer_guide_intro/hero_list_component.dart';
|
||||
import 'package:developer_guide_intro/hero_service.dart';
|
||||
import 'package:developer_guide_intro/logger_service.dart';
|
||||
|
||||
main() {
|
||||
void main() {
|
||||
// #docregion bootstrap
|
||||
bootstrap(HeroListComponent, [BackendService, HeroService, Logger]);
|
||||
bootstrap(AppComponent, [BackendService, HeroService, Logger]);
|
||||
// #enddocregion bootstrap
|
||||
}
|
||||
|
|
|
@ -1,63 +1,99 @@
|
|||
/// <reference path='../_protractor/e2e.d.ts' />
|
||||
'use strict';
|
||||
describe('Architecture', function () {
|
||||
|
||||
let title = 'Hero List';
|
||||
const nameSuffix = 'X';
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
class Hero {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
describe('Architecture', () => {
|
||||
|
||||
const expectedTitle = 'Architecture of Angular 2';
|
||||
const expectedH2 = ['Hero List', 'Sales Tax Calculator'];
|
||||
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it(`has title '${expectedTitle}'`, () => {
|
||||
expect(browser.getTitle()).toEqual(expectedTitle);
|
||||
});
|
||||
|
||||
function itReset(name: string, func: () => any) {
|
||||
it(name, function() {
|
||||
browser.get('').then(func);
|
||||
});
|
||||
}
|
||||
|
||||
it(`should display correct title: ${title}`, function () {
|
||||
expect(element(by.css('h2')).getText()).toEqual(title);
|
||||
});
|
||||
|
||||
it('should display correct detail after selection', function() {
|
||||
let detailView = element(by.css('hero-detail'));
|
||||
expect(detailView.isPresent()).toBe(false);
|
||||
// select the 2nd element
|
||||
let selectEle = element.all(by.css('hero-list > div')).get(1);
|
||||
selectEle.click().then(function() {
|
||||
return selectEle.getText();
|
||||
}).then(function(selectedHeroName) {
|
||||
// works but too specific if we change the app
|
||||
// expect(selectedHeroName).toEqual('Mr. Nice');
|
||||
expect(detailView.isDisplayed()).toBe(true);
|
||||
let detailTitleEle = element(by.css('hero-detail > h4'));
|
||||
expect(detailTitleEle.getText()).toContain(selectedHeroName);
|
||||
});
|
||||
});
|
||||
|
||||
itReset('should display correct detail after modification', function() {
|
||||
let detailView = element(by.css('hero-detail'));
|
||||
expect(detailView.isPresent()).toBe(false);
|
||||
// select the 2nd element
|
||||
let selectEle = element.all(by.css('hero-list > div')).get(1);
|
||||
selectEle.click().then(function () {
|
||||
return selectEle.getText();
|
||||
}).then(function (selectedHeroName) {
|
||||
let detailTitleEle = element(by.css('hero-detail > h4'));
|
||||
expect(detailTitleEle.getText()).toContain(selectedHeroName);
|
||||
let heroNameEle = element.all(by.css('hero-detail input')).get(0);
|
||||
|
||||
// check that both the initial selected item and the detail title reflect changes
|
||||
// made to the input box.
|
||||
// heroNameEle.sendKeys('foo');
|
||||
sendKeys(heroNameEle, 'foo');
|
||||
expect(detailTitleEle.getText()).toContain('foo');
|
||||
expect(selectEle.getText()).toContain('foo');
|
||||
|
||||
// getText on an input element always returns null
|
||||
// http://stackoverflow.com/questions/20310442/how-to-gettext-on-an-input-in-protractor
|
||||
// expect(heroNameEle.getText()).toEqual(selectedHeroName);
|
||||
expect(heroNameEle.getAttribute('value')).toEqual(selectedHeroName + 'foo');
|
||||
});
|
||||
it(`has h2 '${expectedH2}'`, () => {
|
||||
let h2 = element.all(by.css('h2')).map((elt) => elt.getText());
|
||||
expect(h2).toEqual(expectedH2);
|
||||
});
|
||||
|
||||
describe('Hero', heroTests);
|
||||
describe('Salex tax', salesTaxTests);
|
||||
});
|
||||
|
||||
function heroTests() {
|
||||
|
||||
const targetHero: Hero = { id: 2, name: 'Mr. Nice' };
|
||||
|
||||
it('has the right number of heroes', () => {
|
||||
let page = getPageElts();
|
||||
expect(page.heroes.count()).toEqual(3);
|
||||
});
|
||||
|
||||
it('has no hero details initially', function () {
|
||||
let page = getPageElts();
|
||||
expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail');
|
||||
});
|
||||
|
||||
it('shows selected hero details', async () => {
|
||||
await element(by.cssContainingText('li', targetHero.name)).click();
|
||||
let page = getPageElts();
|
||||
let hero = await heroFromDetail(page.heroDetail);
|
||||
expect(hero.id).toEqual(targetHero.id);
|
||||
expect(hero.name).toEqual(targetHero.name);
|
||||
});
|
||||
|
||||
it(`shows updated hero name in details`, async () => {
|
||||
let input = element.all(by.css('input')).first();
|
||||
await sendKeys(input, nameSuffix);
|
||||
let page = getPageElts();
|
||||
let hero = await heroFromDetail(page.heroDetail);
|
||||
let newName = targetHero.name + nameSuffix;
|
||||
expect(hero.id).toEqual(targetHero.id);
|
||||
expect(hero.name).toEqual(newName);
|
||||
});
|
||||
}
|
||||
|
||||
function salesTaxTests() {
|
||||
it('has no sales tax initially', function () {
|
||||
let page = getPageElts();
|
||||
expect(page.salesTaxDetail.isPresent()).toBeFalsy('no sales tax info');
|
||||
});
|
||||
|
||||
it('shows sales tax', async function () {
|
||||
let page = getPageElts();
|
||||
await sendKeys(page.salesTaxAmountInput, '10');
|
||||
// Note: due to Dart bug USD is shown instead of $
|
||||
let re = /The sales tax is (\$|USD)1.00/;
|
||||
expect(page.salesTaxDetail.getText()).toMatch(re);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
function getPageElts() {
|
||||
return {
|
||||
heroes: element.all(by.css('my-app li')),
|
||||
heroDetail: element(by.css('my-app hero-detail')),
|
||||
salesTaxAmountInput: element(by.css('my-app sales-tax input')),
|
||||
salesTaxDetail: element(by.css('my-app sales-tax div'))
|
||||
};
|
||||
}
|
||||
|
||||
async function heroFromDetail(detail: protractor.ElementFinder): Promise<Hero> {
|
||||
// Get hero id from the first <div>
|
||||
let _id = await detail.all(by.css('div')).first().getText();
|
||||
// Get name from the h2
|
||||
let _name = await detail.element(by.css('h4')).getText();
|
||||
return {
|
||||
id: +_id.substr(_id.indexOf(' ') + 1),
|
||||
name: _name.substr(0, _name.lastIndexOf(' '))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<h4>{{hero.name}} Detail</h4>
|
||||
<div>Id: {{hero.id}}</div>
|
||||
<div>Name:
|
||||
<!-- #docregion ngModel -->
|
||||
<input [(ngModel)]="hero.name">
|
||||
<!-- #enddocregion ngModel -->
|
||||
<!-- #docregion ngModel -->
|
||||
<input [(ngModel)]="hero.name">
|
||||
<!-- #enddocregion ngModel -->
|
||||
</div>
|
||||
<div>Power:<input [(ngModel)]="hero.power"></div>
|
||||
<div>Power:<input [(ngModel)]="hero.power"></div>
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
<!--#docregion binding -->
|
||||
<div>{{hero.name}}</div>
|
||||
<li>{{hero.name}}</li>
|
||||
<hero-detail [hero]="selectedHero"></hero-detail>
|
||||
<div (click)="selectHero(hero)"></div>
|
||||
|
||||
<li (click)="selectHero(hero)"></li>
|
||||
<!--#enddocregion binding -->
|
||||
|
||||
<!--#docregion structural -->
|
||||
<div *ngFor="let hero of heroes"></div>
|
||||
<li *ngFor="let hero of heroes"></li>
|
||||
<hero-detail *ngIf="selectedHero"></hero-detail>
|
||||
|
||||
<!--#enddocregion structural -->
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
<h2>Hero List</h2>
|
||||
|
||||
<p><i>Pick a hero from the list</i></p>
|
||||
<div *ngFor="let hero of heroes" (click)="selectHero(hero)">
|
||||
{{hero.name}}
|
||||
</div>
|
||||
<ul>
|
||||
<li *ngFor="let hero of heroes" (click)="selectHero(hero)">
|
||||
{{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// #docplaster
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
@ -6,35 +5,28 @@ import { HeroDetailComponent } from './hero-detail.component';
|
|||
import { HeroService } from './hero.service';
|
||||
|
||||
// #docregion metadata
|
||||
// #docregion providers
|
||||
@Component({
|
||||
// #enddocregion providers
|
||||
selector: 'hero-list',
|
||||
templateUrl: 'app/hero-list.component.html',
|
||||
directives: [HeroDetailComponent],
|
||||
// #docregion providers
|
||||
// #docregion providers
|
||||
providers: [HeroService]
|
||||
// #enddocregion providers
|
||||
})
|
||||
// #enddocregion providers
|
||||
// #enddocregion metadata
|
||||
/*
|
||||
// #docregion metadata, providers
|
||||
export class HeroesComponent { ... }
|
||||
// #enddocregion metadata, providers
|
||||
*/
|
||||
// #docregion class
|
||||
export class HeroListComponent implements OnInit {
|
||||
// #enddocregion metadata
|
||||
heroes: Hero[];
|
||||
selectedHero: Hero;
|
||||
|
||||
// #docregion ctor
|
||||
// #docregion ctor
|
||||
constructor(private service: HeroService) { }
|
||||
// #enddocregion ctor
|
||||
// #enddocregion ctor
|
||||
|
||||
ngOnInit() {
|
||||
this.heroes = this.service.getHeroes();
|
||||
}
|
||||
|
||||
selectHero(hero: Hero) { this.selectedHero = hero; }
|
||||
// #docregion metadata
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -9,11 +9,9 @@ import { Logger } from './logger.service';
|
|||
export class HeroService {
|
||||
private heroes: Hero[] = [];
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
private backend: BackendService,
|
||||
private logger: Logger) { }
|
||||
// #enddocregion ctor
|
||||
|
||||
getHeroes() {
|
||||
this.backend.getAll(Hero).then( (heroes: Hero[]) => {
|
||||
|
@ -23,4 +21,3 @@ export class HeroService {
|
|||
return this.heroes;
|
||||
}
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
|
@ -8,4 +7,3 @@ export class Logger {
|
|||
error(msg: any) { console.error(msg); }
|
||||
warn(msg: any) { console.warn(msg); }
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { SalesTaxService } from './sales-tax.service';
|
||||
import { TaxRateService } from './tax-rate.service';
|
||||
|
||||
// #docregion metadata
|
||||
// #docregion providers
|
||||
@Component({
|
||||
// #enddocregion providers
|
||||
selector: 'sales-tax',
|
||||
template: `
|
||||
<h2>Sales Tax Calculator</h2>
|
||||
|
@ -19,24 +14,12 @@ import { TaxRateService } from './tax-rate.service';
|
|||
{{ getTax(amountBox.value) | currency:'USD':true:'1.2-2' }}
|
||||
</div>
|
||||
`,
|
||||
// #docregion providers
|
||||
providers: [SalesTaxService, TaxRateService]
|
||||
})
|
||||
// #enddocregion providers
|
||||
// #enddocregion metadata
|
||||
/*
|
||||
// #docregion metadata, providers
|
||||
export class SalesTaxComponent { ... }
|
||||
// #enddocregion metadata, providers
|
||||
*/
|
||||
// #docregion class
|
||||
export class SalesTaxComponent {
|
||||
// #docregion ctor
|
||||
constructor(private salesTaxService: SalesTaxService) { }
|
||||
// #enddocregion ctor
|
||||
|
||||
getTax(value: string | number) {
|
||||
return this.salesTaxService.getVAT(value);
|
||||
}
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { TaxRateService } from './tax-rate.service';
|
||||
|
||||
// #docregion class
|
||||
@Injectable()
|
||||
export class SalesTaxService {
|
||||
constructor(private rateService: TaxRateService) { }
|
||||
|
||||
getVAT(value: string | number) {
|
||||
let amount: number;
|
||||
if (typeof value === 'string') {
|
||||
amount = parseFloat(value);
|
||||
} else {
|
||||
amount = value;
|
||||
}
|
||||
let amount = (typeof value === 'string') ?
|
||||
parseFloat(value) : value;
|
||||
return (amount || 0) * this.rateService.getRate('VAT');
|
||||
}
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
// #docregion class
|
||||
@Injectable()
|
||||
export class TaxRateService {
|
||||
getRate(rateName: string) {return 0.10; } // always 10% everywhere
|
||||
getRate(rateName: string) { return 0.10; } // 10% everywhere
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<h3>Routed Movies</h3>
|
||||
<nav>
|
||||
<!-- #docregion router-link -->
|
||||
<a [routerLink]="['Movies']">Movies</a>
|
||||
<a [routerLink]="['/movies']">Movies</a>
|
||||
<!-- #enddocregion router-link -->
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router-deprecated';
|
||||
import { ROUTER_DIRECTIVES } from '@angular/router';
|
||||
|
||||
import { MovieListComponent } from './movie-list.component';
|
||||
import { MovieService } from './movie.service';
|
||||
|
@ -12,11 +12,8 @@ import { StringSafeDatePipe } from './date.pipe';
|
|||
styleUrls: ['app/app.component.css'],
|
||||
directives: [MovieListComponent, ROUTER_DIRECTIVES],
|
||||
pipes: [StringSafeDatePipe],
|
||||
providers: [MovieService, ROUTER_PROVIDERS]
|
||||
providers: [MovieService]
|
||||
})
|
||||
@RouteConfig([
|
||||
{path: '/movies', name: 'Movies', component: MovieListComponent, useAsDefault: true}
|
||||
])
|
||||
export class AppComponent {
|
||||
|
||||
angularDocsUrl = 'https://angular.io/';
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// #docregion
|
||||
import { provideRouter, RouterConfig } from '@angular/router';
|
||||
|
||||
import { MovieListComponent } from './movie-list.component';
|
||||
|
||||
const routes: RouterConfig = [
|
||||
{ path: '', redirectTo: '/movies', pathMatch: 'full' },
|
||||
{ path: 'movies', component: MovieListComponent }
|
||||
];
|
||||
|
||||
export const appRouterProviders = [
|
||||
provideRouter(routes)
|
||||
];
|
|
@ -0,0 +1,5 @@
|
|||
// #docregion
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
bootstrap(AppComponent);
|
|
@ -1,6 +1,8 @@
|
|||
// #docregion
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { appRouterProviders } from './app.routes';
|
||||
|
||||
bootstrap(AppComponent);
|
||||
bootstrap(AppComponent, [
|
||||
appRouterProviders
|
||||
]);
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// #docplaster
|
||||
// #docregion import
|
||||
import { Component } from '@angular/core';
|
||||
import { ROUTER_DIRECTIVES } from '@angular/router-deprecated';
|
||||
// #enddocregion import
|
||||
import { MovieService } from './movie.service';
|
||||
import { IMovie } from './movie';
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { provideRouter, RouterConfig } from '@angular/router';
|
||||
|
||||
const routes: RouterConfig = [];
|
||||
|
||||
export const appRouterProviders = [
|
||||
provideRouter(routes)
|
||||
];
|
|
@ -1,7 +1,7 @@
|
|||
// #docregion
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
import { XHRBackend } from '@angular/http';
|
||||
import { ROUTER_PROVIDERS } from '@angular/router-deprecated';
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
import { XHRBackend } from '@angular/http';
|
||||
import { appRouterProviders } from './app.routes';
|
||||
import { LocationStrategy,
|
||||
HashLocationStrategy } from '@angular/common';
|
||||
|
||||
|
@ -13,7 +13,7 @@ import { AppComponent } from './app.component';
|
|||
|
||||
// #docregion bootstrap
|
||||
bootstrap(AppComponent, [
|
||||
ROUTER_PROVIDERS,
|
||||
appRouterProviders,
|
||||
{ provide: LocationStrategy, useClass: HashLocationStrategy },
|
||||
|
||||
{ provide: XHRBackend, useClass: InMemoryBackendService }, // in-mem server
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { provideRouter, ROUTER_DIRECTIVES } from '@angular/router';
|
||||
import { routes } from './app.routes';
|
||||
// #docregion can-deactivate-guard
|
||||
import { CanDeactivateGuard } from './interfaces';
|
||||
// #enddocregion can-deactivate-guard
|
||||
|
||||
import { DialogService } from './dialog.service';
|
||||
import { HeroService } from './heroes/hero.service';
|
||||
|
||||
// Add these symbols to override the `LocationStrategy`
|
||||
import { LocationStrategy,
|
||||
HashLocationStrategy } from '@angular/common';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
// #docregion template
|
||||
template: `
|
||||
<h1 class="title">Component Router</h1>
|
||||
<nav>
|
||||
<a [routerLink]="['/crisis-center']">Crisis Center</a>
|
||||
<a [routerLink]="['/heroes']">Heroes</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
// #enddocregion template
|
||||
providers: [
|
||||
HeroService,
|
||||
DialogService,
|
||||
provideRouter(routes),
|
||||
CanDeactivateGuard,
|
||||
{ provide: LocationStrategy,
|
||||
useClass: HashLocationStrategy } // .../#/crisis-center/
|
||||
],
|
||||
directives: [ROUTER_DIRECTIVES]
|
||||
})
|
||||
export class AppComponent {
|
||||
}
|
|
@ -14,7 +14,7 @@ import { HeroDetailComponent } from './heroes/hero-detail.component';
|
|||
|
||||
// #docregion
|
||||
// #docregion route-config
|
||||
export const routes: RouterConfig = [
|
||||
const routes: RouterConfig = [
|
||||
// #docregion route-defs
|
||||
{ path: 'crisis-center', component: CrisisCenterComponent },
|
||||
{ path: 'heroes', component: HeroListComponent },
|
||||
|
|
|
@ -8,7 +8,7 @@ import { CrisisListComponent } from './crisis-list.component';
|
|||
import { HeroListComponent } from './hero-list.component';
|
||||
|
||||
// #docregion route-config
|
||||
export const routes: RouterConfig = [
|
||||
const routes: RouterConfig = [
|
||||
{ path: 'crisis-center', component: CrisisListComponent },
|
||||
{ path: 'heroes', component: HeroListComponent }
|
||||
];
|
||||
|
|
|
@ -28,6 +28,7 @@ import { AppComponent as S0704 } from '../07-04/app';
|
|||
import { AppComponent as S0901 } from '../09-01/app';
|
||||
|
||||
const routes: RouterConfig = [
|
||||
{ path: '', redirectTo: '/01-01', pathMatch: 'full' },
|
||||
{ path: '01-01', component: S0101 },
|
||||
{ path: '02-07', component: S0207 },
|
||||
{ path: '02-08', component: S0208 },
|
||||
|
@ -56,6 +57,6 @@ const routes: RouterConfig = [
|
|||
{ path: '09-01', component: S0901 },
|
||||
];
|
||||
|
||||
export const APP_ROUTER_PROVIDERS = [
|
||||
export const appRouterProviders = [
|
||||
provideRouter(routes)
|
||||
];
|
||||
|
|
|
@ -4,12 +4,12 @@ import { HashLocationStrategy, LocationStrategy } from '@angular/common';
|
|||
import { InMemoryBackendService, SEED_DATA } from 'angular2-in-memory-web-api';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { APP_ROUTER_PROVIDERS } from './app.routes';
|
||||
import { appRouterProviders } from './app.routes';
|
||||
import { HeroData } from './hero-data';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
bootstrap(AppComponent, [
|
||||
APP_ROUTER_PROVIDERS,
|
||||
appRouterProviders,
|
||||
HTTP_PROVIDERS,
|
||||
{ provide: LocationStrategy, useClass: HashLocationStrategy },
|
||||
{ provide: XHRBackend, useClass: InMemoryBackendService },
|
||||
|
|
|
@ -5,13 +5,10 @@ import { By } from '@angular/platform-browser';
|
|||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import {
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
async, inject
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/core/testing';
|
||||
|
||||
import { Hero, HeroService, MockHeroService } from './mock-hero.service';
|
||||
|
||||
|
@ -45,7 +42,7 @@ describe('AppComponent', () => {
|
|||
it('can get title from template', () => {
|
||||
fixture.detectChanges();
|
||||
let titleEl = fixture.debugElement.query(By.css('h1')).nativeElement;
|
||||
expect(titleEl).toHaveText(comp.title);
|
||||
expect(titleEl.textContent).toContain(comp.title);
|
||||
});
|
||||
|
||||
it('can get RouterLinks from template', () => {
|
||||
|
|
|
@ -19,13 +19,11 @@ import { DebugElement } from '@angular/core';
|
|||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import {
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
addProviders,
|
||||
async, inject
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/core/testing';
|
||||
|
||||
import { ViewMetadata } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
@ -143,20 +141,20 @@ xdescribe('async & inject testing errors', () => {
|
|||
restoreJasmineIt();
|
||||
}, 10000);
|
||||
|
||||
describe('using beforeEachProviders', () => {
|
||||
beforeEachProviders(() => [{ provide: FancyService, useValue: new FancyService() }]);
|
||||
describe('using addProviders', () => {
|
||||
addProviders([{ provide: FancyService, useValue: new FancyService() }]);
|
||||
|
||||
beforeEach(
|
||||
inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('real value'); }));
|
||||
|
||||
describe('nested beforeEachProviders', () => {
|
||||
describe('nested addProviders', () => {
|
||||
|
||||
it('should fail when the injector has already been used', () => {
|
||||
patchJasmineBeforeEach();
|
||||
expect(() => {
|
||||
beforeEachProviders(() => [{ provide: FancyService, useValue: new FancyService() }]);
|
||||
addProviders([{ provide: FancyService, useValue: new FancyService() }]);
|
||||
})
|
||||
.toThrowError('beforeEachProviders was called after the injector had been used ' +
|
||||
.toThrowError('addProviders was called after the injector had been used ' +
|
||||
'in a beforeEach or it block. This invalidates the test injector');
|
||||
restoreJasmineBeforeEach();
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Based on https://github.com/angular/angular/blob/master/modules/angular2/test/testing/testing_public_spec.ts
|
||||
/* tslint:disable */
|
||||
import {
|
||||
BadTemplateUrl, ButtonComp,
|
||||
ButtonComp,
|
||||
ChildChildComp, ChildComp, ChildWithChildComp,
|
||||
ExternalTemplateComp,
|
||||
FancyService, MockFancyService,
|
||||
|
@ -16,14 +16,12 @@ import { DebugElement } from '@angular/core';
|
|||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import {
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
async, inject,
|
||||
addProviders,
|
||||
inject, async,
|
||||
fakeAsync, tick, withProviders
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/core/testing';
|
||||
|
||||
import { ViewMetadata } from '@angular/core';
|
||||
|
||||
|
@ -31,47 +29,6 @@ import { Observable } from 'rxjs/Rx';
|
|||
|
||||
//////// SPECS /////////////
|
||||
|
||||
/// Verify can use Angular testing's DOM abstraction to access DOM
|
||||
|
||||
describe('angular2 jasmine matchers', () => {
|
||||
describe('toHaveCssClass', () => {
|
||||
it('should assert that the CSS class is present', () => {
|
||||
let el = document.createElement('div');
|
||||
el.classList.add('bombasto');
|
||||
expect(el).toHaveCssClass('bombasto');
|
||||
});
|
||||
|
||||
it('should assert that the CSS class is not present', () => {
|
||||
let el = document.createElement('div');
|
||||
el.classList.add('bombasto');
|
||||
expect(el).not.toHaveCssClass('fatias');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHaveCssStyle', () => {
|
||||
it('should assert that the CSS style is present', () => {
|
||||
let el = document.createElement('div');
|
||||
expect(el).not.toHaveCssStyle('width');
|
||||
|
||||
el.style.setProperty('width', '100px');
|
||||
expect(el).toHaveCssStyle('width');
|
||||
});
|
||||
|
||||
it('should assert that the styles are matched against the element', () => {
|
||||
let el = document.createElement('div');
|
||||
expect(el).not.toHaveCssStyle({width: '100px', height: '555px'});
|
||||
|
||||
el.style.setProperty('width', '100px');
|
||||
expect(el).toHaveCssStyle({width: '100px'});
|
||||
expect(el).not.toHaveCssStyle({width: '100px', height: '555px'});
|
||||
|
||||
el.style.setProperty('height', '555px');
|
||||
expect(el).toHaveCssStyle({height: '555px'});
|
||||
expect(el).toHaveCssStyle({width: '100px', height: '555px'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('using the async helper', () => {
|
||||
let actuallyDone = false;
|
||||
|
||||
|
@ -101,7 +58,7 @@ describe('using the async helper', () => {
|
|||
p.catch(() => { actuallyDone = true; });
|
||||
}));
|
||||
|
||||
it('should run async test with successful Observable', async(() => {
|
||||
xit('should run async test with successful Observable', async(() => {
|
||||
let source = Observable.of(true).delay(10);
|
||||
source.subscribe(
|
||||
val => {},
|
||||
|
@ -114,9 +71,11 @@ describe('using the async helper', () => {
|
|||
describe('using the test injector with the inject helper', () => {
|
||||
|
||||
describe('setting up Providers with FancyService', () => {
|
||||
beforeEachProviders(() => [
|
||||
{ provide: FancyService, useValue: new FancyService() }
|
||||
]);
|
||||
beforeEach(() => {
|
||||
addProviders([
|
||||
{ provide: FancyService, useValue: new FancyService() }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should use FancyService',
|
||||
inject([FancyService], (service: FancyService) => {
|
||||
|
@ -142,7 +101,7 @@ describe('using the test injector with the inject helper', () => {
|
|||
);
|
||||
})));
|
||||
|
||||
it('test should wait for FancyService.getObservableDelayValue',
|
||||
xit('test should wait for FancyService.getObservableDelayValue',
|
||||
async(inject([FancyService], (service: FancyService) => {
|
||||
service.getObservableDelayValue().subscribe(
|
||||
value => { expect(value).toEqual('observable delay value'); }
|
||||
|
@ -197,7 +156,7 @@ describe('test component builder', function() {
|
|||
|
||||
tcb.createAsync(ChildComp).then(fixture => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('Original Child');
|
||||
expect(fixture.nativeElement.textContent).toContain('Original Child');
|
||||
});
|
||||
})));
|
||||
|
||||
|
@ -206,11 +165,11 @@ describe('test component builder', function() {
|
|||
|
||||
tcb.createAsync(MyIfComp).then(fixture => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('MyIf()');
|
||||
expect(fixture.nativeElement.textContent).toContain('MyIf()');
|
||||
|
||||
fixture.debugElement.componentInstance.showMore = true;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('MyIf(More)');
|
||||
expect(fixture.nativeElement.textContent).toContain('MyIf(More)');
|
||||
});
|
||||
})));
|
||||
|
||||
|
@ -262,7 +221,7 @@ describe('test component builder', function() {
|
|||
.createAsync(MockChildComp)
|
||||
.then(fixture => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('Mock');
|
||||
expect(fixture.nativeElement.textContent).toContain('Mock');
|
||||
});
|
||||
})));
|
||||
|
||||
|
@ -276,7 +235,7 @@ describe('test component builder', function() {
|
|||
.createAsync(ChildComp)
|
||||
.then(fixture => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('Modified Child');
|
||||
expect(fixture.nativeElement.textContent).toContain('Modified Child');
|
||||
|
||||
});
|
||||
})));
|
||||
|
@ -288,7 +247,7 @@ describe('test component builder', function() {
|
|||
.createAsync(ParentComp)
|
||||
.then(fixture => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('Parent(Mock)');
|
||||
expect(fixture.nativeElement.textContent).toContain('Parent(Mock)');
|
||||
|
||||
});
|
||||
})));
|
||||
|
@ -302,8 +261,8 @@ describe('test component builder', function() {
|
|||
.createAsync(ParentComp)
|
||||
.then(fixture => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement)
|
||||
.toHaveText('Parent(Original Child(ChildChild Mock))');
|
||||
expect(fixture.nativeElement.textContent)
|
||||
.toContain('Parent(Original Child(ChildChild Mock))');
|
||||
|
||||
});
|
||||
})));
|
||||
|
@ -318,8 +277,8 @@ describe('test component builder', function() {
|
|||
.createAsync(TestProvidersComp)
|
||||
.then(fixture => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement)
|
||||
.toHaveText('injected value: mocked out value');
|
||||
expect(fixture.nativeElement.textContent)
|
||||
.toContain('injected value: mocked out value');
|
||||
});
|
||||
})));
|
||||
|
||||
|
@ -333,8 +292,8 @@ describe('test component builder', function() {
|
|||
.createAsync(TestViewProvidersComp)
|
||||
.then(fixture => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement)
|
||||
.toHaveText('injected value: mocked out value');
|
||||
expect(fixture.nativeElement.textContent)
|
||||
.toContain('injected value: mocked out value');
|
||||
});
|
||||
})));
|
||||
|
||||
|
@ -344,8 +303,8 @@ describe('test component builder', function() {
|
|||
tcb.createAsync(ExternalTemplateComp)
|
||||
.then(fixture => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement)
|
||||
.toHaveText('from external template\n');
|
||||
expect(fixture.nativeElement.textContent)
|
||||
.toContain('from external template\n');
|
||||
});
|
||||
})), 10000); // Long timeout because this test makes an actual XHR.
|
||||
|
||||
|
|
|
@ -4,13 +4,11 @@ import { DashboardComponent } from './dashboard.component';
|
|||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import {
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
addProviders,
|
||||
async, inject
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/core/testing';
|
||||
|
||||
import { Hero, HeroService, MockHeroService } from './mock-hero.service';
|
||||
import { Router, MockRouter } from './mock-router';
|
||||
|
@ -70,13 +68,13 @@ describe('DashboardComponent', () => {
|
|||
let comp: DashboardComponent;
|
||||
let mockHeroService: MockHeroService;
|
||||
|
||||
beforeEachProviders(() => {
|
||||
beforeEach(() => {
|
||||
mockHeroService = new MockHeroService();
|
||||
return [
|
||||
addProviders([
|
||||
{ provide: Router, useClass: MockRouter},
|
||||
{ provide: MockRouter, useExisting: Router},
|
||||
{ provide: HeroService, useValue: mockHeroService }
|
||||
];
|
||||
]);
|
||||
});
|
||||
|
||||
it('can instantiate it',
|
||||
|
@ -138,8 +136,8 @@ describe('DashboardComponent', () => {
|
|||
expect(heroNames.length).toEqual(4, 'should display 4 heroes');
|
||||
|
||||
// the 4th displayed hero should be the 5th mock hero
|
||||
expect(heroNames[3].nativeElement)
|
||||
.toHaveText(mockHeroService.mockHeroes[4].name);
|
||||
expect(heroNames[3].nativeElement.textContent)
|
||||
.toContain(mockHeroService.mockHeroes[4].name);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
// See https://github.com/angular/angular/issues/9017
|
||||
import { expect as expectCore } from '@angular/core/testing';
|
||||
import { NgMatchers } from '@angular/platform-browser/testing';
|
||||
|
||||
export function expect(spy: Function): NgMatchers;
|
||||
export function expect(actual: any): NgMatchers;
|
||||
export function expect(actual: any): NgMatchers {
|
||||
return expectCore(actual) as NgMatchers;
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
/* tslint:disable:no-unused-variable */
|
||||
import {
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
addProviders,
|
||||
async, inject, withProviders
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { TestComponentBuilder } from '@angular/core/testing';
|
||||
|
||||
import {
|
||||
MockBackend,
|
||||
|
@ -42,10 +40,12 @@ const makeResponseData = (data: {}) => {return { data }; };
|
|||
//////// SPECS /////////////
|
||||
describe('Http-HeroService (mockBackend)', () => {
|
||||
|
||||
beforeEachProviders(() => [
|
||||
HTTP_PROVIDERS,
|
||||
{ provide: XHRBackend, useClass: MockBackend }
|
||||
]);
|
||||
beforeEach(() => {
|
||||
addProviders([
|
||||
HTTP_PROVIDERS,
|
||||
{ provide: XHRBackend, useClass: MockBackend }
|
||||
]);
|
||||
});
|
||||
|
||||
it('can instantiate service when inject service',
|
||||
withProviders(() => [HeroService])
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"startPath": "unit-tests.html"
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// #docregion
|
||||
import { Hero } from './hero';
|
||||
|
||||
export var HEROES: Hero[] = [
|
||||
export const HEROES: Hero[] = [
|
||||
{id: 11, name: 'Mr. Nice'},
|
||||
{id: 12, name: 'Narco'},
|
||||
{id: 13, name: 'Bombasto'},
|
||||
|
|
|
@ -6,7 +6,7 @@ import { HeroesComponent } from './heroes.component';
|
|||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
// #enddocregion hero-detail-import
|
||||
|
||||
export const routes: RouterConfig = [
|
||||
const routes: RouterConfig = [
|
||||
// #docregion redirect-route
|
||||
{
|
||||
path: '',
|
||||
|
@ -32,6 +32,6 @@ export const routes: RouterConfig = [
|
|||
}
|
||||
];
|
||||
|
||||
export const APP_ROUTER_PROVIDERS = [
|
||||
export const appRouterProviders = [
|
||||
provideRouter(routes)
|
||||
];
|
||||
|
|
|
@ -9,6 +9,6 @@ const routes: RouterConfig = [
|
|||
}
|
||||
];
|
||||
|
||||
export const APP_ROUTER_PROVIDERS = [
|
||||
export const appRouterProviders = [
|
||||
provideRouter(routes)
|
||||
];
|
||||
|
|
|
@ -7,7 +7,7 @@ import { HeroesComponent } from './heroes.component';
|
|||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
// #enddocregion hero-detail-import
|
||||
|
||||
export const routes: RouterConfig = [
|
||||
const routes: RouterConfig = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/dashboard',
|
||||
|
@ -27,6 +27,6 @@ export const routes: RouterConfig = [
|
|||
}
|
||||
];
|
||||
|
||||
export const APP_ROUTER_PROVIDERS = [
|
||||
export const appRouterProviders = [
|
||||
provideRouter(routes)
|
||||
];
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { APP_ROUTER_PROVIDERS } from './app.routes';
|
||||
import { appRouterProviders } from './app.routes';
|
||||
|
||||
bootstrap(AppComponent, [
|
||||
APP_ROUTER_PROVIDERS
|
||||
appRouterProviders
|
||||
]);
|
||||
|
|
|
@ -5,7 +5,7 @@ import { DashboardComponent } from './dashboard.component';
|
|||
import { HeroesComponent } from './heroes.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
|
||||
export const routes: RouterConfig = [
|
||||
const routes: RouterConfig = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/dashboard',
|
||||
|
@ -25,6 +25,6 @@ export const routes: RouterConfig = [
|
|||
}
|
||||
];
|
||||
|
||||
export const APP_ROUTER_PROVIDERS = [
|
||||
export const appRouterProviders = [
|
||||
provideRouter(routes)
|
||||
];
|
||||
|
|
|
@ -12,20 +12,20 @@ import { bootstrap } from '@angular/platform-browser-dynamic';
|
|||
import { HTTP_PROVIDERS } from '@angular/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { APP_ROUTER_PROVIDERS } from './app.routes';
|
||||
import { appRouterProviders } from './app.routes';
|
||||
|
||||
// #enddocregion v1, final
|
||||
/*
|
||||
// #docregion v1
|
||||
bootstrap(AppComponent, [
|
||||
APP_ROUTER_PROVIDERS,
|
||||
appRouterProviders,
|
||||
HTTP_PROVIDERS
|
||||
]);
|
||||
// #enddocregion v1
|
||||
*/
|
||||
// #docregion final
|
||||
bootstrap(AppComponent, [
|
||||
APP_ROUTER_PROVIDERS,
|
||||
appRouterProviders,
|
||||
HTTP_PROVIDERS,
|
||||
{ provide: XHRBackend, useClass: InMemoryBackendService }, // in-mem server
|
||||
{ provide: SEED_DATA, useClass: InMemoryDataService } // in-mem server data
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"zone.js": "0.6.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular2-template-loader": "^0.4.0",
|
||||
"css-loader": "^0.23.1",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.8.5",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
!= partial("../../../_includes/_ts-temp")
|
|
@ -0,0 +1 @@
|
|||
!= partial("../../../_includes/_ts-temp")
|
|
@ -1,451 +1,72 @@
|
|||
include ../_util-fns
|
||||
extends ../../../ts/latest/guide/architecture.jade
|
||||
|
||||
block includes
|
||||
include ../_util-fns
|
||||
- var _library_module = 'library'
|
||||
- var _at_angular = 'angular2'
|
||||
|
||||
:marked
|
||||
Angular 2 is a framework to help us build client applications in HTML and
|
||||
either JavaScript or a language (like Dart or TypeScript) that compiles to JavaScript.
|
||||
Angular 2 for Dart is published as the `angular2` package, which
|
||||
(like many other Dart packages) is available via the Pub tool.
|
||||
|
||||
With Angular, we write applications by composing HTML *templates* with Angularized markup,
|
||||
writing *component* classes to manage those templates, adding application logic in *services*,
|
||||
and handing the top root component to Angular's *bootstrapper*.
|
||||
|
||||
Angular takes over, presenting our application content in a browser and responding to user interactions
|
||||
according to the instructions we provided.
|
||||
|
||||
<!-- figure img(src="/resources/images/devguide/architecture/airplane.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:10px" ) -->
|
||||
:marked
|
||||
Of course there is more to it than this.
|
||||
We're cruising at high altitude in this overview.
|
||||
We're looking for landmarks. We should expect the object below to be fuzzy and obscured by occasional clouds.
|
||||
Details become more clear and precise when we land in the chapters themselves.
|
||||
<br clear="all">
|
||||
|
||||
:marked
|
||||
An Angular 2 for Dart application rests on seven main building blocks:
|
||||
1. [Components](#component)
|
||||
1. [Templates](#template)
|
||||
1. [Metadata](#metadata)
|
||||
1. [Data binding](#data-binding)
|
||||
1. [Directives](#directive)
|
||||
1. [Services](#service)
|
||||
1. [Dependency injection](#dependency-injection)
|
||||
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/overview.png" alt="overview" style="margin-left:-40px;" width="700")
|
||||
:marked
|
||||
Learn these seven and we're on our way.
|
||||
|
||||
.l-main-section
|
||||
<a id="component"></a>
|
||||
:marked
|
||||
## Components
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/hero-component.png" alt="Component" align="left" style="width:200px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
A **component** controls a patch of screen real estate that we could call a *view*.
|
||||
A set of navigation links, a list of heroes, a hero editor ...
|
||||
they're all views controlled by components.
|
||||
|
||||
We define a component's application logic — what it does to support the view — inside a class.
|
||||
The class interacts with the view through an API of properties and methods.
|
||||
|
||||
<a id="component-code"></a>
|
||||
A `HeroListComponent`, for example, might have a `heroes` property that returns an array of heroes
|
||||
that it acquired from a service.
|
||||
It might have a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list.
|
||||
The component might be a class like this:
|
||||
|
||||
+makeExample('architecture/dart/lib/hero_list_component.dart', 'class', 'lib/hero_list_component.dart')
|
||||
:marked
|
||||
Angular creates, updates, and destroys components as the user moves through the application.
|
||||
The developer can take action at each moment in this lifecycle through optional lifecycle hooks.
|
||||
<!-- PENDING: What was that supposed to link to? -->
|
||||
|
||||
.l-sub-section
|
||||
block angular-parts
|
||||
:marked
|
||||
We may wonder who is calling the component's constructor? Who provides the service parameter?
|
||||
For the moment, have faith that Angular will call the constructor and deliver an
|
||||
appropriate `HeroService` when we need it.
|
||||
Angular 2 for Dart is published as the `angular2` package, which
|
||||
(like many other Dart packages) is available via the Pub tool.
|
||||
|
||||
.l-main-section
|
||||
<a id="template"></a>
|
||||
:marked
|
||||
## Templates
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/template.png" alt="Template" align="left" style="width:200px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
We define a component's view with its companion **template**. A template is a form of HTML
|
||||
that tells Angular how to render the component.
|
||||
block modules-in-dart
|
||||
.callout.is-helpful
|
||||
header Dart difference: Modules are compilation units or packages
|
||||
:marked
|
||||
In this guide, the term _module_ refers to a Dart compilation unit, such
|
||||
as a library, or a package. (If a Dart file has no `library` or `part`
|
||||
directive, then that file itself is a library and thus a compilation
|
||||
unit.) For more information about compilation units, see
|
||||
the chapter on "Libraries and Scripts" in the
|
||||
[Dart Language Specification](https://www.dartlang.org/docs/spec/).
|
||||
|
||||
A template looks like regular HTML much of the time ... and then it gets a bit strange. Here is a
|
||||
template for our `HeroListComponent`:
|
||||
+makeExample('architecture/dart/lib/hero_list_component.html', null, 'lib/hero_list_component.html')
|
||||
:marked
|
||||
This template features typical HTML elements like `<h2>` and `<div>`.
|
||||
But what are `*ngFor`, <code>{‌{hero.name}}</code>, `(click)`, `[hero]`, and `<hero-detail>`?
|
||||
They're examples of Angular's template syntax. <!-- TODO: link to template-syntax.html -->
|
||||
We'll grow accustomed to that syntax and may even learn to love it.
|
||||
block modules-are-optional
|
||||
//- N/A
|
||||
|
||||
Take a look at the last line,
|
||||
which has the `<hero-detail>` tag.
|
||||
That tag adds a custom element representing a component we haven't seen yet,
|
||||
a `HeroDetailComponent`.
|
||||
block export-qualifier
|
||||
.callout.is-helpful
|
||||
header Dart difference: Public names are exported by default
|
||||
:marked
|
||||
Contrary to TypeScript, a Dart library always exports all names and
|
||||
declarations in its **public** namespace, making explicit `export`
|
||||
qualifiers unnecessary.
|
||||
|
||||
When we say that a module _exports_ a declaration, we mean that the
|
||||
declaration is _public_. For more details about name spaces and export
|
||||
statements, see the section on "Exports" in the
|
||||
[Dart Language Specification](https://www.dartlang.org/docs/spec/).
|
||||
|
||||
The `HeroDetailComponent` is a *different* component than the `HeroListComponent` we've seen.
|
||||
The `HeroDetailComponent` (code not shown) presents facts about a particular hero, the
|
||||
hero that the user selects from the list presented by the `HeroListComponent`.
|
||||
The `HeroDetailComponent` is a **child** of the `HeroListComponent`.
|
||||
block ts-import
|
||||
//- N/A
|
||||
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/component-tree.png" alt="Metadata" align="left" style="width:300px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
Notice how `<hero-detail>` rests comfortably among native HTML elements.
|
||||
We can and _will_ mix our custom components with native HTML in the same layouts.
|
||||
|
||||
In this manner we'll compose complex component trees to build out our richly featured application.
|
||||
<br clear="all">
|
||||
|
||||
.l-main-section
|
||||
<a id="metadata"></a>
|
||||
:marked
|
||||
## Metadata
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/metadata.png" alt="Metadata" align="left" style="width:150px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
<p style="padding-top:10px">Metadata tells Angular how to process a class.</p>
|
||||
<br clear="all">
|
||||
:marked
|
||||
[Looking back at the code](#component-code) for `HeroListComponent`, we see that it's just a class.
|
||||
There is no evidence of a framework, no "Angular" in it at all.
|
||||
|
||||
In fact, it really is *just a class*. It's not a component until we *tell Angular about it*.
|
||||
|
||||
We tell Angular that `HeroListComponent` is a component by attaching **metadata** to the class.
|
||||
|
||||
In Dart, we attach metadata by using an **annotation**.
|
||||
Here's some metadata for `HeroListComponent`:
|
||||
|
||||
+makeExample('architecture/dart/lib/hero_list_component.dart', 'metadata', 'lib/hero_list_component.dart')
|
||||
:marked
|
||||
Here we see the `@Component` annotation, which (no surprise) identifies the class
|
||||
immediately below it as a component class.
|
||||
|
||||
Annotations often have configuration parameters.
|
||||
The `@Component` annotation takes parameters to provide the
|
||||
information Angular needs to create and present the component and its view.
|
||||
|
||||
Here we see a few of the possible `@Component` parameters:
|
||||
|
||||
* `selector`: A CSS selector that tells Angular to create and insert an instance of this component
|
||||
where it finds a `<hero-list>` tag in *parent* HTML.
|
||||
For example, if an app's HTML contains `<hero-list></hero-list>`, then
|
||||
Angular inserts an instance of the `HeroListComponent` view between those tags.
|
||||
|
||||
* `templateUrl`: The address of this component's template, which we showed [above](#the-template).
|
||||
|
||||
* `directives`: An array of the components or directives that *this* template requires.
|
||||
We saw in the last line of our template that we expect Angular to insert a `HeroDetailComponent`
|
||||
in the space indicated by `<hero-detail>` tags.
|
||||
Angular will do so only if we mention the `HeroDetailComponent` in this `directives` array.
|
||||
|
||||
* `providers`: An array of **dependency injection providers** for services that the component requires.
|
||||
This is one way to tell Angular that our component's constructor requires a `HeroService`
|
||||
so it can get the list of heroes to display. We'll get to dependency injection later.
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/template-metadata-component.png" alt="Metadata" align="left" style="height:200px; margin-left:-40px;margin-right:10px" )
|
||||
|
||||
:marked
|
||||
At runtime, Angular discovers the metadata specified by the `@Component`
|
||||
annotation. That's how Angular learns how to do "the right thing".
|
||||
|
||||
The template, metadata, and component together describe the view.
|
||||
|
||||
We apply other metadata annotations in a similar fashion to guide Angular behavior.
|
||||
`@Injectable`, `@Input`, `@Output`, and `@RouterConfig` are a few of the more popular annotations
|
||||
we'll master as our Angular knowledge grows.
|
||||
<br clear="all">
|
||||
:marked
|
||||
The architectural takeaway is that we must add metadata to our code
|
||||
so that Angular knows what to do.
|
||||
|
||||
.l-main-section
|
||||
<a id="data-binding"></a>
|
||||
:marked
|
||||
## Data binding
|
||||
Without a framework, we would be responsible for pushing data values into the HTML controls and turning user responses
|
||||
into actions and value updates. Writing such push/pull logic by hand is tedious, error-prone, and a nightmare to
|
||||
read as any experienced jQuery programmer can attest.
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/databinding.png" alt="Data Binding" style="width:220px; float:left; margin-left:-40px;margin-right:20px" )
|
||||
:marked
|
||||
Angular supports **data binding**,
|
||||
a mechanism for coordinating parts of a template with parts of a component.
|
||||
We add binding markup to the template HTML to tell Angular how to connect both sides.
|
||||
|
||||
There are four forms of data binding syntax. Each form has a direction — to the DOM, from the DOM, or in both directions —
|
||||
as indicated by the arrows in the diagram.
|
||||
<br clear="all">
|
||||
:marked
|
||||
We saw three forms of data binding in our [example](#template) template:
|
||||
+makeExample('architecture/dart/lib/hero_list_component_1.html', 'binding')(format=".")
|
||||
:marked
|
||||
* The <code>{‌{hero.name}}</code> [interpolation](displaying-data.html#interpolation)
|
||||
displays the component's `hero.name` property value within the `<div>` tags.
|
||||
|
||||
* The `[hero]` property binding <!-- TODO: link to template-syntax.html#property-binding-->
|
||||
passes the value of `selectedHero` from
|
||||
the parent `HeroListComponent` to the `hero` property of the child `HeroDetailComponent`.
|
||||
|
||||
* The `(click)` [event binding](user-input.html#click) calls the component's `selectHero` method when the user clicks a hero's name.
|
||||
|
||||
**Two-way data binding** is an important fourth form
|
||||
that combines property and event binding in a single notation, using the `ngModel` directive.
|
||||
We didn't have a two-way binding in the `HeroListComponent` template;
|
||||
here's an example from the `HeroDetailComponent` template:
|
||||
|
||||
+makeExample('architecture/dart/lib/hero_detail_component.html', 'ng-model', 'lib/hero_detail_component.html (excerpt)')(format=".")
|
||||
:marked
|
||||
In two-way binding, a data property value flows to the input box from the component as with property binding.
|
||||
The user's changes also flow back to the component, resetting the property to the latest value,
|
||||
as with event binding.
|
||||
|
||||
Angular processes *all* data bindings once per JavaScript event cycle,
|
||||
depth-first from the root of the application component tree.
|
||||
<!-- PENDING: clarify what "depth-first from the root" really means,
|
||||
or reassure that they'll learn it soon. -->
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/component-databinding.png" alt="Data Binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
We don't know all the details yet,
|
||||
but it's clear from these examples that data binding plays an important role in communication
|
||||
between a template and its component.
|
||||
<br clear="all">
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/parent-child-binding.png" alt="Parent/Child binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
Data binding is also important for communication between parent and child components.
|
||||
<br clear="all">
|
||||
|
||||
.l-main-section
|
||||
<a id="directive"></a>
|
||||
:marked
|
||||
## Directives
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/directive.png" alt="Parent child" style="float:left; width:150px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
Angular templates are *dynamic*. When Angular renders them, it transforms the DOM
|
||||
according to the instructions given by **directives**.
|
||||
|
||||
A directive is a class with directive metadata. In Dart we apply the `@Directive` annotation
|
||||
to attach metadata to the class.
|
||||
<br clear="all">
|
||||
:marked
|
||||
We already met one form of directive: the component. A component is a *directive-with-a-template*;
|
||||
a `@Component` annotation is actually a `@Directive` annotation extended with template-oriented features.
|
||||
|
||||
.l-sub-section
|
||||
block angular-library-modules
|
||||
:marked
|
||||
While **a component is technically a directive**,
|
||||
components are so distinctive and central to Angular applications that we chose
|
||||
to separate components from directives in this architectural overview.
|
||||
:marked
|
||||
Two *other* kinds of directives exist: _structural_ and _attribute_ directives.
|
||||
Angular ships as a collection of libraries within the
|
||||
[**angular2**](https://pub.dartlang.org/packages/angular2) package.
|
||||
|
||||
They tend to appear within an element tag like attributes,
|
||||
sometimes by name but more often as the target of an assignment or a binding.
|
||||
block angular-imports
|
||||
+makeExcerpt('app/app.component.ts', 'import')
|
||||
|
||||
**Structural** directives alter layout by adding, removing, and replacing elements in DOM.
|
||||
|
||||
Our [example](#template) template uses two built-in structural directives:
|
||||
+makeExample('architecture/dart/lib/hero_list_component_1.html', 'structural')(format=".")
|
||||
:marked
|
||||
* [`*ngFor`](displaying-data.html#ng-for) tells Angular to stamp out one `<div>` per hero in the `heroes` list.
|
||||
* [`*ngIf`](displaying-data.html#ng-if) includes the `HeroDetail` component only if a selected hero exists.
|
||||
|
||||
.l-sub-section
|
||||
block ts-decorator
|
||||
:marked
|
||||
In Dart, **the only value that is true is the boolean value `true`**; all
|
||||
other values are false. JavaScript and TypeScript, in contrast, treat values
|
||||
such as 1 and most non-null objects as true. For this reason, the JavaScript
|
||||
and TypeScript versions of this app can use just `selectedHero` as the value
|
||||
of the `*ngIf` expression. The Dart version must use a boolean operator such
|
||||
as `!=` instead.
|
||||
Annotations often have configuration parameters.
|
||||
The `@Component` annotation takes parameters to provide the
|
||||
information Angular needs to create and present the component and its view.
|
||||
|
||||
:marked
|
||||
**Attribute** directives alter the appearance or behavior of an existing element.
|
||||
In templates they look like regular HTML attributes, hence the name.
|
||||
Here are a few of the possible `@Component` parameters:
|
||||
|
||||
The `ngModel` directive, which implements two-way data binding, is
|
||||
an example of an attribute directive. `ngModel` modifies the behavior of
|
||||
an existing element (typically an `<input>`)
|
||||
by setting its display value property and responding to change events.
|
||||
|
||||
+makeExample('architecture/dart/lib/hero_detail_component.html', 'ng-model')(format=".")
|
||||
:marked
|
||||
Angular ships with a small number of other directives that either alter the layout structure
|
||||
(for example, `ngSwitch`) <!-- TODO: link to template-syntax.html#ng-switch -->
|
||||
or modify aspects of DOM elements and components
|
||||
(for example, `ngStyle` and `ngClass`).
|
||||
<!-- PENDING: link to template-syntax.html#ng-style template-syntax.html#ng-class-->
|
||||
|
||||
Of course, we can also write our own directives. Components such as
|
||||
`HeroListComponent` are one kind of custom directive.
|
||||
<!-- PENDING: link to where to learn more about other kinds! -->
|
||||
|
||||
.l-main-section
|
||||
<a id="service"></a>
|
||||
:marked
|
||||
## Services
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/service.png" alt="Service" style="float:left; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
_Services_ is a broad category encompassing any value, function, or feature that our application needs.
|
||||
|
||||
Almost anything can be a service.
|
||||
A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.
|
||||
<br clear="all">
|
||||
:marked
|
||||
Examples include:
|
||||
* logging service
|
||||
* data service
|
||||
* message bus
|
||||
* tax calculator
|
||||
* application configuration
|
||||
|
||||
There is nothing specifically _Angular_ about services. Angular itself has no definition of a service.
|
||||
There is no service base class, and no place to register a service.
|
||||
|
||||
Yet services are fundamental to any Angular application. Our components are big consumers of services.
|
||||
|
||||
We prefer our component classes lean. Our components don't fetch data from the server,
|
||||
they don't validate user input, and they don't log directly to console. They delegate such tasks to services.
|
||||
|
||||
A component's job is to enable the user experience and nothing more. It mediates between the view (rendered by the template)
|
||||
and the application logic (which often includes some notion of a _model_).
|
||||
A good component presents properties and methods for data binding.
|
||||
It delegates everything nontrivial to services.
|
||||
|
||||
Angular doesn't *enforce* these principles.
|
||||
It won't complain if we write a "kitchen sink" component with 3000 lines.
|
||||
|
||||
Angular does help us *follow* these principles by making it easy to factor our
|
||||
application logic into services and make those services available to components through *dependency injection*.
|
||||
|
||||
.l-main-section
|
||||
<a id="dependency-injection"></a>
|
||||
:marked
|
||||
## Dependency injection
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/dependency-injection.png" alt="Service" style="float:left; width:200px; margin-left:-40px;margin-right:10px" )
|
||||
:marked
|
||||
Dependency injection is a way to supply a new instance of a class
|
||||
with the fully-formed dependencies it requires. Most dependencies are services.
|
||||
Angular uses dependency injection to provide new components with the services they need.
|
||||
<br clear="all">
|
||||
:marked
|
||||
Angular can tell which services a component needs by looking at the types of its constructor parameters.
|
||||
For example, the constructor of our `HeroListComponent` needs a `HeroService`:
|
||||
+makeExample('architecture/dart/lib/hero_list_component.dart', 'ctor', 'lib/hero_list_component.dart (excerpt)')(format='.')
|
||||
:marked
|
||||
When Angular creates a component, it first asks an **injector** for
|
||||
the services that the component requires.
|
||||
|
||||
An injector maintains a container of service instances that it has previously created.
|
||||
If a requested service instance is not in the container, the injector makes one and adds it to the container
|
||||
before returning the service to Angular.
|
||||
When all requested services have been resolved and returned,
|
||||
Angular can call the component's constructor with those services as arguments.
|
||||
This is what we mean by *dependency injection*.
|
||||
|
||||
The process of `HeroService` injection looks a bit like this:
|
||||
figure
|
||||
img(src="/resources/images/devguide/architecture/injector-injects.png" alt="Service" )
|
||||
:marked
|
||||
If the injector doesn't have a `HeroService`, how does it know how to make one?
|
||||
|
||||
In brief, we must have previously registered a **provider** of the `HeroService` with the injector.
|
||||
A provider is something that can create or return a service, typically the service class itself.
|
||||
|
||||
We can register providers at any level of the application component tree.
|
||||
We often do so at the root when we bootstrap the application so that
|
||||
the same instance of a service is available everywhere.
|
||||
+makeExample('architecture/dart/web/main.dart', 'bootstrap', 'web/main.dart (excerpt)')(format='.')
|
||||
:marked
|
||||
Alternatively, we might register at a component level:
|
||||
+makeExample('architecture/dart/lib/hero_list_component.dart', 'providers', 'lib/hero_list_component.dart (excerpt)')(format='.')
|
||||
:marked
|
||||
Registering at a component level means we get a new instance of the
|
||||
service with each new instance of that component.
|
||||
|
||||
<!-- We've vastly oversimplified dependency injection for this overview.
|
||||
The full story is in the [Dependency Injection](dependency-injection.html) chapter. -->
|
||||
|
||||
Points to remember about dependency injection:
|
||||
|
||||
* Dependency injection is wired into the Angular framework and used everywhere.
|
||||
|
||||
* The *injector* is the main mechanism.
|
||||
* An injector maintains a *container* of service instances that it created.
|
||||
* An injector can create a new service instance from a *provider*.
|
||||
|
||||
* A *provider* is a recipe for creating a service.
|
||||
|
||||
* We register *providers* with injectors.
|
||||
|
||||
<a id="other-stuff"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## Other stuff
|
||||
|
||||
We've learned just a bit about the seven main building blocks of an Angular application:
|
||||
1. [Components](#component)
|
||||
1. [Templates](#template)
|
||||
1. [Metadata](#metadata)
|
||||
1. [Data binding](#data-binding)
|
||||
1. [Directives](#directive)
|
||||
1. [Services](#service)
|
||||
1. [Dependency injection](#dependency-injection)
|
||||
|
||||
That's a foundation for everything else in an Angular application,
|
||||
and it's more than enough to get going.
|
||||
But it doesn't include everything we'll need or want to know.
|
||||
|
||||
Here is a brief, alphabetical list of other important Angular features and services.
|
||||
Most of them are covered in this Developers Guide (or soon will be).
|
||||
|
||||
>**Animations:** A forthcoming animation library makes it easy for developers to animate component behavior
|
||||
without deep knowledge of animation techniques or CSS.
|
||||
|
||||
>**Bootstrap:** A method to configure and launch the root application component.
|
||||
|
||||
>**Change detection:** Learn how Angular decides that a component property value has changed and
|
||||
when to update the screen.
|
||||
Learn how it uses **zones** to intercept asynchronous activity and run its change detection strategies.
|
||||
|
||||
>**Component router:** With the component Router service, users can navigate a multi-screen application
|
||||
in a familiar web browsing style using URLs.
|
||||
|
||||
>**Events:** The DOM raises events. So can components and services. Angular offers mechanisms for
|
||||
publishing and subscribing to events including an implementation of the [RxJS Observable](https://github.com/zenparsing/es-observable) proposal.
|
||||
|
||||
>**[Forms](forms.html):** Support complex data entry scenarios with HTML-based validation and dirty checking.
|
||||
|
||||
>**HTTP:** Communicate with a server to get data, save data, and invoke server-side actions with this Angular HTTP client.
|
||||
|
||||
>**Lifecycle hooks:** We can tap into key moments in the lifetime of a component, from its creation to its destruction,
|
||||
by implementing the lifecycle hook interfaces.
|
||||
|
||||
>**Pipes:** Services that transform values for display.
|
||||
We can put pipes in our templates to improve the user experience. Consider
|
||||
this `currency` pipe expression:
|
||||
<div style="margin-left:40px">
|
||||
code-example(language="javascript" linenumbers=".").
|
||||
price | currency:'USD':true'
|
||||
</div>
|
||||
:marked
|
||||
>It displays a price of "42.33" as `$42.33`.
|
||||
|
||||
>**Testing:** Angular provides a
|
||||
[testing library](https://pub.dartlang.org/packages/angular2_testing)
|
||||
to run unit tests on our application parts as they interact with the Angular framework.
|
||||
block dart-bool
|
||||
.callout.is-helpful
|
||||
header Dart difference: Only true is true
|
||||
:marked
|
||||
In Dart, **the only value that is true is the boolean value `true`**; all
|
||||
other values are false. JavaScript and TypeScript, in contrast, treat values
|
||||
such as 1 and most non-null objects as true. For this reason, the JavaScript
|
||||
and TypeScript versions of this app can use just `selectedHero` as the value
|
||||
of the `*ngIf` expression. The Dart version must use a boolean operator such
|
||||
as `!=` instead.
|
||||
|
|
|
@ -19,7 +19,7 @@ block css-import-url
|
|||
.alert.is-important
|
||||
:marked
|
||||
URLs are currently not interpreted in this way, see
|
||||
[issue 8518](href="https://github.com/angular/angular/issues/8518").
|
||||
[issue 8518](https://github.com/angular/angular/issues/8518).
|
||||
Until this issue is fixed, absolute package-reference style URLs must
|
||||
be given as is illustrated below.
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ block dart-map-alternative
|
|||
As an alternative to using a configuration `Map`, we can define
|
||||
a custom configuration class:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/app.config.ts','config-alt','app/app-config.ts (alternative config)')(format='.')
|
||||
+makeExample('lib/app_config.dart (alternative config)','config-alt')
|
||||
|
||||
:marked
|
||||
Defining a configuration class has a few benefits. One key benefit
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
!= partial("../../../_includes/_ts-temp")
|
|
@ -517,7 +517,7 @@ figure.image-display
|
|||
Recall from the previous section that `ngControl` registered this input box with the
|
||||
`NgForm` directive as "name".
|
||||
|
||||
We didn't add the **[`NgForm`](../api/common/NgForm-directive.html) directive*
|
||||
We didn't add the **[`NgForm`](../api/common/index/NgForm-directive.html) directive*
|
||||
explicitly. Angular added it surreptitiously, wrapping it around the `<form>` element.
|
||||
|
||||
The `NgForm` directive supplements the `<form>` element with additional features.
|
||||
|
|
|
@ -1 +1 @@
|
|||
!= partial("../glossary")
|
||||
include ../glossary
|
||||
|
|
|
@ -33,7 +33,7 @@ block http-providers
|
|||
block getheroes-and-addhero
|
||||
:marked
|
||||
The hero service `getHeroes()` and `addHero()` asynchronous methods return the
|
||||
[`Future`](https://api.dartlang.org/stable/1.16.0/dart-async/Future-class.html)
|
||||
[`Future`](https://api.dartlang.org/stable/dart-async/Future-class.html)
|
||||
values of the current hero list and the newly added hero,
|
||||
respectively. The hero list component methods of the same name specifying
|
||||
the actions to be taken when the asynchronous method calls succeed or fail.
|
||||
|
|
|
@ -5,7 +5,7 @@ block includes
|
|||
- var _JavaScript = 'Dart';
|
||||
- var __chaining_op = '<code>;</code>';
|
||||
- var __new_op = '<code>new</code> or <code>const</code>';
|
||||
- var mapApiRef = 'https://api.dartlang.org/stable/1.16.0/dart-core/Map-class.html';
|
||||
- var mapApiRef = 'https://api.dartlang.org/stable/dart-core/Map-class.html';
|
||||
- var __objectAsMap = '<b><a href="' + mapApiRef + '">Map</a></b>'
|
||||
|
||||
block notable-differences
|
||||
|
@ -69,7 +69,7 @@ block style-property-name-dart-diff
|
|||
and `setProperty()`. Hence, we recommend only using dash-case for style
|
||||
property names.
|
||||
|
||||
[CssSD]: https://api.dartlang.org/stable/1.16.1/dart-html/CssStyleDeclaration-class.html
|
||||
[CssSD]: https://api.dartlang.org/stable/dart-html/CssStyleDeclaration-class.html
|
||||
|
||||
|
||||
block dart-no-truthy-falsey
|
||||
|
|
|
@ -17,8 +17,8 @@ block setup-tooling
|
|||
You can also download [Dart plugins for other IDEs and editors][DT].
|
||||
|
||||
[WS]: https://confluence.jetbrains.com/display/WI/Getting+started+with+Dart
|
||||
[DT]: https://www.dartlang.org/tools
|
||||
[pub]: https://www.dartlang.org/tools/pub
|
||||
[DT]: https://www.dartlang.org/tools/
|
||||
[pub]: https://www.dartlang.org/tools/pub/
|
||||
|
||||
block download-source
|
||||
// exclude this section from Dart
|
||||
|
|
|
@ -132,9 +132,6 @@ code-example(language="bash").
|
|||
|
||||
### Add a base tag
|
||||
|
||||
// Our Tour of Heroes needs routing,
|
||||
// so we load the library in the `index.html` in a script tag immediately *after* the angular script itself.
|
||||
//+makeExample('toh-5/dart/web/index.html', 'router', 'index.html (router)')(format=".")
|
||||
:marked
|
||||
First, edit `index.html` and add `<base href="/">` at the top of the `<head>` section.
|
||||
+makeExample('toh-5/dart/web/index.html', 'base-href', 'index.html (base href)')(format=".")
|
||||
|
@ -442,7 +439,7 @@ code-example(format='').
|
|||
:marked
|
||||
Going back too far could take us out of the application.
|
||||
That's acceptable in a demo. We'd guard against it in a real application,
|
||||
perhaps with the [*routerCanDeactivate* hook](../api/router/CanDeactivate-interface.html).
|
||||
perhaps with the [*routerCanDeactivate* hook](../api/router/index/CanDeactivate-interface.html).
|
||||
:marked
|
||||
Then we wire this method with an event binding to a *Back* button that we add to the bottom of the component template.
|
||||
+makeExample('toh-5/dart/lib/hero_detail_component.html', 'back-button')(format=".")
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
!= partial("../../../_includes/_ts-temp")
|
|
@ -0,0 +1 @@
|
|||
!= partial("../../../_includes/_ts-temp")
|
|
@ -493,7 +493,7 @@ figure.image-display
|
|||
:marked
|
||||
### The NgForm directive
|
||||
We just set a template local variable with the value of an `NgForm` directive.
|
||||
Why did that work? We didn't add the **[`NgForm`](../api/common/NgForm-directive.html) directive** explicitly.
|
||||
Why did that work? We didn't add the **[`NgForm`](../api/common/index/NgForm-directive.html) directive** explicitly.
|
||||
|
||||
Angular added it surreptitiously, wrapping it around the `<form>` element
|
||||
|
||||
|
|
|
@ -491,7 +491,7 @@ figure.image-display
|
|||
.l-sub-section
|
||||
:marked
|
||||
Why "ngModel"?
|
||||
A directive's [exportAs](../api/core/DirectiveMetadata-class.html#!#exportAs) property
|
||||
A directive's [exportAs](../api/core/index/DirectiveMetadata-class.html#!#exportAs) property
|
||||
tells Angular how to link the reference variable to the directive.
|
||||
We set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel".
|
||||
|
||||
|
@ -503,7 +503,7 @@ figure.image-display
|
|||
:marked
|
||||
### The NgForm directive
|
||||
We just set a template local variable with the value of an `NgForm` directive.
|
||||
Why did that work? We didn't add the **[`NgForm`](../api/common/NgForm-directive.html) directive** explicitly.
|
||||
Why did that work? We didn't add the **[`NgForm`](../api/common/index/NgForm-directive.html) directive** explicitly.
|
||||
|
||||
Angular added it surreptitiously, wrapping it around the `<form>` element
|
||||
|
||||
|
|
|
@ -227,7 +227,7 @@ table(width="100%")
|
|||
|
||||
### 引导
|
||||
|
||||
+makeExample('cb-a1-a2-quick-reference/ts/app/main.ts')(format="." )
|
||||
+makeExample('cb-a1-a2-quick-reference/ts/app/main.1.ts')(format="." )
|
||||
:marked
|
||||
Angular 2 does not have a bootstrap directive.
|
||||
We always launch the app in code by explicitly calling a bootstrap function
|
||||
|
|
|
@ -58,7 +58,7 @@ code-example(format='')
|
|||
## 使用*Title*服务
|
||||
|
||||
Fortunately, Angular 2 bridges the gap by providing a `Title` service as part of the *Browser platform*.
|
||||
The [Title](../api/platform/browser/Title-class.html) service is a simple class that provides an API
|
||||
The [Title](../api/platform-browser/index/Title-class.html) service is a simple class that provides an API
|
||||
for getting and setting the current HTML document title:
|
||||
|
||||
幸运的是,Angular 2在*浏览器平台*的包中,提供了一个`Title`服务,弥补了这个差别。
|
||||
|
|
|
@ -73,7 +73,8 @@ include _util-fns
|
|||
Imagine three modules in a `heroes` folder:
|
||||
|
||||
设想在`heroes`目录下有三个模块:
|
||||
code-example(format='').
|
||||
|
||||
code-example.
|
||||
// heroes/hero.component.ts
|
||||
export class HeroComponent {}
|
||||
|
||||
|
@ -86,7 +87,8 @@ include _util-fns
|
|||
Without a barrel, a consumer would need three import statements:
|
||||
|
||||
假如没有封装桶,消费者就需要三条import语句:
|
||||
code-example(format='').
|
||||
|
||||
code-example.
|
||||
import { HeroComponent } from '../heroes/hero.component.ts';
|
||||
import { Hero } from '../heroes/hero.model.ts';
|
||||
import { HeroService } from '../heroes/hero.service.ts';
|
||||
|
@ -94,7 +96,8 @@ include _util-fns
|
|||
We can add a barrel to the `heroes` folder (called `index` by convention) that exports all of these items:
|
||||
|
||||
在`heroes`目录下添加一个封装桶(按规约叫做`index`),它导出所有这三条:
|
||||
code-example(format='').
|
||||
|
||||
code-example.
|
||||
export * from './hero.model.ts'; // re-export all of its exports
|
||||
export * from './hero.service.ts'; // re-export all of its exports
|
||||
export { HeroComponent } from './hero.component.ts'; // re-export the named thing
|
||||
|
@ -102,7 +105,8 @@ include _util-fns
|
|||
Now a consumer can import what it needs from the barrel.
|
||||
|
||||
现在,消费者就就可以从这个封装桶中导入它需要的东西了。
|
||||
code-example(format='').
|
||||
|
||||
code-example.
|
||||
import { Hero, HeroService } from '../heroes'; // index is implied
|
||||
:marked
|
||||
The Angular [scoped packages](#scoped-package) each have a barrel named `index`.
|
||||
|
@ -113,7 +117,8 @@ include _util-fns
|
|||
That's why we can write this:
|
||||
|
||||
这就是为什么可以这样写的原因:
|
||||
+makeExample('../docs/_fragments/quickstart/ts/app/app.component.ts', 'import')(format=".")
|
||||
|
||||
+makeExcerpt('quickstart/ts/app/app.component.ts', 'import', '')
|
||||
// #docregion b-c
|
||||
|
||||
:marked
|
||||
|
@ -862,8 +867,8 @@ include _util-fns
|
|||
使用和导入*普通*包相同的方式导入范围化包。
|
||||
从消费者的视角看,唯一的不同是那些包的名字是用Angular的*范围名*`@angular`开头儿的。
|
||||
|
||||
+makeExample('../docs/_fragments/architecture/ts/app/app.component.ts', 'import')(format=".")
|
||||
// #docregion n-s
|
||||
+makeExcerpt('architecture/ts/app/app.component.ts', 'import', '')
|
||||
// #docregion n-s-2
|
||||
|
||||
:marked
|
||||
## Structural Directive
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -806,10 +806,27 @@ code-example(format="nocode").
|
|||
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-1')
|
||||
|
||||
p
|
||||
| This is actually a short-hand expression for a provider registration
|
||||
block canonical-provider-expr
|
||||
| using a <i>provider</i> object literal with two properties:
|
||||
:marked
|
||||
This is actually a short-hand expression for a provider registration
|
||||
|
||||
这实际上是注册提供商的一种简写表达式。
|
||||
|
||||
<span if-docs="ts">
|
||||
using a _provider_ object literal with two properties:
|
||||
</span>
|
||||
|
||||
<span if-docs="ts">
|
||||
使用一个拥有两个属性的_provider_对象:
|
||||
</span>
|
||||
|
||||
<span if-docs="dart">
|
||||
that creates a new instance of the
|
||||
[Provider](../api/core/index/Provider-class.html) class:
|
||||
</span>
|
||||
|
||||
<span if-docs="dart">
|
||||
创建[Provider](../api/core/index/Provider-class.html)类的一个实例:
|
||||
</span>
|
||||
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-3')
|
||||
|
||||
|
|
|
@ -380,7 +380,7 @@ figure.image-display
|
|||
We'll talk about `NgForm` [later in the chapter](#ngForm).
|
||||
|
||||
The `ngControl` *attribute* in our template actually maps to the
|
||||
[NgControlName](../api/common/NgControlName-directive.html) directive.
|
||||
[NgControlName](../api/common/index/NgControlName-directive.html) directive.
|
||||
There is also a `NgControl` *abstract* directive which is *not the same thing*.
|
||||
We often ignore this technical distinction and refer to `NgControlName` more conveniently (albeit incorrectly) as the *NgControl* directive.
|
||||
|
||||
|
@ -483,7 +483,7 @@ figure.image-display
|
|||
.l-sub-section
|
||||
:marked
|
||||
Why "ngForm"?
|
||||
A directive's [exportAs](../api/core/DirectiveMetadata-class.html#!#exportAs) property
|
||||
A directive's [exportAs](../api/core/index/DirectiveMetadata-class.html#!#exportAs) property
|
||||
tells Angular how to link the reference variable to the directive.
|
||||
We set `name` to `ngForm` because the `NgControlName` directive's `exportAs` property happens to be "ngForm".
|
||||
|
||||
|
@ -589,7 +589,7 @@ figure.image-display
|
|||
.l-sub-section
|
||||
:marked
|
||||
### The NgForm directive
|
||||
What `NgForm` directive? We didn't add an [NgForm](../api/common/NgForm-directive.html) directive!
|
||||
What `NgForm` directive? We didn't add an [NgForm](../api/common/index/NgForm-directive.html) directive!
|
||||
|
||||
Angular did. Angular creates and attaches an `NgForm` directive to the `<form>` tag automatically.
|
||||
|
||||
|
|
|
@ -1064,7 +1064,7 @@ figure.image-display
|
|||
|
||||
### NgForm指令
|
||||
|
||||
What `NgForm` directive? We didn't add an [NgForm](../api/common/NgForm-directive.html) directive!
|
||||
What `NgForm` directive? We didn't add an [NgForm](../api/common/index/NgForm-directive.html) directive!
|
||||
|
||||
什么`NgForm`指令?我们没有添加过[NgForm](../api/common/index/NgForm-directive.html)指令啊!
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
!= partial("../glossary")
|
||||
include ../glossary
|
||||
|
|
|
@ -52,7 +52,7 @@ block includes
|
|||
|
||||
:marked
|
||||
Inside the interpolation expression we flow the component's `birthday` value through the
|
||||
[pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/DatePipe-class.html)
|
||||
[pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/index/DatePipe-class.html)
|
||||
function on the right. All pipes work this way.
|
||||
|
||||
在这个插值表达式中,我们让组件的`birthday`值通过[管道操作符](./template-syntax.html#pipe)( | )流动到
|
||||
|
@ -140,7 +140,7 @@ figure.image-display
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about the `DatePipes` format options in the [API Docs](../api/common/DatePipe-class.html).
|
||||
Learn more about the `DatePipes` format options in the [API Docs](../api/common/index/DatePipe-class.html).
|
||||
|
||||
要了解更多`DatePipes`的格式选项,请参阅[API文档](../api/common/index/DatePipe-class.html)。
|
||||
|
||||
|
@ -624,7 +624,7 @@ figure.image-display
|
|||
header Debugging with the json pipe
|
||||
header 借助json管道进行调试
|
||||
:marked
|
||||
The [JsonPipe](../api/common/JsonPipe-class.html)
|
||||
The [JsonPipe](../api/common/index/JsonPipe-class.html)
|
||||
provides an easy way to diagnosis a mysteriously failing data binding or
|
||||
inspect an object for future binding.
|
||||
|
||||
|
|
|
@ -1465,7 +1465,7 @@ code-example(format=".", language="bash").
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn about the [APP_BASE_HREF](../api/router/APP_BASE_HREF-let.html)
|
||||
Learn about the [APP_BASE_HREF](../api/common/index/APP_BASE_HREF-let.html)
|
||||
in the API Guide.
|
||||
:marked
|
||||
### *HashLocationStrategy*
|
||||
|
|
|
@ -231,11 +231,11 @@ include ../_util-fns
|
|||
|
||||
我们将把这份配置数组传给`provideRouter()`函数,它返回一个经过配置的*Router*[服务提供商](dependency-injection.html#!#injector-providers)(以及别的东西)。
|
||||
|
||||
Finally, we export this provider in the `APP_ROUTER_PROVIDERS` array
|
||||
Finally, we export this provider in the `appRouterProviders` array
|
||||
so we can simplify registration of router dependencies later in `main.ts`.
|
||||
We don't have any other providers to register right now. But we will.
|
||||
|
||||
最后,我们通过`APP_ROUTER_PROVIDERS`数组导出这个提供商,以便我们以后将来在`main.ts`中简单的注册路由器依赖。
|
||||
最后,我们通过`appRouterProviders`数组导出这个提供商,以便我们以后将来在`main.ts`中简单的注册路由器依赖。
|
||||
目前我们还没有注册任何别的提供商,但很快就会这么做了!
|
||||
|
||||
:marked
|
||||
|
@ -743,11 +743,11 @@ h4#define-routes 定义一些路由
|
|||
|
||||
我们的第一个配置中定义了由两个路由构成的数组,它们分别通过路径(path)导航到了`CrisisListComponent`和`HeroListComponent`组件。
|
||||
|
||||
Each definition translates to a [Route](../api/router/index/Route-class.html) object which has a
|
||||
Each definition translates to a [Route](../api/router/index/Route-interface.html) object which has a
|
||||
`path`, the URL path segment for this route, and a
|
||||
`component`, the component associated with this route.
|
||||
|
||||
每个定义都被翻译成了一个[Route](../api/router/index/Route-class.html)对象。该对象有一个`path`字段,表示该路由中的URL路径部分,和一个`component`字段,表示与该路由相关联的组件。
|
||||
每个定义都被翻译成了一个[Route](../api/router/index/Route-interface.html)对象。该对象有一个`path`字段,表示该路由中的URL路径部分,和一个`component`字段,表示与该路由相关联的组件。
|
||||
|
||||
The router draws upon its registry of such route definitions when the browser URL changes
|
||||
or when our code tells the router to navigate along a route path.
|
||||
|
@ -780,15 +780,15 @@ h4#provideRouter 调用<i>provideRouter</i>
|
|||
我们把路由配置传给`provideRouter`函数,它返回一个数组,其中包括配置好的`Router`服务提供商……和路由库所需的某些你从未见过的提供商。
|
||||
|
||||
:marked
|
||||
We add the `provideRouter` array to an `APP_ROUTER_PROVIDERS` array and export it.
|
||||
We add the `provideRouter` array to an `appRouterProviders` array and export it.
|
||||
|
||||
我们把`provideRouter`数组加进一个`APP_ROUTER_PROVIDERS`数组,并导出它。
|
||||
|
||||
We could add *additional* service providers to `APP_ROUTER_PROVIDERS` —
|
||||
We could add *additional* service providers to `appRouterProviders` —
|
||||
providers that are specific to our routing configuration.
|
||||
We don't have any yet. We will have some later in this chapter.
|
||||
|
||||
我们还可以往`APP_ROUTER_PROVIDERS`中添加一些*额外的*提供商,这取决于路由的具体配置。
|
||||
我们还可以往`appRouterProviders`中添加一些*额外的*提供商,这取决于路由的具体配置。
|
||||
虽然目前还一个都没有呢,不过稍后就会看到了。
|
||||
|
||||
.l-sub-section
|
||||
|
@ -809,10 +809,10 @@ h4#register-providers 在启动时注册路由
|
|||
本应用的启动点位于`/app`目录下的`main.ts`文件中。
|
||||
这个文件很短,并且和默认的`main.ts`没有什么不同。
|
||||
|
||||
The important difference: we import the `APP_ROUTER_PROVIDERS` array
|
||||
The important difference: we import the `appRouterProviders` array
|
||||
and pass it as the second parameter of the `bootstrap` function.
|
||||
|
||||
最重要的不同点是:这里我们导入了`APP_ROUTER_PROVIDERS`数组,并且把它作为第二个参数传给了`bootstrap`函数。
|
||||
最重要的不同点是:这里我们导入了`appRouterProviders`数组,并且把它作为第二个参数传给了`bootstrap`函数。
|
||||
|
||||
+makeExample('router/ts/app/main.1.ts','all', 'main.ts')(format=".")
|
||||
|
||||
|
@ -1896,7 +1896,7 @@ code-example(format="").
|
|||
|
||||
当用户点击了“Crisis Center”链接或者在地址栏粘贴`localhost:3000/crisis-center/`时,我们更希望该应用能直接显示危机列表。这就是默认路由。
|
||||
|
||||
The preferred solution is to add a `redirect` route that transparently translates from the initial relative URL (`''`)
|
||||
The preferred solution is to add a `redirect` route that transparently translates from the initial relative URL (`''`)
|
||||
to the desired default path (`/crisis-center`):
|
||||
|
||||
首选的解决方案是添加一个`redirect`路由,它会把初始的相对URL(`''`)悄悄翻译成默认路径(`/crisis-center`)。
|
||||
|
@ -1914,21 +1914,21 @@ code-example(format="").
|
|||
.l-sub-section
|
||||
|
||||
:marked
|
||||
Technically, `pathMatch = 'full'` results in a route hit when the *remaining*, unmatched segments of the URL match `''`.
|
||||
Technically, `pathMatch = 'full'` results in a route hit when the *remaining*, unmatched segments of the URL match `''`.
|
||||
In our example, the redirect is at the top level of the route configuration tree so the *remaining* URL and the *entire* URL
|
||||
are the same thing.
|
||||
|
||||
The other possible `pathMatch` value is `'prefix'` which tells the router
|
||||
The other possible `pathMatch` value is `'prefix'` which tells the router
|
||||
to match the redirect route when the *remaining* URL ***begins*** with the redirect route's _prefix_ path.
|
||||
|
||||
That's not what we want to do here. If the `pathMatch` value were `'prefix'`,
|
||||
_every_ URL would match `''`.
|
||||
We could never navigate to `/crisis-center/1` because the redirect route would match first and
|
||||
|
||||
That's not what we want to do here. If the `pathMatch` value were `'prefix'`,
|
||||
_every_ URL would match `''`.
|
||||
We could never navigate to `/crisis-center/1` because the redirect route would match first and
|
||||
send us to the `CrisisListComponent`.
|
||||
|
||||
|
||||
We should redirect to the `CrisisListComponent` _only_ when the _entire (remaining)_ url is `''`.
|
||||
|
||||
Learn more in Victor Savkin's blog
|
||||
Learn more in Victor Savkin's blog
|
||||
[post on redirects](http://victorsavkin.com/post/146722301646/angular-router-empty-paths-componentless-routes).
|
||||
|
||||
We'll discuss redirects in more detail in a future update to this chapter.
|
||||
|
@ -2357,10 +2357,10 @@ h3#can-deactivate-guard <i>CanDeactivate</i>:处理未保存的更改
|
|||
+makeExample('router/ts/app/crisis-center/crisis-center.routes.4.ts', '', 'crisis-center.routes.ts')
|
||||
|
||||
:marked
|
||||
We also need to add the `Guard` to our main `APP_ROUTER_PROVIDERS` so the `Router` can inject it during the navigation process.
|
||||
|
||||
我们还要把这个`Guard`添加到主文件的`APP_ROUTER_PROVIDERS`中去,以便`Router`可以在导航过程中注入它。
|
||||
We also need to add the `Guard` to our main `appRouterProviders` so the `Router` can inject it during the navigation process.
|
||||
|
||||
我们还要把这个`Guard`添加到主文件的`appRouterProviders`中去,以便`Router`可以在导航过程中注入它。
|
||||
|
||||
+makeExample('router/ts/app/app.routes.ts', '', 'app.routes.ts')
|
||||
|
||||
:marked
|
||||
|
|
|
@ -684,7 +684,7 @@ code-example(format="." language="javascript").
|
|||
|
||||
+ifDocsFor('ts')
|
||||
:marked
|
||||
[Headers](../api/http/Headers-class.html) are one of the [RequestOptions](../api/http/RequestOptions-class.html).
|
||||
[Headers](../api/http/index/Headers-class.html) are one of the [RequestOptions](../api/http/index/RequestOptions-class.html).
|
||||
Compose the options object and pass it in as the *third* parameter of the `post` method, as shown above.
|
||||
|
||||
[Headers](../api/http/index/Headers-class.html)是[RequestOptions](../api/http/index/RequestOptions-class.html)中的一员。
|
||||
|
|
|
@ -108,10 +108,6 @@ a(id='toc')
|
|||
|
||||
1. [生命周期钩子](#lifecycle-hooks)
|
||||
|
||||
1. [Routing](#routing)
|
||||
|
||||
1. [路由](#routing)
|
||||
|
||||
1. [Appendix](#appendix)
|
||||
|
||||
1. [附录](#appendix)
|
||||
|
@ -121,11 +117,10 @@ a(id='toc')
|
|||
## Single Responsibility
|
||||
## 单一职责
|
||||
|
||||
We apply the [Single Responsibility Principle](https:\/\/en.wikipedia.org/wiki/Single_responsibility_principle) to all Components, Services, and other symbols we create.
|
||||
This helps make our app cleaner, easier to read and maintain, and more testable.
|
||||
We apply the [Single Responsibility Principle](https://wikipedia.org/wiki/Single_responsibility_principle) to all Components, Services, and other symbols we create. This helps make our app cleaner, easier to read and maintain, and more testable.
|
||||
|
||||
我们遵循[单一职责原则](https:\/\/en.wikipedia.org/wiki/Single_responsibility_principle)来创建的所有组件、服务和其它标志等。这样能帮助我们把应用程序弄的干净整洁,易于阅读、维护和测试。
|
||||
|
||||
|
||||
### <a id="01-01"></a>Rule of One
|
||||
### <a id="01-01"></a>单一法则
|
||||
#### <a href="#01-01">Style 01-01</a>
|
||||
|
|
|
@ -33,7 +33,7 @@ include ../_util-fns
|
|||
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
|
||||
we introduced in the [QuickStart](../quickstart.html) and
|
||||
the [Tour of Heroes](../tutorial/) tutorial
|
||||
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
||||
such as <code>npm</code>, <code>gulp</code>, and <code>lite-server</code>.
|
||||
|
||||
这一章单元测试是在其它章节的基础上写的。我们建议你按顺序阅读它们。同时,我们假设你已经对Angular 2的原理、[“快速起步”](../quickstart.html)和
|
||||
[英雄指南](../tutorial)中介绍的<code>npm</code>、<code>gulp</code>和<code>live-server</code>等工具都已经有所了解。
|
||||
|
@ -72,25 +72,38 @@ include ../_util-fns
|
|||
pre.prettyprint.lang-bash
|
||||
code npm install jasmine-core --save-dev --save-exact
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
Be sure to install <code>jasmine-core</code> , not <code>jasmine</code>!
|
||||
.alert.is-important Be sure to install <code>jasmine-core</code> , not <code>jasmine</code>!
|
||||
|
||||
.alert.is-important 请确保安装的是<code>jasmine-core</code>,而不是<code>jasmine</code>!
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Configure `lite-server` for serving our test harness
|
||||
|
||||
## 配置`lite-server`来提供测试环境
|
||||
|
||||
:marked
|
||||
First create a configuration file for serving up our test harness through `lite-server`.
|
||||
|
||||
首先为提供测试环境的`lite-server`创建一个配置文件。
|
||||
|
||||
+makeExample('testing/ts/liteserver-test-config.json', '', 'liteserver-test-config.json')
|
||||
|
||||
请确保安装的是<code>jasmine-core</code>,而不是<code>jasmine</code>!
|
||||
:marked
|
||||
Let's make one more change to the `package.json` script commands.
|
||||
|
||||
让我们在`package.json`的脚本命令区再做一项修改。
|
||||
|
||||
**Open the `package.json` ** and scroll to the `scripts` node and add in a new one:
|
||||
**Open the `package.json` ** and scroll to the `scripts` node and add the following two entries:
|
||||
|
||||
**打开`package.json`**,滚动到`scripts`节点,添加下面这一行:
|
||||
**打开`package.json`**,滚动到`scripts`节点,添加下面两行:
|
||||
|
||||
code-example(format="").
|
||||
"test": "live-server --open=unit-tests.html"
|
||||
"lite-server-test": "lite-server --config=liteserver-test-config.json",
|
||||
"test": "tsc && concurrently \"npm run tsc:w\" \"npm run lite-server-test\" "
|
||||
|
||||
:marked
|
||||
That command will launch `live-server` and open a browser to the `unit-tests.html` page we just wrote.
|
||||
The `npm test` command will launch `lite-server` and open a browser to the `unit-tests.html` page we just wrote. It will also take care of recompiling your source code and reloading your browser after any change.
|
||||
|
||||
该指令将启动`live-server`,并在浏览器中打开我们刚写的`unit-tests.html`页面。
|
||||
|
||||
|
@ -236,7 +249,7 @@ code-example(format="").
|
|||
### Run and Fail
|
||||
### 运行,并失败
|
||||
|
||||
Look over at the browser (live-server will have reloaded it). The browser displays
|
||||
Look over at the browser (lite-server will have reloaded it). The browser displays
|
||||
|
||||
看看浏览器(live-server应该已经刷新了它)。其中显示的是:
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ pre.prettyprint.lang-bash
|
|||
|
||||
我们希望这些测试能快速进化,如果浏览器能在我们做出修改和重新编译时自动刷新就好了。
|
||||
|
||||
Let’s launch with **live-server** in a second terminal window:
|
||||
Let’s launch with **lite-server** in a second terminal window:
|
||||
|
||||
没问题,让我们在另一个终端窗口中启动**live-server**:
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ include ../_util-fns
|
|||
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
|
||||
we introduced in the [QuickStart](../quickstart.html) and
|
||||
the [Tour of Heroes](../tutorial/) tutorial
|
||||
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
||||
such as <code>npm</code>, <code>gulp</code>, and <code>lite-server</code>.
|
||||
|
||||
这一章单元测试是在其它章节的基础上写的。我们建议你按顺序阅读它们。同时,我们假设你已经对Angular 2的原理、[“快速起步”](../quickstart.html)和
|
||||
[英雄指南](../tutorial)中介绍的<code>npm</code>、<code>gulp</code>和<code>live-server</code>等工具都已经有所了解。
|
||||
|
|
|
@ -360,11 +360,11 @@ code-example(language="bash").
|
|||
|
||||
### 让路由器生效
|
||||
|
||||
The *Component Router* is a service. We have to import our `APP_ROUTER_PROVIDERS` which
|
||||
The *Component Router* is a service. We have to import our `appRouterProviders` which
|
||||
contains our configured router and make it available to the application by adding it to
|
||||
the `bootstrap` array.
|
||||
|
||||
*组件路由器*是一个服务。我们得导入`APP_ROUTER_PROVIDERS`(它包含了我们配置好的路由器),并通过把它添加到`bootstrap`的数组参数中让它在此应用中可用。
|
||||
*组件路由器*是一个服务。我们得导入`appRouterProviders`(它包含了我们配置好的路由器),并通过把它添加到`bootstrap`的数组参数中让它在此应用中可用。
|
||||
|
||||
+makeExample('toh-5/ts/app/main.ts', '', 'app/main.ts')(format=".")
|
||||
|
||||
|
|
|
@ -4,6 +4,19 @@
|
|||
.clear
|
||||
|
||||
.grid-fluid
|
||||
.c6
|
||||
.article-card
|
||||
.date July 11, 2016
|
||||
.title
|
||||
a(
|
||||
target="_blank"
|
||||
href="http://angularjs.blogspot.com/2016/07/angular-in-china-and-beyond-introducing.html"
|
||||
) Angular in China and beyond: Introducing angular.cn
|
||||
p We're excited to share the newly-launched angular.cn with you! Read on to learn how we did it, and how you can get involved in bringing Angular to your locale....
|
||||
|
||||
.author
|
||||
img(src="/resources/images/bios/naomi.jpg")
|
||||
.posted Posted by <b>Naomi Black</b>
|
||||
.c6
|
||||
.article-card
|
||||
.date June 30, 2016
|
||||
|
@ -17,20 +30,6 @@
|
|||
img(src="/resources/images/bios/stephenfluin.jpg")
|
||||
.posted Posted by <b>Stephen Fluin</b>
|
||||
|
||||
.c6
|
||||
.article-card
|
||||
.date June 21, 2016
|
||||
.title
|
||||
a(
|
||||
target="_blank"
|
||||
href="http://angularjs.blogspot.com/2016/06/rc3-now-available.html"
|
||||
) RC3 Now Available
|
||||
p Today we’re happy to announce that we are shipping Angular 2.0.0-rc3. This release includes a fix for a major performance regression in RC2...
|
||||
|
||||
.author
|
||||
img(src="/resources/images/bios/stephenfluin.jpg")
|
||||
.posted Posted by <b>Stephen Fluin</b>
|
||||
|
||||
.grid-fluid.l-space-bottom-2.l-space-top-4
|
||||
.c12.text-center
|
||||
h3.text-headline.text-uppercase Developer Community
|
||||
|
@ -44,7 +43,7 @@
|
|||
target="_blank"
|
||||
href="http://www.bennadel.com/blog/3116-using-an-item-template-with-an-html-dropdown-menu-component-in-angular-2-rc-3.htm"
|
||||
) Using An Item Template With An HTML Dropdown Menu Component
|
||||
p A while ago, I played around with trying to create an HTML Dropdown menu component in Angular 2. I discovered that you could pass Template references into components for dynamic rendering....
|
||||
p A while ago, I played around with trying to create an HTML Dropdown menu component in Angular 2. I discovered that you could pass Template references into components for dynamic rendering...
|
||||
.author
|
||||
img(src="/resources/images/bios/shield-bio-placeholder.png")
|
||||
.posted Posted by <b>Ben Nadel</b>
|
||||
|
@ -70,23 +69,23 @@
|
|||
target="_blank"
|
||||
href="http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/"
|
||||
) Building an Angular 2 Application for Production
|
||||
p During the keynote of ng-conf, the core team managed to drop the size of the “Hello world!” app to less than 50K! In this blog post we’ll explain all the steps we need to go through in order to achieve such results!.
|
||||
p During the keynote of ng-conf, the core team managed to drop the size of the “Hello world!” app to less than 50K! In this blog post we’ll explain all the steps we need to go through in order to achieve such results!
|
||||
.author
|
||||
img(src="/resources/images/bios/shield-bio-placeholder.png")
|
||||
.posted Posted by <b>Minko Gechev</b>
|
||||
|
||||
.c6
|
||||
.article-card
|
||||
.date June 14, 2016
|
||||
.date June 22, 2016
|
||||
.title
|
||||
a(
|
||||
target="_blank"
|
||||
href="http://blog.thoughtram.io/angular/2016/06/14/routing-in-angular-2-revisited.html"
|
||||
) Routing in Angular 2 Revisited
|
||||
p Just recently, the Angular team announced yet another version of the new router. Take a first look at the new and better APIs, touching on the most common scenarios...
|
||||
href="https://mp.weixin.qq.com/s?__biz=MzIwNjQwMzUwMQ==&mid=2247483837&idx=1&sn=932b359504eec2ae50a3bcba2964b3c2&scene=2&srcid=0622Tab8nj3W3cAkphohB8wM"
|
||||
) Why I chose Angular 2 / 我为什么选择Angular 2 (in Chinese)
|
||||
p Ralph Wang, a senior developer with 18 years’ experience in China, tells why he chooses Angular 2. No choice is painful, but too many choices can be chaotic and cause even more pain...
|
||||
.author
|
||||
img(src="/resources/images/bios/pascalprecht.jpg")
|
||||
.posted Posted by <b>Pascal Precht</b>
|
||||
img(src="/resources/images/bios/angular-gde-bio-placeholder.png")
|
||||
.posted Posted by <b>Ralph Wang</b>
|
||||
|
||||
|
||||
.grid-fluid.l-space-bottom-2.l-space-top-4
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex -o pipefail
|
||||
|
||||
./scripts/deploy-install.sh
|
||||
(cd ../angular && git checkout master)
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex -o pipefail
|
||||
|
||||
(cd ../ && git clone https://github.com/angular/angular.git --branch $LATEST_RELEASE)
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex -o pipefail
|
||||
|
||||
./scripts/examples-install.sh
|
||||
(cd public/docs/_examples && npm install angular/{core,common,compiler,platform-browser,platform-browser-dynamic,http,forms,router-deprecated,router,upgrade}-builds --no-optional)
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
set -ex -o pipefail
|
||||
|
||||
npm install --no-optional
|
||||
(cd public/docs/_examples && npm install --no-optional)
|
||||
(cd public/docs/_examples/_protractor && npm install --no-optional)
|
||||
npm run webdriver:update --prefix public/docs/_examples/_protractor
|
|
@ -0,0 +1,4 @@
|
|||
// A ts2dart compiler annotation that can be ignored in API docs.
|
||||
module.exports = function() {
|
||||
return { name: 'Annotation', ignore: true };
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
module.exports = [
|
||||
require('./Annotation'),
|
||||
require('./deprecated'),
|
||||
require('./howToUse'),
|
||||
require('./whatItDoes'),
|
||||
require('./internal'),
|
||||
require('./stable'),
|
||||
require('./ts2dart_const'),
|
||||
require('./experimental'),
|
||||
require('./docsNotRequired'),
|
||||
require('./security'),
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
// A ts2dart compiler annotation that can be ignored in API docs.
|
||||
module.exports = function() {
|
||||
return { name: 'ts2dart_const', ignore: true };
|
||||
};
|
|
@ -23,8 +23,7 @@ module.exports = function addNotYetDocumentedProperty(EXPORT_DOC_TYPES, log, cre
|
|||
}
|
||||
|
||||
if (doc.notYetDocumented) {
|
||||
// TODO: (ericjim) should I remove this?
|
||||
log.warn(createDocMessage("Not yet documented", doc));
|
||||
log.info(createDocMessage("Not yet documented", doc));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -35,4 +34,4 @@ module.exports = function addNotYetDocumentedProperty(EXPORT_DOC_TYPES, log, cre
|
|||
|
||||
function notYetDocumented(doc) {
|
||||
return !doc.noDescription && doc.description.trim().length == 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,25 @@ var Package = require('dgeni').Package;
|
|||
module.exports = new Package('links', [])
|
||||
|
||||
.factory(require('./inline-tag-defs/link'))
|
||||
.factory(require('./inline-tag-defs/linkDevGuide'))
|
||||
.factory(require('./inline-tag-defs/linkDocs'))
|
||||
.factory(require('./inline-tag-defs/example'))
|
||||
.factory(require('./inline-tag-defs/exampleTabs'))
|
||||
.factory(require('dgeni-packages/links/services/getAliases'))
|
||||
.factory(require('dgeni-packages/links/services/getDocFromAlias'))
|
||||
.factory(require('./services/getLinkInfo'))
|
||||
.factory(require('./services/parseArgString'))
|
||||
.factory(require('./services/moduleScopeLinkDisambiguator'))
|
||||
.factory(require('./services/deprecatedDocsLinkDisambiguator'))
|
||||
.factory(require('./services/getApiFragmentFileName'))
|
||||
|
||||
.config(function(inlineTagProcessor, linkInlineTagDef, linkDevGuideInlineTagDef, exampleInlineTagDef, exampleTabsInlineTagDef) {
|
||||
.config(function(inlineTagProcessor, linkInlineTagDef, linkDocsInlineTagDef, exampleInlineTagDef, exampleTabsInlineTagDef) {
|
||||
inlineTagProcessor.inlineTagDefinitions.push(linkInlineTagDef);
|
||||
inlineTagProcessor.inlineTagDefinitions.push(linkDevGuideInlineTagDef);
|
||||
inlineTagProcessor.inlineTagDefinitions.push(linkDocsInlineTagDef);
|
||||
inlineTagProcessor.inlineTagDefinitions.push(exampleInlineTagDef);
|
||||
inlineTagProcessor.inlineTagDefinitions.push(exampleTabsInlineTagDef);
|
||||
})
|
||||
|
||||
.config(function(getLinkInfo, moduleScopeLinkDisambiguator, deprecatedDocsLinkDisambiguator) {
|
||||
getLinkInfo.disambiguators.push(moduleScopeLinkDisambiguator);
|
||||
getLinkInfo.disambiguators.push(deprecatedDocsLinkDisambiguator);
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue