Merge remote-tracking branch 'remotes/angular.io/master'

# Conflicts:
#	public/docs/ts/latest/guide/_data.json
#	public/docs/ts/latest/guide/architecture.jade
#	public/docs/ts/latest/guide/displaying-data.jade
#	public/docs/ts/latest/guide/index.jade
#	public/docs/ts/latest/guide/lifecycle-hooks.jade
#	public/docs/ts/latest/guide/testing.jade
#	public/docs/ts/latest/tutorial/toh-pt5.jade
This commit is contained in:
Zhimin YE (Rex) 2016-09-26 15:14:39 +01:00
commit a18aa9ec01
99 changed files with 731 additions and 1171 deletions

View File

@ -38,6 +38,7 @@ var DOCS_PATH = path.join(PUBLIC_PATH, 'docs');
var EXAMPLES_PATH = path.join(DOCS_PATH, '_examples'); var EXAMPLES_PATH = path.join(DOCS_PATH, '_examples');
var EXAMPLES_PROTRACTOR_PATH = path.join(EXAMPLES_PATH, '_protractor'); var EXAMPLES_PROTRACTOR_PATH = path.join(EXAMPLES_PATH, '_protractor');
var EXAMPLES_TESTING_PATH = path.join(EXAMPLES_PATH, 'testing/ts');
var NOT_API_DOCS_GLOB = path.join(PUBLIC_PATH, './{docs/*/latest/!(api),!(docs)}/**/*.*'); var NOT_API_DOCS_GLOB = path.join(PUBLIC_PATH, './{docs/*/latest/!(api),!(docs)}/**/*.*');
var RESOURCES_PATH = path.join(PUBLIC_PATH, 'resources'); var RESOURCES_PATH = path.join(PUBLIC_PATH, 'resources');
var LIVE_EXAMPLES_PATH = path.join(RESOURCES_PATH, 'live-examples'); var LIVE_EXAMPLES_PATH = path.join(RESOURCES_PATH, 'live-examples');
@ -97,7 +98,7 @@ var _exampleBoilerplateFiles = [
'tsconfig.json', 'tsconfig.json',
'tslint.json', 'tslint.json',
'typings.json' 'typings.json'
]; ];
var _exampleDartWebBoilerPlateFiles = ['a2docs.css', 'styles.css']; var _exampleDartWebBoilerPlateFiles = ['a2docs.css', 'styles.css'];
@ -105,6 +106,11 @@ var _exampleProtractorBoilerplateFiles = [
'tsconfig.json' 'tsconfig.json'
]; ];
var _exampleUnitTestingBoilerplateFiles = [
'karma-test-shim.js',
'karma.conf.js'
];
var _exampleConfigFilename = 'example-config.json'; var _exampleConfigFilename = 'example-config.json';
var _styleLessName = 'a2docs.less'; var _styleLessName = 'a2docs.less';
@ -497,9 +503,17 @@ function copyExampleBoilerplate() {
.then(function() { .then(function() {
var protractorSourceFiles = var protractorSourceFiles =
_exampleProtractorBoilerplateFiles _exampleProtractorBoilerplateFiles
.map(function(name) {return path.join(EXAMPLES_PROTRACTOR_PATH, name);});; .map(function(name) {return path.join(EXAMPLES_PROTRACTOR_PATH, name); });
var e2eSpecPaths = getE2eSpecPaths(EXAMPLES_PATH); var e2eSpecPaths = getE2eSpecPaths(EXAMPLES_PATH);
return copyFiles(protractorSourceFiles, e2eSpecPaths, destFileMode); return copyFiles(protractorSourceFiles, e2eSpecPaths, destFileMode);
})
// copy the unit test boilerplate
.then(function() {
var unittestSourceFiles =
_exampleUnitTestingBoilerplateFiles
.map(function(name) { return path.join(EXAMPLES_TESTING_PATH, name); });
var unittestPaths = getUnitTestingPaths(EXAMPLES_PATH);
return copyFiles(unittestSourceFiles, unittestPaths, destFileMode);
}); });
} }
@ -894,7 +908,7 @@ function harpCompile() {
} else { } else {
gutil.log(`Harp full site compile, including API docs for all languages.`); gutil.log(`Harp full site compile, including API docs for all languages.`);
if (skipLangs) if (skipLangs)
gutil.log(`Ignoring API docs skip set (${skipLangs}) because full ` + gutil.log(`Ignoring API docs skip set (${skipLangs}) because full ` +
`site has not been built yet or some API HTML files are missing.`); `site has not been built yet or some API HTML files are missing.`);
} }
@ -1130,6 +1144,14 @@ function getDartExampleWebPaths(basePath) {
return paths; return paths;
} }
function getUnitTestingPaths(basePath) {
var examples = getPaths(basePath, _exampleConfigFilename, true);
return examples.filter((example) => {
var exampleConfig = fs.readJsonSync(`${example}/${_exampleConfigFilename}`, {throws: false});
return exampleConfig && !!exampleConfig.unittesting;
});
}
function getPaths(basePath, filename, includeBase) { function getPaths(basePath, filename, includeBase) {
var filenames = getFilenames(basePath, filename, includeBase); var filenames = getFilenames(basePath, filename, includeBase);
var paths = filenames.map(function(fileName) { var paths = filenames.map(function(fileName) {
@ -1168,7 +1190,7 @@ function watchAndSync(options, cb) {
// When using the --focus=name flag, only **/name/**/*.* example files and // When using the --focus=name flag, only **/name/**/*.* example files and
// **/name.jade files are watched. This is useful for performance reasons. // **/name.jade files are watched. This is useful for performance reasons.
// Example: gulp serve-and-sync --focus=architecture // Example: gulp serve-and-sync --focus=architecture
var focus = argv.focus; var focus = argv.focus;
if (options.devGuide) { if (options.devGuide) {

View File

@ -3,8 +3,9 @@ import { Component, Input } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
@Component({ @Component({
moduleId: module.id,
selector: 'hero-detail', selector: 'hero-detail',
templateUrl: 'app/hero-detail.component.html' templateUrl: 'hero-detail.component.html'
}) })
export class HeroDetailComponent { export class HeroDetailComponent {
@Input() hero: Hero; @Input() hero: Hero;

View File

@ -5,9 +5,10 @@ import { HeroService } from './hero.service';
// #docregion metadata, providers // #docregion metadata, providers
@Component({ @Component({
moduleId: module.id,
selector: 'hero-list', selector: 'hero-list',
templateUrl: 'app/hero-list.component.html', templateUrl: 'hero-list.component.html',
providers: [ HeroService ] providers: [ HeroService ]
}) })
// #enddocregion providers // #enddocregion providers
// #docregion class // #docregion class

View File

@ -2,8 +2,9 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html' templateUrl: 'app.component.html'
}) })
export class AppComponent { } export class AppComponent { }

View File

@ -4,10 +4,11 @@ import { MovieService } from './movie.service';
import { IMovie } from './movie'; import { IMovie } from './movie';
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html', templateUrl: 'app.component.html',
styleUrls: ['app/app.component.css'], styleUrls: [ 'app.component.css' ],
providers: [MovieService] providers: [ MovieService ]
}) })
export class AppComponent { export class AppComponent {

View File

@ -8,11 +8,12 @@ import { MovieService } from './movie.service';
// #docregion component // #docregion component
@Component({ @Component({
moduleId: module.id,
selector: 'movie-list', selector: 'movie-list',
templateUrl: 'app/movie-list.component.html', templateUrl: 'movie-list.component.html',
// #enddocregion component // #enddocregion component
// #docregion style-url // #docregion style-url
styleUrls: ['app/movie-list.component.css'], styleUrls: [ 'movie-list.component.css' ],
// #enddocregion style-url // #enddocregion style-url
}) })
// #enddocregion component // #enddocregion component

View File

@ -1,7 +1,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html' templateUrl: 'app.component.html'
}) })
export class AppComponent { } export class AppComponent { }

View File

@ -7,10 +7,11 @@ import { UserContextService } from './user-context.service';
import { UserService } from './user.service'; import { UserService } from './user.service';
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html', templateUrl: 'app.component.html',
// #docregion providers // #docregion providers
providers: [LoggerService, UserContextService, UserService] providers: [ LoggerService, UserContextService, UserService ]
// #enddocregion providers // #enddocregion providers
}) })
export class AppComponent { export class AppComponent {

View File

@ -9,7 +9,7 @@ import { LocationStrategy,
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { HeroData } from './hero-data'; import { HeroData } from './hero-data';
import { InMemoryWebApiModule } from 'angular2-in-memory-web-api'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';

View File

@ -1,27 +0,0 @@
/// <reference path='../_protractor/e2e.d.ts' />
'use strict';
/* tslint:disable:quotemark */
describe('Dynamic Form Deprecated', function () {
beforeAll(function () {
browser.get('');
});
it('should submit form', function () {
let firstNameElement = element.all(by.css('input[id=firstName]')).get(0);
expect(firstNameElement.getAttribute('value')).toEqual('Bombasto');
let emailElement = element.all(by.css('input[id=emailAddress]')).get(0);
let email = 'test@test.com';
emailElement.sendKeys(email);
expect(emailElement.getAttribute('value')).toEqual(email);
element(by.css('select option[value="solid"]')).click();
let saveButton = element.all(by.css('button')).get(0);
saveButton.click().then(function(){
expect(element(by.xpath("//strong[contains(text(),'Saved the following values')]")).isPresent()).toBe(true);
});
});
});

View File

@ -1,24 +0,0 @@
// #docregion
import { Component } from '@angular/core';
import { DynamicFormComponent } from './dynamic-form.component';
import { QuestionService } from './question.service';
@Component({
selector: 'my-app',
template: `
<div>
<h2>Job Application for Heroes</h2>
<dynamic-form [questions]="questions"></dynamic-form>
</div>
`,
directives: [DynamicFormComponent],
providers: [QuestionService]
})
export class AppComponent {
questions: any[];
constructor(service: QuestionService) {
this.questions = service.getQuestions();
}
}

View File

@ -1,17 +0,0 @@
<!-- #docregion -->
<div [ngFormModel]="form">
<label [attr.for]="question.key">{{question.label}}</label>
<div [ngSwitch]="question.controlType">
<input *ngSwitchCase="'textbox'" [ngControl]="question.key"
[id]="question.key" [type]="question.type">
<select [id]="question.key" *ngSwitchCase="'dropdown'" [ngControl]="question.key">
<option *ngFor="let opt of question.options" [value]="opt.key">{{opt.value}}</option>
</select>
</div>
<div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
</div>

View File

@ -1,15 +0,0 @@
// #docregion
import { Component, Input } from '@angular/core';
import { ControlGroup } from '@angular/common';
import { QuestionBase } from './question-base';
@Component({
selector: 'df-question',
templateUrl: 'app/dynamic-form-question.component.html'
})
export class DynamicFormQuestionComponent {
@Input() question: QuestionBase<any>;
@Input() form: ControlGroup;
get isValid() { return this.form.controls[this.question.key].valid; }
}

View File

@ -1,17 +0,0 @@
<!-- #docregion -->
<div>
<form (ngSubmit)="onSubmit()" [ngFormModel]="form">
<div *ngFor="let question of questions" class="form-row">
<df-question [question]="question" [form]="form"></df-question>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid">Save</button>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
</div>

View File

@ -1,30 +0,0 @@
// #docregion
import { Component, Input, OnInit } from '@angular/core';
import { ControlGroup } from '@angular/common';
import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service';
import { DynamicFormQuestionComponent } from './dynamic-form-question.component';
@Component({
selector: 'dynamic-form',
templateUrl: 'app/dynamic-form.component.html',
directives: [DynamicFormQuestionComponent],
providers: [QuestionControlService]
})
export class DynamicFormComponent implements OnInit {
@Input() questions: QuestionBase<any>[] = [];
form: ControlGroup;
payLoad = '';
constructor(private qcs: QuestionControlService) { }
ngOnInit() {
this.form = this.qcs.toControlGroup(this.questions);
}
onSubmit() {
this.payLoad = JSON.stringify(this.form.value);
}
}

View File

@ -1,5 +0,0 @@
import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
bootstrap(AppComponent, []);

View File

@ -1,25 +0,0 @@
// #docregion
export class QuestionBase<T>{
value: T;
key: string;
label: string;
required: boolean;
order: number;
controlType: string;
constructor(options: {
value?: T,
key?: string,
label?: string,
required?: boolean,
order?: number,
controlType?: string
} = {}) {
this.value = options.value;
this.key = options.key || '';
this.label = options.label || '';
this.required = !!options.required;
this.order = options.order === undefined ? 1 : options.order;
this.controlType = options.controlType || '';
}
}

View File

@ -1,18 +0,0 @@
// #docregion
import { Injectable } from '@angular/core';
import { FormBuilder, Validators } from '@angular/common';
import { QuestionBase } from './question-base';
@Injectable()
export class QuestionControlService {
constructor(private fb: FormBuilder) { }
toControlGroup(questions: QuestionBase<any>[] ) {
let group = {};
questions.forEach(question => {
group[question.key] = question.required ? [question.value || '', Validators.required] : [question.value || ''];
});
return this.fb.group(group);
}
}

View File

@ -1,12 +0,0 @@
// #docregion
import { QuestionBase } from './question-base';
export class DropdownQuestion extends QuestionBase<string> {
controlType = 'dropdown';
options: {key: string, value: string}[] = [];
constructor(options: {} = {}) {
super(options);
this.options = options['options'] || [];
}
}

View File

@ -1,12 +0,0 @@
// #docregion
import { QuestionBase } from './question-base';
export class TextboxQuestion extends QuestionBase<string> {
controlType = 'textbox';
type: string;
constructor(options: {} = {}) {
super(options);
this.type = options['type'] || '';
}
}

View File

@ -1,47 +0,0 @@
// #docregion
import { Injectable } from '@angular/core';
import { QuestionBase } from './question-base';
import { TextboxQuestion } from './question-textbox';
import { DropdownQuestion } from './question-dropdown';
@Injectable()
export class QuestionService {
// Todo: get from a remote source of question metadata
// Todo: make asynchronous
getQuestions() {
let questions: QuestionBase<any>[] = [
new DropdownQuestion({
key: 'brave',
label: 'Bravery Rating',
options: [
{key: 'solid', value: 'Solid'},
{key: 'great', value: 'Great'},
{key: 'good', value: 'Good'},
{key: 'unproven', value: 'Unproven'}
],
order: 3
}),
new TextboxQuestion({
key: 'firstName',
label: 'First name',
value: 'Bombasto',
required: true,
order: 1
}),
new TextboxQuestion({
key: 'emailAddress',
label: 'Email',
type: 'email',
order: 2
})
];
return questions.sort((a, b) => a.order - b.order);
}
}

View File

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<base href="/">
<title>Dynamic Form</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- #docregion style -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="sample.css">
<!-- #enddocregion style -->
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading app...</my-app>
</body>
</html>

View File

@ -1,9 +0,0 @@
{
"description": "Dynamic Form Deprecated",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1].*"
],
"tags":["cookbook"]
}

View File

@ -1,7 +0,0 @@
.errorMessage{
color:red;
}
.form-row{
margin-top: 10px;
}

View File

@ -1,12 +1,13 @@
// #docregion // #docregion
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { QuestionBase } from './question-base'; import { QuestionBase } from './question-base';
@Component({ @Component({
moduleId: module.id,
selector: 'df-question', selector: 'df-question',
templateUrl: 'app/dynamic-form-question.component.html' templateUrl: 'dynamic-form-question.component.html'
}) })
export class DynamicFormQuestionComponent { export class DynamicFormQuestionComponent {
@Input() question: QuestionBase<any>; @Input() question: QuestionBase<any>;

View File

@ -6,8 +6,9 @@ import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service'; import { QuestionControlService } from './question-control.service';
@Component({ @Component({
moduleId: module.id,
selector: 'dynamic-form', selector: 'dynamic-form',
templateUrl: 'app/dynamic-form.component.html', templateUrl: 'dynamic-form.component.html',
providers: [ QuestionControlService ] providers: [ QuestionControlService ]
}) })
export class DynamicFormComponent implements OnInit { export class DynamicFormComponent implements OnInit {

View File

@ -6,8 +6,9 @@ import { Component } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
@Component({ @Component({
moduleId: module.id,
selector: 'hero-form', selector: 'hero-form',
templateUrl: 'app/hero-form.component.html' templateUrl: 'hero-form.component.html'
}) })
export class HeroFormComponent { export class HeroFormComponent {

View File

@ -2,11 +2,14 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
// Set the base for module-relative URLs
moduleId: module.id,
// Declare the tag name in index.html to where the component attaches // Declare the tag name in index.html to where the component attaches
selector: 'hello-world', selector: 'hello-world',
// Location of the template for this component // Location of the template for this component
templateUrl: 'app/hello_world.html' templateUrl: 'hello_world.html'
}) })
export class HelloWorldComponent { export class HelloWorldComponent {

View File

@ -1,7 +1,8 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html' templateUrl: 'app.component.html'
}) })
export class AppComponent { } export class AppComponent { }

View File

@ -75,8 +75,9 @@ export class DoCheckComponent implements DoCheck {
/***************************************/ /***************************************/
@Component({ @Component({
moduleId: module.id,
selector: 'do-check-parent', selector: 'do-check-parent',
templateUrl: 'app/do-check-parent.component.html', templateUrl: 'do-check-parent.component.html',
styles: ['.parent {background: Lavender}'] styles: ['.parent {background: Lavender}']
}) })
export class DoCheckParentComponent { export class DoCheckParentComponent {

View File

@ -49,8 +49,9 @@ export class OnChangesComponent implements OnChanges {
/***************************************/ /***************************************/
@Component({ @Component({
moduleId: module.id,
selector: 'on-changes-parent', selector: 'on-changes-parent',
templateUrl: 'app/on-changes-parent.component.html', templateUrl: 'on-changes-parent.component.html',
styles: ['.parent {background: Lavender;}'] styles: ['.parent {background: Lavender;}']
}) })
export class OnChangesParentComponent { export class OnChangesParentComponent {

View File

@ -21,7 +21,7 @@ export class PeekABoo implements OnInit {
// implement OnInit's `ngOnInit` method // implement OnInit's `ngOnInit` method
ngOnInit() { this.logIt(`OnInit`); } ngOnInit() { this.logIt(`OnInit`); }
protected logIt(msg: string) { logIt(msg: string) {
this.logger.log(`#${nextId++} ${msg}`); this.logger.log(`#${nextId++} ${msg}`);
} }
} }

View File

@ -4,8 +4,9 @@ import { Component } from '@angular/core';
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';
@Component({ @Component({
moduleId: module.id,
selector: 'spy-parent', selector: 'spy-parent',
templateUrl: 'app/spy.component.html', templateUrl: 'spy.component.html',
styles: [ styles: [
'.parent {background: khaki;}', '.parent {background: khaki;}',
'.heroes {background: LightYellow; padding: 0 8px}' '.heroes {background: LightYellow; padding: 0 8px}'

View File

@ -8,7 +8,7 @@ import { UserService } from '../user.service';
moduleId: module.id, moduleId: module.id,
selector: 'app-contact', selector: 'app-contact',
templateUrl: 'contact.component.html', templateUrl: 'contact.component.html',
styleUrls: ['contact.component.css'] styleUrls: [ 'contact.component.css' ]
}) })
export class ContactComponent implements OnInit { export class ContactComponent implements OnInit {
contact: Contact; contact: Contact;

View File

@ -9,7 +9,7 @@ import { UserService } from '../core/user.service';
moduleId: module.id, moduleId: module.id,
selector: 'app-contact', selector: 'app-contact',
templateUrl: 'contact.component.html', templateUrl: 'contact.component.html',
styleUrls: ['contact.component.css'] styleUrls: [ 'contact.component.css' ]
}) })
export class ContactComponent implements OnInit { export class ContactComponent implements OnInit {
contact: Contact; contact: Contact;

View File

@ -37,7 +37,7 @@
"@angular/platform-server": "2.0.0", "@angular/platform-server": "2.0.0",
"@angular/router": "3.0.0", "@angular/router": "3.0.0",
"@angular/upgrade": "2.0.0", "@angular/upgrade": "2.0.0",
"angular2-in-memory-web-api": "0.0.20", "angular-in-memory-web-api": "~0.1.0",
"bootstrap": "^3.3.6", "bootstrap": "^3.3.6",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"reflect-metadata": "^0.1.3", "reflect-metadata": "^0.1.3",
@ -45,7 +45,7 @@
"rollup-plugin-node-resolve": "^2.0.0", "rollup-plugin-node-resolve": "^2.0.0",
"rollup-plugin-uglify": "^1.0.1", "rollup-plugin-uglify": "^1.0.1",
"rxjs": "5.0.0-beta.12", "rxjs": "5.0.0-beta.12",
"systemjs": "0.19.27", "systemjs": "0.19.38",
"zone.js": "^0.6.23" "zone.js": "^0.6.23"
}, },
"devDependencies": { "devDependencies": {
@ -60,8 +60,8 @@
"html-loader": "^0.4.3", "html-loader": "^0.4.3",
"html-webpack-plugin": "^2.16.1", "html-webpack-plugin": "^2.16.1",
"http-server": "^0.9.0", "http-server": "^0.9.0",
"jasmine-core": "^2.4.1", "jasmine-core": "^2.5.2",
"karma": "^1.2.0", "karma": "^1.3.0",
"karma-chrome-launcher": "^2.0.0", "karma-chrome-launcher": "^2.0.0",
"karma-cli": "^1.0.1", "karma-cli": "^1.0.1",
"karma-htmlfile-reporter": "^0.3.4", "karma-htmlfile-reporter": "^0.3.4",

View File

@ -2,8 +2,9 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html' templateUrl: 'app.component.html'
}) })
export class AppComponent { export class AppComponent {
birthday = new Date(1988, 3, 15); // April 15, 1988 birthday = new Date(1988, 3, 15); // April 15, 1988

View File

@ -5,8 +5,9 @@ import { Component } from '@angular/core';
import { HEROES } from './heroes'; import { HEROES } from './heroes';
@Component({ @Component({
moduleId: module.id,
selector: 'flying-heroes', selector: 'flying-heroes',
templateUrl: 'app/flying-heroes.component.html', templateUrl: 'flying-heroes.component.html',
styles: ['#flyers, #all {font-style: italic}'] styles: ['#flyers, #all {font-style: italic}']
}) })
// #docregion v1 // #docregion v1
@ -49,8 +50,9 @@ export class FlyingHeroesComponent {
////// Identical except for impure pipe ////// ////// Identical except for impure pipe //////
// #docregion impure-component // #docregion impure-component
@Component({ @Component({
moduleId: module.id,
selector: 'flying-heroes-impure', selector: 'flying-heroes-impure',
templateUrl: 'app/flying-heroes-impure.component.html', templateUrl: 'flying-heroes-impure.component.html',
// #enddocregion impure-component // #enddocregion impure-component
styles: ['.flyers, .all {font-style: italic}'], styles: ['.flyers, .all {font-style: italic}'],
// #docregion impure-component // #docregion impure-component

View File

@ -25,8 +25,8 @@
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
// other libraries // other libraries
'rxjs': 'npm:rxjs', 'rxjs': 'npm:rxjs',
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api', 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
}, },
// packages tells the System loader how to load when no filename and/or no extension // packages tells the System loader how to load when no filename and/or no extension
packages: { packages: {
@ -37,7 +37,7 @@
rxjs: { rxjs: {
defaultExtension: 'js' defaultExtension: 'js'
}, },
'angular2-in-memory-web-api': { 'angular-in-memory-web-api': {
main: './index.js', main: './index.js',
defaultExtension: 'js' defaultExtension: 'js'
} }

View File

@ -5,7 +5,8 @@ import { DomSanitizer, SafeResourceUrl, SafeUrl } from '@angular/platform-browse
@Component({ @Component({
selector: 'bypass-security', selector: 'bypass-security',
templateUrl: 'app/bypass-security.component.html', moduleId: module.id,
templateUrl: 'bypass-security.component.html',
}) })
export class BypassSecurityComponent { export class BypassSecurityComponent {
dangerousUrl: string; dangerousUrl: string;

View File

@ -4,7 +4,7 @@ import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpModule, JsonpModule } from '@angular/http'; import { HttpModule, JsonpModule } from '@angular/http';
import { InMemoryWebApiModule } from 'angular2-in-memory-web-api'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { HeroData } from './hero-data'; import { HeroData } from './hero-data';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';

View File

@ -1,5 +1,5 @@
// #docregion // #docregion
import { InMemoryDbService } from 'angular2-in-memory-web-api'; import { InMemoryDbService } from 'angular-in-memory-web-api';
export class HeroData implements InMemoryDbService { export class HeroData implements InMemoryDbService {
createDb() { createDb() {
let heroes = [ let heroes = [

View File

@ -6,7 +6,8 @@ import { HeroService } from './hero.service.promise';
@Component({ @Component({
selector: 'hero-list-promise', selector: 'hero-list-promise',
templateUrl: 'app/toh/hero-list.component.html', moduleId: module.id,
templateUrl: 'hero-list.component.html',
providers: [ HeroService ] providers: [ HeroService ]
}) })
// #docregion component // #docregion component

View File

@ -5,8 +5,9 @@ import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
@Component({ @Component({
moduleId: module.id,
selector: 'hero-list', selector: 'hero-list',
templateUrl: 'app/toh/hero-list.component.html', templateUrl: 'hero-list.component.html',
providers: [ HeroService ] providers: [ HeroService ]
}) })
// #docregion component // #docregion component

View File

@ -15,14 +15,14 @@ import { Observable } from 'rxjs/Observable';
@Injectable() @Injectable()
export class HeroService { export class HeroService {
// #docregion ctor
constructor (private http: Http) {}
// #enddocregion ctor
// #docregion endpoint // #docregion endpoint
private heroesUrl = 'app/heroes'; // URL to web API private heroesUrl = 'app/heroes'; // URL to web API
// #enddocregion endpoint // #enddocregion endpoint
// #docregion ctor
constructor (private http: Http) {}
// #enddocregion ctor
// #docregion methods, error-handling, http-get // #docregion methods, error-handling, http-get
getHeroes (): Observable<Hero[]> { getHeroes (): Observable<Hero[]> {
return this.http.get(this.heroesUrl) return this.http.get(this.heroesUrl)

View File

@ -3,8 +3,9 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
moduleId: module.id,
selector: 'structural-directives', selector: 'structural-directives',
templateUrl: 'app/structural-directives.component.html', templateUrl: 'structural-directives.component.html',
styles: ['button { min-width: 100px; }'] styles: ['button { min-width: 100px; }']
}) })
export class StructuralDirectivesComponent { export class StructuralDirectivesComponent {

View File

@ -3,9 +3,8 @@ import { Component, OnInit } from '@angular/core';
import { ToastService } from './toast.service'; import { ToastService } from './toast.service';
@Component({ @Component({
moduleId: module.id,
selector: 'toh-toast', selector: 'toh-toast',
templateUrl: '<div>toast</div>' template: '<div>toast</div>'
}) })
export class ToastComponent implements OnInit { export class ToastComponent implements OnInit {
constructor(toastService: ToastService) { } constructor(toastService: ToastService) { }

View File

@ -3,9 +3,8 @@ import { Component, OnInit } from '@angular/core';
import { ToastService } from './toast.service'; import { ToastService } from './toast.service';
@Component({ @Component({
moduleId: module.id,
selector: 'toh-toast', selector: 'toh-toast',
templateUrl: '<div>toast</div>' template: '<div>toast</div>'
}) })
export class ToastComponent implements OnInit { export class ToastComponent implements OnInit {
constructor(toastService: ToastService) { } constructor(toastService: ToastService) { }

View File

@ -1,7 +1,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html' templateUrl: 'app.component.html'
}) })
export class AppComponent { } export class AppComponent { }

View File

@ -3,7 +3,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http';
import { InMemoryWebApiModule } from 'angular2-in-memory-web-api'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';

View File

@ -25,8 +25,8 @@
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js', '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
// other libraries // other libraries
'rxjs': 'npm:rxjs', 'rxjs': 'npm:rxjs',
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api', 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
}, },
// packages tells the System loader how to load when no filename and/or no extension // packages tells the System loader how to load when no filename and/or no extension
packages: { packages: {
@ -37,7 +37,7 @@
rxjs: { rxjs: {
defaultExtension: 'js' defaultExtension: 'js'
}, },
'angular2-in-memory-web-api': { 'angular-in-memory-web-api': {
main: './index.js', main: './index.js',
defaultExtension: 'js' defaultExtension: 'js'
} }

View File

@ -50,10 +50,10 @@
'@angular/forms/testing': 'ng:forms-builds/master/bundles/forms-testing.umd.js', '@angular/forms/testing': 'ng:forms-builds/master/bundles/forms-testing.umd.js',
// other libraries // other libraries
'rxjs': 'npm:rxjs', 'rxjs': 'npm:rxjs',
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api', 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js', 'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
'typescript': 'npm:typescript@2.0.2/lib/typescript.js', 'typescript': 'npm:typescript@2.0.2/lib/typescript.js',
}, },
// packages tells the System loader how to load when no filename and/or no extension // packages tells the System loader how to load when no filename and/or no extension
@ -65,7 +65,7 @@
rxjs: { rxjs: {
defaultExtension: 'js' defaultExtension: 'js'
}, },
'angular2-in-memory-web-api': { 'angular-in-memory-web-api': {
main: './index.js', main: './index.js',
defaultExtension: 'js' defaultExtension: 'js'
} }

View File

@ -37,10 +37,10 @@
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js', '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
// other libraries // other libraries
'rxjs': 'npm:rxjs', 'rxjs': 'npm:rxjs',
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api', 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js', 'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
'typescript': 'npm:typescript@2.0.2/lib/typescript.js', 'typescript': 'npm:typescript@2.0.2/lib/typescript.js',
}, },
// packages tells the System loader how to load when no filename and/or no extension // packages tells the System loader how to load when no filename and/or no extension
@ -52,7 +52,7 @@
rxjs: { rxjs: {
defaultExtension: 'js' defaultExtension: 'js'
}, },
'angular2-in-memory-web-api': { 'angular-in-memory-web-api': {
main: './index.js', main: './index.js',
defaultExtension: 'js' defaultExtension: 'js'
} }

View File

@ -17,8 +17,9 @@ export enum Color {Red, Green, Blue};
* Giant grab bag of stuff to drive the chapter * Giant grab bag of stuff to drive the chapter
*/ */
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html' templateUrl: 'app.component.html'
}) })
export class AppComponent implements AfterViewInit, OnInit { export class AppComponent implements AfterViewInit, OnInit {

View File

@ -1,7 +1,8 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html' templateUrl: 'app.component.html'
}) })
export class AppComponent { } export class AppComponent { }

View File

@ -272,6 +272,7 @@ export class ExternalTemplateComponent implements OnInit {
export class InnerCompWithExternalTemplateComponent { } export class InnerCompWithExternalTemplateComponent { }
@Component({ @Component({
moduleId: module.id,
selector: 'bad-template-comp', selector: 'bad-template-comp',
templateUrl: 'non-existant.html' templateUrl: 'non-existant.html'
}) })

View File

@ -5,9 +5,10 @@ import { Hero } from '../model';
// #docregion component // #docregion component
@Component({ @Component({
moduleId: module.id,
selector: 'dashboard-hero', selector: 'dashboard-hero',
templateUrl: 'app/dashboard/dashboard-hero.component.html', templateUrl: 'dashboard-hero.component.html',
styleUrls: ['app/dashboard/dashboard-hero.component.css'] styleUrls: [ 'dashboard-hero.component.css' ]
}) })
export class DashboardHeroComponent { export class DashboardHeroComponent {
@Input() hero: Hero; @Input() hero: Hero;

View File

@ -5,9 +5,10 @@ import { Router } from '@angular/router';
import { Hero, HeroService } from '../model'; import { Hero, HeroService } from '../model';
@Component({ @Component({
moduleId: module.id,
selector: 'app-dashboard', selector: 'app-dashboard',
templateUrl: 'app/dashboard/dashboard.component.html', templateUrl: 'dashboard.component.html',
styleUrls: ['app/dashboard/dashboard.component.css'] styleUrls: [ 'dashboard.component.css' ]
}) })
export class DashboardComponent implements OnInit { export class DashboardComponent implements OnInit {

View File

@ -9,9 +9,10 @@ import { HeroDetailService } from './hero-detail.service';
// #docregion prototype // #docregion prototype
@Component({ @Component({
moduleId: module.id,
selector: 'app-hero-detail', selector: 'app-hero-detail',
templateUrl: 'app/hero/hero-detail.component.html', templateUrl: 'hero-detail.component.html',
styleUrls: ['app/hero/hero-detail.component.css'], styleUrls: ['hero-detail.component.css' ],
providers: [ HeroDetailService ] providers: [ HeroDetailService ]
}) })
export class HeroDetailComponent implements OnInit { export class HeroDetailComponent implements OnInit {

View File

@ -4,9 +4,10 @@ import { Router } from '@angular/router';
import { Hero, HeroService } from '../model'; import { Hero, HeroService } from '../model';
@Component({ @Component({
moduleId: module.id,
selector: 'app-heroes', selector: 'app-heroes',
templateUrl: 'app/hero/hero-list.component.html', templateUrl: 'hero-list.component.html',
styleUrls: ['app/hero/hero-list.component.css'] styleUrls: [ 'hero-list.component.css' ]
}) })
export class HeroListComponent implements OnInit { export class HeroListComponent implements OnInit {
heroes: Promise<Hero[]>; heroes: Promise<Hero[]>;

View File

@ -7,8 +7,9 @@ import { HeroService } from './hero.service';
// #enddocregion imports // #enddocregion imports
@Component({ @Component({
moduleId: module.id,
selector: 'my-dashboard', selector: 'my-dashboard',
templateUrl: 'app/dashboard.component.html' templateUrl: 'dashboard.component.html'
}) })
// #docregion component // #docregion component
export class DashboardComponent implements OnInit { export class DashboardComponent implements OnInit {

View File

@ -9,12 +9,13 @@ import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
@Component({ @Component({
moduleId: module.id,
selector: 'my-dashboard', selector: 'my-dashboard',
// #docregion templateUrl // #docregion templateUrl
templateUrl: 'app/dashboard.component.html', templateUrl: 'dashboard.component.html',
// #enddocregion templateUrl // #enddocregion templateUrl
// #docregion css // #docregion css
styleUrls: ['app/dashboard.component.css'] styleUrls: [ 'dashboard.component.css' ]
// #enddocregion css // #enddocregion css
}) })
// #docregion component // #docregion component

View File

@ -5,7 +5,8 @@
// #docregion added-imports // #docregion added-imports
// Keep the Input import for now, we'll remove it later: // Keep the Input import for now, we'll remove it later:
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
// #enddocregion added-imports // #enddocregion added-imports
@ -20,8 +21,9 @@ export class HeroDetailComponent implements OnInit {
constructor( constructor(
private heroService: HeroService, private heroService: HeroService,
private route: ActivatedRoute) { private route: ActivatedRoute,
} private location: Location
) {}
ngOnInit() {} ngOnInit() {}
} }

View File

@ -1,17 +1,19 @@
// #docplaster // #docplaster
// #docregion , v2 // #docregion , v2
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
@Component({ @Component({
moduleId: module.id,
selector: 'my-hero-detail', selector: 'my-hero-detail',
// #docregion templateUrl // #docregion templateUrl
templateUrl: 'app/hero-detail.component.html', templateUrl: 'hero-detail.component.html',
// #enddocregion templateUrl, v2 // #enddocregion templateUrl, v2
styleUrls: ['app/hero-detail.component.css'] styleUrls: [ 'hero-detail.component.css' ]
// #docregion v2 // #docregion v2
}) })
// #docregion implement // #docregion implement
@ -22,8 +24,9 @@ export class HeroDetailComponent implements OnInit {
// #docregion ctor // #docregion ctor
constructor( constructor(
private heroService: HeroService, private heroService: HeroService,
private route: ActivatedRoute) { private route: ActivatedRoute,
} private location: Location
) {}
// #enddocregion ctor // #enddocregion ctor
// #docregion ngOnInit // #docregion ngOnInit
@ -38,7 +41,7 @@ export class HeroDetailComponent implements OnInit {
// #docregion goBack // #docregion goBack
goBack(): void { goBack(): void {
window.history.back(); this.location.back();
} }
// #enddocregion goBack // #enddocregion goBack
} }

View File

@ -8,10 +8,11 @@ import { HeroService } from './hero.service';
// #docregion renaming, metadata // #docregion renaming, metadata
@Component({ @Component({
moduleId: module.id,
selector: 'my-heroes', selector: 'my-heroes',
// #enddocregion renaming // #enddocregion renaming
templateUrl: 'app/heroes.component.html', templateUrl: 'heroes.component.html',
styleUrls: ['app/heroes.component.css'] styleUrls: [ 'heroes.component.css' ]
// #docregion renaming // #docregion renaming
}) })
// #enddocregion metadata // #enddocregion metadata

View File

@ -12,7 +12,7 @@ import { HttpModule } from '@angular/http';
// #enddocregion v1 // #enddocregion v1
// Imports for loading & configuring the in-memory web api // Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular2-in-memory-web-api'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service'; import { InMemoryDataService } from './in-memory-data.service';
// #docregion v1 // #docregion v1

View File

@ -6,9 +6,10 @@ import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
@Component({ @Component({
moduleId: module.id,
selector: 'my-dashboard', selector: 'my-dashboard',
templateUrl: 'app/dashboard.component.html', templateUrl: 'dashboard.component.html',
styleUrls: ['app/dashboard.component.css'] styleUrls: [ 'dashboard.component.css' ]
}) })
// #enddocregion search // #enddocregion search
export class DashboardComponent implements OnInit { export class DashboardComponent implements OnInit {

View File

@ -1,22 +1,25 @@
// #docregion // #docregion
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
@Component({ @Component({
moduleId: module.id,
selector: 'my-hero-detail', selector: 'my-hero-detail',
templateUrl: 'app/hero-detail.component.html', templateUrl: 'hero-detail.component.html',
styleUrls: ['app/hero-detail.component.css'] styleUrls: [ 'hero-detail.component.css' ]
}) })
export class HeroDetailComponent implements OnInit { export class HeroDetailComponent implements OnInit {
hero: Hero; hero: Hero;
constructor( constructor(
private heroService: HeroService, private heroService: HeroService,
private route: ActivatedRoute) { private route: ActivatedRoute,
} private location: Location
) {}
ngOnInit(): void { ngOnInit(): void {
this.route.params.forEach((params: Params) => { this.route.params.forEach((params: Params) => {
@ -29,11 +32,11 @@ export class HeroDetailComponent implements OnInit {
// #docregion save // #docregion save
save(): void { save(): void {
this.heroService.update(this.hero) this.heroService.update(this.hero)
.then(this.goBack); .then(() => this.goBack());
} }
// #enddocregion save // #enddocregion save
goBack(): void { goBack(): void {
window.history.back(); this.location.back();
} }
} }

View File

@ -9,9 +9,10 @@ import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero'; import { Hero } from './hero';
@Component({ @Component({
moduleId: module.id,
selector: 'hero-search', selector: 'hero-search',
templateUrl: 'app/hero-search.component.html', templateUrl: 'hero-search.component.html',
styleUrls: ['app/hero-search.component.css'], styleUrls: [ 'hero-search.component.css' ],
providers: [HeroSearchService] providers: [HeroSearchService]
}) })
export class HeroSearchComponent implements OnInit { export class HeroSearchComponent implements OnInit {

View File

@ -6,9 +6,10 @@ import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
@Component({ @Component({
moduleId: module.id,
selector: 'my-heroes', selector: 'my-heroes',
templateUrl: 'app/heroes.component.html', templateUrl: 'heroes.component.html',
styleUrls: ['app/heroes.component.css'] styleUrls: [ 'heroes.component.css' ]
}) })
export class HeroesComponent implements OnInit { export class HeroesComponent implements OnInit {
heroes: Hero[]; heroes: Hero[];

View File

@ -1,5 +1,5 @@
// #docregion , init // #docregion , init
import { InMemoryDbService } from 'angular2-in-memory-web-api'; import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService { export class InMemoryDataService implements InMemoryDbService {
createDb() { createDb() {
let heroes = [ let heroes = [

View File

@ -8,8 +8,9 @@ import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
// #docregion initialclass // #docregion initialclass
@Component({ @Component({
moduleId: module.id,
selector: 'phone-detail', selector: 'phone-detail',
templateUrl: 'phone-detail/phone-detail.template.html', templateUrl: 'phone-detail.template.html',
// #enddocregion initialclass // #enddocregion initialclass
pipes: [ CheckmarkPipe ] pipes: [ CheckmarkPipe ]
// #docregion initialclass // #docregion initialclass

View File

@ -4,8 +4,9 @@ import { Component } from '@angular/core';
import { Phone, PhoneData } from '../core/phone/phone.service'; import { Phone, PhoneData } from '../core/phone/phone.service';
@Component({ @Component({
moduleId: module.id,
selector: 'phone-list', selector: 'phone-list',
templateUrl: 'phone-list/phone-list.template.html' templateUrl: 'phone-list.template.html'
}) })
export class PhoneListComponent { export class PhoneListComponent {
phones: PhoneData[]; phones: PhoneData[];

View File

@ -0,0 +1,3 @@
{
"unittesting": true
}

View File

@ -7,17 +7,17 @@
// map tells the System loader where to look for things // map tells the System loader where to look for things
// #docregion paths // #docregion paths
var map = { var map = {
'app': '/app', // 'dist', 'app': '/app', // 'dist',
'@angular': '/node_modules/@angular', '@angular': '/node_modules/@angular',
'angular2-in-memory-web-api': '/node_modules/angular2-in-memory-web-api', 'angular-in-memory-web-api': '/node_modules/angular-in-memory-web-api',
'rxjs': '/node_modules/rxjs' 'rxjs': '/node_modules/rxjs'
}; };
var packages = { var packages = {
'/app': { main: 'main.js', defaultExtension: 'js' }, '/app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' }, 'rxjs': { defaultExtension: 'js' },
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' }, 'angular-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
}; };
// #enddocregion paths // #enddocregion paths

View File

@ -6,8 +6,9 @@ import { Phone, PhoneData } from '../core/phone/phone.service';
import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe'; import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
@Component({ @Component({
moduleId: module.id,
selector: 'phone-detail', selector: 'phone-detail',
templateUrl: 'phone-detail/phone-detail.template.html', templateUrl: 'phone-detail.template.html',
pipes: [ CheckmarkPipe ] pipes: [ CheckmarkPipe ]
}) })
export class PhoneDetailComponent { export class PhoneDetailComponent {

View File

@ -5,8 +5,9 @@ import { RouterLink } from '@angular/router-deprecated';
import { Phone, PhoneData } from '../core/phone/phone.service'; import { Phone, PhoneData } from '../core/phone/phone.service';
@Component({ @Component({
moduleId: module.id,
selector: 'phone-list', selector: 'phone-list',
templateUrl: 'phone-list/phone-list.template.html', templateUrl: 'phone-list.template.html',
directives: [ RouterLink ] directives: [ RouterLink ]
}) })
// #enddocregion top // #enddocregion top

View File

@ -0,0 +1,3 @@
{
"unittesting": true
}

View File

@ -7,17 +7,17 @@
// map tells the System loader where to look for things // map tells the System loader where to look for things
// #docregion paths // #docregion paths
var map = { var map = {
'app': '/app', // 'dist', 'app': '/app', // 'dist',
'@angular': '/node_modules/@angular', '@angular': '/node_modules/@angular',
'angular2-in-memory-web-api': '/node_modules/angular2-in-memory-web-api', 'angular-in-memory-web-api': '/node_modules/angular-in-memory-web-api',
'rxjs': '/node_modules/rxjs' 'rxjs': '/node_modules/rxjs'
}; };
var packages = { var packages = {
'/app': { main: 'main.js', defaultExtension: 'js' }, '/app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' }, 'rxjs': { defaultExtension: 'js' },
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' }, 'angular-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
}; };
// #enddocregion paths // #enddocregion paths

View File

@ -2,7 +2,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html' templateUrl: 'app.component.html'
}) })
export class AppComponent { } export class AppComponent { }

View File

@ -166,6 +166,8 @@ figure.image-display
1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag. 1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
1. The `moduleId` property sets the base for module-relative URLs such as the `templateUrl`.
1. The `templateUrl` property points to a separate file for template HTML called `hero_form_component.html`. 1. The `templateUrl` property points to a separate file for template HTML called `hero_form_component.html`.
1. We defined dummy data for `model` and `powers`, as befits a demo. 1. We defined dummy data for `model` and `powers`, as befits a demo.

View File

@ -108,7 +108,7 @@
"npm-packages": { "npm-packages": {
"title": "Npm Packages", "title": "Npm Packages",
"intro": "Details of the recommended npm packages and the different kinds of package dependencies" "intro": "Recommended npm packages, and how to specify package dependencies"
}, },
"pipes": { "pipes": {
@ -123,7 +123,7 @@
"security": { "security": {
"title": "Security", "title": "Security",
"intro": "Prevent security vulnerabilities" "intro": "Developing for content security in Angular applications"
}, },
"structural-directives": { "structural-directives": {

View File

@ -1,12 +1,12 @@
include ../_util-fns extends ../../../ts/latest/guide/index.jade
+includeShared('{ts}', 'intro') block includes
+includeShared('{ts}', 'how-to-read-1') include ../_util-fns
.alert.is-important
:marked block js-alert
Most of the documentation has been written for TypeScript developers .alert.is-important
and has not yet been translated to JavaScript. :marked
Please bear with us. Meanwhile, we've provide links to the TypeScript chapters Most of the documentation has been written for TypeScript developers
where JavaScript versions are unavailable. and has not yet been translated to JavaScript.
+includeShared('{ts}', 'how-to-read-2') Please bear with us. Meanwhile, we've provide links to the TypeScript chapters
+includeShared('{ts}', 'the-rest') where JavaScript versions are unavailable.

View File

@ -669,7 +669,7 @@ a#in-mem-web-api
.l-sub-section .l-sub-section
:marked :marked
The in-memory web api is not part of the Angular core. The in-memory web api is not part of the Angular core.
It's an optional service in its own `angular2-in-memory-web-api` library It's an optional service in its own `angular-in-memory-web-api` library
that we installed with npm (see `package.json`) and that we installed with npm (see `package.json`) and
registered for module loading by SystemJS (see `systemjs.config.js`) registered for module loading by SystemJS (see `systemjs.config.js`)

View File

@ -36,13 +36,6 @@
"intro": "依赖注入技术" "intro": "依赖注入技术"
}, },
"dynamic-form-deprecated": {
"title": "Dynamic Forms",
"intro": "Render dynamic forms with NgFormModel",
"basics": true,
"hide": true
},
"dynamic-form": { "dynamic-form": {
"title": "动态表单", "title": "动态表单",
"intro": "用FormGroup渲染动态表单" "intro": "用FormGroup渲染动态表单"

View File

@ -1,149 +0,0 @@
include ../_util-fns
.alert.is-important
:marked
This cookbook is using the deprecated forms API, which is disabled as of RC5, thus this sample only works up to RC4.
We have created a new version of this cookbook using the new API <a href='/docs/ts/latest/cookbook/dynamic-form.html'>here</a>.
:marked
We can't always justify the cost and time to build handcrafted forms,
especially if we'll need a great number of them, they're similar to each other, and they change frequently
to meet rapidly changing business and regulatory requirements.
It may be more economical to create the forms dynamically, based on metadata that describe the business object model.
In this cookbook we show how to use `ngFormModel` to dynamically render a simple form with different control types and validation.
It's a primitive start.
It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience.
All such greatness has humble beginnings.
In our example we use a dynamic form to build an online application experience for heroes seeking employment.
The agency is constantly tinkering with the application process.
We can create the forms on the fly *without changing our application code*.
<a id="toc"></a>
:marked
## Table of contents
[Question Model](#object-model)
[Form Component](#form-component)
[Questionnaire Metadata](#questionnaire-metadata)
[Dynamic Template](#dynamic-template)
:marked
**See the <live-example name="cb-dynamic-form-deprecated"></live-example>**.
.l-main-section
<a id="object-model"></a>
:marked
## Question Model
The first step is to define an object model that can describe all scenarios needed by the form functionality.
The hero application process involves a form with a lot of questions.
The "question" is the most fundamental object in the model.
We have created `QuestionBase` as the most fundamental question class.
+makeExample('cb-dynamic-form-deprecated/ts/app/question-base.ts','','app/question-base.ts')
:marked
From this base we derived two new classes in `TextboxQuestion` and `DropdownQuestion` that represent Textbox and Dropdown questions.
The idea is that the form will be bound to specific question types and render the appropriate controls dynamically.
`TextboxQuestion` supports multiple html5 types like text, email, url etc via the `type` property.
+makeExample('cb-dynamic-form-deprecated/ts/app/question-textbox.ts',null,'app/question-textbox.ts')(format='.')
:marked
`DropdownQuestion` presents a list of choices in a select box.
+makeExample('cb-dynamic-form-deprecated/ts/app/question-dropdown.ts',null,'app/question-dropdown.ts')(format='.')
:marked
Next we have defined `QuestionControlService`, a simple service for transforming our questions to an ngForm control group.
In a nutshell, the control group consumes the metadata from the question model and allows us to specify default values and validation rules.
+makeExample('cb-dynamic-form-deprecated/ts/app/question-control.service.ts',null,'app/question-control.service.ts')(format='.')
<a id="form-component"></a>
:marked
## Question form components
Now that we have defined the complete model we are ready to create components to represent the dynamic form.
:marked
`DynamicFormComponent` is the entry point and the main container for the form.
+makeTabs(
`cb-dynamic-form-deprecated/ts/app/dynamic-form.component.html,
cb-dynamic-form-deprecated/ts/app/dynamic-form.component.ts`,
null,
`dynamic-form.component.html,
dynamic-form.component.ts`
)
:marked
It presents a list of questions, each question bound to a `<df-question>` component element.
The `<df-question>` tag matches the `DynamicFormQuestionComponent`,
the component responsible for rendering the details of each _individual_ question based on values in the data-bound question object.
+makeTabs(
`cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.html,
cb-dynamic-form-deprecated/ts/app/dynamic-form-question.component.ts`,
null,
`dynamic-form-question.component.html,
dynamic-form-question.component.ts`
)
:marked
Notice this component can present any type of question in our model.
We only have two types of questions at this point but we can imagine many more.
The `ngSwitch` determines which type of question to display.
In both components we're relying on Angular's **ngFormModel** to connect the template HTML to the
underlying control objects, populated from the question model with display and validation rules.
<a id="questionnaire-metadata"></a>
:marked
## Questionnaire data
:marked
`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
The set of questions we have defined for the job application is returned from the `QuestionService`.
In a real app we'd retrieve these questions from storage.
The key point is that we control the hero job application questions entirely through the objects returned from `QuestionService`.
Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array.
+makeExample('cb-dynamic-form-deprecated/ts/app/question.service.ts','','app/question.service.ts')
:marked
Finally, we display an instance of the form in the `AppComponent` shell.
+makeExample('cb-dynamic-form-deprecated/ts/app/app.component.ts','','app.component.ts')
<a id="dynamic-template"></a>
:marked
## Dynamic Template
Although in this example we're modelling a job application for heroes, there are no references to any specific hero question
outside the objects returned by `QuestionService`.
This is very important since it allows us to repurpose the components for any type of survey
as long as it's compatible with our *question* object model.
The key is the dynamic data binding of metadata used to render the form
without making any hardcoded assumptions about specific questions.
In addition to control metadata, we are also adding validation dynamically.
The *Save* button is disabled until the form is in a valid state.
When the form is valid, we can click *Save* and the app renders the current form values as JSON.
This proves that any user input is bound back to the data model.
Saving and retrieving the data is an exercise for another time.
:marked
The final form looks like this:
figure.image-display
img(src="/resources/images/cookbooks/dynamic-form/dynamic-form.png" alt="Dynamic-Form")
:marked
[Back to top](#top)

View File

@ -17,7 +17,7 @@
"displaying-data": { "displaying-data": {
"title": "显示数据", "title": "显示数据",
"intro": "利用插值表达式和其它形式的属性绑定机制把数据显示到UI上。", "intro": "属性绑定机制把数据显示到UI上。",
"nextable": true, "nextable": true,
"basics": true "basics": true
}, },

View File

@ -442,18 +442,22 @@ block ts-decorator
来看下`@Component`中的一些配置项: 来看下`@Component`中的一些配置项:
:marked :marked
- `selector` - a css selector that tells Angular to create and insert an instance of this component - `moduleId: module.id`: sets the base for module-relative loading of the `templateUrl`.
where it finds a `<hero-list>` tag in *parent* HTML.
- `moduleId: module.id`: 为`templateUrl`的模块相对加载提供基础。
- `selector`: 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 For example, if an app's HTML contains `<hero-list></hero-list>`, then
Angular inserts an instance of the `HeroListComponent` view between those tags. Angular inserts an instance of the `HeroListComponent` view between those tags.
- `selector` - 一个css选择器它告诉Angular在*父级*HTML中寻找一个`<hero-list>`标签,然后创建该组件,并插入此标签中。 - `selector` 一个CSS选择器它告诉Angular在*父级*HTML中寻找一个`<hero-list>`标签,然后创建该组件,并插入此标签中。
比如如果应用的HTML包含`<hero-list></hero-list>`Angular就会把`HeroListComponent`的一个实例插入到这个标签中。 比如如果应用的HTML包含`<hero-list></hero-list>`Angular就会把`HeroListComponent`的一个实例插入到这个标签中。
- `templateUrl`: address of this component's HTML template, shown [above](#templates). - `templateUrl`: module-relative address of this component's HTML template, shown [above](#templates).
- `templateUrl` - 组件HTML模板的地址我们在[前面](#templates)展示过它。
- `templateUrl`组件HTML模板的模块相对地址我们在[前面](#templates)展示过它。
- `directives`: !{_array} of the components or directives that *this* template requires. - `directives`: !{_array} of the components or directives that *this* template requires.
In the last line of `hero-list.component.html`, Angular inserts a `HeroDetailComponent` In the last line of `hero-list.component.html`, Angular inserts a `HeroDetailComponent`
in the space indicated by `<hero-detail>` tags. in the space indicated by `<hero-detail>` tags.

View File

@ -2,18 +2,17 @@ block includes
include ../_util-fns include ../_util-fns
- var _iterableUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols'; - var _iterableUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols';
- var _boolean = 'truthy/falsey'; - var _boolean = 'truthy/falsey';
:marked :marked
We typically display data in Angular by binding controls in an HTML template You can display data by binding controls in an HTML template to properties of an Angular component.
to properties of an Angular component.
在Angular中最典型的数据显示方式就是把HTML模板中的控件绑定到Angular组件的属性。 在Angular中最典型的数据显示方式就是把HTML模板中的控件绑定到Angular组件的属性。
In this chapter, we'll create a component with a list of heroes. Each hero has a name. In this page, you'll create a component with a list of heroes.
We'll display the list of hero names and You'll display the list of hero names and
conditionally show a message below the list. conditionally show a message below the list.
本章中,我们将创建一个英雄列表组件。每位英雄都有一个名字。我们将显示英雄名字的列表,当选中一位英雄时,就在列表下方显示一条消息。 本章中,我们将创建一个英雄列表组件。我们将显示英雄名字的列表,当选中一位英雄时,就在列表下方显示一条消息。
The final UI looks like this: The final UI looks like this:
@ -23,7 +22,7 @@ figure.image-display
img(src="/resources/images/devguide/displaying-data/final.png" alt="Final UI") img(src="/resources/images/devguide/displaying-data/final.png" alt="Final UI")
:marked :marked
# Table Of Contents # Contents
# 目录 # 目录
* [Showing component properties with interpolation](#interpolation) * [Showing component properties with interpolation](#interpolation)
@ -36,7 +35,7 @@ figure.image-display
.l-sub-section .l-sub-section
:marked :marked
The <live-example></live-example> demonstrates all of the syntax and code The <live-example></live-example> demonstrates all of the syntax and code
snippets described in this chapter. snippets described in this page.
这个<live-example>在线例子</live-example>演示了本章中描述的所有语法和代码片段。 这个<live-example>在线例子</live-example>演示了本章中描述的所有语法和代码片段。
@ -47,38 +46,40 @@ figure.image-display
The easiest way to display a component property The easiest way to display a component property
is to bind the property name through interpolation. is to bind the property name through interpolation.
With interpolation, we put the property name in the view template, enclosed in double curly braces: `{{myHero}}`. With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
要显示组件的属性,最简单的方式就是通过插值表达式(Interpolation)来绑定属性名。 要显示组件的属性,最简单的方式就是通过插值表达式(Interpolation)来绑定属性名。
要使用插值表达式,就把属性名包裹在双重花括号里放进视图模板,如`{{myHero}}`。 要使用插值表达式,就把属性名包裹在双重花括号里放进视图模板,如`{{myHero}}`。
Let's build a small illustrative example together. To build an illustrative example, start by creating a new project folder called <ngio-ex path="displaying-data"></ngio-ex>
and following the steps in [QuickStart](../quickstart.html).
我们来一起做个简明的小例子。创建一个新的项目文件夹(<ngio-ex path="displaying-data"></ngio-ex>),并且完成[“快速起步”](../quickstart.html)中的步骤。
我们来一起做个简明的小例子。
Create a new project folder (<ngio-ex path="displaying-data"></ngio-ex>) and follow the steps in the [QuickStart](../quickstart.html).
创建一个新的项目文件夹(<ngio-ex path="displaying-data"></ngio-ex>),并且完成[“快速起步”](../quickstart.html)中的步骤。
block quickstart-repo block quickstart-repo
include ../_quickstart_repo include ../_quickstart_repo
:marked :marked
Then modify the <ngio-ex path="app.component.ts"></ngio-ex> file by Then modify the <ngio-ex path="app.component.ts"></ngio-ex> file by
changing the template and the body of the component. changing the template and the body of the component.
When we're done, it should look like this:
然后,到`app.component.ts`文件中修改组件的模板和代码。 然后,到`app.component.ts`文件中修改组件的模板和代码。
When you're done, it should look like this:
修改完之后,它看起来应该是这样的: 修改完之后,它看起来应该是这样的:
+makeExample('app/app.component.1.ts') +makeExample('app/app.component.1.ts')
:marked :marked
We added two properties to the formerly empty component: `title` and `myHero`. You added two properties to the formerly empty component: `title` and `myHero`.
再把两个属性`title`和`myHero`添加到之前空白的组件中。 再把两个属性`title`和`myHero`添加到之前空白的组件中。
Our revised template displays the two component properties using double curly brace The revised template displays the two component properties using double curly brace
interpolation: interpolation:
修改完的模板会使用双花括号形式的插值表达式来显示这两个模板属性: 修改完的模板会使用双花括号形式的插值表达式来显示这两个模板属性:
@ -89,13 +90,14 @@ block quickstart-repo
.l-sub-section .l-sub-section
:marked :marked
The template is a multi-line string within ECMAScript 2015 backticks (<code>\`</code>). The template is a multi-line string within ECMAScript 2015 backticks (<code>\`</code>).
The backtick (<code>\`</code>) &mdash; which is *not* the same character as a single The backtick (<code>\`</code>)&mdash;which is *not* the same character as a single
quote (`'`) &mdash; has many nice features. The feature we're exploiting here quote (`'`)&mdash;allows you to compose a string over several lines, which makes the
is the ability to compose the string over several lines, which makes for HTML more readable.
much more readable HTML.
模板是包在反引号(<code>\`</code>)中的一个多行字符串。 模板是包在反引号(<code>\`</code>)中的一个多行字符串。
反引号(<code>\`</code>) —— 注意,不是单引号(') —— 有很多好用的特性。我们在这里用到的是它把一个字符串写在多行上的能力这样我们的HTML模板就会更容易阅读。 反引号(<code>\`</code>) —— 注意,不是单引号(') —— 有很多好用的特性。
我们在这里用到的是它把一个字符串写在多行上的能力这样我们的HTML模板就会更容易阅读。
:marked :marked
Angular automatically pulls the value of the `title` and `myHero` properties from the component and Angular automatically pulls the value of the `title` and `myHero` properties from the component and
@ -106,55 +108,56 @@ block quickstart-repo
.l-sub-section .l-sub-section
:marked :marked
More precisely, the redisplay occurs after some kind of asynchronous event related to More precisely, the redisplay occurs after some kind of asynchronous event related to
the view such as a keystroke, a timer completion, or an async `XHR` response. the view, such as a keystroke, a timer completion, or a response to an HTTP request.
We don't have those in this sample.
But then the properties aren't changing on their own either. For the moment we must operate on faith.
严格来说,“重新显示”是在某些与视图有关的异步事件之后发生的,比如:按键、定时器或收到异步`XHR`响应。本例中没有体现这一点。 严格来说,“重新显示”是在某些与视图有关的异步事件之后发生的,比如:按键、定时器或收到异步`XHR`响应。
很显然属性肯定不会无缘无故的变化。但是目前我们只要先相信Angular会处理好就行了。
:marked :marked
Notice that we haven't called **new** to create an instance of the `AppComponent` class. Notice that you don't call **new** to create an instance of the `AppComponent` class.
Angular is creating an instance for us. How? Angular is creating an instance for you. How?
注意,我们从没调用过**new**来创建`AppComponent`类的实例是Angular替我们创建了它。那么它是如何创建的呢 注意,我们从没调用过**new**来创建`AppComponent`类的实例是Angular替我们创建了它。那么它是如何创建的呢
Notice the CSS `selector` in the `@Component` !{_decorator} that specifies an element named `my-app`. The CSS `selector` in the `@Component` !{_decorator} specifies an element named `my-app`.
Remember back in [QuickStart](../quickstart.html) that we added the `<my-app>` element to the body of our `index.html` file: Remember back in [QuickStart](../quickstart.html) that you added the `<my-app>`
element to the body of your `index.html` file:
注意`@Component`装饰器中指定的CSS选择器`selector`,它指定了一个叫`my-app`的元素。 注意`@Component`装饰器中指定的CSS选择器`selector`,它指定了一个叫`my-app`的元素。
回忆下,在[“快速起步”](../quickstart.html)一章中,我们曾把一个`<my-app>`元素添加到`index.html`的`body`里。 回忆下,在[“快速起步”](../quickstart.html)一章中,我们曾把一个`<my-app>`元素添加到`index.html`的`body`里。
+makeExcerpt('index.html', 'body') +makeExcerpt('index.html', 'body')
:marked :marked
When we bootstrap with the `AppComponent` class (in <ngio-ex path="main.ts"></ngio-ex>), Angular looks for a `<my-app>` When you bootstrap with the `AppComponent` class (in <ngio-ex path="main.ts"></ngio-ex>), Angular looks for a `<my-app>`
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
inside the `<my-app>` tag. inside the `<my-app>` tag.
当我们通过`main.ts`中的`AppComponent`类启动时Angular在`index.html`中查找一个`<my-app>`元素, 当我们通过`main.ts`中的`AppComponent`类启动时Angular在`index.html`中查找一个`<my-app>`元素,
然后实例化一个`AppComponent`,并将其渲染到`<my-app>`标签中。 然后实例化一个`AppComponent`,并将其渲染到`<my-app>`标签中。
Try running the app. It should display the title and hero name: Now run the app. It should display the title and hero name:
试一下本应用。它应该显示出标题和英雄名。 试一下本应用。它应该显示出标题和英雄名。
figure.image-display figure.image-display
img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero") img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero")
+ifDocsFor('ts') +ifDocsFor('ts')
:marked :marked
Let's review some of the choices we made and consider alternatives. The next few sections review some of the coding choices in the app.
我们来回顾一下以前所做的决定,看看还有哪些其它选择。 我们来回顾一下以前所做的决定,看看还有哪些其它选择。
:marked :marked
## Template inline or template file? ## Template inline or template file?
## 内联(inline)模板还是模板文件? ## 内联(inline)模板还是模板文件?
We can store our component's template in one of two places. You can store your component's template in one of two places.
We can define it *inline* using the `template` property, as we do here. You can define it *inline* using the `template` property, or you can define
Or we can define the template in a separate HTML file and link to it in the template in a separate HTML file and link to it in
the component metadata using the `@Component` !{_decorator}'s `templateUrl` property. the component metadata using the `@Component` !{_decorator}'s `templateUrl` property.
我们有两种地方可以用来存放组件模板。 我们有两种地方可以用来存放组件模板。
@ -163,7 +166,7 @@ figure.image-display
The choice between inline and separate HTML is a matter of taste, The choice between inline and separate HTML is a matter of taste,
circumstances, and organization policy. circumstances, and organization policy.
Here we're using inline HTML because the template is small, and the demo Here the app uses inline HTML because the template is small and the demo
is simpler without the additional HTML file. is simpler without the additional HTML file.
到底选择内联HTML还是独立HTML取决于个人喜好、具体状况和组织级策略。 到底选择内联HTML还是独立HTML取决于个人喜好、具体状况和组织级策略。
@ -179,25 +182,18 @@ figure.image-display
## 初始化:使用构造函数还是变量? ## 初始化:使用构造函数还是变量?
We initialized our component properties using variable assignment. Although this example uses variable assignment to initialize the components, you can instead declare and initialize the properties using a constructor:
This is a wonderfully concise and compact technique.
这里我们使用了变量赋值的方式初始化组件的属性。 虽然这个例子使用了变量赋值的方式初始化组件的属性。你可以使用构造函数来声明和初始化属性。
这是个特别简洁用法。
Some folks prefer to declare the properties and initialize them within a constructor like this:
也有些人喜欢单独声明属性,并且在构造函数中初始化它们,就像这样:
+makeExcerpt('app/app-ctor.component.ts', 'class') +makeExcerpt('app/app-ctor.component.ts', 'class')
:marked :marked
That's fine too. The choice is a matter of taste and organization policy. This app uses more terse "variable assignment" style simply for brevity.
We'll adopt the more terse "variable assignment" style in this chapter simply because
there will be less code to read. 本应用为了假话,使用了更简单的“变量赋值”风格。
这样也不错。这只是个人偏好和组织策略的问题。
我们在本章中只是简单的采纳了“变量赋值”的风格,这样要阅读的代码会少一点。
.l-main-section#ngFor .l-main-section#ngFor
:marked :marked
@ -205,14 +201,14 @@ figure.image-display
## 使用***ngFor***显示数组属性 ## 使用***ngFor***显示数组属性
We want to display a list of heroes. We begin by adding !{_an} !{_array} of hero names to the component and redefine `myHero` to be the first name in the !{_array}. To display a list of heroes, begin by adding !{_an} !{_array} of hero names to the component and redefine `myHero` to be the first name in the !{_array}.
我们准备显示一个英雄列表。先往组件中添加一个英雄名字数组,然后把`myHero`重定义为数组中的第一个名字。 我们准备显示一个英雄列表。先往组件中添加一个英雄名字数组,然后把`myHero`重定义为数组中的第一个名字。
+makeExcerpt('app/app.component.2.ts', 'class') +makeExcerpt('app/app.component.2.ts', 'class')
:marked :marked
Now we use the Angular `ngFor` directive in the template to display Now use the Angular `ngFor` directive in the template to display
each item in the `heroes` list. each item in the `heroes` list.
现在我们在模板中使用Angular的`ngFor`“重复器”指令来显示`heroes`列表中的每一个条目。 现在我们在模板中使用Angular的`ngFor`“重复器”指令来显示`heroes`列表中的每一个条目。
@ -220,30 +216,26 @@ figure.image-display
+makeExcerpt('app/app.component.2.ts', 'template') +makeExcerpt('app/app.component.2.ts', 'template')
:marked :marked
Our presentation is the familiar HTML unordered list with `<ul>` and `<li>` tags. Let's focus on the `<li>` tag. This UI uses the HTML unordered list with `<ul>` and `<li>` tags. The `*ngFor`
in the `<li>` element is the Angular "repeater" directive.
我们看到了熟悉的HTML —— 由`<ul>`和`<li>`标签组成的无序列表。请聚焦在`<li>`标签上。 It marks that `<li>` element (and its children) as the "repeater template":
这个我们看到了熟悉的HTML —— 由`<ul>`和`<li>`标签组成的无序列表。请聚焦在`<li>`标签上。
+makeExcerpt('app/app.component.2.ts ()', 'li', '') +makeExcerpt('app/app.component.2.ts ()', 'li', '')
:marked
We added a somewhat mysterious `*ngFor` to the `<li>` element.
That's the Angular "repeater" directive.
Its presence on the `<li>` tag marks that `<li>` element (and its children) as the "repeater template".
我们把看起来有点神秘的`*ngFor`加到`<li>`元素上。
这就是Angular的“重复器”指令。
它出现在`<li>`标签上就表示把`<li>`及其子元素作为“重复器的模板”。
.alert.is-important .alert.is-important
:marked :marked
Don't forget the leading asterisk (\*) in `*ngFor`. It is an essential part of the syntax. Don't forget the leading asterisk (\*) in `*ngFor`. It is an essential part of the syntax.
Learn more about this and `ngFor` in the [Template Syntax](./template-syntax.html#ngFor) chapter. For more information, see the [Template Syntax](./template-syntax.html#ngFor) page.
不要忘记`*ngFor`中的前导星号(\*)。它是语法中不可或缺的一部分。 不要忘记`*ngFor`中的前导星号(\*)。它是语法中不可或缺的一部分。
要了解关于此语法和`ngFor`的更多知识,请参见[模板语法](./template-syntax.html#ngFor)一章。 要了解关于此语法和`ngFor`的更多知识,请参见[模板语法](./template-syntax.html#ngFor)一章。
:marked :marked
Notice the `hero` in the `ngFor` double-quoted instruction; Notice the `hero` in the `ngFor` double-quoted instruction;
it is an example of a [template input variable](./template-syntax.html#ngForMicrosyntax). it is an example of a template input variable. Read
more about template input variables in the [microsyntax](./template-syntax.html#ngForMicrosyntax) section of
the [Template Syntax](./template-syntax.html) page.
注意看`ngFor`双引号表达式中的`hero`。 注意看`ngFor`双引号表达式中的`hero`。
它是一个[模板输入变量](./template-syntax.html#ngForMicrosyntax)(译注即ngFor模板中从外界输入的变量)。 它是一个[模板输入变量](./template-syntax.html#ngForMicrosyntax)(译注即ngFor模板中从外界输入的变量)。
@ -256,12 +248,12 @@ figure.image-display
Angular把`hero`变量作为双花括号插值表达式的上下文。 Angular把`hero`变量作为双花括号插值表达式的上下文。
.l-sub-section .l-sub-section
:marked :marked
We happened to give `ngFor` !{_an} !{_array} to display. In this case, `ngFor` is displaying !{_an} !{_array}, but `ngFor` can
In fact, `ngFor` can repeat items for any [iterable](!{_iterableUrl}) repeat items for any [iterable](!{_iterableUrl}) object.
object.
本例中,`ngFor`显示“数组”,
但`ngFor`可以为任何[可迭代Iterable](!{_iterableUrl})对象重复渲染条目。
这里我们传给`ngFor`一个“数组”让它显示。
但实际上,`ngFor`可以为任何[可迭代Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)对象重复渲染条目。
:marked :marked
Now the heroes appear in an unordered list. Now the heroes appear in an unordered list.
@ -276,22 +268,21 @@ figure.image-display
## 为数据创建一个类 ## 为数据创建一个类
We are defining our data directly inside our component. The app's code defines the data directly inside the component, which isn't best practice.
That's fine for a demo but certainly isn't a best practice. It's not even a good practice. In a simple demo, however, it's fine.
Although we won't do anything about that in this chapter, we'll make a mental note to fix this down the road.
我们在组件内部直接定义了数据。 应用代码直接在组件内部直接定义了数据。
作为演示还可以,但它显然不是最佳实践。它甚至是一个很不好的实践。 作为演示还可以,但它显然不是最佳实践。
我们在本章中先不管它,只是记下来,等将来再修复这个问题。
At the moment, we're binding to !{_an} !{_array} of strings. We do that occasionally in real applications, but At the moment, the binding is to !{_an} !{_array} of strings.
most of the time we're binding to more specialized objects. In real applications, most bindings are to more specialized objects.
现在,我们绑定到了一个字符串数组。在真实的应用中偶尔这么做。但绝大多数时候,我们会绑定一些对象数组上。 现在,我们绑定到了一个字符串数组。在真实的应用中偶尔这么做。但绝大多数时候,我们会绑定一些对象数组上。
Let's turn our !{_array} of hero names into !{_an} !{_array} of `Hero` objects. For that we'll need a `Hero` class. To convert this binding to use specialized objects, turn the !{_array}
of hero names into !{_an} !{_array} of `Hero` objects. For that you'll need a `Hero` class.
我们来把英雄名字的数组转换成`Hero`对象的数组。但首先得有一个`Hero`类。 要将此绑定转换成使用特殊对象,把英雄名字放到`Hero`对象的数组。但首先得有一个`Hero`类。
Create a new file in the `!{_appDir}` folder called <ngio-ex path="hero.ts"></ngio-ex> with the following code: Create a new file in the `!{_appDir}` folder called <ngio-ex path="hero.ts"></ngio-ex> with the following code:
@ -301,14 +292,14 @@ figure.image-display
block hero-class block hero-class
:marked :marked
We've defined a class with a constructor and two properties: `id` and `name`. You've defined a class with a constructor and two properties: `id` and `name`.
我们这样就定义了一个带有构造函数和两个属性:`id`和`name`的类。 我们这样就定义了一个带有构造函数和两个属性:`id`和`name`的类。
It might not look like we have properties, but we do. We're taking It might not look like the class has properties, but it does.
advantage of a TypeScript shortcut in our declaration of the constructor parameters. The declaration of the constructor parameters takes advantage of a TypeScript shortcut.
它可能看上去不像是有属性的类但我们确实有。我们利用的是TypeScript提供的简写形式 —— 用构造函数的参数直接定义属性。 它可能看上去不像是有属性的类但我们确实有。我们利用的是TypeScript提供的简写形式 —— 用构造函数的参数直接定义属性。
Consider the first parameter: Consider the first parameter:
@ -340,20 +331,22 @@ block hero-class
## 使用Hero类 ## 使用Hero类
Let's make the `heroes` property in our component return !{_an} !{_array} of these `Hero` objects. The `heroes` property in the component can now use the `Hero` class to return !{_an} !{_array}
of `Hero` objects:
我们让组件中的`heroes`属性返回这些`Hero`对象的数组。 我们让组件中的`heroes`属性返回这些`Hero`对象的数组。
+makeExcerpt('app/app.component.3.ts', 'heroes') +makeExcerpt('app/app.component.3.ts', 'heroes')
:marked :marked
We'll have to update the template. Next, update the template.
At the moment it displays the hero's `id` and `name`. At the moment it displays the hero's `id` and `name`.
Let's fix that so we display only the hero's `name` property. Fix that to display only the hero's `name` property.
我们还得更新一下模板。 我们还得更新一下模板。
现在它显示的是英雄的`id`和`name`。 现在它显示的是英雄的`id`和`name`。
我们要修复它,所以,只显示英雄的`name`属性就行了。 要修复它,只显示英雄的`name`属性就行了。
+makeExcerpt('app/app.component.3.ts', 'template') +makeExcerpt('app/app.component.3.ts', 'template')
@ -361,6 +354,7 @@ block hero-class
Our display looks the same, but now we know much better what a hero really is. Our display looks the same, but now we know much better what a hero really is.
从显示上看还是一样,但现在除了名字之外,我们可以有更多英雄信息。 从显示上看还是一样,但现在除了名字之外,我们可以有更多英雄信息。
.l-main-section#ngIf .l-main-section#ngIf
:marked :marked
@ -372,44 +366,48 @@ block hero-class
有时候,应用只需要在特定情况下显示视图或视图的一部分。 有时候,应用只需要在特定情况下显示视图或视图的一部分。
In our example, we'd like to display a message if we have a large number of heroes, say, more than 3. Let's change the example to display a message if there are more than three heroes.
让我们来修改这个例子让它显示在多于3位英雄的情况下显示一条消息。
在我们的例子中,假设如果有大量的英雄 —— 比如大于3位的情况下我们想要显示一条消息。
The Angular `ngIf` directive inserts or removes an element based on a !{_boolean} condition. The Angular `ngIf` directive inserts or removes an element based on a !{_boolean} condition.
We can see it in action by adding the following paragraph at the bottom of the template: To see it in action, add the following paragraph at the bottom of the template:
Angular的`NgIf`指令会基于条件的真假来显示或移除一个元素。 Angular的`NgIf`指令会基于条件的真假来显示或移除一个元素。
我们来亲自动手试一下,把下列语句加到模板的底部: 我们来亲自动手试一下,把下列语句加到模板的底部:
+makeExcerpt('app/app.component.ts', 'message') +makeExcerpt('app/app.component.ts', 'message')
.alert.is-important .alert.is-important
:marked :marked
Don't forget the leading asterisk (\*) in `*ngIf`. It is an essential part of the syntax. Don't forget the leading asterisk (\*) in `*ngIf`. It is an essential part of the syntax.
Learn more about this and `ngIf` in the [Template Syntax](./template-syntax.html#ngIf) chapter. Read more about `ngIf` and `*` in the [ngIf section](./template-syntax.html#ngIf) of the [Template Syntax](./template-syntax.html) page.
不要忘了`*ngIf`中的前导星号(\*)。它是本语法中不可或缺的一部分。 不要忘了`*ngIf`中的前导星号(\*)。它是本语法中不可或缺的一部分。
要学习关于此语法和`NgIf`的更多知识,请参见[模板语法](./template-syntax.html#ngIf)一章。 要学习关于此语法和`NgIf`的更多知识,请参见[模板语法](./template-syntax.html#ngIf)一章。
:marked :marked
The [template expression](./template-syntax.html#template-expressions) inside the double quotes The template expression inside the double quotes,
looks much like !{_Lang}, and it _is_ much like !{_Lang}. `*ngIf="heros.length > 3"`, looks and behaves much like !{_Lang}.
When the component's list of heroes has more than 3 items, Angular adds the paragraph to the DOM and the message appears. When the component's list of heroes has more than three items, Angular adds the paragraph
If there are 3 or fewer items, Angular omits the paragraph, so no message appears. to the DOM and the message appears. If there are three or fewer items, Angular omits the
paragraph, so no message appears. For more information,
see the [template expressions](./template-syntax.html#template-expressions) section of the
[Template Syntax](./template-syntax.html) page.
双引号中的[模板表达式](./template-syntax.html#template-expressions)看起来很像JavaScript但它也_只是_“像”JavaScript。 双引号中的[模板表达式](./template-syntax.html#template-expressions)看起来很像JavaScript但它也_只是_“像”JavaScript。
当组件中的英雄列表有三个以上的条目时Angular把这些语句添加到DOM中于是消息显示了出来。 当组件中的英雄列表有三个以上的条目时Angular把这些语句添加到DOM中于是消息显示了出来。
如果少于或等于三个条目Angular会移除这些语句于是没有消息显示。 如果少于或等于三个条目Angular会移除这些语句于是没有消息显示。
.alert.is-helpful .alert.is-helpful
:marked :marked
Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM. Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM. That improves performance, especially in larger projects when conditionally including or excluding
That hardly matters here. But it would matter a great deal, from a performance perspective, if big chunks of HTML with many data bindings.
we were conditionally including or excluding a big chunk of HTML with many data bindings.
Angular并不是在显示和隐藏这条消息它是在从DOM中添加和移除这些元素。 Angular并不是在显示和隐藏这条消息它是在从DOM中添加和移除这些元素。
在这个范例中,它们(在性能上)几乎等价。但是如果我们想要有条件的包含或排除“一大堆”带着很多数据绑定的HTML性能上的区别就会更加显著。 在这个范例中,它们(在性能上)几乎等价。但是如果我们想要有条件的包含或排除“一大堆”带着很多数据绑定的HTML性能上的区别就会更加显著。
:marked :marked
Try it out. Because the !{_array} has four items, the message should appear. Try it out. Because the !{_array} has four items, the message should appear.
Go back into <ngio-ex path="app.component.ts"></ngio-ex> and delete or comment out one of the elements from the hero !{_array}. Go back into <ngio-ex path="app.component.ts"></ngio-ex> and delete or comment out one of the elements from the hero !{_array}.
@ -424,7 +422,7 @@ block hero-class
## 小结 ## 小结
Now we know how to use: Now you know how to use:
现在我们知道了如何: 现在我们知道了如何:
@ -444,9 +442,10 @@ block hero-class
- **ngIf**用来根据一个布尔表达式有条件的显示一段HTML - **ngIf**用来根据一个布尔表达式有条件的显示一段HTML
Here's our final code: Here's the final code:
下面是我们的最终代码: 下面是我们的最终代码:
block final-code block final-code
+makeTabs(`displaying-data/ts/app/app.component.ts, +makeTabs(`displaying-data/ts/app/app.component.ts,

View File

@ -262,6 +262,8 @@ code-example(format="").
1. `@Component`选择器"hero-form"表示我们可以通过一个`<hero-form>`标签,把此表单扔进父模板中。 1. `@Component`选择器"hero-form"表示我们可以通过一个`<hero-form>`标签,把此表单扔进父模板中。
1. The `moduleId: module.id` property sets the base for module-relative loading of the `templateUrl`.
1. The `templateUrl` property points to a separate file for the template HTML called `hero-form.component.html`. 1. The `templateUrl` property points to a separate file for the template HTML called `hero-form.component.html`.
1. `templateUrl`属性指向一个独立的HTML模板文件名叫`hero-form.component.html`。 1. `templateUrl`属性指向一个独立的HTML模板文件名叫`hero-form.component.html`。

View File

@ -1,7 +1,6 @@
block includes block includes
include ../_util-fns include ../_util-fns
// #docregion intro
- var langName = current.path[1] == 'ts' ? 'TypeScript' : 'JavaScript' - var langName = current.path[1] == 'ts' ? 'TypeScript' : 'JavaScript'
figure figure
img(src="/resources/images/devguide/intro/people.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:10px" ) img(src="/resources/images/devguide/intro/people.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:10px" )
@ -13,9 +12,7 @@ figure
这是一份Angular实战指南。面向的是正在用HTML和#{langName}构建客户端应用的、有经验的程序员。 这是一份Angular实战指南。面向的是正在用HTML和#{langName}构建客户端应用的、有经验的程序员。
<br clear="all"> <br clear="all">
// #enddocregion intro
// #docregion how-to-read-1
<a id="learning-path"></a> <a id="learning-path"></a>
:marked :marked
# Organization # Organization
@ -27,8 +24,8 @@ figure
本文档分成几大主题区,每个区包含一组围绕自己主题的页面。 本文档分成几大主题区,每个区包含一组围绕自己主题的页面。
// #enddocregion how-to-read-1 block js-alert
// #docregion how-to-read-2
- var top="vertical-align:top" - var top="vertical-align:top"
table.vertical-table(width="100%") table.vertical-table(width="100%")
col(width="15%") col(width="15%")
@ -161,8 +158,6 @@ table.vertical-table(width="100%")
读完这些,你就可以跳到本网站的任意页面去阅读了。 读完这些,你就可以跳到本网站的任意页面去阅读了。
// #enddocregion how-to-read-2
// #docregion the-rest
:marked :marked
# Code samples # Code samples
@ -225,5 +220,4 @@ block example-links
Use the [Angular Github repo](https://github.com/angular/angular) to report issues with **Angular** itself. Use the [Angular Github repo](https://github.com/angular/angular) to report issues with **Angular** itself.
到[Angular Github库](https://github.com/angular/angular)报告与**Angular本身**有关的issues。 到[Angular Github库](https://github.com/angular/angular)报告与**Angular本身**有关的issues。
// #enddocregion the-rest

File diff suppressed because it is too large Load Diff

View File

@ -220,7 +220,7 @@ a(id="other")
### Other helper libraries ### Other helper libraries
### 其它辅助库 ### 其它辅助库
***angular2-in-memory-web-api*** - An Angular-supported library that simulates a remote server's web api ***angular-in-memory-web-api*** - An Angular-supported library that simulates a remote server's web api
without requiring an actual server or real http calls. without requiring an actual server or real http calls.
Good for demos, samples, and early stage development (before we even have a server). Good for demos, samples, and early stage development (before we even have a server).
Read about it in the [Http Client](server-communication.html#appendix-tour-of-heroes-in-memory-server) page. Read about it in the [Http Client](server-communication.html#appendix-tour-of-heroes-in-memory-server) page.

View File

@ -55,7 +55,7 @@ block includes
:marked :marked
Inside the interpolation expression we flow the component's `birthday` value through the 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/index/DatePipe-class.html) [pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/index/DatePipe-pipe.html)
function on the right. All pipes work this way. function on the right. All pipes work this way.
在这个插值表达式中,我们让组件的`birthday`值通过[管道操作符](./template-syntax.html#pipe)( | )流动到 在这个插值表达式中,我们让组件的`birthday`值通过[管道操作符](./template-syntax.html#pipe)( | )流动到

View File

@ -1147,7 +1147,7 @@ a#in-mem-web-api
.l-sub-section .l-sub-section
:marked :marked
The in-memory web api is not part of the Angular core. The in-memory web api is not part of the Angular core.
It's an optional service in its own `angular2-in-memory-web-api` library It's an optional service in its own `angular-in-memory-web-api` library
that we installed with npm (see `package.json`) and that we installed with npm (see `package.json`) and
registered for module loading by SystemJS (see `systemjs.config.js`) registered for module loading by SystemJS (see `systemjs.config.js`)

View File

@ -1285,24 +1285,23 @@ a(href="#top").to-top 回到顶部
### 同步测试 ### 同步测试
The first two tests are synchronous. The first two tests are synchronous.
Neither test can prove that a value from the service will be displayed. Thanks to the spy, they verify that `getQuote` is called _after_
the first change detection cycle during which Angular calls `ngOnInit`.
前两个测试是同步的。它们都不能证明服务返回的值将会被显示。 前两个测试是同步的。
在Spy的帮助下它们验证了在Angular调用`ngOnInit`期间发生的第一次变化检测后,`getQuote`被调用了。
Thanks to the spy, the second test verifies that `getQuote` is called. Neither test can prove that a value from the service is be displayed.
But the quote itself has not arrived, despite the fact that the spy returns a resolved promise. The quote itself has not arrived, despite the fact that the spy returns a resolved promise.
在Spy的帮助下第二个测试验证了`getQuote`被调用了。
虽然Spy返回了解析了的承诺但是确没有收到名言。
This test must wait at least one full turn of the JavaScript engine, a least one "tick", before the 两者都不能证明被显示的值是从服务的。
value becomes available. By that time, the test runner has moved on to the next test in the suite. 虽然spy返回了解析的承诺名言本身还没有到来。
This test must wait at least one full turn of the JavaScript engine before the
value becomes available. The test must become _asynchronous_.
这个测试必须等待JavaScript引擎一整个回合至少一个"tick"后,返回值才会有效。这个时候,测试运行器已经移到了测试套件下个测试。 这个测试必须等待JavaScript引擎一整个回合返回值才会有效。该测试必须要变成**异步的**。
The test must become an "async test" ... like the third test
测试必须变成“异步测试”...就像第三个测试那样。
#async #async
:marked :marked
@ -1316,13 +1315,10 @@ a(href="#top").to-top 回到顶部
+makeExample('testing/ts/app/shared/twain.component.spec.ts', 'async-test', 'app/shared/twain.component.spec.ts (async test)')(format='.') +makeExample('testing/ts/app/shared/twain.component.spec.ts', 'async-test', 'app/shared/twain.component.spec.ts (async test)')(format='.')
:marked :marked
The `async` function is one of the Angular testing utilities. The `async` function is one of the Angular testing utilities.
It simplifies coding of asynchronous tests by arranging for the tester's code to run in a special _async test zone_.
`async`函数是**Angular TestBed**的一部分。 `async`函数是**Angular TestBed**的一部分。通过将测试代码放到特殊的**异步测试区域**来运行,`async`函数简化了异步测试的代码。
It simplifyies coding of asynchronous tests by arranging for the tester's code to run in a special _async test zone_.
通过将测试代码放到特殊的**异步测试区域**来运行,`async`函数简化了异步测试的代码。
The `async` function _takes_ a parameterless function and _returns_ a parameterless function The `async` function _takes_ a parameterless function and _returns_ a parameterless function
which becomes the argument to the Jasmine `it` call. which becomes the argument to the Jasmine `it` call.
@ -1338,11 +1334,15 @@ a(href="#top").to-top 回到顶部
没有任何地方显示异步特征。 没有任何地方显示异步特征。
比如,它不返回承诺,并且没有`done`方法可调用因为它是标准的Jasmine异步测试。 比如,它不返回承诺,并且没有`done`方法可调用因为它是标准的Jasmine异步测试。
Some functions called within a test (such as `fixture.whenStable`) continue to reveal their asynchronous behavior. Some functions called within a test (such as `fixture.whenStable`) continue to reveal their asynchronous behavior.
Consider also the [_fakeAsync_](#fake-async) alternative which affords a more linear coding experience.
在测试(比如`fixture.whenStable`)里面调用函数时,会继续体现它们的异步行为。 在测试(比如`fixture.whenStable`)里面调用函数时,会继续体现它们的异步行为。
考虑使用[_fakeAsync_](#fake-async),以获得更加直观的代码经验。
.l-sub-section
:marked
The `fakeAsync` alternative, [covered below](#fake-async), removes this artifact and affords a more linear coding experience.
`fakeAsync`可选方法,[正如下面解释的](#fake-async),移除了这个需要,提供了更加直观的代码经验。
#when-stable #when-stable
:marked :marked
@ -1350,17 +1350,9 @@ a(href="#top").to-top 回到顶部
## **whenStable** ## **whenStable**
The test must wait for the `getQuote` promise to resolve. The test must wait for the `getQuote` promise to resolve in the next turn of the JavaScript engine.
测试必须等待`getQuote`承诺的解析。 测试必须等待`getQuote`在JavaScript引擎的下一回合中被解析。
The `getQuote` promise promise resolves in the next turn of the JavaScript engine, thanks to the spy.
But a different test implementation of `getQuote` could take longer.
An integration test might call the _real_ `getQuote`, resulting in an XHR request
that took many seconds to respond.
在spy的帮助下`getQuote`承诺在JavaScript引擎的下一回合中被解析。但是不同的`getQuote`测试的实施可能需要更长时间。
集成的测试有可能调用**真实**的`getQuote`导致XHR请求需要好几秒来响应。
This test has no direct access to the promise returned by the call to `testService.getQuote` This test has no direct access to the promise returned by the call to `testService.getQuote`
which is private and inaccessible inside `TwainComponent`. which is private and inaccessible inside `TwainComponent`.
@ -1373,16 +1365,15 @@ a(href="#top").to-top 回到顶部
幸运的是,**异步测试区域**可以访问`getQuote`承诺,因为它拦截所有调用**异步**方法所发出的承诺。 幸运的是,**异步测试区域**可以访问`getQuote`承诺,因为它拦截所有调用**异步**方法所发出的承诺。
The `ComponentFixture.whenStable` method returns its own promise which resolves when the `getQuote` promise completes. The `ComponentFixture.whenStable` method returns its own promise which resolves when the `getQuote` promise completes.
In fact, the _whenStable_ promise resolves when _all pending asynchronous activities_ complete ... the definition of "stable". In fact, the _whenStable_ promise resolves when _all pending asynchronous activities within this test_ complete ... the definition of "stable".
`ComponentFixture.whenStable`方法返回它自己的承诺,它在`getQuote`承诺完成时被解析。实际上,当**所有待处理异步行为**完成时即为“stable”在“stable”后**whenStable**承诺被解析。 `ComponentFixture.whenStable`方法返回它自己的承诺,它在`getQuote`承诺完成时被解析。实际上,当**所有待处理异步行为**完成时即为“stable”在“stable”后**whenStable**承诺被解析。
Then the testing continues. Then the test resumes and kicks off another round of change detection (`fixture.detectChanges`)
The test kicks off another round of change detection (`fixture.detectChanges`) which tells Angular to update the DOM with the quote. which tells Angular to update the DOM with the quote.
The `getQuote` helper method extracts the display element text and the expectation confirms that the text matches the test quote. The `getQuote` helper method extracts the display element text and the expectation confirms that the text matches the test quote.
然后测试继续运行。 然后测试继续运行,并开始另一轮的变化检测(`fixture.detectChanges`,通知Angular使用名言来更新DOM。
测试开始另一轮的变化检测(`fixture.detectChanges`,通知Angular使用名言来更新DOM。
`getQuote`辅助方法提取出显示元素文本然后expect语句确认这个文本与预备的名言相符。 `getQuote`辅助方法提取出显示元素文本然后expect语句确认这个文本与预备的名言相符。
#fakeAsync #fakeAsync
@ -1404,25 +1395,27 @@ a(href="#top").to-top 回到顶部
注意,在`it`的参数中,`async`被`faceAsync`替换。 注意,在`it`的参数中,`async`被`faceAsync`替换。
`fakeAsync`是另一种Angular测试工具。 `fakeAsync`是另一种Angular测试工具。
Like [async](#async-fn-in-it), it _takes_ a parameterless function and _returns_ a parameterless function Like [async](#async), it _takes_ a parameterless function and _returns_ a function
which becomes the argument to the Jasmine `it` call. that becomes the argument to the Jasmine `it` call.
和[async](#async-fn-in-it)一样,它也**接受**无参数函数并**返回**无参数函数变成Jasmine的`it`函数的参数。 和[async](#async)一样,它也**接受**无参数函数并**返回**一个函数变成Jasmine的`it`函数的参数。
The `fakeAsync` function enables a linear coding style by running the test body in a special _fakeAsync test zone_. The `fakeAsync` function enables a linear coding style by running the test body in a special _fakeAsync test zone_.
`fakeAsync`函数通过在特殊的**fakeAsync测试区域**运行测试,让测试代码更加简单直观。 `fakeAsync`函数通过在特殊的**fakeAsync测试区域**运行测试,让测试代码更加简单直观。
The principle advantage of `fakeAsync` over `async` is that the test appears to be synchronous. The principle advantage of `fakeAsync` over `async` is that the test appears to be synchronous.
There are no promises at all. There is no `then(...)` to disrupt the visible flow of control.
No `then(...)` chains to disrupt the visible flow of control. The promise-returning `fixture.whenStable` is gone, replaced by `tick()`.
对于`async`来说,`fakeAsync`最重要的好处时测试看起来像同步的。里面没有任何承诺。 对于`async`来说,`fakeAsync`最重要的好处时测试看起来像同步的。里面没有任何承诺。
没有`then(...)`链来打断控制流。 没有`then(...)`链来打断控制流。
.l-sub-section
:marked
There _are_ limitations. For example, you cannot make an XHR call from within a `fakeAsync`.
There are limitations. For example, you cannot make an XHR call from within a `fakeAsync`. 但是`fakeAsync`有局限性。比如,你不能从`fakeAsync`发起XHR请求。
但是`fakeAsync`有局限性。比如,你不能从`fakeAsync`发起XHR请求。
#tick #tick
#tick-first-look #tick-first-look
@ -1430,11 +1423,7 @@ a(href="#top").to-top 回到顶部
## The _tick_ function ## The _tick_ function
## **tick**函数 ## **tick**函数
Compare the third and fourth tests. Notice that `fixture.whenStable` is gone, replaced by `tick()`.
比较一下第三和第四个测试。注意`fixture.whenStable`没有了,被`tick()`替换。
The `tick` function is one of the Angular testing utilities and a companion to `fakeAsync`. The `tick` function is one of the Angular testing utilities and a companion to `fakeAsync`.
It can only be called within a `fakeAsync` body. It can only be called within a `fakeAsync` body.
@ -1552,11 +1541,11 @@ a(href="#top").to-top 回到顶部
## **beforeEach**里的**async**函数 ## **beforeEach**里的**async**函数
Notice the `async` call in the `beforeEach`. Notice the `async` call in the `beforeEach`, made necessary by the asynchronous `TestBed.compileComponents` method.
The `async` function arranges for the tester's code to run in a special _async test zone_ The `async` function arranges for the tester's code to run in a special _async test zone_
that hides the mechanics of asynchronous execution, just as it does when passed to an [_it_ test)(#async). that hides the mechanics of asynchronous execution, just as it does when passed to an [_it_ test)(#async).
注意`beforeEach`里面对`async`的调用。 注意`beforeEach`里面对`async`的调用,因为异步方法`TestBed.compileComponents`而变得必要
`async`函数将测试代码安排到特殊的**异步测试区域**来运行,该区域隐藏了异步执行的细节,就像它被传递给[_it_ 测试)(#async)一样。 `async`函数将测试代码安排到特殊的**异步测试区域**来运行,该区域隐藏了异步执行的细节,就像它被传递给[_it_ 测试)(#async)一样。
#compile-components #compile-components
@ -1574,13 +1563,14 @@ a(href="#top").to-top 回到顶部
Tests later in this chapter have more declared components and some of them import application Tests later in this chapter have more declared components and some of them import application
modules that declare yet more components. modules that declare yet more components.
Some or all of these components could have external templates and css files. Some or all of these components could have external templates and css files.
`TestBed.compileComponents` compiles them all asynchonously at one time. `TestBed.compileComponents` compiles them all asynchronously at one time.
本章后面的测试有更多声明组件,它们中间的一些导入应用模块,这些模块有更多的声明组件。 本章后面的测试有更多声明组件,它们中间的一些导入应用模块,这些模块有更多的声明组件。
一部分或者全部组件可能有外部模板和CSS文件。 一部分或者全部组件可能有外部模板和CSS文件。
`TestBed.compileComponents`一次性异步编译所有组件。 `TestBed.compileComponents`一次性异步编译所有组件。
The `compileComponents` method returns a promise so you can perform additional tasks _after_ it finishes. The `compileComponents` method returns a promise so you can perform additional tasks _after_ it finishes.
The promise isn't needed here.
`compileComponents`方法返回承诺,可以用来在它完成时候,执行更多额外任务。 `compileComponents`方法返回承诺,可以用来在它完成时候,执行更多额外任务。
@ -1588,21 +1578,21 @@ a(href="#top").to-top 回到顶部
### _compileComponents_ 关闭配置 ### _compileComponents_ 关闭配置
After `compileComponents` runs, the current `TestBed` instance is closed to further configuration. Calling `compileComponents` closes the current `TestBed` instance is further configuration.
You cannot call any more `TestBed` configuration methods, not `configureTestModule` You cannot call any more `TestBed` configuration methods, not `configureTestModule`
nor any of the `override...` methods. The `TestBed` throws an error if you try. nor any of the `override...` methods. The `TestBed` throws an error if you try.
`compileComponents`运行之后,当前的`TestBed`实例就不能再次被配置了 调用`compileComponents`关闭当前的`TestBed`实例,使它不能再被配置
你不能再调用任何`TestBed`配置方法、`configureTestModule`或者任何`override...`方法,否则`TestBed`将会抛出错误。 你不能再调用任何`TestBed`配置方法、`configureTestModule`或者任何`override...`方法,否则`TestBed`将会抛出错误。
.alert.is-important .alert.is-important
:marked :marked
Do not configure the `TestBed` after calling `compileComponents`. Do not configure the `TestBed` after calling `compileComponents`.
Make `compileComponents` the last step Make `compileComponents` the last step
before calling `TestBed.createInstance` to instantiate the _component-under-test_. before calling `TestBed.createComponent` to instantiate the _component-under-test_.
不要再调用`compileComponents`之后再配置`TestBed`。 不要再调用`compileComponents`之后再配置`TestBed`。
在调用`TestBed.createInstance`来初始化被测试的组件之前,把`compileComponents`的调用放到最后一步。 在调用`TestBed.createComponent`来初始化被测试的组件之前,把`compileComponents`的调用放到最后一步。
:marked :marked
The `DashboardHeroComponent` spec follows the asynchonous `beforeEach` with a The `DashboardHeroComponent` spec follows the asynchonous `beforeEach` with a
_synchronous_ `beforeEach` that completes the setup steps and runs tests ... as described in the next section. _synchronous_ `beforeEach` that completes the setup steps and runs tests ... as described in the next section.

View File

@ -40,7 +40,7 @@ block includes
figure.image-display figure.image-display
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="查看导航") img(src='/resources/images/devguide/toh/nav-diagram.png' alt="查看导航")
:marked :marked
We'll add Angulars *Component Router* to our app to satisfy these requirements. We'll add Angulars *Router* to our app to satisfy these requirements.
我们将把Angular*路由器*加入应用中,以满足这些需求。(译注:硬件领域中的路由器是用来帮你找到另一台网络设备的,而这里的路由器用于帮你找到一个组件) 我们将把Angular*路由器*加入应用中,以满足这些需求。(译注:硬件领域中的路由器是用来帮你找到另一台网络设备的,而这里的路由器用于帮你找到一个组件)
@ -309,7 +309,7 @@ block app-comp-v1
与其自动显示英雄列表,我们更希望在用户点击按钮之后才显示它。 与其自动显示英雄列表,我们更希望在用户点击按钮之后才显示它。
换句话说,我们希望“导航”到英雄列表。 换句话说,我们希望“导航”到英雄列表。
We'll need the Angular *Component Router*. We'll need the Angular *Router*.
我们需要Angular的*路由器*。 我们需要Angular的*路由器*。
@ -625,20 +625,12 @@ block redirect-vs-use-as-default
把元数据中的`template`属性替换为`templateUrl`属性,它将指向一个新的模板文件。 把元数据中的`template`属性替换为`templateUrl`属性,它将指向一个新的模板文件。
Set the `moduleId` property to `module.id` for module-relative loading of the `templateUrl`.
设置`moduleId`属性到`module.id`,相对模块加载`templateUrl`。
+makeExcerpt('app/dashboard.component.ts', 'templateUrl') +makeExcerpt('app/dashboard.component.ts', 'templateUrl')
.l-sub-section
block templateUrl-path-resolution
:marked
We specify the path _all the way back to the application root_ &mdash;
<span if-docs="ts">`app/` in this case &mdash;</span>
because Angular doesn't support relative paths _by default_.
We _can_ switch to [component-relative paths](../cookbook/component-relative-paths.html) if we prefer.
我们指定的所有路径_都是相对于该应用的根目录 - <span if-docs="ts">这里是`app/`</span>。
因为Angular_默认_不支持使用相对于当前模块的路径。
只要喜欢我们也_可以_切换成[相对于组件的路径](../cookbook/component-relative-paths.html)模式。
:marked :marked
Create that file with this content: Create that file with this content:
@ -888,7 +880,7 @@ block route-params
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute' - var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
:marked :marked
Let's have the `!{_ActivatedRoute}` service and the `HeroService` injected Let's have the `!{_ActivatedRoute}` service, the `HeroService` and the `Location` service injected
into the constructor, saving their values in private fields: into the constructor, saving their values in private fields:
然后注入`!{_ActivatedRoute}`和`HeroService`服务到构造函数中,将它们的值保存到私有变量中: 然后注入`!{_ActivatedRoute}`和`HeroService`服务到构造函数中,将它们的值保存到私有变量中:
@ -958,7 +950,8 @@ block extract-id
但当我们完工时,我们该导航到那里呢? 但当我们完工时,我们该导航到那里呢?
The user could click one of the two links in the `AppComponent`. Or click the browser's back button. The user could click one of the two links in the `AppComponent`. Or click the browser's back button.
We'll add a third option, a `goBack` method that navigates backward one step in the browser's history stack. We'll add a third option, a `goBack` method that navigates backward one step in the browser's history stack
using the `Location` service we injected previously.
现在用户可以点击`AppComponent`中的两个链接,或点击浏览器的“后退”按钮。 现在用户可以点击`AppComponent`中的两个链接,或点击浏览器的“后退”按钮。
我们来添加第三个选项:一个`goBack`方法,来根据浏览器的历史堆栈,后退一步。 我们来添加第三个选项:一个`goBack`方法,来根据浏览器的历史堆栈,后退一步。
@ -1199,19 +1192,17 @@ figure.image-display
1. 把样式内容*剪切并粘贴*到新的<span ngio-ex>heroes.component.css</span>文件。 1. 把样式内容*剪切并粘贴*到新的<span ngio-ex>heroes.component.css</span>文件。
1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files. 1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files.
1. *Set* the `moduleId` property to `module.id` so that 'templateUrl` and `styleUrls` are relative to the component.
1. *设置*组件元数据的`templateUrl`和`styleUrls`属性,来分别引用这两个文件。 1. *设置*组件元数据的`templateUrl`和`styleUrls`属性,来分别引用这两个文件。
.l-sub-section .l-sub-section
:marked :marked
The `styleUrls` property is !{_an} !{_array} of style file names (with paths). The `styleUrls` property is !{_an} !{_array} of style file names (with paths).
We could list multiple style files from different locations if we needed them. We could list multiple style files from different locations if we needed them.
<span if-docs="ts">As with `templateUrl`, we must specify the path _all the way
back to the application root_.</span>
`styleUrls`属性是一个由样式文件的文件名(包括路径)组成的数组。 `styleUrls`属性是一个由样式文件的文件名(包括路径)组成的数组。
我们还可以列出来自多个不同位置的样式文件。 我们还可以列出来自多个不同位置的样式文件。
和`templateUrl`一样必须指定_相对于此应用根目录_的路径。
block heroes-component-cleanup block heroes-component-cleanup
//- Only relevant for Dart. //- Only relevant for Dart.
@ -1473,7 +1464,7 @@ block file-tree-end
在本章中,我们往前走了很远: 在本章中,我们往前走了很远:
- We added the Angular *Component Router* to navigate among different components. - We added the Angular *Router* to navigate among different components.
- 我们添加了Angular*路由器*在各个不同组件之间导航。 - 我们添加了Angular*路由器*在各个不同组件之间导航。

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB