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:
commit
a18aa9ec01
30
gulpfile.js
30
gulpfile.js
|
@ -38,6 +38,7 @@ var DOCS_PATH = path.join(PUBLIC_PATH, 'docs');
|
|||
|
||||
var EXAMPLES_PATH = path.join(DOCS_PATH, '_examples');
|
||||
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 RESOURCES_PATH = path.join(PUBLIC_PATH, 'resources');
|
||||
var LIVE_EXAMPLES_PATH = path.join(RESOURCES_PATH, 'live-examples');
|
||||
|
@ -97,7 +98,7 @@ var _exampleBoilerplateFiles = [
|
|||
'tsconfig.json',
|
||||
'tslint.json',
|
||||
'typings.json'
|
||||
];
|
||||
];
|
||||
|
||||
var _exampleDartWebBoilerPlateFiles = ['a2docs.css', 'styles.css'];
|
||||
|
||||
|
@ -105,6 +106,11 @@ var _exampleProtractorBoilerplateFiles = [
|
|||
'tsconfig.json'
|
||||
];
|
||||
|
||||
var _exampleUnitTestingBoilerplateFiles = [
|
||||
'karma-test-shim.js',
|
||||
'karma.conf.js'
|
||||
];
|
||||
|
||||
var _exampleConfigFilename = 'example-config.json';
|
||||
|
||||
var _styleLessName = 'a2docs.less';
|
||||
|
@ -497,9 +503,17 @@ function copyExampleBoilerplate() {
|
|||
.then(function() {
|
||||
var protractorSourceFiles =
|
||||
_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);
|
||||
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 {
|
||||
gutil.log(`Harp full site compile, including API docs for all languages.`);
|
||||
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.`);
|
||||
}
|
||||
|
||||
|
@ -1130,6 +1144,14 @@ function getDartExampleWebPaths(basePath) {
|
|||
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) {
|
||||
var filenames = getFilenames(basePath, filename, includeBase);
|
||||
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
|
||||
// **/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;
|
||||
|
||||
if (options.devGuide) {
|
||||
|
|
|
@ -3,8 +3,9 @@ import { Component, Input } from '@angular/core';
|
|||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'hero-detail',
|
||||
templateUrl: 'app/hero-detail.component.html'
|
||||
templateUrl: 'hero-detail.component.html'
|
||||
})
|
||||
export class HeroDetailComponent {
|
||||
@Input() hero: Hero;
|
||||
|
|
|
@ -5,9 +5,10 @@ import { HeroService } from './hero.service';
|
|||
|
||||
// #docregion metadata, providers
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'hero-list',
|
||||
templateUrl: 'app/hero-list.component.html',
|
||||
providers: [ HeroService ]
|
||||
templateUrl: 'hero-list.component.html',
|
||||
providers: [ HeroService ]
|
||||
})
|
||||
// #enddocregion providers
|
||||
// #docregion class
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html'
|
||||
templateUrl: 'app.component.html'
|
||||
})
|
||||
|
||||
export class AppComponent { }
|
||||
|
|
|
@ -4,10 +4,11 @@ import { MovieService } from './movie.service';
|
|||
import { IMovie } from './movie';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html',
|
||||
styleUrls: ['app/app.component.css'],
|
||||
providers: [MovieService]
|
||||
templateUrl: 'app.component.html',
|
||||
styleUrls: [ 'app.component.css' ],
|
||||
providers: [ MovieService ]
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
|
|
|
@ -8,11 +8,12 @@ import { MovieService } from './movie.service';
|
|||
|
||||
// #docregion component
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'movie-list',
|
||||
templateUrl: 'app/movie-list.component.html',
|
||||
templateUrl: 'movie-list.component.html',
|
||||
// #enddocregion component
|
||||
// #docregion style-url
|
||||
styleUrls: ['app/movie-list.component.css'],
|
||||
styleUrls: [ 'movie-list.component.css' ],
|
||||
// #enddocregion style-url
|
||||
})
|
||||
// #enddocregion component
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html'
|
||||
templateUrl: 'app.component.html'
|
||||
})
|
||||
export class AppComponent { }
|
||||
|
|
|
@ -7,10 +7,11 @@ import { UserContextService } from './user-context.service';
|
|||
import { UserService } from './user.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html',
|
||||
templateUrl: 'app.component.html',
|
||||
// #docregion providers
|
||||
providers: [LoggerService, UserContextService, UserService]
|
||||
providers: [ LoggerService, UserContextService, UserService ]
|
||||
// #enddocregion providers
|
||||
})
|
||||
export class AppComponent {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { LocationStrategy,
|
|||
import { NgModule } from '@angular/core';
|
||||
|
||||
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';
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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; }
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
bootstrap(AppComponent, []);
|
|
@ -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 || '';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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'] || [];
|
||||
}
|
||||
}
|
|
@ -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'] || '';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"description": "Dynamic Form Deprecated",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
],
|
||||
"tags":["cookbook"]
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
.errorMessage{
|
||||
color:red;
|
||||
}
|
||||
|
||||
.form-row{
|
||||
margin-top: 10px;
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
// #docregion
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { QuestionBase } from './question-base';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'df-question',
|
||||
templateUrl: 'app/dynamic-form-question.component.html'
|
||||
templateUrl: 'dynamic-form-question.component.html'
|
||||
})
|
||||
export class DynamicFormQuestionComponent {
|
||||
@Input() question: QuestionBase<any>;
|
||||
|
|
|
@ -6,8 +6,9 @@ import { QuestionBase } from './question-base';
|
|||
import { QuestionControlService } from './question-control.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'dynamic-form',
|
||||
templateUrl: 'app/dynamic-form.component.html',
|
||||
templateUrl: 'dynamic-form.component.html',
|
||||
providers: [ QuestionControlService ]
|
||||
})
|
||||
export class DynamicFormComponent implements OnInit {
|
||||
|
|
|
@ -6,8 +6,9 @@ import { Component } from '@angular/core';
|
|||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'hero-form',
|
||||
templateUrl: 'app/hero-form.component.html'
|
||||
templateUrl: 'hero-form.component.html'
|
||||
})
|
||||
export class HeroFormComponent {
|
||||
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
// Set the base for module-relative URLs
|
||||
moduleId: module.id,
|
||||
|
||||
// Declare the tag name in index.html to where the component attaches
|
||||
selector: 'hello-world',
|
||||
|
||||
// Location of the template for this component
|
||||
templateUrl: 'app/hello_world.html'
|
||||
templateUrl: 'hello_world.html'
|
||||
})
|
||||
export class HelloWorldComponent {
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html'
|
||||
templateUrl: 'app.component.html'
|
||||
})
|
||||
export class AppComponent { }
|
||||
|
|
|
@ -75,8 +75,9 @@ export class DoCheckComponent implements DoCheck {
|
|||
/***************************************/
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'do-check-parent',
|
||||
templateUrl: 'app/do-check-parent.component.html',
|
||||
templateUrl: 'do-check-parent.component.html',
|
||||
styles: ['.parent {background: Lavender}']
|
||||
})
|
||||
export class DoCheckParentComponent {
|
||||
|
|
|
@ -49,8 +49,9 @@ export class OnChangesComponent implements OnChanges {
|
|||
/***************************************/
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'on-changes-parent',
|
||||
templateUrl: 'app/on-changes-parent.component.html',
|
||||
templateUrl: 'on-changes-parent.component.html',
|
||||
styles: ['.parent {background: Lavender;}']
|
||||
})
|
||||
export class OnChangesParentComponent {
|
||||
|
|
|
@ -21,7 +21,7 @@ export class PeekABoo implements OnInit {
|
|||
// implement OnInit's `ngOnInit` method
|
||||
ngOnInit() { this.logIt(`OnInit`); }
|
||||
|
||||
protected logIt(msg: string) {
|
||||
logIt(msg: string) {
|
||||
this.logger.log(`#${nextId++} ${msg}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@ import { Component } from '@angular/core';
|
|||
import { LoggerService } from './logger.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'spy-parent',
|
||||
templateUrl: 'app/spy.component.html',
|
||||
templateUrl: 'spy.component.html',
|
||||
styles: [
|
||||
'.parent {background: khaki;}',
|
||||
'.heroes {background: LightYellow; padding: 0 8px}'
|
||||
|
|
|
@ -8,7 +8,7 @@ import { UserService } from '../user.service';
|
|||
moduleId: module.id,
|
||||
selector: 'app-contact',
|
||||
templateUrl: 'contact.component.html',
|
||||
styleUrls: ['contact.component.css']
|
||||
styleUrls: [ 'contact.component.css' ]
|
||||
})
|
||||
export class ContactComponent implements OnInit {
|
||||
contact: Contact;
|
||||
|
|
|
@ -9,7 +9,7 @@ import { UserService } from '../core/user.service';
|
|||
moduleId: module.id,
|
||||
selector: 'app-contact',
|
||||
templateUrl: 'contact.component.html',
|
||||
styleUrls: ['contact.component.css']
|
||||
styleUrls: [ 'contact.component.css' ]
|
||||
})
|
||||
export class ContactComponent implements OnInit {
|
||||
contact: Contact;
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"@angular/platform-server": "2.0.0",
|
||||
"@angular/router": "3.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",
|
||||
"core-js": "^2.4.1",
|
||||
"reflect-metadata": "^0.1.3",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"rollup-plugin-node-resolve": "^2.0.0",
|
||||
"rollup-plugin-uglify": "^1.0.1",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
"systemjs": "0.19.27",
|
||||
"systemjs": "0.19.38",
|
||||
"zone.js": "^0.6.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -60,8 +60,8 @@
|
|||
"html-loader": "^0.4.3",
|
||||
"html-webpack-plugin": "^2.16.1",
|
||||
"http-server": "^0.9.0",
|
||||
"jasmine-core": "^2.4.1",
|
||||
"karma": "^1.2.0",
|
||||
"jasmine-core": "^2.5.2",
|
||||
"karma": "^1.3.0",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
"karma-cli": "^1.0.1",
|
||||
"karma-htmlfile-reporter": "^0.3.4",
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html'
|
||||
templateUrl: 'app.component.html'
|
||||
})
|
||||
export class AppComponent {
|
||||
birthday = new Date(1988, 3, 15); // April 15, 1988
|
||||
|
|
|
@ -5,8 +5,9 @@ import { Component } from '@angular/core';
|
|||
import { HEROES } from './heroes';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'flying-heroes',
|
||||
templateUrl: 'app/flying-heroes.component.html',
|
||||
templateUrl: 'flying-heroes.component.html',
|
||||
styles: ['#flyers, #all {font-style: italic}']
|
||||
})
|
||||
// #docregion v1
|
||||
|
@ -49,8 +50,9 @@ export class FlyingHeroesComponent {
|
|||
////// Identical except for impure pipe //////
|
||||
// #docregion impure-component
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'flying-heroes-impure',
|
||||
templateUrl: 'app/flying-heroes-impure.component.html',
|
||||
templateUrl: 'flying-heroes-impure.component.html',
|
||||
// #enddocregion impure-component
|
||||
styles: ['.flyers, .all {font-style: italic}'],
|
||||
// #docregion impure-component
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
|
||||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
|
||||
'rxjs': 'npm:rxjs',
|
||||
'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: {
|
||||
|
@ -37,7 +37,7 @@
|
|||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
},
|
||||
'angular2-in-memory-web-api': {
|
||||
'angular-in-memory-web-api': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ import { DomSanitizer, SafeResourceUrl, SafeUrl } from '@angular/platform-browse
|
|||
|
||||
@Component({
|
||||
selector: 'bypass-security',
|
||||
templateUrl: 'app/bypass-security.component.html',
|
||||
moduleId: module.id,
|
||||
templateUrl: 'bypass-security.component.html',
|
||||
})
|
||||
export class BypassSecurityComponent {
|
||||
dangerousUrl: string;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { BrowserModule } from '@angular/platform-browser';
|
|||
import { FormsModule } from '@angular/forms';
|
||||
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 { AppComponent } from './app.component';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// #docregion
|
||||
import { InMemoryDbService } from 'angular2-in-memory-web-api';
|
||||
import { InMemoryDbService } from 'angular-in-memory-web-api';
|
||||
export class HeroData implements InMemoryDbService {
|
||||
createDb() {
|
||||
let heroes = [
|
||||
|
|
|
@ -6,7 +6,8 @@ import { HeroService } from './hero.service.promise';
|
|||
|
||||
@Component({
|
||||
selector: 'hero-list-promise',
|
||||
templateUrl: 'app/toh/hero-list.component.html',
|
||||
moduleId: module.id,
|
||||
templateUrl: 'hero-list.component.html',
|
||||
providers: [ HeroService ]
|
||||
})
|
||||
// #docregion component
|
||||
|
|
|
@ -5,8 +5,9 @@ import { Hero } from './hero';
|
|||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'hero-list',
|
||||
templateUrl: 'app/toh/hero-list.component.html',
|
||||
templateUrl: 'hero-list.component.html',
|
||||
providers: [ HeroService ]
|
||||
})
|
||||
// #docregion component
|
||||
|
|
|
@ -15,14 +15,14 @@ import { Observable } from 'rxjs/Observable';
|
|||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
// #docregion ctor
|
||||
constructor (private http: Http) {}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion endpoint
|
||||
private heroesUrl = 'app/heroes'; // URL to web API
|
||||
// #enddocregion endpoint
|
||||
|
||||
// #docregion ctor
|
||||
constructor (private http: Http) {}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion methods, error-handling, http-get
|
||||
getHeroes (): Observable<Hero[]> {
|
||||
return this.http.get(this.heroesUrl)
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'structural-directives',
|
||||
templateUrl: 'app/structural-directives.component.html',
|
||||
templateUrl: 'structural-directives.component.html',
|
||||
styles: ['button { min-width: 100px; }']
|
||||
})
|
||||
export class StructuralDirectivesComponent {
|
||||
|
|
|
@ -3,9 +3,8 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { ToastService } from './toast.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'toh-toast',
|
||||
templateUrl: '<div>toast</div>'
|
||||
template: '<div>toast</div>'
|
||||
})
|
||||
export class ToastComponent implements OnInit {
|
||||
constructor(toastService: ToastService) { }
|
||||
|
|
|
@ -3,9 +3,8 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { ToastService } from './toast.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'toh-toast',
|
||||
templateUrl: '<div>toast</div>'
|
||||
template: '<div>toast</div>'
|
||||
})
|
||||
export class ToastComponent implements OnInit {
|
||||
constructor(toastService: ToastService) { }
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html'
|
||||
templateUrl: 'app.component.html'
|
||||
})
|
||||
export class AppComponent { }
|
||||
|
|
|
@ -3,7 +3,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
|||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
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';
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
|
||||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
|
||||
'rxjs': 'npm:rxjs',
|
||||
'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: {
|
||||
|
@ -37,7 +37,7 @@
|
|||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
},
|
||||
'angular2-in-memory-web-api': {
|
||||
'angular-in-memory-web-api': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
|
|
|
@ -50,10 +50,10 @@
|
|||
'@angular/forms/testing': 'ng:forms-builds/master/bundles/forms-testing.umd.js',
|
||||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
|
||||
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
|
||||
'typescript': 'npm:typescript@2.0.2/lib/typescript.js',
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
|
||||
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.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
|
||||
|
@ -65,7 +65,7 @@
|
|||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
},
|
||||
'angular2-in-memory-web-api': {
|
||||
'angular-in-memory-web-api': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
|
|
|
@ -37,10 +37,10 @@
|
|||
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
|
||||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
|
||||
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
|
||||
'typescript': 'npm:typescript@2.0.2/lib/typescript.js',
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
|
||||
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.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
|
||||
|
@ -52,7 +52,7 @@
|
|||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
},
|
||||
'angular2-in-memory-web-api': {
|
||||
'angular-in-memory-web-api': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@ export enum Color {Red, Green, Blue};
|
|||
* Giant grab bag of stuff to drive the chapter
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html'
|
||||
templateUrl: 'app.component.html'
|
||||
})
|
||||
export class AppComponent implements AfterViewInit, OnInit {
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html'
|
||||
templateUrl: 'app.component.html'
|
||||
})
|
||||
export class AppComponent { }
|
||||
|
|
|
@ -272,6 +272,7 @@ export class ExternalTemplateComponent implements OnInit {
|
|||
export class InnerCompWithExternalTemplateComponent { }
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'bad-template-comp',
|
||||
templateUrl: 'non-existant.html'
|
||||
})
|
||||
|
|
|
@ -5,9 +5,10 @@ import { Hero } from '../model';
|
|||
|
||||
// #docregion component
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'dashboard-hero',
|
||||
templateUrl: 'app/dashboard/dashboard-hero.component.html',
|
||||
styleUrls: ['app/dashboard/dashboard-hero.component.css']
|
||||
templateUrl: 'dashboard-hero.component.html',
|
||||
styleUrls: [ 'dashboard-hero.component.css' ]
|
||||
})
|
||||
export class DashboardHeroComponent {
|
||||
@Input() hero: Hero;
|
||||
|
|
|
@ -5,9 +5,10 @@ import { Router } from '@angular/router';
|
|||
import { Hero, HeroService } from '../model';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: 'app/dashboard/dashboard.component.html',
|
||||
styleUrls: ['app/dashboard/dashboard.component.css']
|
||||
templateUrl: 'dashboard.component.html',
|
||||
styleUrls: [ 'dashboard.component.css' ]
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
|
||||
|
|
|
@ -9,9 +9,10 @@ import { HeroDetailService } from './hero-detail.service';
|
|||
|
||||
// #docregion prototype
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'app-hero-detail',
|
||||
templateUrl: 'app/hero/hero-detail.component.html',
|
||||
styleUrls: ['app/hero/hero-detail.component.css'],
|
||||
templateUrl: 'hero-detail.component.html',
|
||||
styleUrls: ['hero-detail.component.css' ],
|
||||
providers: [ HeroDetailService ]
|
||||
})
|
||||
export class HeroDetailComponent implements OnInit {
|
||||
|
|
|
@ -4,9 +4,10 @@ import { Router } from '@angular/router';
|
|||
import { Hero, HeroService } from '../model';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'app-heroes',
|
||||
templateUrl: 'app/hero/hero-list.component.html',
|
||||
styleUrls: ['app/hero/hero-list.component.css']
|
||||
templateUrl: 'hero-list.component.html',
|
||||
styleUrls: [ 'hero-list.component.css' ]
|
||||
})
|
||||
export class HeroListComponent implements OnInit {
|
||||
heroes: Promise<Hero[]>;
|
||||
|
|
|
@ -7,8 +7,9 @@ import { HeroService } from './hero.service';
|
|||
// #enddocregion imports
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-dashboard',
|
||||
templateUrl: 'app/dashboard.component.html'
|
||||
templateUrl: 'dashboard.component.html'
|
||||
})
|
||||
// #docregion component
|
||||
export class DashboardComponent implements OnInit {
|
||||
|
|
|
@ -9,12 +9,13 @@ import { Hero } from './hero';
|
|||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-dashboard',
|
||||
// #docregion templateUrl
|
||||
templateUrl: 'app/dashboard.component.html',
|
||||
templateUrl: 'dashboard.component.html',
|
||||
// #enddocregion templateUrl
|
||||
// #docregion css
|
||||
styleUrls: ['app/dashboard.component.css']
|
||||
styleUrls: [ 'dashboard.component.css' ]
|
||||
// #enddocregion css
|
||||
})
|
||||
// #docregion component
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
// #docregion added-imports
|
||||
// Keep the Input import for now, we'll remove it later:
|
||||
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';
|
||||
// #enddocregion added-imports
|
||||
|
@ -20,8 +21,9 @@ export class HeroDetailComponent implements OnInit {
|
|||
|
||||
constructor(
|
||||
private heroService: HeroService,
|
||||
private route: ActivatedRoute) {
|
||||
}
|
||||
private route: ActivatedRoute,
|
||||
private location: Location
|
||||
) {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
// #docplaster
|
||||
// #docregion , v2
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-hero-detail',
|
||||
// #docregion templateUrl
|
||||
templateUrl: 'app/hero-detail.component.html',
|
||||
templateUrl: 'hero-detail.component.html',
|
||||
// #enddocregion templateUrl, v2
|
||||
styleUrls: ['app/hero-detail.component.css']
|
||||
styleUrls: [ 'hero-detail.component.css' ]
|
||||
// #docregion v2
|
||||
})
|
||||
// #docregion implement
|
||||
|
@ -22,8 +24,9 @@ export class HeroDetailComponent implements OnInit {
|
|||
// #docregion ctor
|
||||
constructor(
|
||||
private heroService: HeroService,
|
||||
private route: ActivatedRoute) {
|
||||
}
|
||||
private route: ActivatedRoute,
|
||||
private location: Location
|
||||
) {}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion ngOnInit
|
||||
|
@ -38,7 +41,7 @@ export class HeroDetailComponent implements OnInit {
|
|||
|
||||
// #docregion goBack
|
||||
goBack(): void {
|
||||
window.history.back();
|
||||
this.location.back();
|
||||
}
|
||||
// #enddocregion goBack
|
||||
}
|
||||
|
|
|
@ -8,10 +8,11 @@ import { HeroService } from './hero.service';
|
|||
|
||||
// #docregion renaming, metadata
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-heroes',
|
||||
// #enddocregion renaming
|
||||
templateUrl: 'app/heroes.component.html',
|
||||
styleUrls: ['app/heroes.component.css']
|
||||
templateUrl: 'heroes.component.html',
|
||||
styleUrls: [ 'heroes.component.css' ]
|
||||
// #docregion renaming
|
||||
})
|
||||
// #enddocregion metadata
|
||||
|
|
|
@ -12,7 +12,7 @@ import { HttpModule } from '@angular/http';
|
|||
|
||||
// #enddocregion v1
|
||||
// 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';
|
||||
|
||||
// #docregion v1
|
||||
|
|
|
@ -6,9 +6,10 @@ import { Hero } from './hero';
|
|||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-dashboard',
|
||||
templateUrl: 'app/dashboard.component.html',
|
||||
styleUrls: ['app/dashboard.component.css']
|
||||
templateUrl: 'dashboard.component.html',
|
||||
styleUrls: [ 'dashboard.component.css' ]
|
||||
})
|
||||
// #enddocregion search
|
||||
export class DashboardComponent implements OnInit {
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-hero-detail',
|
||||
templateUrl: 'app/hero-detail.component.html',
|
||||
styleUrls: ['app/hero-detail.component.css']
|
||||
templateUrl: 'hero-detail.component.html',
|
||||
styleUrls: [ 'hero-detail.component.css' ]
|
||||
})
|
||||
export class HeroDetailComponent implements OnInit {
|
||||
hero: Hero;
|
||||
|
||||
constructor(
|
||||
private heroService: HeroService,
|
||||
private route: ActivatedRoute) {
|
||||
}
|
||||
private route: ActivatedRoute,
|
||||
private location: Location
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.params.forEach((params: Params) => {
|
||||
|
@ -29,11 +32,11 @@ export class HeroDetailComponent implements OnInit {
|
|||
// #docregion save
|
||||
save(): void {
|
||||
this.heroService.update(this.hero)
|
||||
.then(this.goBack);
|
||||
.then(() => this.goBack());
|
||||
}
|
||||
// #enddocregion save
|
||||
|
||||
goBack(): void {
|
||||
window.history.back();
|
||||
this.location.back();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ import { HeroSearchService } from './hero-search.service';
|
|||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'hero-search',
|
||||
templateUrl: 'app/hero-search.component.html',
|
||||
styleUrls: ['app/hero-search.component.css'],
|
||||
templateUrl: 'hero-search.component.html',
|
||||
styleUrls: [ 'hero-search.component.css' ],
|
||||
providers: [HeroSearchService]
|
||||
})
|
||||
export class HeroSearchComponent implements OnInit {
|
||||
|
|
|
@ -6,9 +6,10 @@ import { Hero } from './hero';
|
|||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-heroes',
|
||||
templateUrl: 'app/heroes.component.html',
|
||||
styleUrls: ['app/heroes.component.css']
|
||||
templateUrl: 'heroes.component.html',
|
||||
styleUrls: [ 'heroes.component.css' ]
|
||||
})
|
||||
export class HeroesComponent implements OnInit {
|
||||
heroes: Hero[];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// #docregion , init
|
||||
import { InMemoryDbService } from 'angular2-in-memory-web-api';
|
||||
import { InMemoryDbService } from 'angular-in-memory-web-api';
|
||||
export class InMemoryDataService implements InMemoryDbService {
|
||||
createDb() {
|
||||
let heroes = [
|
||||
|
|
|
@ -8,8 +8,9 @@ import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
|
|||
|
||||
// #docregion initialclass
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'phone-detail',
|
||||
templateUrl: 'phone-detail/phone-detail.template.html',
|
||||
templateUrl: 'phone-detail.template.html',
|
||||
// #enddocregion initialclass
|
||||
pipes: [ CheckmarkPipe ]
|
||||
// #docregion initialclass
|
||||
|
|
|
@ -4,8 +4,9 @@ import { Component } from '@angular/core';
|
|||
import { Phone, PhoneData } from '../core/phone/phone.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'phone-list',
|
||||
templateUrl: 'phone-list/phone-list.template.html'
|
||||
templateUrl: 'phone-list.template.html'
|
||||
})
|
||||
export class PhoneListComponent {
|
||||
phones: PhoneData[];
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"unittesting": true
|
||||
}
|
|
@ -7,17 +7,17 @@
|
|||
// map tells the System loader where to look for things
|
||||
// #docregion paths
|
||||
var map = {
|
||||
'app': '/app', // 'dist',
|
||||
'app': '/app', // 'dist',
|
||||
|
||||
'@angular': '/node_modules/@angular',
|
||||
'angular2-in-memory-web-api': '/node_modules/angular2-in-memory-web-api',
|
||||
'rxjs': '/node_modules/rxjs'
|
||||
'@angular': '/node_modules/@angular',
|
||||
'angular-in-memory-web-api': '/node_modules/angular-in-memory-web-api',
|
||||
'rxjs': '/node_modules/rxjs'
|
||||
};
|
||||
|
||||
var packages = {
|
||||
'/app': { main: 'main.js', defaultExtension: 'js' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
'/app': { main: 'main.js', defaultExtension: 'js' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
};
|
||||
// #enddocregion paths
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ import { Phone, PhoneData } from '../core/phone/phone.service';
|
|||
import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'phone-detail',
|
||||
templateUrl: 'phone-detail/phone-detail.template.html',
|
||||
templateUrl: 'phone-detail.template.html',
|
||||
pipes: [ CheckmarkPipe ]
|
||||
})
|
||||
export class PhoneDetailComponent {
|
||||
|
|
|
@ -5,8 +5,9 @@ import { RouterLink } from '@angular/router-deprecated';
|
|||
import { Phone, PhoneData } from '../core/phone/phone.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'phone-list',
|
||||
templateUrl: 'phone-list/phone-list.template.html',
|
||||
templateUrl: 'phone-list.template.html',
|
||||
directives: [ RouterLink ]
|
||||
})
|
||||
// #enddocregion top
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"unittesting": true
|
||||
}
|
|
@ -7,17 +7,17 @@
|
|||
// map tells the System loader where to look for things
|
||||
// #docregion paths
|
||||
var map = {
|
||||
'app': '/app', // 'dist',
|
||||
'app': '/app', // 'dist',
|
||||
|
||||
'@angular': '/node_modules/@angular',
|
||||
'angular2-in-memory-web-api': '/node_modules/angular2-in-memory-web-api',
|
||||
'rxjs': '/node_modules/rxjs'
|
||||
'@angular': '/node_modules/@angular',
|
||||
'angular-in-memory-web-api': '/node_modules/angular-in-memory-web-api',
|
||||
'rxjs': '/node_modules/rxjs'
|
||||
};
|
||||
|
||||
var packages = {
|
||||
'/app': { main: 'main.js', defaultExtension: 'js' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
'/app': { main: 'main.js', defaultExtension: 'js' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
};
|
||||
// #enddocregion paths
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html'
|
||||
templateUrl: 'app.component.html'
|
||||
})
|
||||
export class AppComponent { }
|
||||
|
|
|
@ -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 `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. We defined dummy data for `model` and `powers`, as befits a demo.
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
|
||||
"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": {
|
||||
|
@ -123,7 +123,7 @@
|
|||
|
||||
"security": {
|
||||
"title": "Security",
|
||||
"intro": "Prevent security vulnerabilities"
|
||||
"intro": "Developing for content security in Angular applications"
|
||||
},
|
||||
|
||||
"structural-directives": {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
include ../_util-fns
|
||||
extends ../../../ts/latest/guide/index.jade
|
||||
|
||||
+includeShared('{ts}', 'intro')
|
||||
+includeShared('{ts}', 'how-to-read-1')
|
||||
.alert.is-important
|
||||
:marked
|
||||
Most of the documentation has been written for TypeScript developers
|
||||
and has not yet been translated to JavaScript.
|
||||
Please bear with us. Meanwhile, we've provide links to the TypeScript chapters
|
||||
where JavaScript versions are unavailable.
|
||||
+includeShared('{ts}', 'how-to-read-2')
|
||||
+includeShared('{ts}', 'the-rest')
|
||||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
block js-alert
|
||||
.alert.is-important
|
||||
:marked
|
||||
Most of the documentation has been written for TypeScript developers
|
||||
and has not yet been translated to JavaScript.
|
||||
Please bear with us. Meanwhile, we've provide links to the TypeScript chapters
|
||||
where JavaScript versions are unavailable.
|
||||
|
|
|
@ -669,7 +669,7 @@ a#in-mem-web-api
|
|||
.l-sub-section
|
||||
:marked
|
||||
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
|
||||
registered for module loading by SystemJS (see `systemjs.config.js`)
|
||||
|
||||
|
|
|
@ -36,13 +36,6 @@
|
|||
"intro": "依赖注入技术"
|
||||
},
|
||||
|
||||
"dynamic-form-deprecated": {
|
||||
"title": "Dynamic Forms",
|
||||
"intro": "Render dynamic forms with NgFormModel",
|
||||
"basics": true,
|
||||
"hide": true
|
||||
},
|
||||
|
||||
"dynamic-form": {
|
||||
"title": "动态表单",
|
||||
"intro": "用FormGroup渲染动态表单"
|
||||
|
|
|
@ -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)
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
"displaying-data": {
|
||||
"title": "显示数据",
|
||||
"intro": "利用插值表达式和其它形式的属性绑定机制,把数据显示到UI上。",
|
||||
"intro": "属性绑定机制把数据显示到UI上。",
|
||||
"nextable": true,
|
||||
"basics": true
|
||||
},
|
||||
|
|
|
@ -442,18 +442,22 @@ block ts-decorator
|
|||
来看下`@Component`中的一些配置项:
|
||||
|
||||
:marked
|
||||
- `selector` - a css selector that tells Angular to create and insert an instance of this component
|
||||
where it finds a `<hero-list>` tag in *parent* HTML.
|
||||
- `moduleId: module.id`: sets the base for module-relative loading of the `templateUrl`.
|
||||
|
||||
- `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
|
||||
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`的一个实例插入到这个标签中。
|
||||
|
||||
- `templateUrl`: address of this component's HTML template, shown [above](#templates).
|
||||
|
||||
- `templateUrl` - 组件HTML模板的地址,我们在[前面](#templates)展示过它。
|
||||
- `templateUrl`: module-relative address of this component's HTML template, shown [above](#templates).
|
||||
|
||||
- `templateUrl`:组件HTML模板的模块相对地址,我们在[前面](#templates)展示过它。
|
||||
|
||||
- `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 space indicated by `<hero-detail>` tags.
|
||||
|
|
|
@ -2,18 +2,17 @@ block includes
|
|||
include ../_util-fns
|
||||
- var _iterableUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols';
|
||||
- var _boolean = 'truthy/falsey';
|
||||
|
||||
|
||||
:marked
|
||||
We typically display data in Angular by binding controls in an HTML template
|
||||
to properties of an Angular component.
|
||||
You can display data by binding controls in an HTML template to properties of an Angular component.
|
||||
|
||||
在Angular中最典型的数据显示方式,就是把HTML模板中的控件绑定到Angular组件的属性。
|
||||
|
||||
In this chapter, we'll create a component with a list of heroes. Each hero has a name.
|
||||
We'll display the list of hero names and
|
||||
In this page, you'll create a component with a list of heroes.
|
||||
You'll display the list of hero names and
|
||||
conditionally show a message below the list.
|
||||
|
||||
本章中,我们将创建一个英雄列表组件。每位英雄都有一个名字。我们将显示英雄名字的列表,当选中一位英雄时,就在列表下方显示一条消息。
|
||||
本章中,我们将创建一个英雄列表组件。我们将显示英雄名字的列表,当选中一位英雄时,就在列表下方显示一条消息。
|
||||
|
||||
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")
|
||||
|
||||
:marked
|
||||
# Table Of Contents
|
||||
# Contents
|
||||
# 目录
|
||||
|
||||
* [Showing component properties with interpolation](#interpolation)
|
||||
|
@ -36,7 +35,7 @@ figure.image-display
|
|||
.l-sub-section
|
||||
:marked
|
||||
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>演示了本章中描述的所有语法和代码片段。
|
||||
|
||||
|
@ -47,38 +46,40 @@ figure.image-display
|
|||
|
||||
The easiest way to display a component property
|
||||
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)来绑定属性名。
|
||||
要使用插值表达式,就把属性名包裹在双重花括号里放进视图模板,如`{{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
|
||||
include ../_quickstart_repo
|
||||
|
||||
:marked
|
||||
Then modify the <ngio-ex path="app.component.ts"></ngio-ex> file by
|
||||
changing the template and the body of the component.
|
||||
When we're done, it should look like this:
|
||||
Then modify the <ngio-ex path="app.component.ts"></ngio-ex> file by
|
||||
changing the template and the body of the component.
|
||||
|
||||
然后,到`app.component.ts`文件中修改组件的模板和代码。
|
||||
|
||||
When you're done, it should look like this:
|
||||
|
||||
修改完之后,它看起来应该是这样的:
|
||||
|
||||
|
||||
|
||||
+makeExample('app/app.component.1.ts')
|
||||
|
||||
: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:
|
||||
|
||||
修改完的模板会使用双花括号形式的插值表达式来显示这两个模板属性:
|
||||
|
@ -89,13 +90,14 @@ block quickstart-repo
|
|||
.l-sub-section
|
||||
:marked
|
||||
The template is a multi-line string within ECMAScript 2015 backticks (<code>\`</code>).
|
||||
The backtick (<code>\`</code>) — which is *not* the same character as a single
|
||||
quote (`'`) — has many nice features. The feature we're exploiting here
|
||||
is the ability to compose the string over several lines, which makes for
|
||||
much more readable HTML.
|
||||
The backtick (<code>\`</code>)—which is *not* the same character as a single
|
||||
quote (`'`)—allows you to compose a string over several lines, which makes the
|
||||
HTML more readable.
|
||||
|
||||
模板是包在反引号(<code>\`</code>)中的一个多行字符串。
|
||||
反引号(<code>\`</code>) —— 注意,不是单引号(') —— 有很多好用的特性。我们在这里用到的是它把一个字符串写在多行上的能力,这样我们的HTML模板就会更容易阅读。
|
||||
反引号(<code>\`</code>) —— 注意,不是单引号(') —— 有很多好用的特性。
|
||||
我们在这里用到的是它把一个字符串写在多行上的能力,这样我们的HTML模板就会更容易阅读。
|
||||
|
||||
|
||||
:marked
|
||||
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
|
||||
:marked
|
||||
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.
|
||||
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.
|
||||
the view, such as a keystroke, a timer completion, or a response to an HTTP request.
|
||||
|
||||
严格来说,“重新显示”是在某些与视图有关的异步事件之后发生的,比如:按键、定时器或收到异步`XHR`响应。本例中没有体现这一点。
|
||||
很显然,属性肯定不会无缘无故的变化。但是目前我们只要先相信Angular会处理好就行了。
|
||||
严格来说,“重新显示”是在某些与视图有关的异步事件之后发生的,比如:按键、定时器或收到异步`XHR`响应。
|
||||
|
||||
:marked
|
||||
Notice that we haven't called **new** to create an instance of the `AppComponent` class.
|
||||
Angular is creating an instance for us. How?
|
||||
Notice that you don't call **new** to create an instance of the `AppComponent` class.
|
||||
Angular is creating an instance for you. How?
|
||||
|
||||
注意,我们从没调用过**new**来创建`AppComponent`类的实例,是Angular替我们创建了它。那么它是如何创建的呢?
|
||||
|
||||
|
||||
Notice the CSS `selector` in the `@Component` !{_decorator} that 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:
|
||||
The CSS `selector` in the `@Component` !{_decorator} specifies an element named `my-app`.
|
||||
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`的元素。
|
||||
回忆下,在[“快速起步”](../quickstart.html)一章中,我们曾把一个`<my-app>`元素添加到`index.html`的`body`里。
|
||||
|
||||
|
||||
+makeExcerpt('index.html', 'body')
|
||||
|
||||
: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
|
||||
inside the `<my-app>` tag.
|
||||
|
||||
当我们通过`main.ts`中的`AppComponent`类启动时,Angular在`index.html`中查找一个`<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
|
||||
img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero")
|
||||
|
||||
+ifDocsFor('ts')
|
||||
: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
|
||||
## Template inline or template file?
|
||||
|
||||
## 内联(inline)模板还是模板文件?
|
||||
|
||||
We can store our component's template in one of two places.
|
||||
We can define it *inline* using the `template` property, as we do here.
|
||||
Or we can define the template in a separate HTML file and link to it in
|
||||
You can store your component's template in one of two places.
|
||||
You can define it *inline* using the `template` property, or you can define
|
||||
the template in a separate HTML file and link to it in
|
||||
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,
|
||||
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.
|
||||
|
||||
到底选择内联HTML还是独立HTML取决于:个人喜好、具体状况和组织级策略。
|
||||
|
@ -179,25 +182,18 @@ figure.image-display
|
|||
|
||||
## 初始化:使用构造函数还是变量?
|
||||
|
||||
We initialized our component properties using variable assignment.
|
||||
This is a wonderfully concise and compact technique.
|
||||
Although this example uses variable assignment to initialize the components, you can instead declare and initialize the properties using a constructor:
|
||||
|
||||
这里我们使用了变量赋值的方式初始化组件的属性。
|
||||
这是个特别简洁用法。
|
||||
虽然这个例子使用了变量赋值的方式初始化组件的属性。你可以使用构造函数来声明和初始化属性。
|
||||
|
||||
Some folks prefer to declare the properties and initialize them within a constructor like this:
|
||||
|
||||
也有些人喜欢单独声明属性,并且在构造函数中初始化它们,就像这样:
|
||||
|
||||
+makeExcerpt('app/app-ctor.component.ts', 'class')
|
||||
|
||||
:marked
|
||||
That's fine too. The choice is a matter of taste and organization policy.
|
||||
We'll adopt the more terse "variable assignment" style in this chapter simply because
|
||||
there will be less code to read.
|
||||
This app uses more terse "variable assignment" style simply for brevity.
|
||||
|
||||
本应用为了假话,使用了更简单的“变量赋值”风格。
|
||||
|
||||
这样也不错。这只是个人偏好和组织策略的问题。
|
||||
我们在本章中只是简单的采纳了“变量赋值”的风格,这样要阅读的代码会少一点。
|
||||
|
||||
.l-main-section#ngFor
|
||||
:marked
|
||||
|
@ -205,14 +201,14 @@ figure.image-display
|
|||
|
||||
## 使用***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')
|
||||
|
||||
: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.
|
||||
|
||||
现在,我们在模板中使用Angular的`ngFor`“重复器”指令来显示`heroes`列表中的每一个条目。
|
||||
|
@ -220,30 +216,26 @@ figure.image-display
|
|||
+makeExcerpt('app/app.component.2.ts', 'template')
|
||||
|
||||
:marked
|
||||
Our presentation is the familiar HTML unordered list with `<ul>` and `<li>` tags. Let's focus on the `<li>` tag.
|
||||
|
||||
我们看到了熟悉的HTML —— 由`<ul>`和`<li>`标签组成的无序列表。请聚焦在`<li>`标签上。
|
||||
This UI uses the HTML unordered list with `<ul>` and `<li>` tags. The `*ngFor`
|
||||
in the `<li>` element is the Angular "repeater" directive.
|
||||
It marks that `<li>` element (and its children) as the "repeater template":
|
||||
|
||||
这个我们看到了熟悉的HTML —— 由`<ul>`和`<li>`标签组成的无序列表。请聚焦在`<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
|
||||
:marked
|
||||
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`的更多知识,请参见[模板语法](./template-syntax.html#ngFor)一章。
|
||||
:marked
|
||||
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`。
|
||||
它是一个[模板输入变量](./template-syntax.html#ngForMicrosyntax)(译注:即ngFor模板中从外界输入的变量)。
|
||||
|
@ -256,12 +248,12 @@ figure.image-display
|
|||
Angular把`hero`变量作为双花括号插值表达式的上下文。
|
||||
.l-sub-section
|
||||
:marked
|
||||
We happened to give `ngFor` !{_an} !{_array} to display.
|
||||
In fact, `ngFor` can repeat items for any [iterable](!{_iterableUrl})
|
||||
object.
|
||||
In this case, `ngFor` is displaying !{_an} !{_array}, but `ngFor` can
|
||||
repeat items for any [iterable](!{_iterableUrl}) object.
|
||||
|
||||
本例中,`ngFor`显示“数组”,
|
||||
但`ngFor`可以为任何[可迭代Iterable](!{_iterableUrl})对象重复渲染条目。
|
||||
|
||||
这里我们传给`ngFor`一个“数组”让它显示。
|
||||
但实际上,`ngFor`可以为任何[可迭代Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)对象重复渲染条目。
|
||||
:marked
|
||||
Now the heroes appear in an unordered list.
|
||||
|
||||
|
@ -276,22 +268,21 @@ figure.image-display
|
|||
|
||||
## 为数据创建一个类
|
||||
|
||||
We are defining our data directly inside our component.
|
||||
That's fine for a demo but certainly isn't a best practice. It's not even a good practice.
|
||||
Although we won't do anything about that in this chapter, we'll make a mental note to fix this down the road.
|
||||
The app's code defines the data directly inside the component, which isn't best practice.
|
||||
In a simple demo, however, it's fine.
|
||||
|
||||
我们在组件内部直接定义了数据。
|
||||
作为演示还可以,但它显然不是最佳实践。它甚至是一个很不好的实践。
|
||||
我们在本章中先不管它,只是记下来,等将来再修复这个问题。
|
||||
应用代码直接在组件内部直接定义了数据。
|
||||
作为演示还可以,但它显然不是最佳实践。
|
||||
|
||||
At the moment, we're binding to !{_an} !{_array} of strings. We do that occasionally in real applications, but
|
||||
most of the time we're binding to more specialized objects.
|
||||
At the moment, the binding is to !{_an} !{_array} of strings.
|
||||
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:
|
||||
|
||||
|
@ -301,14 +292,14 @@ figure.image-display
|
|||
|
||||
block hero-class
|
||||
: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
|
||||
advantage of a TypeScript shortcut in our declaration of the constructor parameters.
|
||||
It might not look like the class has properties, but it does.
|
||||
The declaration of the constructor parameters takes advantage of a TypeScript shortcut.
|
||||
|
||||
它可能看上去不像是有属性的类,但我们确实有。我们利用的是TypeScript提供的简写形式 —— 用构造函数的参数直接定义属性。
|
||||
它可能看上去不像是有属性的类,但我们确实有。我们利用的是TypeScript提供的简写形式 —— 用构造函数的参数直接定义属性。
|
||||
|
||||
Consider the first parameter:
|
||||
|
||||
|
@ -340,20 +331,22 @@ block hero-class
|
|||
|
||||
## 使用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')
|
||||
|
||||
:marked
|
||||
We'll have to update the template.
|
||||
Next, update the template.
|
||||
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`。
|
||||
我们要修复它,所以,只显示英雄的`name`属性就行了。
|
||||
要修复它,只显示英雄的`name`属性就行了。
|
||||
|
||||
|
||||
+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.
|
||||
|
||||
从显示上看还是一样,但现在除了名字之外,我们可以有更多英雄信息。
|
||||
|
||||
|
||||
.l-main-section#ngIf
|
||||
: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.
|
||||
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`指令会基于条件的真假来显示或移除一个元素。
|
||||
我们来亲自动手试一下,把下列语句加到模板的底部:
|
||||
|
||||
我们来亲自动手试一下,把下列语句加到模板的底部:
|
||||
|
||||
+makeExcerpt('app/app.component.ts', 'message')
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
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`的更多知识,请参见[模板语法](./template-syntax.html#ngIf)一章。
|
||||
|
||||
:marked
|
||||
The [template expression](./template-syntax.html#template-expressions) inside the double quotes
|
||||
looks much like !{_Lang}, and it _is_ 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.
|
||||
If there are 3 or fewer items, Angular omits the paragraph, so no message appears.
|
||||
The template expression inside the double quotes,
|
||||
`*ngIf="heros.length > 3"`, looks and behaves much like !{_Lang}.
|
||||
When the component's list of heroes has more than three items, Angular adds the paragraph
|
||||
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。
|
||||
当组件中的英雄列表有三个以上的条目时,Angular把这些语句添加到DOM中,于是消息显示了出来。
|
||||
如果少于或等于三个条目,Angular会移除这些语句,于是没有消息显示。
|
||||
.alert.is-helpful
|
||||
:marked
|
||||
Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM.
|
||||
That hardly matters here. But it would matter a great deal, from a performance perspective, if
|
||||
we were conditionally including or excluding a big chunk of HTML with many data bindings.
|
||||
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
|
||||
big chunks of HTML with many data bindings.
|
||||
|
||||
Angular并不是在显示和隐藏这条消息,它是在从DOM中添加和移除这些元素。
|
||||
在这个范例中,它们(在性能上)几乎等价。但是如果我们想要有条件的包含或排除“一大堆”带着很多数据绑定的HTML,性能上的区别就会更加显著。
|
||||
在这个范例中,它们(在性能上)几乎等价。但是如果我们想要有条件的包含或排除“一大堆”带着很多数据绑定的HTML,性能上的区别就会更加显著。
|
||||
|
||||
|
||||
:marked
|
||||
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}.
|
||||
|
@ -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
|
||||
|
||||
Here's our final code:
|
||||
Here's the final code:
|
||||
|
||||
下面是我们的最终代码:
|
||||
|
||||
|
||||
block final-code
|
||||
+makeTabs(`displaying-data/ts/app/app.component.ts,
|
||||
|
|
|
@ -262,6 +262,8 @@ code-example(format="").
|
|||
|
||||
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. `templateUrl`属性指向一个独立的HTML模板文件,名叫`hero-form.component.html`。
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
block includes
|
||||
include ../_util-fns
|
||||
|
||||
// #docregion intro
|
||||
- var langName = current.path[1] == 'ts' ? 'TypeScript' : 'JavaScript'
|
||||
figure
|
||||
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}构建客户端应用的、有经验的程序员。
|
||||
|
||||
<br clear="all">
|
||||
// #enddocregion intro
|
||||
|
||||
// #docregion how-to-read-1
|
||||
<a id="learning-path"></a>
|
||||
:marked
|
||||
# Organization
|
||||
|
@ -27,8 +24,8 @@ figure
|
|||
|
||||
本文档分成几大主题区,每个区包含一组围绕自己主题的页面。
|
||||
|
||||
// #enddocregion how-to-read-1
|
||||
// #docregion how-to-read-2
|
||||
block js-alert
|
||||
|
||||
- var top="vertical-align:top"
|
||||
table.vertical-table(width="100%")
|
||||
col(width="15%")
|
||||
|
@ -161,8 +158,6 @@ table.vertical-table(width="100%")
|
|||
|
||||
读完这些,你就可以跳到本网站的任意页面去阅读了。
|
||||
|
||||
// #enddocregion how-to-read-2
|
||||
// #docregion the-rest
|
||||
:marked
|
||||
# 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.
|
||||
|
||||
到[Angular Github库](https://github.com/angular/angular)报告与**Angular本身**有关的issues。
|
||||
|
||||
// #enddocregion the-rest
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -220,7 +220,7 @@ a(id="other")
|
|||
### 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.
|
||||
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.
|
||||
|
|
|
@ -55,7 +55,7 @@ block includes
|
|||
|
||||
:marked
|
||||
Inside the interpolation expression we flow the component's `birthday` value through the
|
||||
[pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/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.
|
||||
|
||||
在这个插值表达式中,我们让组件的`birthday`值通过[管道操作符](./template-syntax.html#pipe)( | )流动到
|
||||
|
|
|
@ -1147,7 +1147,7 @@ a#in-mem-web-api
|
|||
.l-sub-section
|
||||
:marked
|
||||
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
|
||||
registered for module loading by SystemJS (see `systemjs.config.js`)
|
||||
|
||||
|
|
|
@ -1285,24 +1285,23 @@ a(href="#top").to-top 回到顶部
|
|||
### 同步测试
|
||||
|
||||
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.
|
||||
But the quote itself has not arrived, despite the fact that the spy returns a resolved promise.
|
||||
|
||||
在Spy的帮助下,第二个测试验证了`getQuote`被调用了。
|
||||
虽然Spy返回了解析了的承诺,但是确没有收到名言。
|
||||
Neither test can prove that a value from the service is be displayed.
|
||||
The quote itself has not arrived, despite the fact that the spy returns a resolved promise.
|
||||
|
||||
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"后,返回值才会有效。这个时候,测试运行器已经移到了测试套件下个测试。
|
||||
|
||||
The test must become an "async test" ... like the third test
|
||||
|
||||
测试必须变成“异步测试”...就像第三个测试那样。
|
||||
这个测试必须等待JavaScript引擎一整个回合,返回值才会有效。该测试必须要变成**异步的**。
|
||||
|
||||
|
||||
#async
|
||||
: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='.')
|
||||
: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**的一部分。
|
||||
|
||||
It simplifyies coding of asynchronous tests by arranging for the tester's code to run in a special _async test zone_.
|
||||
|
||||
通过将测试代码放到特殊的**异步测试区域**来运行,`async`函数简化了异步测试的代码。
|
||||
`async`函数是**Angular TestBed**的一部分。通过将测试代码放到特殊的**异步测试区域**来运行,`async`函数简化了异步测试的代码。
|
||||
|
||||
The `async` function _takes_ a parameterless function and _returns_ a parameterless function
|
||||
which becomes the argument to the Jasmine `it` call.
|
||||
|
@ -1338,11 +1334,15 @@ a(href="#top").to-top 回到顶部
|
|||
没有任何地方显示异步特征。
|
||||
比如,它不返回承诺,并且没有`done`方法可调用,因为它是标准的Jasmine异步测试。
|
||||
|
||||
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.
|
||||
Some functions called within a test (such as `fixture.whenStable`) continue to reveal their asynchronous behavior.
|
||||
|
||||
在测试(比如`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
|
||||
:marked
|
||||
|
@ -1350,17 +1350,9 @@ a(href="#top").to-top 回到顶部
|
|||
|
||||
## **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`承诺的解析。
|
||||
|
||||
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请求,需要好几秒来响应。
|
||||
测试必须等待`getQuote`在JavaScript引擎的下一回合中被解析。
|
||||
|
||||
This test has no direct access to the promise returned by the call to `testService.getQuote`
|
||||
which is private and inaccessible inside `TwainComponent`.
|
||||
|
@ -1373,16 +1365,15 @@ a(href="#top").to-top 回到顶部
|
|||
幸运的是,**异步测试区域**可以访问`getQuote`承诺,因为它拦截所有调用**异步**方法所发出的承诺。
|
||||
|
||||
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**承诺被解析。
|
||||
|
||||
Then the testing continues.
|
||||
The test kicks off another round of change detection (`fixture.detectChanges`) which tells Angular to update the DOM with the quote.
|
||||
Then the test resumes and kicks off another round of change detection (`fixture.detectChanges`)
|
||||
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.
|
||||
|
||||
然后测试继续运行。
|
||||
测试开始另一轮的变化检测(`fixture.detectChanges`),通知Angular使用名言来更新DOM。
|
||||
然后测试继续运行,并开始另一轮的变化检测(`fixture.detectChanges`),通知Angular使用名言来更新DOM。
|
||||
`getQuote`辅助方法提取出显示元素文本,然后expect语句确认这个文本与预备的名言相符。
|
||||
|
||||
#fakeAsync
|
||||
|
@ -1404,25 +1395,27 @@ a(href="#top").to-top 回到顶部
|
|||
注意,在`it`的参数中,`async`被`faceAsync`替换。
|
||||
`fakeAsync`是另一种Angular测试工具。
|
||||
|
||||
Like [async](#async-fn-in-it), it _takes_ a parameterless function and _returns_ a parameterless function
|
||||
which becomes the argument to the Jasmine `it` call.
|
||||
Like [async](#async), it _takes_ a parameterless function and _returns_ a function
|
||||
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_.
|
||||
|
||||
`fakeAsync`函数通过在特殊的**fakeAsync测试区域**运行测试,让测试代码更加简单直观。
|
||||
|
||||
The principle advantage of `fakeAsync` over `async` is that the test appears to be synchronous.
|
||||
There are no promises at all.
|
||||
No `then(...)` chains to disrupt the visible flow of control.
|
||||
There is no `then(...)` to disrupt the visible flow of control.
|
||||
The promise-returning `fixture.whenStable` is gone, replaced by `tick()`.
|
||||
|
||||
对于`async`来说,`fakeAsync`最重要的好处时测试看起来像同步的。里面没有任何承诺。
|
||||
没有`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-first-look
|
||||
|
@ -1430,11 +1423,7 @@ a(href="#top").to-top 回到顶部
|
|||
## The _tick_ function
|
||||
|
||||
## **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`.
|
||||
It can only be called within a `fakeAsync` body.
|
||||
|
||||
|
@ -1552,11 +1541,11 @@ a(href="#top").to-top 回到顶部
|
|||
|
||||
## **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_
|
||||
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)一样。
|
||||
|
||||
#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
|
||||
modules that declare yet more components.
|
||||
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文件。
|
||||
`TestBed.compileComponents`一次性异步编译所有组件。
|
||||
|
||||
The `compileComponents` method returns a promise so you can perform additional tasks _after_ it finishes.
|
||||
The promise isn't needed here.
|
||||
|
||||
`compileComponents`方法返回承诺,可以用来在它完成时候,执行更多额外任务。
|
||||
|
||||
|
@ -1588,21 +1578,21 @@ a(href="#top").to-top 回到顶部
|
|||
|
||||
### _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`
|
||||
nor any of the `override...` methods. The `TestBed` throws an error if you try.
|
||||
|
||||
`compileComponents`运行之后,当前的`TestBed`实例就不能再次被配置了。
|
||||
调用`compileComponents`关闭当前的`TestBed`实例,使它不能再被配置。
|
||||
你不能再调用任何`TestBed`配置方法、`configureTestModule`或者任何`override...`方法,否则`TestBed`将会抛出错误。
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
Do not configure the `TestBed` after calling `compileComponents`.
|
||||
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`。
|
||||
在调用`TestBed.createInstance`来初始化被测试的组件之前,把`compileComponents`的调用放到最后一步。
|
||||
在调用`TestBed.createComponent`来初始化被测试的组件之前,把`compileComponents`的调用放到最后一步。
|
||||
:marked
|
||||
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.
|
||||
|
|
|
@ -40,7 +40,7 @@ block includes
|
|||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="查看导航")
|
||||
:marked
|
||||
We'll add Angular’s *Component Router* to our app to satisfy these requirements.
|
||||
We'll add Angular’s *Router* to our app to satisfy these requirements.
|
||||
|
||||
我们将把Angular*路由器*加入应用中,以满足这些需求。(译注:硬件领域中的路由器是用来帮你找到另一台网络设备的,而这里的路由器用于帮你找到一个组件)
|
||||
|
||||
|
@ -309,7 +309,7 @@ block app-comp-v1
|
|||
与其自动显示英雄列表,我们更希望在用户点击按钮之后才显示它。
|
||||
换句话说,我们希望“导航”到英雄列表。
|
||||
|
||||
We'll need the Angular *Component Router*.
|
||||
We'll need the Angular *Router*.
|
||||
|
||||
我们需要Angular的*路由器*。
|
||||
|
||||
|
@ -625,20 +625,12 @@ block redirect-vs-use-as-default
|
|||
|
||||
把元数据中的`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')
|
||||
|
||||
.l-sub-section
|
||||
block templateUrl-path-resolution
|
||||
:marked
|
||||
We specify the path _all the way back to the application root_ —
|
||||
<span if-docs="ts">`app/` in this case —</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
|
||||
Create that file with this content:
|
||||
|
||||
|
@ -888,7 +880,7 @@ block route-params
|
|||
|
||||
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
|
||||
: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:
|
||||
|
||||
然后注入`!{_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.
|
||||
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`中的两个链接,或点击浏览器的“后退”按钮。
|
||||
我们来添加第三个选项:一个`goBack`方法,来根据浏览器的历史堆栈,后退一步。
|
||||
|
@ -1199,19 +1192,17 @@ figure.image-display
|
|||
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 `moduleId` property to `module.id` so that 'templateUrl` and `styleUrls` are relative to the component.
|
||||
|
||||
1. *设置*组件元数据的`templateUrl`和`styleUrls`属性,来分别引用这两个文件。
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
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.
|
||||
<span if-docs="ts">As with `templateUrl`, we must specify the path _all the way
|
||||
back to the application root_.</span>
|
||||
We could list multiple style files from different locations if we needed them.
|
||||
|
||||
`styleUrls`属性是一个由样式文件的文件名(包括路径)组成的数组。
|
||||
我们还可以列出来自多个不同位置的样式文件。
|
||||
和`templateUrl`一样,必须指定_相对于此应用根目录_的路径。
|
||||
我们还可以列出来自多个不同位置的样式文件。
|
||||
|
||||
block heroes-component-cleanup
|
||||
//- 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*路由器*在各个不同组件之间导航。
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Loading…
Reference in New Issue