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
26
gulpfile.js
26
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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,8 +5,9 @@ import { HeroService } from './hero.service';
|
|||
|
||||
// #docregion metadata, providers
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'hero-list',
|
||||
templateUrl: 'app/hero-list.component.html',
|
||||
templateUrl: 'hero-list.component.html',
|
||||
providers: [ HeroService ]
|
||||
})
|
||||
// #enddocregion providers
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -5,8 +5,9 @@ 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
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
|
||||
},
|
||||
// packages tells the System loader how to load when no filename and/or no extension
|
||||
packages: {
|
||||
|
@ -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';
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
|
||||
},
|
||||
// packages tells the System loader how to load when no filename and/or no extension
|
||||
packages: {
|
||||
|
@ -37,7 +37,7 @@
|
|||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
},
|
||||
'angular2-in-memory-web-api': {
|
||||
'angular-in-memory-web-api': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
|
||||
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
|
||||
'typescript': 'npm:typescript@2.0.2/lib/typescript.js',
|
||||
|
||||
|
@ -65,7 +65,7 @@
|
|||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
},
|
||||
'angular2-in-memory-web-api': {
|
||||
'angular-in-memory-web-api': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
|
||||
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
|
||||
'typescript': 'npm:typescript@2.0.2/lib/typescript.js',
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// 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 { 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() {}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,18 @@
|
|||
// #docregion , v2
|
||||
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',
|
||||
// #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 { 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
|
||||
}
|
|
@ -10,14 +10,14 @@
|
|||
'app': '/app', // 'dist',
|
||||
|
||||
'@angular': '/node_modules/@angular',
|
||||
'angular2-in-memory-web-api': '/node_modules/angular2-in-memory-web-api',
|
||||
'angular-in-memory-web-api': '/node_modules/angular-in-memory-web-api',
|
||||
'rxjs': '/node_modules/rxjs'
|
||||
};
|
||||
|
||||
var packages = {
|
||||
'/app': { main: 'main.js', defaultExtension: 'js' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
'angular-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
};
|
||||
// #enddocregion paths
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -10,14 +10,14 @@
|
|||
'app': '/app', // 'dist',
|
||||
|
||||
'@angular': '/node_modules/@angular',
|
||||
'angular2-in-memory-web-api': '/node_modules/angular2-in-memory-web-api',
|
||||
'angular-in-memory-web-api': '/node_modules/angular-in-memory-web-api',
|
||||
'rxjs': '/node_modules/rxjs'
|
||||
};
|
||||
|
||||
var packages = {
|
||||
'/app': { main: 'main.js', defaultExtension: 'js' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
'angular-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
};
|
||||
// #enddocregion paths
|
||||
|
||||
|
|
|
@ -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
|
||||
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.
|
||||
+includeShared('{ts}', 'how-to-read-2')
|
||||
+includeShared('{ts}', 'the-rest')
|
||||
|
|
|
@ -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,17 +442,21 @@ block ts-decorator
|
|||
来看下`@Component`中的一些配置项:
|
||||
|
||||
:marked
|
||||
- `selector` - a css selector that tells Angular to create and insert an instance of this component
|
||||
- `moduleId: module.id`: sets the base for module-relative loading of the `templateUrl`.
|
||||
|
||||
- `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`: module-relative address of this component's HTML template, shown [above](#templates).
|
||||
|
||||
- `templateUrl` - 组件HTML模板的地址,我们在[前面](#templates)展示过它。
|
||||
- `templateUrl`:组件HTML模板的模块相对地址,我们在[前面](#templates)展示过它。
|
||||
|
||||
- `directives`: !{_array} of the components or directives that *this* template requires.
|
||||
In the last line of `hero-list.component.html`, Angular inserts a `HeroDetailComponent`
|
||||
|
|
|
@ -4,16 +4,15 @@ block includes
|
|||
- 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,18 +46,16 @@ 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}}`。
|
||||
|
||||
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
|
||||
|
@ -66,19 +63,23 @@ block 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:
|
||||
|
||||
然后,到`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`添加到之前空白的组件中。
|
||||
|
||||
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,35 +108,36 @@ 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`响应。
|
||||
|
||||
严格来说,“重新显示”是在某些与视图有关的异步事件之后发生的,比如:按键、定时器或收到异步`XHR`响应。本例中没有体现这一点。
|
||||
很显然,属性肯定不会无缘无故的变化。但是目前我们只要先相信Angular会处理好就行了。
|
||||
: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:
|
||||
|
||||
试一下本应用。它应该显示出标题和英雄名。
|
||||
|
||||
|
@ -143,7 +146,7 @@ figure.image-display
|
|||
|
||||
+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.
|
||||
|
||||
我们来回顾一下以前所做的决定,看看还有哪些其它选择。
|
||||
|
||||
|
@ -152,9 +155,9 @@ figure.image-display
|
|||
|
||||
## 内联(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`重定义为数组中的第一个名字。
|
||||
|
||||
+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.
|
||||
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>`标签上。
|
||||
这个我们看到了熟悉的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,12 +292,12 @@ 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`的类。
|
||||
|
||||
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提供的简写形式 —— 用构造函数的参数直接定义属性。
|
||||
|
||||
|
@ -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`对象的数组。
|
||||
|
||||
+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')
|
||||
|
||||
|
@ -362,6 +355,7 @@ block hero-class
|
|||
|
||||
从显示上看还是一样,但现在除了名字之外,我们可以有更多英雄信息。
|
||||
|
||||
|
||||
.l-main-section#ngIf
|
||||
:marked
|
||||
## Conditional display with NgIf
|
||||
|
@ -372,12 +366,13 @@ 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`指令会基于条件的真假来显示或移除一个元素。
|
||||
我们来亲自动手试一下,把下列语句加到模板的底部:
|
||||
|
@ -387,29 +382,32 @@ block hero-class
|
|||
.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,性能上的区别就会更加显著。
|
||||
|
||||
|
||||
: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,10 +442,11 @@ 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,
|
||||
displaying-data/ts/app/hero.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
|
||||
|
||||
|
@ -226,4 +221,3 @@ block example-links
|
|||
|
||||
到[Angular Github库](https://github.com/angular/angular)报告与**Angular本身**有关的issues。
|
||||
|
||||
// #enddocregion the-rest
|
||||
|
|
|
@ -8,60 +8,47 @@ block includes
|
|||
|
||||
# 组件生命周期
|
||||
|
||||
A Component has a lifecycle managed by Angular itself. Angular creates it, renders it, creates and renders its children,
|
||||
figure
|
||||
img(src="/resources/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px" )
|
||||
:marked
|
||||
A component has a lifecycle managed by Angular itself.
|
||||
|
||||
每个组件都有一个被Angular管理的生命周期。
|
||||
|
||||
Angular creates it, renders it, creates and renders its children,
|
||||
checks it when its data-bound properties change, and destroys it before removing it from the DOM.
|
||||
|
||||
每个组件都有一个被Angular管理的生命周期。Angular创建它,渲染它,创建并渲染它的子组件,在它被绑定的属性发生变化时检查它,并在它从DOM中被移除前销毁它。
|
||||
Angular创建它,渲染它,创建并渲染它的子组件,在它被绑定的属性发生变化时检查它,并在它从DOM中被移除前销毁它。
|
||||
|
||||
Angular offers **component lifecycle hooks**
|
||||
that give us visibility into these key moments and the ability to act when they occur.
|
||||
Angular offers **lifecycle hooks**
|
||||
that provide visibility into these key moments and the ability to act when they occur.
|
||||
|
||||
Angular提供了**组件生命周期钩子**,把这些关键时刻暴露出来,赋予我们在它们发生时采取行动的能力。
|
||||
Angular提供了**生命周期钩子**,把这些关键时刻暴露出来,赋予我们在它们发生时采取行动的能力。
|
||||
|
||||
We cover these hooks in this chapter and demonstrate how they work in code.
|
||||
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
|
||||
|
||||
在本章中,我们将全面讲解这些钩子,在代码层面演示它们是如何工作的。
|
||||
|
||||
* [The lifecycle hooks](#hooks-overview)
|
||||
|
||||
* [生命周期钩子概览](#hooks-overview)
|
||||
|
||||
* [The hook-call sequence](#hook-sequence)
|
||||
|
||||
* [钩子的调用顺序](#hook-sequence)
|
||||
|
||||
* [Other Angular lifecycle hooks](#other-lifecycles)
|
||||
|
||||
* [其它Angular生命周期钩子](#other-lifecycles)
|
||||
除了那些组件内容和视图相关的钩子外,指令有相同生命周期钩子。
|
||||
<br clear="all">
|
||||
## Table of Contents
|
||||
|
||||
## 目录
|
||||
* [Overview](#hooks-overview)
|
||||
<br><br>
|
||||
* [Each hook's purpose and timing](#hooks-purpose-timing)
|
||||
+ifDocsFor('ts|js')
|
||||
:marked
|
||||
* [Interfaces are optional (technically)](#interface-optional)
|
||||
:marked
|
||||
* [Other Angular lifecycle hooks](#other-lifecycle-hooks)
|
||||
<br><br>
|
||||
* [The lifecycle sample](#the-sample)
|
||||
|
||||
* [范例](#the-sample)
|
||||
|
||||
* [All](#peek-a-boo)
|
||||
|
||||
* [全部](#peek-a-boo)
|
||||
|
||||
* [Spying OnInit and OnDestroy](#spy)
|
||||
|
||||
* [窥测OnInit和OnDestroy](#spy)
|
||||
|
||||
* [OnChanges](#onchanges)
|
||||
|
||||
* [OnChanges](#onchanges)
|
||||
|
||||
* [DoCheck](#docheck)
|
||||
|
||||
* [DoCheck](#docheck)
|
||||
|
||||
* [AfterViewInit and AfterViewChecked](#afterview)
|
||||
|
||||
* [AfterViewInit和AfterViewChecked](#afterview)
|
||||
|
||||
* [AfterContentInit and AfterContentChecked](#aftercontent)
|
||||
|
||||
* [AfterContentInit和AfterContentChecked](#aftercontent)
|
||||
|
||||
:marked
|
||||
Try the <live-example></live-example>.
|
||||
|
||||
p 试一试#[+liveExampleLink2('在线例子')]。
|
||||
|
@ -84,148 +71,20 @@ a#hooks-overview
|
|||
通过实现一个或多个Angular `core`库里定义的*生命周期钩子*接口,开发者可以介入该生命周期中的这些关键时刻。
|
||||
|
||||
Each interface has a single hook method whose name is the interface name prefixed with `ng`.
|
||||
For example, the `OnInit` interface has a hook method named `ngOnInit`.
|
||||
We might implement it in a component class like this:
|
||||
For example, the `OnInit` interface has a hook method named `ngOnInit`
|
||||
that Angular calls shortly after creating the component:
|
||||
|
||||
每个接口都有唯一的一个钩子方法,它们的名字是由接口名再加上`ng`前缀构成的。比如,`OnInit`接口的钩子方法叫做`ngOnInit`,
|
||||
Angular在创建组建后立刻调用它,:
|
||||
|
||||
每个接口都有唯一的一个钩子方法,它们的名字是由接口名再加上`ng`前缀构成的。比如,`OnInit`接口的钩子方法叫做`ngOnInit`。
|
||||
我们可以在一个组件类中实现它,就像这样:
|
||||
+makeExample('lifecycle-hooks/ts/app/peek-a-boo.component.ts', 'ngOnInit', 'peek-a-boo.component.ts (excerpt)')(format='.')
|
||||
:marked
|
||||
No directive or component will implement all of them and some of the hooks only make sense for components.
|
||||
No directive or component will implement all of the lifecycle hooks and some of the hooks only make sense for components.
|
||||
Angular only calls a directive/component hook method *if it is defined*.
|
||||
|
||||
没有指令或者组件会实现所有这些接口,并且有些钩子只对组件有意义。只有在指令/组件中*定义过的*那些钩子方法才会被Angular调用。
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Interface optional?
|
||||
|
||||
### 接口是可选的?
|
||||
|
||||
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
||||
The JavaScript language doesn't have interfaces.
|
||||
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
|
||||
|
||||
从纯技术的角度讲,接口对JavaScript和TypeScript的开发者都是可选的。JavaScript语言本身没有接口。
|
||||
Angular在运行时看不到TypeScript接口,因为它们在编译为JavaScript的时候已经消失了。
|
||||
|
||||
Fortunately, they aren't necessary.
|
||||
We don't have to add the lifecycle hook interfaces to our directives and components to benefit from the hooks themselves.
|
||||
|
||||
幸运的是,它们也不是必须的。我们不需要在指令和组件上添加生命周期钩子接口就能获得钩子带来的好处。
|
||||
|
||||
Angular instead inspects our directive and component classes and calls the hook methods *if they are defined*.
|
||||
Angular will find and call methods like `ngOnInit()`, with or without the interfaces.
|
||||
|
||||
Angular会去检测我们的指令和组件的类,一旦发现钩子方法被定义了,就调用它们。
|
||||
Agnular会找到并调用像`ngOnInit()`这样的钩子方法,有没有接口无所谓。
|
||||
|
||||
Nonetheless, we strongly recommend adding interfaces to TypeScript directive classes
|
||||
in order to benefit from strong typing and editor tooling.
|
||||
|
||||
虽然如此,我们还是强烈建议你在TypeScript指令类中添加接口,以获得强类型和IDE等编辑器带来的好处。
|
||||
|
||||
:marked
|
||||
Here are the component lifecycle hook methods:
|
||||
|
||||
这里是组件生命周期钩子的方法列表:
|
||||
|
||||
### Directives and Components
|
||||
|
||||
### 指令和组件
|
||||
|
||||
table(width="100%")
|
||||
col(width="20%")
|
||||
col(width="80%")
|
||||
tr
|
||||
th
|
||||
p Hook
|
||||
p 钩子
|
||||
th
|
||||
p Purpose
|
||||
p 用途
|
||||
tr(style=top)
|
||||
td ngOnInit
|
||||
td
|
||||
:marked
|
||||
Initialize the directive/component after Angular initializes the data-bound input properties.
|
||||
|
||||
当Angular初始化完数据绑定的输入属性后,用来初始化指令或组件。
|
||||
tr(style=top)
|
||||
td ngOnChanges
|
||||
td
|
||||
:marked
|
||||
Respond after Angular sets a data-bound input property.
|
||||
The method receives a `changes` object of current and previous values.
|
||||
|
||||
当Angular设置了一个被绑定的输入属性后触发。该回调方法会收到一个包含当前值和原值的`changes`对象。
|
||||
tr(style=top)
|
||||
td ngDoCheck
|
||||
td
|
||||
:marked
|
||||
Detect and act upon changes that Angular can't or won't
|
||||
detect on its own. Called every change detection run.
|
||||
|
||||
用来监测所有变化(无论是Angular本身能检测的还是无法检测的),并作出相应行动。在每次执行“变更检测”时被调用。
|
||||
tr(style=top)
|
||||
td ngOnDestroy
|
||||
td
|
||||
:marked
|
||||
Cleanup just before Angular destroys the directive/component.
|
||||
Unsubscribe observables and detach event handlers to avoid memory leaks.
|
||||
|
||||
在Angular销毁指令或组件之前做一些清理工作,比如退订可观察对象和移除事件处理器,以免导致内存泄露。
|
||||
|
||||
:marked
|
||||
### Components only
|
||||
|
||||
### 只适用于组件
|
||||
|
||||
table(width="100%")
|
||||
col(width="20%")
|
||||
col(width="80%")
|
||||
tr
|
||||
th
|
||||
p Hook
|
||||
p 钩子
|
||||
th
|
||||
p Purpose
|
||||
p 用途
|
||||
tr(style=top)
|
||||
td ngAfterContentInit
|
||||
td
|
||||
:marked
|
||||
After Angular projects external content into its view.
|
||||
|
||||
当Angular把外来内容投影进自己的视图之后调用。
|
||||
tr(style=top)
|
||||
td ngAfterContentChecked
|
||||
td
|
||||
:marked
|
||||
After Angular checks the bindings of the external content that it projected into its view.#
|
||||
|
||||
当Angular检查完那些投影到自己视图中的外来内容的数据绑定之后调用。
|
||||
tr(style=top)
|
||||
td ngAfterViewInit
|
||||
td
|
||||
:marked
|
||||
After Angular creates the component's view(s).
|
||||
|
||||
在Angular创建完组件的视图后调用。
|
||||
tr(style=top)
|
||||
td ngAfterViewChecked
|
||||
td
|
||||
:marked
|
||||
After Angular checks the bindings of the component's view(s).
|
||||
|
||||
在Angular检查完组件视图中的绑定后调用。
|
||||
:marked
|
||||
Angular does not call the hook methods in this order.
|
||||
|
||||
Angular并不会按照表中所列的顺序调用这些钩子方法。
|
||||
|
||||
a(id="hook-sequence")
|
||||
a#hooks-purpose-timing
|
||||
.l-main-section
|
||||
:marked
|
||||
## Lifecycle sequence
|
||||
|
@ -244,82 +103,160 @@ table(width="100%")
|
|||
p Hook
|
||||
p 钩子
|
||||
th
|
||||
p Timing
|
||||
p 调用时机
|
||||
p Purpose and Timing
|
||||
p 目的和时机
|
||||
tr(style=top)
|
||||
td ngOnChanges
|
||||
td
|
||||
:marked
|
||||
before `ngOnInit` and when a data-bound input property value changes.
|
||||
Respond when Angular (re)sets data-bound input properties.
|
||||
The method receives a `SimpleChanges` object of current and previous property values.
|
||||
|
||||
当Angular(重新)设置数据绑定输入属性时响应。
|
||||
该方法接受当前和上一属性值的`SimpleChanges`对象
|
||||
|
||||
Called before `ngOnInit` and whenever one or more data-bound input properties change.
|
||||
|
||||
当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在`ngOnInit`之前。
|
||||
|
||||
tr(style=top)
|
||||
td ngOnInit
|
||||
td
|
||||
:marked
|
||||
after the first `ngOnChanges`.
|
||||
Initialize the directive/component after Angular first displays the data-bound properties
|
||||
and sets the directive/component's input properties.
|
||||
|
||||
在Angular第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。
|
||||
|
||||
Called _once_, after the _first_ `ngOnChanges`.
|
||||
|
||||
在第一轮`ngOnChanges`完成之后调用,只调用**一次**。
|
||||
|
||||
在第一轮`ngOnChanges`完成之后调用。
|
||||
(译注:也就是说当每个输入属性的值都被触发了一次ngOnChanges之后才会调用ngOnInit,此时所有输入属性都已经有了正确的初始绑定值)
|
||||
tr(style=top)
|
||||
td ngDoCheck
|
||||
td
|
||||
:marked
|
||||
during every Angular change detection cycle.
|
||||
Detect and act upon changes that Angular can't or won't detect on its own.
|
||||
|
||||
检测,并在发生Angular无法或不愿意自己检测的变化时作出反应。
|
||||
|
||||
Called during every change detection run, immediately after `ngOnChanges` and `ngOnInit`.
|
||||
|
||||
在每个Angular变更检测周期中调用,`ngOnChanges`和`ngOnInit`之后。
|
||||
|
||||
在每个Angular变更检测周期中调用。
|
||||
tr(style=top)
|
||||
td ngAfterContentInit
|
||||
td
|
||||
:marked
|
||||
after projecting content into the component.
|
||||
Respond after Angular projects external content into the component's view.
|
||||
|
||||
当把内容投影进组件之后调用。
|
||||
|
||||
Called _once_ after the first `NgDoCheck`.
|
||||
|
||||
第一次`NgDoCheck`之后调用,只调用一次。
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
**只适用于组件**。
|
||||
|
||||
tr(style=top)
|
||||
td ngAfterContentChecked
|
||||
td
|
||||
:marked
|
||||
after every check of projected component content.
|
||||
Respond after Angular checks the content projected into the component.
|
||||
|
||||
每次完成被投影组件内容的变更检测之后调用。
|
||||
|
||||
Called after the `ngAfterContentInit` and every subsequent `NgDoCheck`.
|
||||
|
||||
`ngAfterContentInit`和每次`NgDoCheck`之后调用
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
**只适合组件**。
|
||||
|
||||
tr(style=top)
|
||||
td ngAfterViewInit
|
||||
td
|
||||
:marked
|
||||
after initializing the component's views and child views.
|
||||
Respond after Angular initializes the component's views and child views.
|
||||
|
||||
初始化完组件视图及其子视图之后调用。
|
||||
|
||||
Called _once_ after the first `ngAfterContentChecked`.
|
||||
|
||||
第一次`ngAfterContentChecked`之后调用,只调用一次。
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
**只适合组件**。
|
||||
|
||||
tr(style=top)
|
||||
td ngAfterViewChecked
|
||||
td
|
||||
:marked
|
||||
after every check of the component's views and child views.
|
||||
Respond after Angular checks the component's views and child views.
|
||||
|
||||
每次做完组件视图和子视图的变更检测之后调用。
|
||||
|
||||
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked`.
|
||||
|
||||
`ngAfterViewInit`和每次`ngAfterContentChecked`之后调用。
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
**只适合组件**。
|
||||
|
||||
tr(style=top)
|
||||
td ngOnDestroy
|
||||
td
|
||||
:marked
|
||||
just before Angular destroys the directive/component.
|
||||
Cleanup just before Angular destroys the directive/component.
|
||||
Unsubscribe observables and detach event handlers to avoid memory leaks.
|
||||
|
||||
当Angular每次销毁指令/组件之前调用。
|
||||
当Angular每次销毁指令/组件之前调用并清扫。
|
||||
在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。
|
||||
|
||||
a(id="other-lifecycles")
|
||||
Called _just before_ Angular destroys the directive/component.
|
||||
|
||||
在Angular销毁指令/组件之前调用。
|
||||
|
||||
+ifDocsFor('ts|js')
|
||||
a#interface-optional
|
||||
.l-main-section
|
||||
:marked
|
||||
## Interface are optional (technically)
|
||||
|
||||
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
||||
The JavaScript language doesn't have interfaces.
|
||||
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
|
||||
|
||||
Fortunately, they aren't necessary.
|
||||
You don't have to add the lifecycle hook interfaces to directives and components to benefit from the hooks themselves.
|
||||
|
||||
Angular instead inspects directive and component classes and calls the hook methods *if they are defined*.
|
||||
Angular finds and calls methods like `ngOnInit()`, with or without the interfaces.
|
||||
|
||||
Nonetheless, it's good practice to add interfaces to TypeScript directive classes
|
||||
in order to benefit from strong typing and editor tooling.
|
||||
|
||||
a#other-lifecycle-hooks
|
||||
.l-main-section
|
||||
:marked
|
||||
## Other lifecycle hooks
|
||||
|
||||
## 其它生命周期钩子
|
||||
|
||||
Other Angular sub-systems may have their own lifecycle hooks apart from the component hooks we've listed.
|
||||
Other Angular sub-systems may have their own lifecycle hooks apart from these component hooks.
|
||||
|
||||
Angular的其它子系统除了有我们已经列出来的这些组件钩子外,还可能有它们自己的生命周期钩子。
|
||||
Angular的其它子系统除了有这些组件钩子外,还可能有它们自己的生命周期钩子。
|
||||
|
||||
block other-angular-subsystems
|
||||
//- N/A for TS.
|
||||
|
||||
:marked
|
||||
3rd party libraries might implement their hooks as well in order to give us, the developers, more
|
||||
3rd party libraries might implement their hooks as well in order to give developers more
|
||||
control over how these libraries are used.
|
||||
|
||||
第三方库也可能会实现它们自己的钩子,以便让我们这些开发者在使用时能做更多的控制。
|
||||
|
@ -369,15 +306,15 @@ table(width="100%")
|
|||
td
|
||||
:marked
|
||||
Directives have lifecycle hooks too.
|
||||
We create a `SpyDirective` that logs when the element it spies upon is
|
||||
A `SpyDirective` can log when the element it spies upon is
|
||||
created or destroyed using the `ngOnInit` and `ngOnDestroy` hooks.
|
||||
|
||||
指令也同样有生命周期钩子。我们新建了一个`SpyDirective`,利用`ngOnInit`和`ngOnDestroy`钩子,在它所监视的每个元素被创建或销毁时输出日志。
|
||||
|
||||
We apply the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
||||
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
||||
managed by the parent `SpyComponent`.
|
||||
|
||||
我们把`SpyDirective`应用到父组件里的`ngFor`*英雄*重复器(repeater)的`<div>`里面。
|
||||
本例把`SpyDirective`应用到父组件里的`ngFor`*英雄*重复器(repeater)的`<div>`里面。
|
||||
tr(style=top)
|
||||
td <a href="#onchanges">OnChanges</a>
|
||||
td
|
||||
|
@ -429,18 +366,21 @@ table(width="100%")
|
|||
|
||||
In this example, a `CounterComponent` logs a change (via `ngOnChanges`)
|
||||
every time the parent component increments its input counter property.
|
||||
Meanwhile, we apply the `SpyDirective` from the previous example
|
||||
to the `CounterComponent` log and watch log entries be created and destroyed.
|
||||
Meanwhile, the `SpyDirective` from the previous example is applied
|
||||
to the `CounterComponent` log where it watches log entries being created and destroyed.
|
||||
|
||||
在这个例子中,每当父组件递增它的输入属性`counter`时,`CounterComponent`就会通过`ngOnChanges`记录一条变更。
|
||||
同时,我们还把前一个例子中的`SpyDirective`用在`CounterComponent`上,来提供日志,可以同时观察到日志的创建和销毁过程。
|
||||
|
||||
|
||||
|
||||
:marked
|
||||
We discuss the exercises in further detail over this chapter as we learn more about the lifecycle hooks.
|
||||
The remainder of this chapter discusses selected exercises in further detail.
|
||||
|
||||
接下来,我们将详细讨论这些练习,以学习更多关于生命周期钩子的知识。
|
||||
接下来,我们将详细讨论这些练习。
|
||||
|
||||
a(id="peek-a-boo")
|
||||
|
||||
a#peek-a-boo
|
||||
.l-main-section
|
||||
:marked
|
||||
## Peek-a-boo: all hooks
|
||||
|
@ -449,15 +389,18 @@ a(id="peek-a-boo")
|
|||
|
||||
`PeekABooComponent`组件演示了组件中所有可能存在的钩子。
|
||||
|
||||
In real life, we'd rarely if ever implement all of the interfaces like this.
|
||||
We do so in peek-a-boo in order to watch Angular call the hooks in the expected order.
|
||||
You would rarely, if ever, implement all of the interfaces like this.
|
||||
The peek-a-boo exists to show how Angular calls the hooks in the expected order.
|
||||
|
||||
现实中,我们几乎永远不会像这里一样实现所有这些接口。
|
||||
你可能很少、或者永远不会像这里一样实现所有这些接口。
|
||||
我们之所以在peek-a-boo中这么做,只是为了观看Angular是如何按照期望的顺序调用这些钩子的。
|
||||
|
||||
In this snapshot, we clicked the *Create...* button and then the *Destroy...* button.
|
||||
|
||||
在下面这个快照中,我们先点击了*Create...*按钮,然后点击了*Destroy...*按钮。
|
||||
|
||||
|
||||
This snapshot reflects the state of the log after the user clicked the *Create...* button and then the *Destroy...* button.
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/lifecycle-hooks/peek-a-boo.png" alt="Peek-a-boo")
|
||||
:marked
|
||||
|
@ -471,61 +414,63 @@ figure.image-display
|
|||
.l-sub-section
|
||||
:marked
|
||||
The constructor isn't an Angular hook *per se*.
|
||||
We log in it to confirm that input properties (the `name` property in this case) have no assigned values at construction.
|
||||
The log confirms that input properties (the `name` property in this case) have no assigned values at construction.
|
||||
|
||||
构造函数本质上不应该算作Angular的钩子。
|
||||
我们把它记录在这里只是为了确认在创建期间那些输入属性(这里是`name`属性)没有被赋值。
|
||||
记录确认了在创建期间那些输入属性(这里是`name`属性)没有被赋值。
|
||||
|
||||
:marked
|
||||
Had we clicked the *Update Hero* button, we'd have seen another `OnChanges` and two more triplets of
|
||||
Had the user clicked the *Update Hero* button, the log would show another `OnChanges` and two more triplets of
|
||||
`DoCheck`, `AfterContentChecked` and `AfterViewChecked`.
|
||||
Clearly these three hooks fire a *lot* and we must keep the logic we put in these hooks
|
||||
as lean as possible!
|
||||
Clearly these three hooks fire a *often*. Keep the logic in these hooks as lean as possible!
|
||||
|
||||
如果我们点击*Update Hero*按钮,就会看到另一个`OnChanges`和至少两组`DoCheck`、`AfterContentChecked`和`AfterViewChecked`钩子。
|
||||
显然,这三种钩子被触发了*很多次*,所以我们必须让这三种钩子里的逻辑尽可能的精简!
|
||||
|
||||
Our next examples focus on hook details.
|
||||
|
||||
我们的下一个例子就聚焦于这些钩子的细节上。
|
||||
The next examples focus on hook details.
|
||||
|
||||
.a(id="spy")
|
||||
下一个例子就聚焦于这些钩子的细节上。
|
||||
|
||||
|
||||
a#spy
|
||||
.l-main-section
|
||||
:marked
|
||||
## Spying *OnInit* and *OnDestroy*
|
||||
## 窥探*OnInit*和*OnDestroy*
|
||||
|
||||
We're going undercover for these two hooks. We want to know when an element is initialized or destroyed,
|
||||
but we don't want *it* to know we're watching.
|
||||
Go undercover with these two spy hooks to discover when an element is initialized or destroyed.
|
||||
|
||||
我们将揭开这两个钩子的奥秘。我们想知道一个元素是什么时候被初始化或销毁的,但我们不希望*它*知道我们正在监视它。
|
||||
潜入这两个spy钩子来发现一个元素时什么时候被初始化或者销毁的。
|
||||
|
||||
This is the perfect infiltration job for a directive.
|
||||
Our heroes will never know it's there.
|
||||
The heroes will never know they're being watched.
|
||||
|
||||
指令是一种完美的渗透方式,我们的英雄永远不会知道该指令的存在。
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Kidding aside, we're emphasizing two key points:
|
||||
Kidding aside, pay attention to two key points:
|
||||
|
||||
不开玩笑了,我们要强调的是两点:
|
||||
不开玩笑了,注意下面两个关键点:
|
||||
|
||||
1. Angular calls hook methods for *directives* as well as components.
|
||||
|
||||
1. 就像对组件一样,Angular也会对*指令*调用这些钩子方法。
|
||||
<br><br>
|
||||
|
||||
2. A spy directive can gives us insight into a DOM object that we cannot change directly.
|
||||
Obviously we can't change the implementation of a native `div`.
|
||||
We can't modify a third party component either.
|
||||
2. A spy directive can provide insight into a DOM object that we cannot change directly.
|
||||
Obviously you can't touch the implementation of a native `div`.
|
||||
You can't modify a third party component either.
|
||||
But we can watch both with a directive.
|
||||
|
||||
2. 一个侦探(spy)指令可以让我们在无法直接修改DOM对象实现代码的情况下,透视其内部细节。
|
||||
显然,我们不能修改一个原生`div`元素的实现代码。
|
||||
我们同样不能修改第三方组件。
|
||||
显然,你不能修改一个原生`div`元素的实现代码。
|
||||
你同样不能修改第三方组件。
|
||||
但我们用一个指令就能监视它们了。
|
||||
|
||||
:marked
|
||||
Our sneaky spy directive is simple, consisting almost entirely of `ngOnInit` and `ngOnDestroy` hooks
|
||||
The sneaky spy directive is simple, consisting almost entirely of `ngOnInit` and `ngOnDestroy` hooks
|
||||
that log messages to the parent via an injected `LoggerService`.
|
||||
|
||||
我们这个鬼鬼祟祟的侦探指令很简单,几乎完全由`ngOnInit`和`ngOnDestroy`钩子组成,它通过一个注入进来的`LoggerService`来把消息记录到父组件中去。
|
||||
|
@ -533,17 +478,18 @@ figure.image-display
|
|||
+makeExample('lifecycle-hooks/ts/app/spy.directive.ts', 'spy-directive')(format=".")
|
||||
|
||||
:marked
|
||||
We can apply the spy to any native or component element and it'll be initialized and destroyed
|
||||
You can apply the spy to any native or component element and it'll be initialized and destroyed
|
||||
at the same time as that element.
|
||||
Here we attach it to the repeated hero `<div>`
|
||||
Here it is attached to the repeated hero `<div>`
|
||||
|
||||
我们可以把这个侦探指令写到任何原生元素或组件元素上,它将与所在的组件同时初始化和销毁。
|
||||
这里我们把它附加到用来重复显示英雄数据的这个`<div>`上。
|
||||
下面是把它附加到用来重复显示英雄数据的这个`<div>`上。
|
||||
|
||||
+makeExample('lifecycle-hooks/ts/app/spy.component.html', 'template')(format=".")
|
||||
|
||||
:marked
|
||||
Each spy's birth and death marks the birth and death of the attached hero `<div>`
|
||||
with an entry in the *Hook Log* as we see here:
|
||||
with an entry in the *Hook Log* as seen here:
|
||||
|
||||
每个“侦探”的出生和死亡也同时标记出了存放英雄的那个`<div>`的出生和死亡。*钩子记录*中的结构看起来是这样的:
|
||||
|
||||
|
@ -552,13 +498,11 @@ figure.image-display
|
|||
|
||||
:marked
|
||||
Adding a hero results in a new hero `<div>`. The spy's `ngOnInit` logs that event.
|
||||
We see a new entry for each hero.
|
||||
|
||||
添加一个英雄就会产生一个新的英雄`<div>`。侦探的`ngOnInit`记录下了这个事件。
|
||||
我们看到每添加一个英雄都产生了一条新的日志结构。
|
||||
|
||||
The *Reset* button clears the `heroes` list.
|
||||
Angular removes all hero divs from the DOM and destroys their spy directives at the same time.
|
||||
Angular removes all hero `<div>` elements from the DOM and destroys their spy directives at the same time.
|
||||
The spy's `ngOnDestroy` method reports its last moments.
|
||||
|
||||
*Reset*按钮清除了这个`heroes`列表。
|
||||
|
@ -566,124 +510,136 @@ figure.image-display
|
|||
侦探的`ngOnDestroy`方法汇报了它自己的临终时刻。
|
||||
|
||||
The `ngOnInit` and `ngOnDestroy` methods have more vital roles to play in real applications.
|
||||
Let's see why we need them.
|
||||
|
||||
在真实的应用程序中,`ngOnInit`和`ngOnDestroy`方法扮演着更重要的角色。
|
||||
来看看我们为什么需要它们。
|
||||
|
||||
### OnInit
|
||||
|
||||
We turn to `ngOnInit` for two main reasons:
|
||||
Use `ngOnInit` for two main reasons:
|
||||
|
||||
我们会因为两个主要理由而求助于`ngOnInit`:
|
||||
1. To perform complex initializations shortly after construction
|
||||
使用`ngOnInit`有两个原因:
|
||||
|
||||
1. to perform complex initializations shortly after construction
|
||||
1. 在构造函数之后马上执行复杂的初始化逻辑
|
||||
1. To set up the component after Angular sets the input properties
|
||||
1. to set up the component after Angular sets the input properties
|
||||
1. 在Angular设置完输入属性之后,对该组件进行准备。
|
||||
|
||||
An `ngOnInit` often fetches data for the component as shown in the
|
||||
[Tutorial](../tutorial/toh-pt4.html#oninit) and [HTTP](server-communication.html#oninit) chapters.
|
||||
Experienced developers agree that components should be cheap and safe to construct.
|
||||
|
||||
`ngOnInit`通常用来为组件获取数据,就像在[教程](../tutorial/toh-pt4.html#oninit)和[HTTP](server-communication.html#oninit)中所展示的那样。
|
||||
有经验的开发者认同组件的构建应该很便宜和安全。
|
||||
|
||||
We don't fetch data in a component constructor. Why?
|
||||
Because experienced developers agree that components should be cheap and safe to construct.
|
||||
We shouldn't worry that a new component will try to contact a remote server when
|
||||
created under test or before we decide to display it.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Misko Hevery, Angular team lead,
|
||||
[explains why](http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/)
|
||||
you should avoid complex constructor logic.
|
||||
|
||||
Misko Hevery,Angular项目的头,在[这里解释](http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/)了你为什么应该避免复杂的构造函数逻辑。
|
||||
|
||||
:marked
|
||||
|
||||
Don't fetch data in a component constructor.
|
||||
You shouldn't worry that a new component will try to contact a remote server when
|
||||
created under test or before you decide to display it.
|
||||
Constructors should do no more than set the initial local variables to simple values.
|
||||
|
||||
我们没有在组件的构造函数中获取数据。为什么呢?
|
||||
因为有经验的开发者都同意,组件应该能被“便宜”而且安全的被构造出来。
|
||||
不要在组件的构造函数中获取数据?
|
||||
在测试环境下新建组件时或在我们决定显示它之前,我们不应该担心它会尝试联系远程服务器。
|
||||
构造函数中除了使用简单的值对局部变量进行初始化之外,什么都不应该做。
|
||||
|
||||
When a component must start working _soon_ after creation,
|
||||
we can count on Angular to call the `ngOnInit` method to jumpstart it.
|
||||
That's where the heavy initialization logic belongs.
|
||||
An `ngOnInit` is a good place for a component to fetch its initial data. The
|
||||
[Tutorial](../tutorial/toh-pt4.html#oninit) and [HTTP](server-communication.html#oninit) chapter
|
||||
show how.
|
||||
|
||||
如果组件必须在创建之后_很快_就开始工作,我们可以等Angular调用`ngOnInit`方法来启动它。
|
||||
该方法中才是放重量级初始化逻辑的地方。
|
||||
`ngOnInit`是组件获取初始数据的好地方。[指南](../tutorial/toh-pt4.html#oninit)和[HTTP](server-communication.html#oninit)章讲解了如何这样做。
|
||||
|
||||
Remember also that a directive's data-bound input properties are not set until _after construction_.
|
||||
That's a problem if we need to initialize the directive based on those properties.
|
||||
They'll have been set when our `ngOninit` runs.
|
||||
That's a problem if you need to initialize the directive based on those properties.
|
||||
They'll have been set when `ngOninit` runs.
|
||||
|
||||
另外还要记住,在指令的_构造函数完成之前_,那些被绑定的输入属性还都没有值。
|
||||
如果我们需要基于这些属性的值来初始化这个指令,这种情况就会出问题。
|
||||
而当`ngOnInit`执行的时候,这些属性都已经被正确的赋值过了。
|
||||
.l-sub-section
|
||||
:marked
|
||||
Our first opportunity to access those properties is the `ngOnChanges` method which
|
||||
Angular calls before `ngOnInit`. But Angular calls `ngOnChanges` many times after that.
|
||||
The `ngOnChanges` method is your first opportunity to access those properties.
|
||||
Angular calls `ngOnChanges` before `ngOnInit` ... and many times after that.
|
||||
It only calls `ngOnInit` once.
|
||||
|
||||
我们访问这些属性的第一次机会,实际上是`ngOnChanges`方法,Angular会在`ngOnInit`之前调用它。
|
||||
但是在那之后,Angular还会调用`ngOnChanges`很多次。而`ngOnInit`只会被调用一次。
|
||||
:marked
|
||||
You can count on Angular to call the `ngOnInit` method _soon_ after creating the component.
|
||||
That's where the heavy initialization logic belongs.
|
||||
|
||||
### OnDestroy
|
||||
|
||||
Put cleanup logic in `ngOnDestroy`, the logic that *must* run before Angular destroys the directive.
|
||||
|
||||
一些清理逻辑*必须*在Angular销毁指令之前运行,把它们放在`ngOnDestroy`中。
|
||||
|
||||
This is the time to notify another part of the application that this component is going away.
|
||||
This is the time to notify another part of the application that the component is going away.
|
||||
|
||||
这是在该组件消失之前,可用来通知应用程序中其它部分的最后一个时间点。
|
||||
|
||||
This is the place to free resources that won't be garbage collected automatically.
|
||||
Unsubscribe from observables and DOM events. Stop interval timers.
|
||||
Unregister all callbacks that this directive registered with global or application services.
|
||||
We risk memory leaks if we neglect to do so.
|
||||
You risk memory leaks if you neglect to do so.
|
||||
|
||||
这里是用来释放那些不会被垃圾收集器自动回收的各类资源的地方。
|
||||
取消那些对可观察对象和DOM事件的订阅。停止定时器。注销该指令曾注册到全局服务或应用级服务中的各种回调函数。
|
||||
如果我们不这么做,就会有导致内存泄露的风险。
|
||||
如果不这么做,就会有导致内存泄露的风险。
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## OnChanges
|
||||
|
||||
We monitor the `OnChanges` hook in this example.
|
||||
Angular calls its `ngOnChanges` method whenever it detects changes to ***input properties*** of the component (or directive).
|
||||
|
||||
在这个例子中,我们监听了`OnChanges`钩子。
|
||||
一旦检测到该组件(或指令)的***输入属性***发生了变化,Angular就会调用它的`ngOnChanges`方法。
|
||||
|
||||
Here is our implementation of the hook.
|
||||
This example monitors the `OnChanges` hook.
|
||||
|
||||
本例监控`OnChanges`钩子。
|
||||
|
||||
这里是我们对此钩子的实现。
|
||||
+makeExample('lifecycle-hooks/ts/app/on-changes.component.ts', 'ng-on-changes', 'OnChangesComponent (ngOnChanges)')(format=".")
|
||||
:marked
|
||||
The `ngOnChanges` method takes an object that maps each changed property name to a
|
||||
[SimpleChange](../api/core/index/SimpleChange-class.html) object with the current and previous property values.
|
||||
We iterate over the changed properties and log them.
|
||||
[SimpleChange](../api/core/index/SimpleChange-class.html) object holding the current and previous property values.
|
||||
This hook iterates over the changed properties and logs them.
|
||||
|
||||
`ngOnChanges`方法获取了一个对象,它把每个发生变化的属性名都映射到了一个[SimpleChange](../api/core/index/SimpleChange-class.html)对象,
|
||||
该对象中有属性的当前值和前一个值。我们在这些发生了变化的属性上进行迭代,并记录它们。
|
||||
|
||||
The input properties for our example `OnChangesComponent` are `hero` and `power`.
|
||||
The example component, `OnChangesComponent`, has two input properties: `hero` and `power`.
|
||||
|
||||
这个例子中的`OnChangesComponent`组件有两个输入属性:`hero`和`power`。
|
||||
|
||||
我们这个例子中的`OnChangesComponent`组件的输入属性是`hero`和`power`。
|
||||
+makeExample('lifecycle-hooks/ts/app/on-changes.component.ts', 'inputs')(format=".")
|
||||
:marked
|
||||
The parent binds to them like this:
|
||||
The host `OnChangesParentComponent` binds to them like this:
|
||||
|
||||
宿主`OnChangesParentComponent`绑定了它们,就像这样:
|
||||
|
||||
父组件中绑定了它们,就像这样:
|
||||
|
||||
+makeExample('lifecycle-hooks/ts/app/on-changes-parent.component.html', 'on-changes')
|
||||
:marked
|
||||
Here's the sample in action as we make changes.
|
||||
Here's the sample in action as the user makes changes.
|
||||
|
||||
下面是此例子中的当我们做出更改时的操作演示:
|
||||
下面是此例子中的当用户做出更改时的操作演示:
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/lifecycle-hooks/on-changes-anim.gif' alt="OnChanges")
|
||||
|
||||
:marked
|
||||
We see log entries as the string value of the *power* property changes. But the `ngOnChanges` did not catch changes to `hero.name`
|
||||
The log entries appear as the string value of the *power* property changes.
|
||||
But the `ngOnChanges` does not catch changes to `hero.name`
|
||||
That's surprising at first.
|
||||
|
||||
当*power*属性的字符串值变化时,我们看到了相应的日志。但是`ngOnChanges`并没有捕捉到`hero.name`的变化。
|
||||
当*power*属性的字符串值变化时,相应的日志就出现了。
|
||||
但是`ngOnChanges`并没有捕捉到`hero.name`的变化。
|
||||
这是第一个意外。
|
||||
|
||||
Angular only calls the hook when the value of the input property changes.
|
||||
|
@ -699,50 +655,48 @@ figure.image-display
|
|||
.l-main-section
|
||||
:marked
|
||||
## DoCheck
|
||||
We can use the `DoCheck` hook to detect and act upon changes that Angular doesn't catch on its own.
|
||||
Use the `DoCheck` hook to detect and act upon changes that Angular doesn't catch on its own.
|
||||
|
||||
使用`DoCheck`钩子来检测那些Angular自身无法捕获的变更并采取行动。
|
||||
|
||||
我们可以使用`DoCheck`钩子来检测那些Angular自身无法捕获的变更并采取行动。
|
||||
.l-sub-section
|
||||
:marked
|
||||
With this method we can detect a change that Angular overlooked.
|
||||
What we do with that information to refresh the display is a separate matter.
|
||||
Use this method to detect a change that Angular overlooked.
|
||||
|
||||
用这个方法来检测那些被Angular忽略的更改。
|
||||
|
||||
|
||||
用这个方法我们可以检测到那些被Angular忽略的更改。
|
||||
至于如何用此信息来刷新显示就是另一个问题了。
|
||||
:marked
|
||||
The *DoCheck* sample extends the *OnChanges* sample with this implementation of `DoCheck`:
|
||||
The *DoCheck* sample extends the *OnChanges* sample with the following `ngDoCheck` hook:
|
||||
|
||||
*DoCheck*范例通过下面的`DoCheck`实现扩展了*OnChanges*范例:
|
||||
|
||||
+makeExample('lifecycle-hooks/ts/app/do-check.component.ts', 'ng-do-check', 'DoCheckComponent (ngDoCheck)')(format=".")
|
||||
:marked
|
||||
We manually check everything that we care about, capturing and comparing against previous values.
|
||||
We write a special message to the log when there are no substantive changes
|
||||
to the hero or the power so we can keep an eye on the method's performance characteristics.
|
||||
This code inspects certain _values-of-interest_, capturing and comparing their current state against previous values.
|
||||
It writes a special message to the log when there are no substantive changes to the `hero` or the `power`
|
||||
so you can see how often `DoCheck` is called. The results are illuminating:
|
||||
|
||||
我们手动检测了自己关心的一切,捕获当前值并与以前的值进行比较。
|
||||
当英雄或它的超能力发生了非实质性改变时,我们就往日志中写一条特殊的消息,以便看到该方法的工作特征。
|
||||
该代码检测一些**相关的值**,捕获当前值并与以前的值进行比较。
|
||||
当英雄或它的超能力发生了非实质性改变时,我们就往日志中写一条特殊的消息。
|
||||
这样你可以看到`DoCheck`被调用的频率。结果非常显眼:
|
||||
|
||||
The results are illuminating:
|
||||
|
||||
其结果就像下面展示的这样:
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/lifecycle-hooks/do-check-anim.gif' alt="DoCheck")
|
||||
:marked
|
||||
We now are able to detect when the hero's `name` has changed. But we must be careful.
|
||||
|
||||
我们现在可以监测到英雄的`name`什么时候发生了变化。但我们必须小心。
|
||||
|
||||
The `ngDoCheck` hook is called with enormous frequency —
|
||||
While the `ngDoCheck` hook can detect when the hero's `name` has changed, it has a frightful cost.
|
||||
This hook is called with enormous frequency —
|
||||
after _every_ change detection cycle no matter where the change occurred.
|
||||
It's called over twenty times in this example before the user can do anything.
|
||||
|
||||
`ngDoCheck`钩子被非常频繁的调用 —— 在_每次_变更检测周期之后,发生了变化的每个地方都会调它。
|
||||
虽然`ngDoCheck`钩子可以可以监测到英雄的`name`什么时候发生了变化。但我们必须小心。
|
||||
这个`ngDoCheck`钩子被非常频繁的调用 —— 在_每次_变更检测周期之后,发生了变化的每个地方都会调它。
|
||||
在这个例子中,用户还没有做任何操作之前,它就被调用了超过二十次。
|
||||
|
||||
Most of these initial checks are triggered by Angular's first rendering of *unrelated data elsewhere on the page*.
|
||||
Mere mousing into another input box triggers a call.
|
||||
Relatively few calls reveal actual changes to pertinent data.
|
||||
Clearly our implementation must be very lightweight or the user experience may suffer.
|
||||
Clearly our implementation must be very lightweight or the user experience will suffer.
|
||||
|
||||
大部分检查的第一次调用都是在Angular首次渲染该页面中*其它不相关数据*时触发的。
|
||||
仅仅把鼠标移到其它输入框中就会触发一次调用。
|
||||
|
@ -775,13 +729,13 @@ figure.image-display
|
|||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'template', 'AfterViewComponent (template)')(format=".")
|
||||
:marked
|
||||
The following hooks take action based on changing values *within the child view*
|
||||
which we can only reach by querying for the child view via the property decorated with
|
||||
which can only be reached by querying for the child view via the property decorated with
|
||||
[@ViewChild](../api/core/index/ViewChild-var.html).
|
||||
|
||||
下列钩子基于*子视图中*的每一次数据变更采取行动,我们只能通过带[@ViewChild](../api/core/index/ViewChild-var.html)装饰器的属性来访问子视图。
|
||||
|
||||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'hooks', 'AfterViewComponent (class excerpts)')(format=".")
|
||||
.a(id="wait-a-tick")
|
||||
#wait-a-tick
|
||||
:marked
|
||||
### Abide by the unidirectional data flow rule
|
||||
### 遵循单向数据流规则
|
||||
|
@ -795,21 +749,21 @@ figure.image-display
|
|||
|
||||
为什么在更新`comment`属性之前,`doSomething`方法要等上一拍(tick)?
|
||||
|
||||
Because we must adhere to Angular's unidirectional data flow rule which says that
|
||||
we may not update the view *after* it has been composed.
|
||||
Both hooks fire after the component's view has been composed.
|
||||
Angular's unidirectional data flow rule forbids updates to the view *after* it has been composed.
|
||||
Both of these hooks fire _after_ the component's view has been composed.
|
||||
|
||||
因为我们必须遵守Angular的“单向数据流”规则,这条规则是说我们不能在一个视图已经被组合好*之后*再更新视图。
|
||||
Angular的“单向数据流”规则禁止在一个视图已经被组合好*之后*再更新视图。
|
||||
而这两个钩子都是在组件的视图已经被组合好之后触发的。
|
||||
|
||||
Angular throws an error if we update component's data-bound `comment` property immediately (try it!).
|
||||
Angular throws an error if the hook updates the component's data-bound `comment` property immediately (try it!).
|
||||
|
||||
如果我们立即更新组件中被绑定的`comment`属性,Angular就会抛出一个错误(试试!)。
|
||||
block tick-methods
|
||||
:marked
|
||||
The `LoggerService.tick` methods, which are implemented by a call to `setTimeout`, postpone the update one turn of the of the browser's JavaScript cycle ... and that's long enough.
|
||||
The `LoggerService.tick_then()` postpones the log update
|
||||
for one turn of the browser's JavaScript cycle ... and that's just long enough.
|
||||
|
||||
`LoggerService.tick`方法,是通过调用`setTimeout`的方式实现的 —— 如果想推迟到浏览器的下一轮JavaScript周期中执行,这样就够了。
|
||||
`LoggerService.tick_then()`方法延迟更新日志一个回合(浏览器JavaScript周期回合),这样就够了。
|
||||
|
||||
:marked
|
||||
Here's *AfterView* in action
|
||||
|
@ -846,19 +800,16 @@ figure.image-display
|
|||
Angular 1的开发者大概知道一项叫做*transclusion*的技术,对,这就是它的马甲。
|
||||
|
||||
:marked
|
||||
We'll illustrate with a variation on the [previous](#afterview) example
|
||||
whose behavior and output is almost the same.
|
||||
|
||||
在[前一个](#afterview)例子中,我们解说过此功能的一个变体,它的行为和输出与这个例子完全相同。
|
||||
|
||||
This time, instead of including the child view within the template, we'll import it from
|
||||
Consider this variation on the [previous _AfterView_](#afterview) example.
|
||||
This time, instead of including the child view within the template, it imports the content from
|
||||
the `AfterContentComponent`'s parent. Here's the parent's template.
|
||||
|
||||
对比[前一个](#afterview)例子考虑这个变化。
|
||||
这次,我们不再通过模板来把子视图包含进来,而是改从`AfterContentComponent`的父组件中导入它。下面是父组件的模板。
|
||||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'parent-template', 'AfterContentParentComponent (template excerpt)')(format=".")
|
||||
:marked
|
||||
Notice that the `<my-child>` tag is tucked between the `<after-content>` tags.
|
||||
We never put content between a component's element tags *unless we intend to project that content
|
||||
Never put content between a component's element tags *unless you intend to project that content
|
||||
into the component*.
|
||||
|
||||
注意,`<my-child>`标签被包含在`<after-content>`标签中。
|
||||
|
@ -870,7 +821,7 @@ figure.image-display
|
|||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'template', 'AfterContentComponent (template)')(format=".")
|
||||
:marked
|
||||
The `<ng-content>` tag is a *placeholder* for the external content.
|
||||
They tell Angular where to insert that content.
|
||||
It tells Angular where to insert that content.
|
||||
In this case, the projected content is the `<my-child>` from the parent.
|
||||
|
||||
`<ng-content>`标签是外来内容的*占位符*。
|
||||
|
@ -888,8 +839,8 @@ figure.image-display
|
|||
:marked
|
||||
### AfterContent hooks
|
||||
### AfterContent钩子
|
||||
*AfterContent* hooks are similar to the *AfterView* hooks. The key difference is the kind of child component
|
||||
that we're looking for.
|
||||
*AfterContent* hooks are similar to the *AfterView* hooks.
|
||||
The key difference is in the child component
|
||||
|
||||
*AfterContent*钩子和*AfterView*相似。关键的不同点是子组件的类型不同。
|
||||
|
||||
|
@ -904,7 +855,7 @@ figure.image-display
|
|||
* *AfterContent*钩子所关心的是`ContentChildren`,这些子组件被Angular投影进该组件中。
|
||||
|
||||
The following *AfterContent* hooks take action based on changing values in a *content child*
|
||||
which we can only reach by querying for it via the property decorated with
|
||||
which can only be reached by querying for it via the property decorated with
|
||||
[@ContentChild](../api/core/index/ContentChild-var.html).
|
||||
|
||||
下列*AfterContent*钩子基于*子级内容*中值的变化而采取相应的行动,这里我们只能通过带有[@ContentChild](../api/core/index/ContentChild-var.html)装饰器的属性来查询到“子级内容”。
|
||||
|
@ -912,8 +863,8 @@ figure.image-display
|
|||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'hooks', 'AfterContentComponent (class excerpts)')(format=".")
|
||||
|
||||
:marked
|
||||
### No unidirectional flow worries
|
||||
### 这里不用担心单向数据流规则
|
||||
### No unidirectional flow worries with _AfterContent..._
|
||||
### 使用**AfterContent**时,无需担心单向数据流规则
|
||||
|
||||
This component's `doSomething` method update's the component's data-bound `comment` property immediately.
|
||||
There's no [need to wait](#wait-a-tick).
|
||||
|
@ -923,7 +874,7 @@ figure.image-display
|
|||
|
||||
Recall that Angular calls both *AfterContent* hooks before calling either of the *AfterView* hooks.
|
||||
Angular completes composition of the projected content *before* finishing the composition of this component's view.
|
||||
We still have a window of opportunity to modify that view.
|
||||
There is a small window between the `AfterContent...` and `AfterView...` hooks to modify the host view.
|
||||
|
||||
回忆一下,Angular在每次调用*AfterView*钩子之前也会同时调用*AfterContent*。
|
||||
Angular在完成当前组件的视图合成之前,就已经完成了被投影内容的合成。
|
||||
|
|
|
@ -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.
|
||||
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.
|
||||
|
||||
在Spy的帮助下,第二个测试验证了`getQuote`被调用了。
|
||||
虽然Spy返回了解析了的承诺,但是确没有收到名言。
|
||||
两者都不能证明被显示的值是从服务的。
|
||||
虽然spy返回了解析的承诺,名言本身还没有到来。
|
||||
|
||||
This test must wait at least one full turn of the JavaScript engine, a least one "tick", before the
|
||||
value becomes available. By that time, the test runner has moved on to the next test in the suite.
|
||||
This test must wait at least one full turn of the JavaScript engine before the
|
||||
value becomes available. The test must become _asynchronous_.
|
||||
|
||||
这个测试必须等待JavaScript引擎一整个回合,至少一个"tick"后,返回值才会有效。这个时候,测试运行器已经移到了测试套件下个测试。
|
||||
这个测试必须等待JavaScript引擎一整个回合,返回值才会有效。该测试必须要变成**异步的**。
|
||||
|
||||
The test must become an "async test" ... like the third test
|
||||
|
||||
测试必须变成“异步测试”...就像第三个测试那样。
|
||||
|
||||
#async
|
||||
:marked
|
||||
|
@ -1317,12 +1316,9 @@ 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.
|
||||
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.
|
||||
|
@ -1339,10 +1335,14 @@ 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.
|
||||
|
||||
在测试(比如`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,23 +1395,25 @@ 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(...)`链来打断控制流。
|
||||
|
||||
There are limitations. For example, you cannot make an XHR call from within a `fakeAsync`.
|
||||
.l-sub-section
|
||||
:marked
|
||||
There _are_ limitations. For example, you cannot make an XHR call from within a `fakeAsync`.
|
||||
|
||||
但是`fakeAsync`有局限性。比如,你不能从`fakeAsync`发起XHR请求。
|
||||
|
||||
|
@ -1431,10 +1424,6 @@ a(href="#top").to-top 回到顶部
|
|||
|
||||
## **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,6 +1192,7 @@ 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`属性,来分别引用这两个文件。
|
||||
|
||||
|
@ -1206,12 +1200,9 @@ figure.image-display
|
|||
: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>
|
||||
|
||||
`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