build(aio): new migration
This commit is contained in:
parent
309bae0a0b
commit
0e38bf9de0
4
aio/content/examples/.gitignore
vendored
4
aio/content/examples/.gitignore
vendored
@ -22,6 +22,7 @@ protractor-helpers.js
|
||||
**/js-es6*/**/*.js
|
||||
dist/
|
||||
|
||||
|
||||
# special
|
||||
!/*
|
||||
!*.1.*
|
||||
@ -62,3 +63,6 @@ dist/
|
||||
|
||||
# style-guide
|
||||
!style-guide/src/systemjs.custom.js
|
||||
|
||||
# plunkers
|
||||
*plnkr.no-link.html
|
||||
|
@ -15,8 +15,6 @@ export default {
|
||||
|
||||
// should intercept ... but doesn't in some rollup versions
|
||||
if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; }
|
||||
// intercepts in some rollup versions
|
||||
if ( warning.indexOf("The 'this' keyword is equivalent to 'undefined'") > -1 ) { return; }
|
||||
|
||||
// console.warn everything else
|
||||
console.warn( warning.message );
|
||||
|
@ -4,29 +4,10 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { LoggerService } from './logger.service';
|
||||
|
||||
// #docregion minimal-logger
|
||||
// class used as a restricting interface (hides other public members)
|
||||
export abstract class MinimalLogger {
|
||||
logInfo: (msg: string) => void;
|
||||
logs: string[];
|
||||
}
|
||||
// #enddocregion minimal-logger
|
||||
|
||||
/*
|
||||
// Transpiles to:
|
||||
// #docregion minimal-logger-transpiled
|
||||
var MinimalLogger = (function () {
|
||||
function MinimalLogger() {}
|
||||
return MinimalLogger;
|
||||
}());
|
||||
exports("MinimalLogger", MinimalLogger);
|
||||
// #enddocregion minimal-logger-transpiled
|
||||
*/
|
||||
|
||||
// #docregion date-logger-service
|
||||
@Injectable()
|
||||
// #docregion date-logger-service-signature
|
||||
export class DateLoggerService extends LoggerService implements MinimalLogger
|
||||
export class DateLoggerService extends LoggerService
|
||||
// #enddocregion date-logger-service-signature
|
||||
{
|
||||
logInfo(msg: any) { super.logInfo(stamp(msg)); }
|
||||
|
@ -0,0 +1,26 @@
|
||||
// Illustrative (not used), mini-version of the actual HeroOfTheMonthComponent
|
||||
// Injecting with the MinimalLogger "interface-class"
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import { LoggerService } from './logger.service';
|
||||
import { MinimalLogger } from './minimal-logger.service';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'hero-of-the-month',
|
||||
templateUrl: './hero-of-the-month.component.html',
|
||||
// Todo: move this aliasing, `useExisting` provider to the AppModule
|
||||
providers: [{ provide: MinimalLogger, useExisting: LoggerService }]
|
||||
})
|
||||
export class HeroOfTheMonthComponent {
|
||||
logs: string[] = [];
|
||||
constructor(logger: MinimalLogger) {
|
||||
logger.logInfo('starting up');
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// This NgModule exists only to avoid the Angular language service's "undeclared component" error
|
||||
@NgModule({
|
||||
declarations: [ HeroOfTheMonthComponent ]
|
||||
})
|
||||
class NoopModule {}
|
@ -0,0 +1,9 @@
|
||||
<h3>{{title}}</h3>
|
||||
<div>Winner: <strong>{{heroOfTheMonth.name}}</strong></div>
|
||||
<div>Reason for award: <strong>{{heroOfTheMonth.description}}</strong></div>
|
||||
<div>Runners-up: <strong id="rups1">{{runnersUp}}</strong></div>
|
||||
|
||||
<p>Logs:</p>
|
||||
<div id="logs">
|
||||
<div *ngFor="let log of logs">{{log}}</div>
|
||||
</div>
|
@ -1,19 +1,19 @@
|
||||
/* tslint:disable:one-line:check-open-brace*/
|
||||
// #docplaster
|
||||
// #docregion opaque-token
|
||||
import { OpaqueToken } from '@angular/core';
|
||||
// #docregion injection-token
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
export const TITLE = new OpaqueToken('title');
|
||||
// #enddocregion opaque-token
|
||||
export const TITLE = new InjectionToken<string>('title');
|
||||
// #enddocregion injection-token
|
||||
|
||||
// #docregion hero-of-the-month
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
import { DateLoggerService,
|
||||
MinimalLogger } from './date-logger.service';
|
||||
import { DateLoggerService } from './date-logger.service';
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
import { LoggerService } from './logger.service';
|
||||
import { MinimalLogger } from './minimal-logger.service';
|
||||
import { RUNNERS_UP,
|
||||
runnersUpFactory } from './runners-up';
|
||||
|
||||
@ -22,28 +22,16 @@ import { RUNNERS_UP,
|
||||
const someHero = new Hero(42, 'Magma', 'Had a great month!', '555-555-5555');
|
||||
// #enddocregion some-hero
|
||||
|
||||
const template = `
|
||||
<h3>{{title}}</h3>
|
||||
<div>Winner: <strong>{{heroOfTheMonth.name}}</strong></div>
|
||||
<div>Reason for award: <strong>{{heroOfTheMonth.description}}</strong></div>
|
||||
<div>Runners-up: <strong id="rups1">{{runnersUp}}</strong></div>
|
||||
|
||||
<p>Logs:</p>
|
||||
<div id="logs">
|
||||
<div *ngFor="let log of logs">{{log}}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// #docregion hero-of-the-month
|
||||
@Component({
|
||||
selector: 'hero-of-the-month',
|
||||
template: template,
|
||||
templateUrl: './hero-of-the-month.component.html',
|
||||
providers: [
|
||||
// #docregion use-value
|
||||
{ provide: Hero, useValue: someHero },
|
||||
// #docregion provide-opaque-token
|
||||
// #docregion provide-injection-token
|
||||
{ provide: TITLE, useValue: 'Hero of the Month' },
|
||||
// #enddocregion provide-opaque-token
|
||||
// #enddocregion provide-injection-token
|
||||
// #enddocregion use-value
|
||||
// #docregion use-class
|
||||
{ provide: HeroService, useClass: HeroService },
|
||||
@ -52,9 +40,9 @@ const template = `
|
||||
// #docregion use-existing
|
||||
{ provide: MinimalLogger, useExisting: LoggerService },
|
||||
// #enddocregion use-existing
|
||||
// #docregion provide-opaque-token, use-factory
|
||||
// #docregion provide-injection-token, use-factory
|
||||
{ provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] }
|
||||
// #enddocregion provide-opaque-token, use-factory
|
||||
// #enddocregion provide-injection-token, use-factory
|
||||
]
|
||||
})
|
||||
export class HeroOfTheMonthComponent {
|
||||
|
@ -0,0 +1,22 @@
|
||||
// #docregion
|
||||
// Class used as a "narrowing" interface that exposes a minimal logger
|
||||
// Other members of the actual implementation are invisible
|
||||
export abstract class MinimalLogger {
|
||||
logs: string[];
|
||||
logInfo: (msg: string) => void;
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
/*
|
||||
// Transpiles to:
|
||||
// #docregion minimal-logger-transpiled
|
||||
var MinimalLogger = (function () {
|
||||
function MinimalLogger() {}
|
||||
return MinimalLogger;
|
||||
}());
|
||||
exports("MinimalLogger", MinimalLogger);
|
||||
// #enddocregion minimal-logger-transpiled
|
||||
*/
|
||||
|
||||
// See http://stackoverflow.com/questions/43154832/unexpected-token-export-in-angular-app-with-systemjs-and-typescript/
|
||||
export const _ = 0;
|
@ -1,12 +1,12 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { OpaqueToken } from '@angular/core';
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
// #docregion runners-up
|
||||
export const RUNNERS_UP = new OpaqueToken('RunnersUp');
|
||||
export const RUNNERS_UP = new InjectionToken<string>('RunnersUp');
|
||||
// #enddocregion runners-up
|
||||
|
||||
// #docregion factory-synopsis
|
||||
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"description": "Dynamic Component Loader",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags":["cookbook component"]
|
||||
}
|
@ -11,11 +11,12 @@ import { AdComponent } from './ad.component';
|
||||
template: `
|
||||
<div class="ad-banner">
|
||||
<h3>Advertisements</h3>
|
||||
<template ad-host></template>
|
||||
<ng-template ad-host></ng-template>
|
||||
</div>
|
||||
`
|
||||
// #enddocregion ad-host
|
||||
})
|
||||
// #docregion class
|
||||
export class AdBannerComponent implements AfterViewInit, OnDestroy {
|
||||
@Input() ads: AdItem[];
|
||||
currentAddIndex: number = -1;
|
||||
@ -53,3 +54,4 @@ export class AdBannerComponent implements AfterViewInit, OnDestroy {
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
// #enddocregion class
|
||||
|
@ -3,7 +3,7 @@
|
||||
// #docregion imports
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
import { APP_CONFIG, AppConfig } from './app.config';
|
||||
import { APP_CONFIG, AppConfig } from './app.config';
|
||||
import { Logger } from './logger.service';
|
||||
import { UserService } from './user.service';
|
||||
// #enddocregion imports
|
||||
|
@ -1,7 +1,7 @@
|
||||
// #docregion token
|
||||
import { OpaqueToken } from '@angular/core';
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
export let APP_CONFIG = new OpaqueToken('app.config');
|
||||
export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');
|
||||
// #enddocregion token
|
||||
|
||||
// #docregion config
|
||||
|
@ -1,30 +0,0 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Homepage Hello World', function () {
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
// Does it even launch?
|
||||
let expectedLabel = 'Name:';
|
||||
it(`should display the label: ${expectedLabel}`, function () {
|
||||
expect(element(by.css('label')).getText()).toEqual(expectedLabel);
|
||||
});
|
||||
|
||||
it('should display entered name', function () {
|
||||
let testName = 'Bobby Joe';
|
||||
let nameEle = element.all(by.css('input')).get(0);
|
||||
nameEle.getAttribute('value').then(function(value: string) {
|
||||
nameEle.sendKeys(testName);
|
||||
let newValue = value + testName; // old input box value + new name
|
||||
expect(nameEle.getAttribute('value')).toEqual(newValue);
|
||||
}).then(function() {
|
||||
// Check the interpolated message built from name
|
||||
let helloEle = element.all(by.css('h1')).get(0);
|
||||
expect(helloEle.getText()).toEqual('Hello ' + testName + '!');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "Hello World",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
]
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { HelloWorldComponent } from './hello_world';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule
|
||||
],
|
||||
declarations: [ HelloWorldComponent ],
|
||||
bootstrap: [ HelloWorldComponent ]
|
||||
})
|
||||
export class AppModule { }
|
@ -1,7 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<label>Name:</label>
|
||||
<!-- data-bind to the input element; store value in yourName -->
|
||||
<input type="text" [(ngModel)]="yourName" placeholder="Enter a name here">
|
||||
<hr>
|
||||
<!-- conditionally display `yourName` -->
|
||||
<h1 [hidden]="!yourName">Hello {{yourName}}!</h1>
|
@ -1,15 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
// Declare the tag name in index.html to where the component attaches
|
||||
selector: 'hello-world',
|
||||
|
||||
// Location of the template for this component
|
||||
templateUrl: './hello_world.html'
|
||||
})
|
||||
export class HelloWorldComponent {
|
||||
|
||||
// Declaring the variable for binding with initial value
|
||||
yourName: string = '';
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular Hello World</title>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- 1. Load libraries -->
|
||||
<!-- Polyfills -->
|
||||
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="https://unpkg.com/zone.js@0.8.4"></script>
|
||||
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
|
||||
<script src="https://unpkg.com/typescript@2.2.1/lib/typescript.js"></script>
|
||||
|
||||
<!-- 2. Configure SystemJS -->
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<!-- Display the application -->
|
||||
<body>
|
||||
<hello-world>Loading...</hello-world>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,5 +0,0 @@
|
||||
// #docregion
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
@ -1,17 +0,0 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Homepage Tabs', function () {
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
// Does it even launch?
|
||||
let expectedAppTitle = 'Tabs Demo';
|
||||
it(`should display app title: ${expectedAppTitle}`, function () {
|
||||
expect(element(by.css('h4')).getText()).toEqual(expectedAppTitle);
|
||||
});
|
||||
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "Tabs",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
]
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { DiDemoComponent } from './di_demo';
|
||||
import { UiTabsComponent, UiPaneDirective } from './ui_tabs';
|
||||
|
||||
@NgModule({
|
||||
imports: [ BrowserModule ],
|
||||
declarations: [
|
||||
DiDemoComponent,
|
||||
UiTabsComponent,
|
||||
UiPaneDirective
|
||||
],
|
||||
bootstrap: [ DiDemoComponent ]
|
||||
})
|
||||
export class AppModule { }
|
@ -1,45 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
class Detail {
|
||||
title: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'di-demo',
|
||||
template: `
|
||||
<h4>Tabs Demo</h4>
|
||||
<ui-tabs>
|
||||
<template uiPane title='Overview' active="true">
|
||||
You have {{details.length}} details.
|
||||
</template>
|
||||
<template *ngFor="let detail of details" uiPane [title]="detail.title">
|
||||
{{detail.text}} <br><br>
|
||||
<button class="btn" (click)="removeDetail(detail)">Remove</button>
|
||||
</template>
|
||||
<template uiPane title='Summary'>
|
||||
Next last ID is {{id}}.
|
||||
</template>
|
||||
</ui-tabs>
|
||||
<hr>
|
||||
<button class="btn" (click)="addDetail()">Add Detail</button>
|
||||
`
|
||||
})
|
||||
export class DiDemoComponent {
|
||||
details: Detail[] = [];
|
||||
id: number = 0;
|
||||
|
||||
addDetail() {
|
||||
this.id++;
|
||||
this.details.push({
|
||||
title: `Detail ${this.id}`,
|
||||
text: `Some detail text for ${this.id}...`
|
||||
});
|
||||
}
|
||||
|
||||
removeDetail(detail: Detail) {
|
||||
this.details = this.details.filter((d) => d !== detail);
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +0,0 @@
|
||||
// #docregion
|
||||
import { Component, Directive, Input, QueryList,
|
||||
ViewContainerRef, TemplateRef, ContentChildren } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[uiPane]'
|
||||
})
|
||||
export class UiPaneDirective {
|
||||
@Input() title: string;
|
||||
private _active: boolean = false;
|
||||
|
||||
constructor(public viewContainer: ViewContainerRef,
|
||||
public templateRef: TemplateRef<any>) { }
|
||||
|
||||
@Input() set active(active: boolean) {
|
||||
if (active === this._active) { return; }
|
||||
this._active = active;
|
||||
if (active) {
|
||||
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
} else {
|
||||
this.viewContainer.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
get active(): boolean {
|
||||
return this._active;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ui-tabs',
|
||||
template: `
|
||||
<ul class="nav nav-tabs">
|
||||
<li *ngFor="let pane of panes"
|
||||
(click)="select(pane)"
|
||||
role="presentation" [class.active]="pane.active">
|
||||
<a>{{pane.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
styles: ['a { cursor: pointer; cursor: hand; }']
|
||||
})
|
||||
export class UiTabsComponent {
|
||||
@ContentChildren(UiPaneDirective) panes: QueryList<UiPaneDirective>;
|
||||
|
||||
select(pane: UiPaneDirective) {
|
||||
this.panes.toArray().forEach((p: UiPaneDirective) => p.active = p === pane);
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular Tabs</title>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- 1. Load libraries -->
|
||||
<!-- Polyfills -->
|
||||
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="https://unpkg.com/zone.js@0.8.4"></script>
|
||||
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
|
||||
<script src="https://unpkg.com/typescript@2.2.1/lib/typescript.js"></script>
|
||||
|
||||
<!-- 2. Configure SystemJS -->
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<!-- 3. Display the application -->
|
||||
<body>
|
||||
<di-demo class="container" style="display: block;">Loading...</di-demo>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,29 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular Tabs</title>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<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/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- Display the application -->
|
||||
<body>
|
||||
<di-demo class="container" style="display: block">Loading...</di-demo>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,17 +0,0 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Homepage Todo', function () {
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
// Does it even launch?
|
||||
let expectedAppTitle = 'Todo';
|
||||
it(`should display app title: ${expectedAppTitle}`, function () {
|
||||
expect(element(by.css('h2')).getText()).toEqual(expectedAppTitle);
|
||||
});
|
||||
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "Todo",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
]
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { TodoAppComponent } from './todo_app';
|
||||
import { TodoListComponent } from './todo_list';
|
||||
import { TodoFormComponent } from './todo_form';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule
|
||||
],
|
||||
declarations: [
|
||||
TodoAppComponent,
|
||||
TodoListComponent,
|
||||
TodoFormComponent
|
||||
],
|
||||
bootstrap: [ TodoAppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
@ -1,6 +0,0 @@
|
||||
// #docregion
|
||||
// Declare an interaface for type safety
|
||||
export interface Todo {
|
||||
text: string;
|
||||
done: boolean;
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Todo } from './todo';
|
||||
|
||||
@Component({
|
||||
selector: 'todo-app',
|
||||
template: `
|
||||
<h2>Todo</h2>
|
||||
<span>{{remaining}} of {{todos.length}} remaining</span>
|
||||
[ <a (click)="archive()">archive</a> ]
|
||||
|
||||
<todo-list [todos]="todos"></todo-list>
|
||||
<todo-form (newTask)="addTask($event)"></todo-form>`,
|
||||
styles: ['a { cursor: pointer; cursor: hand; }']
|
||||
})
|
||||
export class TodoAppComponent {
|
||||
todos: Todo[] = [
|
||||
{text: 'learn angular', done: true},
|
||||
{text: 'build an angular app', done: false}
|
||||
];
|
||||
|
||||
get remaining() {
|
||||
return this.todos.filter(todo => !todo.done).length;
|
||||
}
|
||||
|
||||
archive(): void {
|
||||
let oldTodos = this.todos;
|
||||
this.todos = [];
|
||||
oldTodos.forEach(todo => {
|
||||
if (!todo.done) { this.todos.push(todo); }
|
||||
});
|
||||
}
|
||||
|
||||
addTask(task: Todo) {
|
||||
this.todos.push(task);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// #docregion
|
||||
import { Component, Output, EventEmitter } from '@angular/core';
|
||||
import { Todo } from './todo';
|
||||
|
||||
@Component({
|
||||
selector: 'todo-form',
|
||||
template: `
|
||||
<form (ngSubmit)="addTodo()">
|
||||
<input type="text" [(ngModel)]="task" size="30"
|
||||
placeholder="add new todo here">
|
||||
<input class="btn-primary" type="submit" value="add">
|
||||
</form>`
|
||||
})
|
||||
export class TodoFormComponent {
|
||||
@Output() newTask = new EventEmitter<Todo>();
|
||||
task: string = '';
|
||||
|
||||
addTodo() {
|
||||
if (this.task) {
|
||||
this.newTask.emit({text: this.task, done: false});
|
||||
}
|
||||
this.task = '';
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
// #docregion
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { Todo } from './todo';
|
||||
|
||||
@Component({
|
||||
selector: 'todo-list',
|
||||
styles: [`
|
||||
.done-true {
|
||||
text-decoration: line-through;
|
||||
color: grey;
|
||||
}`
|
||||
],
|
||||
template: `
|
||||
<ul class="list-unstyled">
|
||||
<li *ngFor="let todo of todos">
|
||||
<input type="checkbox" [(ngModel)]="todo.done">
|
||||
<span class="done-{{todo.done}}">{{todo.text}}</span>
|
||||
</li>
|
||||
</ul>`
|
||||
})
|
||||
export class TodoListComponent {
|
||||
@Input() todos: Todo[];
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular Todos</title>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- 1. Load libraries -->
|
||||
<!-- Polyfills -->
|
||||
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="https://unpkg.com/zone.js@0.8.4"></script>
|
||||
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
|
||||
<script src="https://unpkg.com/typescript@2.2.1/lib/typescript.js"></script>
|
||||
|
||||
<!-- 2. Configure SystemJS -->
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<!-- Display the application -->
|
||||
<body>
|
||||
<todo-app class="container" style="display: block">Loading...</todo-app>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,29 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular Todos</title>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<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/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- Display the application -->
|
||||
<body>
|
||||
<todo-app class="container" style="display: block">Loading...</todo-app>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,5 +0,0 @@
|
||||
// #docregion
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
@ -16,8 +16,6 @@ export default {
|
||||
|
||||
// should intercept ... but doesn't in some rollup versions
|
||||
if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; }
|
||||
// intercepts in some rollup versions
|
||||
if ( warning.indexOf("The 'this' keyword is equivalent to 'undefined'") > -1 ) { return; }
|
||||
|
||||
// console.warn everything else
|
||||
console.warn( warning.message );
|
||||
|
8
aio/content/examples/universal/.gitignore
vendored
Normal file
8
aio/content/examples/universal/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
aot/**/*.ts
|
||||
**/*.ngfactory.ts
|
||||
**/*.ngsummary.json
|
||||
**/*.metadata.json
|
||||
**/*.js
|
||||
dist
|
||||
!app/tsconfig.json
|
||||
!/*.js
|
62
aio/content/examples/universal/package.steve.json
Normal file
62
aio/content/examples/universal/package.steve.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "toh-universal",
|
||||
"version": "1.0.0",
|
||||
"description": "Tour-of-Heroes application with ng-universal server-side rendering",
|
||||
"scripts": {
|
||||
"build": "tsc -p src/",
|
||||
"build:watch": "tsc -w",
|
||||
"build:aot": "webpack --config webpack.config.aot.js",
|
||||
"build:uni": "webpack --config webpack.config.uni.js",
|
||||
|
||||
"serve": "lite-server -c=bs-config.json",
|
||||
"serve:aot": "lite-server -c=bs-config.aot.js",
|
||||
"serve:uni": "node src/dist/server.js",
|
||||
"serve:uni2": "lite-server -c bs-config.uni.js",
|
||||
|
||||
"prestart": "npm run build",
|
||||
"start": "concurrently \"npm run build:watch\" \"npm run serve\"",
|
||||
|
||||
"lint": "tslint ./src/**/*.ts -t verbose",
|
||||
"ngc": "ngc",
|
||||
"clean": "rimraf src/dist && rimraf src/app/*.js* && rimraf src/uni/*.js* && rimraf src/main.js*"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/common": "angular/common-builds",
|
||||
"@angular/compiler": "angular/compiler-builds",
|
||||
"@angular/compiler-cli": "angular/compiler-cli-builds",
|
||||
"@angular/core": "angular/core-builds",
|
||||
"@angular/forms": "angular/forms-builds",
|
||||
"@angular/http": "angular/http-builds",
|
||||
"@angular/platform-browser": "angular/platform-browser-builds",
|
||||
"@angular/platform-browser-dynamic": "angular/platform-browser-dynamic-builds",
|
||||
"@angular/platform-server": "angular/platform-server-builds",
|
||||
"@angular/router": "angular/router-builds",
|
||||
|
||||
"angular-in-memory-web-api": "^0.3.1",
|
||||
"systemjs": "0.19.40",
|
||||
"core-js": "^2.4.1",
|
||||
"rxjs": "5.1.1",
|
||||
"zone.js": "^0.7.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^3.2.0",
|
||||
"lite-server": "^2.2.2",
|
||||
"typescript": "~2.1.6",
|
||||
|
||||
"canonical-path": "0.0.2",
|
||||
"tslint": "^3.15.1",
|
||||
"lodash": "^4.16.4",
|
||||
"rimraf": "^2.5.4",
|
||||
|
||||
"@types/node": "^6.0.46",
|
||||
|
||||
"@ngtools/webpack": "^1.2.11",
|
||||
"@types/express": "^4.0.35",
|
||||
"raw-loader": "^0.5.1",
|
||||
"webpack": "^2.2.1"
|
||||
},
|
||||
"repository": {}
|
||||
}
|
19
aio/content/examples/universal/src/app/app-routing.module.ts
Normal file
19
aio/content/examples/universal/src/app/app-routing.module.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { HeroesComponent } from './heroes.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
|
||||
{ path: 'dashboard', component: DashboardComponent },
|
||||
{ path: 'detail/:id', component: HeroDetailComponent },
|
||||
{ path: 'heroes', component: HeroesComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [ RouterModule.forRoot(routes) ],
|
||||
exports: [ RouterModule ]
|
||||
})
|
||||
export class AppRoutingModule {}
|
29
aio/content/examples/universal/src/app/app.component.css
Normal file
29
aio/content/examples/universal/src/app/app.component.css
Normal file
@ -0,0 +1,29 @@
|
||||
/* #docregion */
|
||||
h1 {
|
||||
font-size: 1.2em;
|
||||
color: #999;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: 2em;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
nav a {
|
||||
padding: 5px 10px;
|
||||
text-decoration: none;
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
background-color: #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
nav a:visited, a:link {
|
||||
color: #607D8B;
|
||||
}
|
||||
nav a:hover {
|
||||
color: #039be5;
|
||||
background-color: #CFD8DC;
|
||||
}
|
||||
nav a.active {
|
||||
color: #039be5;
|
||||
}
|
19
aio/content/examples/universal/src/app/app.component.ts
Normal file
19
aio/content/examples/universal/src/app/app.component.ts
Normal file
@ -0,0 +1,19 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
<h1>{{title}}</h1>
|
||||
<nav>
|
||||
<a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
|
||||
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Tour of Heroes';
|
||||
}
|
54
aio/content/examples/universal/src/app/app.module.ts
Normal file
54
aio/content/examples/universal/src/app/app.module.ts
Normal file
@ -0,0 +1,54 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
// #docregion v1, v2
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
// #enddocregion v1
|
||||
// Imports for loading & configuring the in-memory web api
|
||||
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
|
||||
import { InMemoryDataService } from './in-memory-data.service';
|
||||
|
||||
// #docregion v1
|
||||
import { AppComponent } from './app.component';
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { HeroesComponent } from './heroes.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
import { HeroService } from './hero.service';
|
||||
// #enddocregion v1, v2
|
||||
import { HeroSearchComponent } from './hero-search.component';
|
||||
// #docregion v1, v2
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({
|
||||
appId: 'toh-universal'
|
||||
}),
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
// #enddocregion v1
|
||||
// #docregion in-mem-web-api
|
||||
InMemoryWebApiModule.forRoot(InMemoryDataService),
|
||||
// #enddocregion in-mem-web-api
|
||||
// #docregion v1
|
||||
AppRoutingModule
|
||||
],
|
||||
// #docregion search
|
||||
declarations: [
|
||||
AppComponent,
|
||||
DashboardComponent,
|
||||
HeroDetailComponent,
|
||||
HeroesComponent,
|
||||
// #enddocregion v1, v2
|
||||
HeroSearchComponent
|
||||
// #docregion v1, v2
|
||||
],
|
||||
// #enddocregion search
|
||||
providers: [ HeroService ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
@ -0,0 +1,62 @@
|
||||
/* #docregion */
|
||||
[class*='col-'] {
|
||||
float: left;
|
||||
padding-right: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
[class*='col-']:last-of-type {
|
||||
padding-right: 0;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
*, *:after, *:before {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
h3 {
|
||||
text-align: center; margin-bottom: 0;
|
||||
}
|
||||
h4 {
|
||||
position: relative;
|
||||
}
|
||||
.grid {
|
||||
margin: 0;
|
||||
}
|
||||
.col-1-4 {
|
||||
width: 25%;
|
||||
}
|
||||
.module {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #eee;
|
||||
max-height: 120px;
|
||||
min-width: 120px;
|
||||
background-color: #607D8B;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.module:hover {
|
||||
background-color: #EEE;
|
||||
cursor: pointer;
|
||||
color: #607d8b;
|
||||
}
|
||||
.grid-pad {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.grid-pad > [class*='col-']:last-of-type {
|
||||
padding-right: 20px;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.module {
|
||||
font-size: 10px;
|
||||
max-height: 75px; }
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.grid {
|
||||
margin: 0;
|
||||
}
|
||||
.module {
|
||||
min-width: 60px;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<!-- #docregion -->
|
||||
<h3>Top Heroes</h3>
|
||||
<div class="grid grid-pad">
|
||||
<a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4">
|
||||
<div class="module hero">
|
||||
<h4>{{hero.name}}</h4>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<hero-search></hero-search>
|
@ -0,0 +1,22 @@
|
||||
// #docregion , search
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'my-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrls: [ './dashboard.component.css' ]
|
||||
})
|
||||
// #enddocregion search
|
||||
export class DashboardComponent implements OnInit {
|
||||
heroes: Hero[] = [];
|
||||
|
||||
constructor(private heroService: HeroService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.heroService.getHeroes()
|
||||
.then(heroes => this.heroes = heroes.slice(1, 5));
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/* #docregion */
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 3em;
|
||||
margin: .5em 0;
|
||||
color: #607D8B;
|
||||
font-weight: bold;
|
||||
}
|
||||
input {
|
||||
height: 2em;
|
||||
font-size: 1em;
|
||||
padding-left: .4em;
|
||||
}
|
||||
button {
|
||||
margin-top: 20px;
|
||||
font-family: Arial;
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer; cursor: hand;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #cfd8dc;
|
||||
}
|
||||
button:disabled {
|
||||
background-color: #eee;
|
||||
color: #ccc;
|
||||
cursor: auto;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<!-- #docregion -->
|
||||
<div *ngIf="hero">
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
<div>
|
||||
<label>id: </label>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ngModel)]="hero.name" placeholder="name" />
|
||||
</div>
|
||||
<button (click)="goBack()">Back</button>
|
||||
<!-- #docregion save -->
|
||||
<button (click)="save()">Save</button>
|
||||
<!-- #enddocregion save -->
|
||||
</div>
|
@ -0,0 +1,40 @@
|
||||
// #docregion
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
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({
|
||||
selector: 'my-hero-detail',
|
||||
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 location: Location
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.params
|
||||
.switchMap((params: Params) => this.heroService.getHero(+params['id']))
|
||||
.subscribe(hero => this.hero = hero);
|
||||
}
|
||||
|
||||
// #docregion save
|
||||
save(): void {
|
||||
this.heroService.update(this.hero)
|
||||
.then(() => this.goBack());
|
||||
}
|
||||
// #enddocregion save
|
||||
|
||||
goBack(): void {
|
||||
this.location.back();
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/* #docregion */
|
||||
.search-result{
|
||||
border-bottom: 1px solid gray;
|
||||
border-left: 1px solid gray;
|
||||
border-right: 1px solid gray;
|
||||
width:195px;
|
||||
height: 16px;
|
||||
padding: 5px;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-result:hover {
|
||||
color: #eee;
|
||||
background-color: #607D8B;
|
||||
}
|
||||
|
||||
#search-box{
|
||||
width: 200px;
|
||||
height: 20px;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<!-- #docregion -->
|
||||
<div id="search-component">
|
||||
<h4>Hero Search</h4>
|
||||
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
|
||||
<div>
|
||||
<div *ngFor="let hero of heroes | async"
|
||||
(click)="gotoDetail(hero)" class="search-result" >
|
||||
{{hero.name}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,69 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
// #docregion rxjs-imports
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
// Observable class extensions
|
||||
import 'rxjs/add/observable/of';
|
||||
|
||||
// Observable operators
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
// #enddocregion rxjs-imports
|
||||
|
||||
import { HeroSearchService } from './hero-search.service';
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-search',
|
||||
templateUrl: './hero-search.component.html',
|
||||
styleUrls: [ './hero-search.component.css' ],
|
||||
providers: [HeroSearchService]
|
||||
})
|
||||
export class HeroSearchComponent implements OnInit {
|
||||
// #docregion search
|
||||
heroes: Observable<Hero[]>;
|
||||
// #enddocregion search
|
||||
// #docregion searchTerms
|
||||
private searchTerms = new Subject<string>();
|
||||
// #enddocregion searchTerms
|
||||
|
||||
constructor(
|
||||
private heroSearchService: HeroSearchService,
|
||||
private router: Router) {}
|
||||
// #docregion searchTerms
|
||||
|
||||
// Push a search term into the observable stream.
|
||||
search(term: string): void {
|
||||
this.searchTerms.next(term);
|
||||
}
|
||||
// #enddocregion searchTerms
|
||||
// #docregion search
|
||||
|
||||
ngOnInit(): void {
|
||||
this.heroes = this.searchTerms
|
||||
.debounceTime(300) // wait 300ms after each keystroke before considering the term
|
||||
.distinctUntilChanged() // ignore if next search term is same as previous
|
||||
.switchMap(term => term // switch to new observable each time the term changes
|
||||
// return the http search observable
|
||||
? this.heroSearchService.search(term)
|
||||
// or the observable of empty heroes if there was no search term
|
||||
: Observable.of<Hero[]>([]))
|
||||
.catch(error => {
|
||||
// TODO: add real error handling
|
||||
console.log(error);
|
||||
return Observable.of<Hero[]>([]);
|
||||
});
|
||||
}
|
||||
// #enddocregion search
|
||||
|
||||
gotoDetail(hero: Hero): void {
|
||||
let link = ['/detail', hero.id];
|
||||
this.router.navigate(link);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Injectable()
|
||||
export class HeroSearchService {
|
||||
|
||||
constructor(private http: Http) {}
|
||||
|
||||
search(term: string): Observable<Hero[]> {
|
||||
return this.http
|
||||
.get(`app/heroes/?name=${term}`)
|
||||
.map(response => response.json().data as Hero[]);
|
||||
}
|
||||
}
|
87
aio/content/examples/universal/src/app/hero.service.ts
Normal file
87
aio/content/examples/universal/src/app/hero.service.ts
Normal file
@ -0,0 +1,87 @@
|
||||
// #docplaster
|
||||
// #docregion , imports
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Headers, Http } from '@angular/http';
|
||||
|
||||
// #docregion rxjs
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
// #enddocregion rxjs
|
||||
|
||||
import { Hero } from './hero';
|
||||
// #enddocregion imports
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
|
||||
// #docregion update
|
||||
private headers = new Headers({'Content-Type': 'application/json'});
|
||||
// #enddocregion update
|
||||
// #docregion getHeroes
|
||||
private heroesUrl = 'api/heroes'; // URL to web api
|
||||
|
||||
constructor(private http: Http) { }
|
||||
|
||||
getHeroes(): Promise<Hero[]> {
|
||||
return this.http.get(this.heroesUrl)
|
||||
// #docregion to-promise
|
||||
.toPromise()
|
||||
// #enddocregion to-promise
|
||||
// #docregion to-data
|
||||
.then(response => response.json().data as Hero[])
|
||||
// #enddocregion to-data
|
||||
// #docregion catch
|
||||
.catch(this.handleError);
|
||||
// #enddocregion catch
|
||||
}
|
||||
|
||||
// #enddocregion getHeroes
|
||||
|
||||
// #docregion getHero
|
||||
getHero(id: number): Promise<Hero> {
|
||||
const url = `${this.heroesUrl}/${id}`;
|
||||
return this.http.get(url)
|
||||
.toPromise()
|
||||
.then(response => response.json().data as Hero)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
// #enddocregion getHero
|
||||
|
||||
// #docregion delete
|
||||
delete(id: number): Promise<void> {
|
||||
const url = `${this.heroesUrl}/${id}`;
|
||||
return this.http.delete(url, {headers: this.headers})
|
||||
.toPromise()
|
||||
.then(() => null)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
// #enddocregion delete
|
||||
|
||||
// #docregion create
|
||||
create(name: string): Promise<Hero> {
|
||||
return this.http
|
||||
.post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
|
||||
.toPromise()
|
||||
.then(res => res.json().data)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
// #enddocregion create
|
||||
// #docregion update
|
||||
|
||||
update(hero: Hero): Promise<Hero> {
|
||||
const url = `${this.heroesUrl}/${hero.id}`;
|
||||
return this.http
|
||||
.put(url, JSON.stringify(hero), {headers: this.headers})
|
||||
.toPromise()
|
||||
.then(() => hero)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
// #enddocregion update
|
||||
|
||||
// #docregion getHeroes, handleError
|
||||
private handleError(error: any): Promise<any> {
|
||||
console.error('An error occurred', error); // for demo purposes only
|
||||
return Promise.reject(error.message || error);
|
||||
}
|
||||
// #enddocregion getHeroes, handleError
|
||||
}
|
||||
|
4
aio/content/examples/universal/src/app/hero.ts
Normal file
4
aio/content/examples/universal/src/app/hero.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export class Hero {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
68
aio/content/examples/universal/src/app/heroes.component.css
Normal file
68
aio/content/examples/universal/src/app/heroes.component.css
Normal file
@ -0,0 +1,68 @@
|
||||
/* #docregion */
|
||||
.selected {
|
||||
background-color: #CFD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 15em;
|
||||
}
|
||||
.heroes li {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
left: 0;
|
||||
background-color: #EEE;
|
||||
margin: .5em;
|
||||
padding: .3em 0;
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.heroes li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.8em 0.7em 0 0.7em;
|
||||
background-color: #607D8B;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
button {
|
||||
font-family: Arial;
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #cfd8dc;
|
||||
}
|
||||
/* #docregion additions */
|
||||
button.delete {
|
||||
float:right;
|
||||
margin-top: 2px;
|
||||
margin-right: .8em;
|
||||
background-color: gray !important;
|
||||
color:white;
|
||||
}
|
29
aio/content/examples/universal/src/app/heroes.component.html
Normal file
29
aio/content/examples/universal/src/app/heroes.component.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!-- #docregion -->
|
||||
<h2>My Heroes</h2>
|
||||
<!-- #docregion add -->
|
||||
<div>
|
||||
<label>Hero name:</label> <input #heroName />
|
||||
<button (click)="add(heroName.value); heroName.value=''">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<!-- #enddocregion add -->
|
||||
<ul class="heroes">
|
||||
<!-- #docregion li-element -->
|
||||
<li *ngFor="let hero of heroes" (click)="onSelect(hero)"
|
||||
[class.selected]="hero === selectedHero">
|
||||
<span class="badge">{{hero.id}}</span>
|
||||
<span>{{hero.name}}</span>
|
||||
<!-- #docregion delete -->
|
||||
<button class="delete"
|
||||
(click)="delete(hero); $event.stopPropagation()">x</button>
|
||||
<!-- #enddocregion delete -->
|
||||
</li>
|
||||
<!-- #enddocregion li-element -->
|
||||
</ul>
|
||||
<div *ngIf="selectedHero">
|
||||
<h2>
|
||||
{{selectedHero.name | uppercase}} is my hero
|
||||
</h2>
|
||||
<button (click)="gotoDetail()">View Details</button>
|
||||
</div>
|
61
aio/content/examples/universal/src/app/heroes.component.ts
Normal file
61
aio/content/examples/universal/src/app/heroes.component.ts
Normal file
@ -0,0 +1,61 @@
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'my-heroes',
|
||||
templateUrl: './heroes.component.html',
|
||||
styleUrls: [ './heroes.component.css' ]
|
||||
})
|
||||
export class HeroesComponent implements OnInit {
|
||||
heroes: Hero[];
|
||||
selectedHero: Hero;
|
||||
|
||||
constructor(
|
||||
private heroService: HeroService,
|
||||
private router: Router) { }
|
||||
|
||||
getHeroes(): void {
|
||||
this.heroService
|
||||
.getHeroes()
|
||||
.then(heroes => this.heroes = heroes);
|
||||
}
|
||||
|
||||
// #docregion add
|
||||
add(name: string): void {
|
||||
name = name.trim();
|
||||
if (!name) { return; }
|
||||
this.heroService.create(name)
|
||||
.then(hero => {
|
||||
this.heroes.push(hero);
|
||||
this.selectedHero = null;
|
||||
});
|
||||
}
|
||||
// #enddocregion add
|
||||
|
||||
// #docregion delete
|
||||
delete(hero: Hero): void {
|
||||
this.heroService
|
||||
.delete(hero.id)
|
||||
.then(() => {
|
||||
this.heroes = this.heroes.filter(h => h !== hero);
|
||||
if (this.selectedHero === hero) { this.selectedHero = null; }
|
||||
});
|
||||
}
|
||||
// #enddocregion delete
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getHeroes();
|
||||
}
|
||||
|
||||
onSelect(hero: Hero): void {
|
||||
this.selectedHero = hero;
|
||||
}
|
||||
|
||||
gotoDetail(): void {
|
||||
this.router.navigate(['/detail', this.selectedHero.id]);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// #docregion , init
|
||||
import { InMemoryDbService } from 'angular-in-memory-web-api';
|
||||
export class InMemoryDataService implements InMemoryDbService {
|
||||
createDb() {
|
||||
let heroes = [
|
||||
{id: 11, name: 'Mr. Nice'},
|
||||
{id: 12, name: 'Narco'},
|
||||
{id: 13, name: 'Bombasto'},
|
||||
{id: 14, name: 'Celeritas'},
|
||||
{id: 15, name: 'Magneta'},
|
||||
{id: 16, name: 'RubberMan'},
|
||||
{id: 17, name: 'Dynama'},
|
||||
{id: 18, name: 'Dr IQ'},
|
||||
{id: 19, name: 'Magma'},
|
||||
{id: 20, name: 'Tornado'}
|
||||
];
|
||||
return {heroes};
|
||||
}
|
||||
}
|
18
aio/content/examples/universal/src/index-aot.html
Normal file
18
aio/content/examples/universal/src/index-aot.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>Angular Tour of Heroes</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
<script src="node_modules/zone.js/dist/zone.min.js"></script>
|
||||
<script src="dist/build.js"></script>
|
||||
</html>
|
@ -2,10 +2,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular Hello World</title>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<title>Angular Universal Tour of Heroes</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
@ -20,9 +20,7 @@
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- Display the application -->
|
||||
<body>
|
||||
<hello-world class="container" style="display: block">Loading...</hello-world>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
5
aio/content/examples/universal/src/main-aot.ts
Normal file
5
aio/content/examples/universal/src/main-aot.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// #docregion
|
||||
import { platformBrowser } from '@angular/platform-browser';
|
||||
import { AppModuleNgFactory } from '../aot/src/app/app.module.ngfactory';
|
||||
|
||||
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
|
21
aio/content/examples/universal/src/uni/app.server.ts
Normal file
21
aio/content/examples/universal/src/uni/app.server.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { ServerModule } from '@angular/platform-server';
|
||||
import { AppComponent } from '../app/app.component';
|
||||
import { AppModule } from '../app/app.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ServerModule,
|
||||
AppModule
|
||||
],
|
||||
bootstrap: [
|
||||
AppComponent
|
||||
],
|
||||
providers: [
|
||||
{provide: APP_BASE_HREF, useValue: '/'}
|
||||
// { provide: NgModuleFactoryLoader, useClass: ServerRouterLoader }
|
||||
]
|
||||
})
|
||||
export class AppServerModule {
|
||||
}
|
40
aio/content/examples/universal/src/uni/server-aot.ts
Normal file
40
aio/content/examples/universal/src/uni/server-aot.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import 'zone.js/dist/zone-node';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
// import { AppServerModule } from './app.server';
|
||||
import { AppServerModuleNgFactory } from '../../aot/src/uni/app.server.ngfactory';
|
||||
import * as express from 'express';
|
||||
import { ngUniversalEngine } from './universal-engine';
|
||||
|
||||
enableProdMode();
|
||||
|
||||
const server = express();
|
||||
|
||||
// set our angular engine as the handler for html files, so it will be used to render them.
|
||||
server.engine('html', ngUniversalEngine({
|
||||
bootstrap: [AppServerModuleNgFactory]
|
||||
}));
|
||||
|
||||
// set default view directory
|
||||
server.set('views', 'src');
|
||||
|
||||
// handle requests for routes in the app. ngExpressEngine does the rendering.
|
||||
server.get(['/', '/dashboard', '/heroes', '/detail/:id'], (req, res) => {
|
||||
res.render('index-aot.html', {req});
|
||||
});
|
||||
|
||||
// handle requests for static files
|
||||
server.get(['/*.js', '/*.css'], (req, res, next) => {
|
||||
let fileName: string = req.originalUrl;
|
||||
console.log(fileName);
|
||||
let root = fileName.startsWith('/node_modules/') ? '.' : 'src';
|
||||
res.sendFile(fileName, { root: root }, function (err) {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// start the server
|
||||
server.listen(3200, () => {
|
||||
console.log('listening on port 3200...');
|
||||
});
|
38
aio/content/examples/universal/src/uni/universal-engine.ts
Normal file
38
aio/content/examples/universal/src/uni/universal-engine.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Express/Connect middleware for rendering pages using Angular Universal
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
import { renderModuleFactory } from '@angular/platform-server';
|
||||
|
||||
const templateCache = {}; // cache for page templates
|
||||
const outputCache = {}; // cache for rendered pages
|
||||
|
||||
export function ngUniversalEngine(setupOptions: any) {
|
||||
|
||||
return function (filePath: string, options: { req: Request }, callback: (err: Error, html: string) => void) {
|
||||
let url: string = options.req.url;
|
||||
let html: string = outputCache[url];
|
||||
if (html) {
|
||||
// return already-built page for this url
|
||||
console.log('from cache: ' + url);
|
||||
callback(null, html);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('building: ' + url);
|
||||
if (!templateCache[filePath]) {
|
||||
let file = fs.readFileSync(filePath);
|
||||
templateCache[filePath] = file.toString();
|
||||
}
|
||||
|
||||
// render the page via angular platform-server
|
||||
let appModuleFactory = setupOptions.bootstrap[0];
|
||||
renderModuleFactory(appModuleFactory, {
|
||||
document: templateCache[filePath],
|
||||
url: url
|
||||
}).then(str => {
|
||||
outputCache[url] = str;
|
||||
callback(null, str);
|
||||
});
|
||||
};
|
||||
}
|
27
aio/content/examples/universal/tsconfig-aot.json
Normal file
27
aio/content/examples/universal/tsconfig-aot.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["es2015", "dom"],
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"typeRoots": [
|
||||
"node_modules/@types/"
|
||||
]
|
||||
},
|
||||
|
||||
"files": [
|
||||
"src/app/app.module.ts",
|
||||
"src/main-aot.ts"
|
||||
],
|
||||
|
||||
"angularCompilerOptions": {
|
||||
"genDir": "aot",
|
||||
"entryModule": "./src/app/app.module#AppModule",
|
||||
"skipMetadataEmit" : true
|
||||
}
|
||||
}
|
27
aio/content/examples/universal/tsconfig-uni.json
Normal file
27
aio/content/examples/universal/tsconfig-uni.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["es2015", "dom"],
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"typeRoots": [
|
||||
"../../node_modules/@types/"
|
||||
]
|
||||
},
|
||||
|
||||
"files": [
|
||||
"src/uni/app.server.ts",
|
||||
"src/uni/server-aot.ts"
|
||||
],
|
||||
|
||||
"angularCompilerOptions": {
|
||||
"genDir": "aot",
|
||||
"entryModule": "./src/app/app.module#AppModule",
|
||||
"skipMetadataEmit" : true
|
||||
}
|
||||
}
|
20
aio/content/examples/universal/tsconfig.json
Normal file
20
aio/content/examples/universal/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [ "es2015", "dom" ],
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"typeRoots": [
|
||||
"../../../node_modules/@types/"
|
||||
]
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"exclude": [
|
||||
"node_modules/*"
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,8 @@ Animations
|
||||
A guide to Angular's animation system.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
Motion is an important aspect in the design of modern web applications. Good
|
||||
user interfaces transition smoothly between states with engaging animations
|
||||
that call attention where it's needed. Well-designed animations can make a UI not only
|
||||
@ -17,6 +19,8 @@ animation logic with the rest of your application code, for ease of control.
|
||||
|
||||
~~~ {.alert.is-helpful}
|
||||
|
||||
|
||||
|
||||
Angular animations are built on top of the standard [Web Animations API](https://w3c.github.io/web-animations/)
|
||||
and run natively on [browsers that support it](http://caniuse.com/#feat=web-animation).
|
||||
|
||||
@ -28,6 +32,8 @@ add it to your page.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
# Contents
|
||||
|
||||
* [Example: Transitioning between two states](guide/animations#example-transitioning-between-states).
|
||||
@ -44,6 +50,8 @@ add it to your page.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The examples in this page are available as a <live-example></live-example>.
|
||||
|
||||
|
||||
@ -53,12 +61,16 @@ The examples in this page are available as a <live-example></live-example>.
|
||||
|
||||
{@a example-transitioning-between-states}
|
||||
|
||||
|
||||
|
||||
## Quickstart example: Transitioning between two states
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/animations/animation_basic_click.gif" alt="A simple transition animation" align="right" style="width:220px;margin-left:20px"> </img>
|
||||
<img src="assets/images/devguide/animations/animation_basic_click.gif" alt="A simple transition animation" align="right" style="width:220px;margin-left:20px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
You can build a simple animation that transitions an element between two states
|
||||
driven by a model attribute.
|
||||
|
||||
@ -66,22 +78,24 @@ Animations are defined inside `@Component` metadata. Before you can add animatio
|
||||
to import a few animation-specific imports and functions:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/app.module.ts" region="animations-module" linenums="false">
|
||||
<code-example path="animations/src/app/app.module.ts" region="animations-module" title="app.module.ts (@NgModule imports excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="imports" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="imports" title="hero-list-basic.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
With these, you can define an *animation trigger* called `heroState` in the component
|
||||
metadata. It uses animations to transition between two states: `active` and `inactive`. When a
|
||||
hero is active, the element appears in a slightly larger size and lighter color.
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="animationdef" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="animationdef" title="hero-list-basic.component.ts (@Component excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -89,20 +103,26 @@ hero is active, the element appears in a slightly larger size and lighter color.
|
||||
|
||||
~~~ {.alert.is-helpful}
|
||||
|
||||
|
||||
|
||||
In this example, you are defining animation styles (color and transform) inline in the
|
||||
animation metadata.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Now, using the `[@triggerName]` syntax, attach the animation that you just defined to
|
||||
one or more elements in the component's template.
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="template" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="template" title="hero-list-basic.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here, the animation trigger applies to every element repeated by an `ngFor`. Each of
|
||||
the repeated elements animates independently. The value of the
|
||||
attribute is bound to the expression `hero.state` and is always either `active` or `inactive`.
|
||||
@ -111,10 +131,12 @@ With this setup, an animated transition appears whenever a hero object changes s
|
||||
Here's the full component implementation:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts">
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" title="hero-list-basic.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
## States and transitions
|
||||
|
||||
Angular animations are defined as logical **states** and **transitions**
|
||||
@ -129,10 +151,12 @@ component's template.
|
||||
You can define *styles* for each animation state:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="states" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="states" title="src/app/hero-list-basic.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
These `state` definitions specify the *end styles* of each state.
|
||||
They are applied to the element once it has transitioned to that state, and stay
|
||||
*as long as it remains in that state*. In effect, you're defining what styles the element has in different states.
|
||||
@ -141,32 +165,38 @@ After you define states, you can define *transitions* between the states. Each t
|
||||
controls the timing of switching between one set of styles and the next:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="transitions" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="transitions" title="src/app/hero-list-basic.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/animations/ng_animate_transitions_inactive_active.png" alt="In Angular animations you define states and transitions between states" width="400"> </img>
|
||||
<img src="assets/images/devguide/animations/ng_animate_transitions_inactive_active.png" alt="In Angular animations you define states and transitions between states" width="400"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
If several transitions have the same timing configuration, you can combine
|
||||
them into the same `transition` definition:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-combined-transitions.component.ts" region="transitions" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-combined-transitions.component.ts" region="transitions" title="src/app/hero-list-combined-transitions.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
When both directions of a transition have the same timing, as in the previous
|
||||
example, you can use the shorthand syntax `<=>`:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-twoway.component.ts" region="transitions" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-twoway.component.ts" region="transitions" title="src/app/hero-list-twoway.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You can also apply a style during an animation but not keep it around
|
||||
after the animation finishes. You can define such styles inline, in the `transition`. In this example,
|
||||
the element receives one set of styles immediately and is then animated to the next.
|
||||
@ -174,10 +204,12 @@ When the transition finishes, none of these styles are kept because they're not
|
||||
defined in a `state`.
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-inline-styles.component.ts" region="transitions" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-inline-styles.component.ts" region="transitions" title="src/app/hero-list-inline-styles.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### The wildcard state `*`
|
||||
|
||||
The `*` ("wildcard") state matches *any* animation state. This is useful for defining styles and
|
||||
@ -188,9 +220,11 @@ transitions that apply regardless of which state the animation is in. For exampl
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/animations/ng_animate_transitions_inactive_active_wildcards.png" alt="The wildcard state can be used to match many different transitions at once" width="400"> </img>
|
||||
<img src="assets/images/devguide/animations/ng_animate_transitions_inactive_active_wildcards.png" alt="The wildcard state can be used to match many different transitions at once" width="400"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
### The `void` state
|
||||
|
||||
The special state called `void` can apply to any animation. It applies
|
||||
@ -203,17 +237,21 @@ regardless of what state it was in before it left.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/animations/ng_animate_transitions_void_in.png" alt="The void state can be used for enter and leave transitions" width="400"> </img>
|
||||
<img src="assets/images/devguide/animations/ng_animate_transitions_void_in.png" alt="The void state can be used for enter and leave transitions" width="400"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The wildcard state `*` also matches `void`.
|
||||
|
||||
## Example: Entering and leaving
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/animations/animation_enter_leave.gif" alt="Enter and leave animations" align="right" style="width:250px;"> </img>
|
||||
<img src="assets/images/devguide/animations/animation_enter_leave.gif" alt="Enter and leave animations" align="right" style="width:250px;"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Using the `void` and `*` states you can define transitions that animate the
|
||||
entering and leaving of elements:
|
||||
|
||||
@ -223,10 +261,12 @@ entering and leaving of elements:
|
||||
For example, in the `animations` array below there are two transitions that use
|
||||
the `void => *` and `* => void` syntax to animate the element in and out of the view.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-enter-leave.component.ts" region="animationdef" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-enter-leave.component.ts" region="animationdef" title="hero-list-enter-leave.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Note that in this case the styles are applied to the void state directly in the
|
||||
transition definitions, and not in a separate `state(void)` definition. Thus, the transforms
|
||||
are different on enter and leave: the element enters from the left
|
||||
@ -235,11 +275,13 @@ and leaves to the right.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
These two common animations have their own aliases:
|
||||
|
||||
<code-example language="typescript">
|
||||
transition(':enter', [ ... ]); // void => *
|
||||
transition(':leave', [ ... ]); // * => void
|
||||
transition(':leave', [ ... ]); // * => void
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -247,12 +289,16 @@ These two common animations have their own aliases:
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Example: Entering and leaving from different states
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/animations/animation_enter_leave_states.gif" alt="Enter and leave animations combined with state animations" align="right" style="width:200px"> </img>
|
||||
<img src="assets/images/devguide/animations/animation_enter_leave_states.gif" alt="Enter and leave animations combined with state animations" align="right" style="width:200px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
You can also combine this animation with the earlier state transition animation by
|
||||
using the hero state as the animation state. This lets you configure
|
||||
different transitions for entering and leaving based on what the state of the hero
|
||||
@ -267,15 +313,17 @@ This gives you fine-grained control over each transition:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/animations/ng_animate_transitions_inactive_active_void.png" alt="This example transitions between active, inactive, and void states" width="400"> </img>
|
||||
<img src="assets/images/devguide/animations/ng_animate_transitions_inactive_active_void.png" alt="This example transitions between active, inactive, and void states" width="400"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-enter-leave-states.component.ts" region="animationdef" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-enter-leave-states.component.ts" region="animationdef" title="hero-list-enter-leave.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
## Animatable properties and units
|
||||
|
||||
Since Angular's animation support builds on top of Web Animations, you can animate any property
|
||||
@ -298,9 +346,11 @@ If you don't provide a unit when specifying dimension, Angular assumes the defau
|
||||
## Automatic property calculation
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/animations/animation_auto.gif" alt="Animation with automated height calculation" align="right" style="width:220px;margin-left:20px"> </img>
|
||||
<img src="assets/images/devguide/animations/animation_auto.gif" alt="Animation with automated height calculation" align="right" style="width:220px;margin-left:20px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Sometimes you don't know the value of a dimensional style property until runtime.
|
||||
For example, elements often have widths and heights that
|
||||
depend on their content and the screen size. These properties are often tricky
|
||||
@ -313,10 +363,12 @@ In this example, the leave animation takes whatever height the element has befor
|
||||
leaves and animates from that height to zero:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-auto.component.ts" region="animationdef" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-auto.component.ts" region="animationdef" title="src/app/hero-list-auto.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
## Animation timing
|
||||
|
||||
There are three timing properties you can tune for every animated transition:
|
||||
@ -353,9 +405,11 @@ and the delay (or as the *second* value when there is no delay):
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/animations/animation_timings.gif" alt="Animations with specific timings" align="right" style="width:220px;margin-left:20px"> </img>
|
||||
<img src="assets/images/devguide/animations/animation_timings.gif" alt="Animations with specific timings" align="right" style="width:220px;margin-left:20px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
Here are a couple of custom timings in action. Both enter and leave last for
|
||||
@ -364,16 +418,20 @@ slight delay of 10 milliseconds as specified in `'0.2s 10 ease-out'`:
|
||||
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-timings.component.ts" region="animationdef" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-timings.component.ts" region="animationdef" title="hero-list-timings.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
## Multi-step animations with keyframes
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/animations/animation_multistep.gif" alt="Animations with some bounce implemented with keyframes" align="right" style="width:220px;margin-left:20px"> </img>
|
||||
<img src="assets/images/devguide/animations/animation_multistep.gif" alt="Animations with some bounce implemented with keyframes" align="right" style="width:220px;margin-left:20px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Animation *keyframes* go beyond a simple transition to a more intricate animation
|
||||
that goes through one or more intermediate styles when transitioning between two sets of styles.
|
||||
|
||||
@ -385,10 +443,12 @@ This example adds some "bounce" to the enter and leave animations with
|
||||
keyframes:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="animationdef" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="animationdef" title="hero-list-multistep.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Note that the offsets are *not* defined in terms of absolute time. They are relative
|
||||
measures from zero to one. The final timeline of the animation is based on the combination
|
||||
of keyframe offsets, duration, delay, and easing.
|
||||
@ -396,12 +456,16 @@ of keyframe offsets, duration, delay, and easing.
|
||||
Defining offsets for keyframes is optional. If you omit them, offsets with even
|
||||
spacing are automatically assigned. For example, three keyframes without predefined
|
||||
offsets receive offsets `0`, `0.5`, and `1`.
|
||||
|
||||
|
||||
## Parallel animation groups
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/animations/animation_groups.gif" alt="Parallel animations with different timings, implemented with groups" align="right" style="width:220px;margin-left:20px"> </img>
|
||||
<img src="assets/images/devguide/animations/animation_groups.gif" alt="Parallel animations with different timings, implemented with groups" align="right" style="width:220px;margin-left:20px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
You've seen how to animate multiple style properties at the same time:
|
||||
just put all of them into the same `style()` definition.
|
||||
|
||||
@ -414,11 +478,15 @@ enter and leave allows for two different timing configurations. Both
|
||||
are applied to the same element in parallel, but run independently of each other:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" title="hero-list-groups.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
One group animates the element transform and width; the other group animates the opacity.
|
||||
|
||||
|
||||
## Animation callbacks
|
||||
|
||||
A callback is fired when an animation is started and also when it is done.
|
||||
@ -427,10 +495,12 @@ In the keyframes example, you have a `trigger` called `@flyInOut`. You can hook
|
||||
those callbacks like this:
|
||||
|
||||
|
||||
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="template" linenums="false">
|
||||
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="template" title="hero-list-multistep.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The callbacks receive an `AnimationEvent` that contains contains useful properties such as
|
||||
`fromState`, `toState` and `totalTime`.
|
||||
|
||||
|
@ -5,37 +5,48 @@ Ahead-of-Time Compilation
|
||||
Learn how to use ahead-of-time compilation.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
This cookbook describes how to radically improve performance by compiling _ahead-of-time_ (AOT)
|
||||
during a build process.
|
||||
|
||||
|
||||
{@a toc}
|
||||
|
||||
|
||||
# Contents
|
||||
|
||||
- [Overview](guide/overview)
|
||||
- [Ahead-of-time (AOT) vs just-in-time (JIT)](guide/aot-compiler#aot-jit)
|
||||
- [Why do AOT compilation?](guide/aot-compiler#why-aot)
|
||||
- [Compile with AOT](guide/aot-compiler#compile)
|
||||
- [Bootstrap](guide/aot-compiler#bootstrap)
|
||||
- [Tree shaking](guide/aot-compiler#tree-shaking)
|
||||
- [Rollup](guide/aot-compiler#rollup)
|
||||
- [Rollup Plugins](guide/aot-compiler#rollup-plugins)
|
||||
- [Run Rollup](guide/aot-compiler#run-rollup)
|
||||
- [Load the bundle](guide/aot-compiler#load)
|
||||
- [Serve the app](guide/aot-compiler#serve)
|
||||
- [AOT QuickStart source code](guide/aot-compiler#source-code)
|
||||
- [Workflow and convenience script](guide/aot-compiler#workflow)
|
||||
- [Develop JIT along with AOT](guide/aot-compiler#run-jit)
|
||||
- [Tour of Heroes](guide/aot-compiler#toh)
|
||||
- [JIT in development, AOT in production](guide/aot-compiler#jit-dev-aot-prod)
|
||||
- [Tree shaking](guide/aot-compiler#shaking)
|
||||
- [Running the application](guide/aot-compiler#running-app)
|
||||
- [Inspect the Bundle](guide/aot-compiler#inspect-bundle)
|
||||
* [Overview](guide/overview)
|
||||
* [Ahead-of-time (AOT) vs just-in-time (JIT)](guide/aot-compiler#aot-jit)
|
||||
* [Why do AOT compilation?](guide/aot-compiler#why-aot)
|
||||
* [Compile with AOT](guide/aot-compiler#compile)
|
||||
* [Bootstrap](guide/aot-compiler#bootstrap)
|
||||
* [Tree shaking](guide/aot-compiler#tree-shaking)
|
||||
|
||||
*[Rollup](guide/aot-compiler#rollup)
|
||||
*[Rollup Plugins](guide/aot-compiler#rollup-plugins)
|
||||
*[Run Rollup](guide/aot-compiler#run-rollup)
|
||||
|
||||
* [Load the bundle](guide/aot-compiler#load)
|
||||
* [Serve the app](guide/aot-compiler#serve)
|
||||
* [AOT QuickStart source code](guide/aot-compiler#source-code)
|
||||
* [Workflow and convenience script](guide/aot-compiler#workflow)
|
||||
|
||||
*[Develop JIT along with AOT](guide/aot-compiler#run-jit)
|
||||
|
||||
* [Tour of Heroes](guide/aot-compiler#toh)
|
||||
|
||||
*[JIT in development, AOT in production](guide/aot-compiler#jit-dev-aot-prod)
|
||||
*[Tree shaking](guide/aot-compiler#shaking)
|
||||
*[Running the application](guide/aot-compiler#running-app)
|
||||
*[Inspect the Bundle](guide/aot-compiler#inspect-bundle)
|
||||
|
||||
|
||||
|
||||
{@a overview}
|
||||
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
An Angular application consists largely of components and their HTML templates.
|
||||
@ -44,10 +55,14 @@ the components and templates must be converted to executable JavaScript by the _
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=kW9cJsvcsGo" target="_blank">Watch compiler author Tobias Bosch explain the Angular Compiler</a> at AngularConnect 2016.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
You can compile the app in the browser, at runtime, as the application loads, using the **_just-in-time_ (JIT) compiler**.
|
||||
This is the standard development approach shown throughout the documentation.
|
||||
It's great but it has shortcomings.
|
||||
@ -68,6 +83,8 @@ by compiling at build time.
|
||||
|
||||
{@a aot-jit}
|
||||
|
||||
|
||||
|
||||
## _Ahead-of-time_ (AOT) vs _just-in-time_ (JIT)
|
||||
|
||||
There is actually only one Angular compiler. The difference between AOT and JIT is a matter of timing and tooling.
|
||||
@ -75,6 +92,8 @@ With AOT, the compiler runs once at build time using one set of libraries;
|
||||
with JIT it runs every time for every user at runtime using a different set of libraries.
|
||||
|
||||
{@a why-aot}
|
||||
|
||||
|
||||
## Why do AOT compilation?
|
||||
|
||||
*Faster rendering*
|
||||
@ -108,6 +127,8 @@ there are fewer opportunities for injection attacks.
|
||||
|
||||
{@a compile}
|
||||
|
||||
|
||||
|
||||
## Compile with AOT
|
||||
|
||||
Preparing for offline compilation takes a few simple steps.
|
||||
@ -127,12 +148,16 @@ A few minor changes to the lone `app.component` lead to these two class and HTML
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
Install a few new npm dependencies with the following command:
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
npm install @angular/compiler-cli @angular/platform-server --save
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You will run the `ngc` compiler provided in the `@angular/compiler-cli` npm package
|
||||
instead of the TypeScript compiler (`tsc`).
|
||||
|
||||
@ -143,10 +168,12 @@ Copy the original `src/tsconfig.json` to a file called `tsconfig-aot.json` on th
|
||||
then modify it as follows.
|
||||
|
||||
|
||||
<code-example path="cb-aot-compiler/tsconfig-aot.json" linenums="false">
|
||||
<code-example path="cb-aot-compiler/tsconfig-aot.json" title="tsconfig-aot.json" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `compilerOptions` section is unchanged except for one property.
|
||||
**Set the `module` to `es2015`**.
|
||||
This is important as explained later in the [Tree Shaking](guide/aot-compiler#tree-shaking) section.
|
||||
@ -157,6 +184,8 @@ to store the compiled output files in a new `aot` folder.
|
||||
|
||||
The `"skipMetadataEmit" : true` property prevents the compiler from generating metadata files with the compiled application.
|
||||
Metadata files are not necessary when targeting TypeScript files, so there is no reason to include them.
|
||||
|
||||
|
||||
***Component-relative template URLS***
|
||||
|
||||
The AOT compiler requires that `@Component` URLS for external templates and CSS files be _component-relative_.
|
||||
@ -164,6 +193,8 @@ That means that the value of `@Component.templateUrl` is a URL value _relative_
|
||||
For example, an `'app.component.html'` URL means that the template file is a sibling of its companion `app.component.ts` file.
|
||||
|
||||
While JIT app URLs are more flexible, stick with _component-relative_ URLs for compatibility with AOT compilation.
|
||||
|
||||
|
||||
***Compiling the application***
|
||||
|
||||
Initiate AOT compilation from the command line using the previously installed `ngc` compiler by executing:
|
||||
@ -176,6 +207,8 @@ Initiate AOT compilation from the command line using the previously installed `n
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Windows users should surround the `ngc` command in double quotes:
|
||||
|
||||
<code-example format='.'>
|
||||
@ -186,6 +219,8 @@ Windows users should surround the `ngc` command in double quotes:
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
`ngc` expects the `-p` switch to point to a `tsconfig.json` file or a folder containing a `tsconfig.json` file.
|
||||
|
||||
After `ngc` completes, look for a collection of _NgFactory_ files in the `aot` folder.
|
||||
@ -198,6 +233,8 @@ Note that the original component class is still referenced internally by the gen
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The curious can open `aot/app.component.ngfactory.ts` to see the original Angular template syntax
|
||||
compiled to TypeScript, its intermediate form.
|
||||
|
||||
@ -209,8 +246,12 @@ AOT compilation reveals them as separate, physical files.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
Do not edit the _NgFactories_! Re-compilation replaces these files and all edits will be lost.
|
||||
|
||||
|
||||
@ -220,6 +261,8 @@ Do not edit the _NgFactories_! Re-compilation replaces these files and all edits
|
||||
|
||||
{@a bootstrap}
|
||||
|
||||
|
||||
|
||||
## Bootstrap
|
||||
|
||||
The AOT approach changes application bootstrapping.
|
||||
@ -248,10 +291,14 @@ Here is AOT bootstrap in `main.ts` next to the original JIT version:
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
Be sure to [recompile](guide/aot-compiler#compiling-aot) with `ngc`!
|
||||
|
||||
|
||||
{@a tree-shaking}
|
||||
|
||||
|
||||
## Tree shaking
|
||||
|
||||
AOT compilation sets the stage for further optimization through a process called _tree shaking_.
|
||||
@ -272,6 +319,8 @@ which in turn makes more of the application "tree shakable".
|
||||
|
||||
|
||||
{@a rollup}
|
||||
|
||||
|
||||
### Rollup
|
||||
|
||||
This cookbook illustrates a tree shaking utility called _Rollup_.
|
||||
@ -283,27 +332,35 @@ Rollup can only tree shake `ES2015` modules which have `import` and `export` sta
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Recall that `tsconfig-aot.json` is configured to produce `ES2015` modules.
|
||||
It's not important that the code itself be written with `ES2015` syntax such as `class` and `const`.
|
||||
What matters is that the code uses ES `import` and `export` statements rather than `require` statements.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
In the terminal window, install the Rollup dependencies with this command:
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
npm install rollup rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-uglify --save-dev
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Next, create a configuration file (`rollup-config.js`)
|
||||
in the project root directory to tell Rollup how to process the application.
|
||||
The cookbook configuration file looks like this.
|
||||
|
||||
|
||||
<code-example path="cb-aot-compiler/rollup-config.js" linenums="false">
|
||||
<code-example path="cb-aot-compiler/rollup-config.js" title="rollup-config.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
This config file tells Rollup that the app entry point is `src/app/main.js` .
|
||||
The `dest` attribute tells Rollup to create a bundle called `build.js` in the `dist` folder.
|
||||
It overrides the default `onwarn` method in order to skip annoying messages about the AOT compiler's use of the `this` keyword.
|
||||
@ -312,6 +369,8 @@ The next section covers the plugins in more depth.
|
||||
|
||||
|
||||
{@a rollup-plugins}
|
||||
|
||||
|
||||
### Rollup Plugins
|
||||
|
||||
Optional plugins filter and transform the Rollup inputs and output.
|
||||
@ -331,10 +390,12 @@ in the final bundle. Using it is straigthforward. Add the following to
|
||||
the `plugins` array in `rollup-config.js`:
|
||||
|
||||
|
||||
<code-example path="cb-aot-compiler/rollup-config.js" region="commonjs" linenums="false">
|
||||
<code-example path="cb-aot-compiler/rollup-config.js" region="commonjs" title="rollup-config.js (CommonJs to ES2015 Plugin)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
*Minification*
|
||||
|
||||
Rollup tree shaking reduces code size considerably. Minification makes it smaller still.
|
||||
@ -342,7 +403,7 @@ This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code
|
||||
Add the following to the `plugins` array:
|
||||
|
||||
|
||||
<code-example path="cb-aot-compiler/rollup-config.js" region="uglify" linenums="false">
|
||||
<code-example path="cb-aot-compiler/rollup-config.js" region="uglify" title="rollup-config.js (CommonJs to ES2015 Plugin)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -350,6 +411,8 @@ Add the following to the `plugins` array:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
In a production setting, you would also enable gzip on the web server to compress
|
||||
the code into an even smaller package going over the wire.
|
||||
|
||||
@ -359,6 +422,8 @@ the code into an even smaller package going over the wire.
|
||||
|
||||
|
||||
{@a run-rollup}
|
||||
|
||||
|
||||
### Run Rollup
|
||||
Execute the Rollup process with this command:
|
||||
|
||||
@ -370,6 +435,8 @@ Execute the Rollup process with this command:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Windows users should surround the `rollup` command in double quotes:
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
@ -382,8 +449,12 @@ Windows users should surround the `rollup` command in double quotes:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{@a load}
|
||||
|
||||
|
||||
|
||||
## Load the bundle
|
||||
|
||||
Loading the generated application bundle does not require a module loader like SystemJS.
|
||||
@ -391,7 +462,7 @@ Remove the scripts that concern SystemJS.
|
||||
Instead, load the bundle file using a single `<script>` tag **_after_** the `</body>` tag:
|
||||
|
||||
|
||||
<code-example path="cb-aot-compiler/src/index.html" region="bundle" linenums="false">
|
||||
<code-example path="cb-aot-compiler/src/index.html" region="bundle" title="index.html (load bundle)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -399,6 +470,8 @@ Instead, load the bundle file using a single `<script>` tag **_after_** the `</b
|
||||
|
||||
{@a serve}
|
||||
|
||||
|
||||
|
||||
## Serve the app
|
||||
|
||||
You'll need a web server to host the application.
|
||||
@ -408,11 +481,15 @@ Use the same `lite-server` employed elsewhere in the documentation:
|
||||
npm run lite
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The server starts, launches a browser, and the app should appear.
|
||||
|
||||
|
||||
{@a source-code}
|
||||
|
||||
|
||||
|
||||
## AOT QuickStart source code
|
||||
|
||||
Here's the pertinent source code:
|
||||
@ -449,12 +526,16 @@ Here's the pertinent source code:
|
||||
|
||||
{@a workflow}
|
||||
|
||||
|
||||
|
||||
## Workflow and convenience script
|
||||
|
||||
You'll rebuild the AOT version of the application every time you make a change.
|
||||
Those _npm_ commands are long and difficult to remember.
|
||||
|
||||
Add the following _npm_ convenience script to the `package.json` so you can compile and rollup in one command.Open a terminal window and try it.
|
||||
Add the following _npm_ convenience script to the `package.json` so you can compile and rollup in one command.
|
||||
|
||||
Open a terminal window and try it.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
npm run build:aot
|
||||
@ -464,6 +545,8 @@ Add the following _npm_ convenience script to the `package.json` so you can comp
|
||||
|
||||
|
||||
{@a run-jit}
|
||||
|
||||
|
||||
### Develop JIT along with AOT
|
||||
|
||||
AOT compilation and rollup together take several seconds.
|
||||
@ -474,18 +557,24 @@ The same source code can be built both ways. Here's one way to do that.
|
||||
* Delete the script at the bottom of `index-jit.html` that loads `bundle.js`
|
||||
* Restore the SystemJS scripts like this:
|
||||
|
||||
<code-example path="cb-aot-compiler/src/index-jit.html" region="jit" linenums="false">
|
||||
<code-example path="cb-aot-compiler/src/index-jit.html" region="jit" title="src/index-jit.html (SystemJS scripts)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Notice the slight change to the `system.import` which now specifies `src/app/main-jit`.
|
||||
That's the JIT version of the bootstrap file that we preserved [above](guide/aot-compiler#bootstrap).
|
||||
|
||||
|
||||
Open a _different_ terminal window and enter `npm start`.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
npm start
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
That compiles the app with JIT and launches the server.
|
||||
The server loads `index.html` which is still the AOT version, which you can confirm in the browser console.
|
||||
Change the address bar to `index-jit.html` and it loads the JIT version.
|
||||
@ -504,6 +593,8 @@ Now you can develop JIT and AOT, side-by-side.
|
||||
|
||||
{@a toh}
|
||||
|
||||
|
||||
|
||||
## Tour of Heroes
|
||||
|
||||
The sample above is a trivial variation of the QuickStart application.
|
||||
@ -512,6 +603,8 @@ to an app with more substance, the [_Tour of Heroes_](tutorial/toh-pt6) applicat
|
||||
|
||||
|
||||
{@a jit-dev-aot-prod}
|
||||
|
||||
|
||||
### JIT in development, AOT in production
|
||||
|
||||
Today AOT compilation and tree shaking take more time than is practical for development. That will change soon.
|
||||
@ -538,6 +631,8 @@ Here they are for comparison:
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
The JIT version relies on `SystemJS` to load individual modules.
|
||||
Its scripts appear in its `index.html`.
|
||||
|
||||
@ -563,6 +658,8 @@ are evident in these `main` files which can and should reside in the same folder
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
***TypeScript configuration***
|
||||
|
||||
JIT-compiled applications transpile to `commonjs` modules.
|
||||
@ -594,6 +691,8 @@ You'll need separate TypeScript configuration files such as these:
|
||||
@Types and node modules
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
In the file structure of _this particular sample project_,
|
||||
the `node_modules` folder happens to be two levels up from the project root.
|
||||
Therefore, `"typeRoots"` must be set to `"../../node_modules/@types/"`.
|
||||
@ -607,23 +706,29 @@ Edit your `tsconfig-aot.json` to fit your project's file structure.
|
||||
|
||||
|
||||
{@a shaking}
|
||||
|
||||
|
||||
### Tree shaking
|
||||
|
||||
Rollup does the tree shaking as before.
|
||||
|
||||
|
||||
<code-example path="toh-6/rollup-config.js" linenums="false">
|
||||
<code-example path="toh-6/rollup-config.js" title="rollup-config.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a running-app}
|
||||
|
||||
|
||||
### Running the application
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
The general audience instructions for running the AOT build of the Tour of Heroes app are not ready.
|
||||
|
||||
The following instructions presuppose that you have cloned the
|
||||
@ -634,6 +739,8 @@ The _Tour of Heroes_ source code is in the `public/docs/_examples/toh-6/ts` fold
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Run the JIT-compiled app with `npm start` as for all other JIT examples.
|
||||
|
||||
Compiling with AOT presupposes certain supporting files, most of them discussed above.
|
||||
@ -658,7 +765,11 @@ Compiling with AOT presupposes certain supporting files, most of them discussed
|
||||
|
||||
</code-tabs>
|
||||
|
||||
Extend the `scripts` section of the `package.json` with these npm scripts:Copy the AOT distribution files into the `/aot` folder with the node script:
|
||||
|
||||
|
||||
Extend the `scripts` section of the `package.json` with these npm scripts:
|
||||
|
||||
Copy the AOT distribution files into the `/aot` folder with the node script:
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
node copy-dist-files
|
||||
@ -668,10 +779,14 @@ Extend the `scripts` section of the `package.json` with these npm scripts:Copy t
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
You won't do that again until there are updates to `zone.js` or the `core-js` shim for old browsers.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Now AOT-compile the app and launch it with the `lite-server`:
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
@ -682,6 +797,8 @@ Now AOT-compile the app and launch it with the `lite-server`:
|
||||
|
||||
|
||||
{@a inspect-bundle}
|
||||
|
||||
|
||||
### Inspect the Bundle
|
||||
|
||||
It's fascinating to see what the generated JavaScript bundle looks like after Rollup.
|
||||
@ -695,6 +812,8 @@ Install it:
|
||||
npm install source-map-explorer --save-dev
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Run the following command to generate the map.
|
||||
|
||||
|
||||
@ -703,12 +822,14 @@ Run the following command to generate the map.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `source-map-explorer` analyzes the source map generated with the bundle and draws a map of all dependencies,
|
||||
showing exactly which application and Angular modules and classes are included in the bundle.
|
||||
|
||||
Here's the map for _Tour of Heroes_.
|
||||
<a href="assets/images/cookbooks/aot-compiler/toh6-bundle.png" target="_blank" title="View larger image">
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/aot-compiler/toh6-bundle.png" alt="TOH-6-bundle"> </img>
|
||||
<img src="assets/images/cookbooks/aot-compiler/toh6-bundle.png" alt="TOH-6-bundle"></img>
|
||||
</figure>
|
||||
</a>
|
@ -5,6 +5,8 @@ AppModule: the root module
|
||||
Tell Angular how to construct and bootstrap the app in the root "AppModule".
|
||||
|
||||
@description
|
||||
|
||||
|
||||
An Angular module class describes how the application parts fit together.
|
||||
Every application has at least one Angular module, the _root_ module
|
||||
that you [bootstrap](guide/appmodule#main) to launch the application.
|
||||
@ -14,12 +16,14 @@ The [setup](guide/setup) instructions produce a new project with the following m
|
||||
You'll evolve this module as your application grows.
|
||||
|
||||
|
||||
<code-example path="setup/src/app/app.module.ts" linenums="false">
|
||||
<code-example path="setup/src/app/app.module.ts" title="src/app/app.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
After the `import` statements, you come to a class adorned with the
|
||||
**`@NgModule`** [_decorator_](guide/glossary).
|
||||
**`@NgModule`** [_decorator_](guide/glossary#decorator '"Decorator" explained').
|
||||
|
||||
The `@NgModule` decorator identifies `AppModule` as an Angular module class (also called an `NgModule` class).
|
||||
`@NgModule` takes a _metadata_ object that tells Angular how to compile and launch the application.
|
||||
@ -33,6 +37,8 @@ All you need to know at the moment is a few basics about these three properties.
|
||||
|
||||
|
||||
{@a imports}
|
||||
|
||||
|
||||
### The _imports_ array
|
||||
|
||||
Angular modules are a way to consolidate features that belong together into discrete units.
|
||||
@ -50,6 +56,8 @@ Other guide and cookbook pages will tell you when you need to add additional mod
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
**Only `NgModule` classes** go in the `imports` array. Do not put any other kind of class in `imports`.
|
||||
|
||||
|
||||
@ -59,6 +67,8 @@ Other guide and cookbook pages will tell you when you need to add additional mod
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The `import` statements at the top of the file and the Angular module's `imports` array
|
||||
are unrelated and have completely different jobs.
|
||||
|
||||
@ -76,6 +86,8 @@ that the application needs to function properly.
|
||||
|
||||
|
||||
{@a declarations}
|
||||
|
||||
|
||||
### The _declarations_ array
|
||||
|
||||
You tell Angular which components belong to the `AppModule` by listing it in the module's `declarations` array.
|
||||
@ -91,6 +103,8 @@ that you must also add to the `declarations` array.
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
**Only _declarables_** — _components_, _directives_ and _pipes_ — belong in the `declarations` array.
|
||||
Do not put any other kind of class in `declarations`; _not_ `NgModule` classes, _not_ service classes, _not_ model classes.
|
||||
|
||||
@ -100,6 +114,8 @@ Do not put any other kind of class in `declarations`; _not_ `NgModule` classes,
|
||||
|
||||
|
||||
{@a bootstrap-array}
|
||||
|
||||
|
||||
### The _bootstrap_ array
|
||||
|
||||
You launch the application by [_bootstrapping_](guide/appmodule#main) the root `AppModule`.
|
||||
@ -124,6 +140,8 @@ Which brings us to the _bootstrapping_ process itself.
|
||||
|
||||
</l-main-section>
|
||||
|
||||
|
||||
|
||||
## Bootstrap in _main.ts_
|
||||
|
||||
There are many ways to bootstrap an application.
|
||||
@ -135,10 +153,12 @@ and you'll run it in a browser. You can learn about other options later.
|
||||
The recommended place to bootstrap a JIT-compiled browser application is in a separate file
|
||||
in the `src` folder named `src/main.ts`
|
||||
|
||||
<code-example path="setup/src/main.ts" linenums="false">
|
||||
<code-example path="setup/src/main.ts" title="src/main.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
This code creates a browser platform for dynamic (JIT) compilation and
|
||||
bootstraps the `AppModule` described above.
|
||||
|
||||
@ -149,10 +169,12 @@ creates an instance of the component and inserts it within the element tag ident
|
||||
The `AppComponent` selector — here and in most documentation samples — is `my-app`
|
||||
so Angular looks for a `<my-app>` tag in the `index.html` like this one ...
|
||||
|
||||
<code-example path="setup/src/index.html" region="my-app" linenums="false">
|
||||
<code-example path="setup/src/index.html" region="my-app" title="setup/src/index.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
... and displays the `AppComponent` there.
|
||||
|
||||
This file is very stable. Once you've set it up, you may never change it again.
|
||||
@ -162,6 +184,8 @@ This file is very stable. Once you've set it up, you may never change it again.
|
||||
|
||||
</l-main-section>
|
||||
|
||||
|
||||
|
||||
## More about Angular Modules
|
||||
|
||||
Your initial app has only a single module, the _root_ module.
|
||||
|
@ -6,10 +6,14 @@ The basic building blocks of Angular applications.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
|
||||
Angular is a framework for building client applications in HTML and
|
||||
either JavaScript or a language like TypeScript that compiles to JavaScript.
|
||||
|
||||
The framework consists of several libraries, some of them core and some optional.
|
||||
|
||||
|
||||
You write Angular applications by composing HTML *templates* with Angularized markup,
|
||||
writing *component* classes to manage those templates, adding application logic in *services*,
|
||||
and boxing components and services in *modules*.
|
||||
@ -23,9 +27,11 @@ You'll learn the details in the pages that follow. For now, focus on the big pic
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/overview2.png" alt="overview" style="margin-left:-40px;" width="700"> </img>
|
||||
<img src="assets/images/devguide/architecture/overview2.png" alt="overview" style="margin-left:-40px;" width="700"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The architecture diagram identifies the eight main building blocks of an Angular application:
|
||||
|
||||
* [Modules](guide/architecture#modules)
|
||||
@ -53,17 +59,23 @@ Learn these building blocks, and you're on your way.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Modules
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Angular apps are modular and Angular has its own modularity system called _Angular modules_ or _NgModules_.
|
||||
|
||||
_Angular modules_ are a big deal.
|
||||
This page introduces modules; the [Angular modules](guide/ngmodule) page covers them in depth.
|
||||
<br class="l-clear-both"><br>Every Angular app has at least one Angular module class, [the _root module_](guide/appmodule),
|
||||
<br class="l-clear-both"><br>
|
||||
|
||||
Every Angular app has at least one Angular module class, [the _root module_](guide/appmodule "AppModule: the root module"),
|
||||
conventionally named `AppModule`.
|
||||
|
||||
While the _root module_ may be the only module in a small application, most apps have many more
|
||||
@ -74,6 +86,8 @@ An Angular module, whether a _root_ or _feature_, is a class with an `@NgModule`
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Decorators are functions that modify JavaScript classes.
|
||||
Angular has many decorators that attach metadata to classes so that it knows
|
||||
what those classes mean and how they should work.
|
||||
@ -82,6 +96,8 @@ Learn more</a> about decorators on the web.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
`NgModule` is a decorator function that takes a single metadata object whose properties describe the module.
|
||||
The most important properties are:
|
||||
* `declarations` - the _view classes_ that belong to this module.
|
||||
@ -99,7 +115,7 @@ that hosts all other app views. Only the _root module_ should set this `bootstra
|
||||
|
||||
Here's a simple root module:
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="module" linenums="false">
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="module" title="src/app/app.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -107,18 +123,24 @@ Here's a simple root module:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The `export` of `AppComponent` is just to show how to export; it isn't actually necessary in this example. A root module has no reason to _export_ anything because other components don't need to _import_ the root module.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Launch an application by _bootstrapping_ its root module.
|
||||
During development you're likely to bootstrap the `AppModule` in a `main.ts` file like this one.
|
||||
|
||||
|
||||
<code-example path="architecture/src/main.ts" linenums="false">
|
||||
<code-example path="architecture/src/main.ts" title="src/main.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### Angular modules vs. JavaScript modules
|
||||
|
||||
The Angular module — a class decorated with `@NgModule` — is a fundamental feature of Angular.
|
||||
@ -145,18 +167,26 @@ Other JavaScript modules use *import statements* to access public objects from o
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
<a href="http://exploringjs.com/es6/ch_modules.html" target="_blank">Learn more about the JavaScript module system on the web.</a>
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
These are two different and _complementary_ module systems. Use them both to write your apps.
|
||||
|
||||
|
||||
### Angular libraries
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/library-module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/library-module.png" alt="Component" align="left" style="width:240px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Angular ships as a collection of JavaScript modules. You can think of them as library modules.
|
||||
|
||||
Each Angular library name begins with the `@angular` prefix.
|
||||
@ -170,18 +200,24 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You also import Angular _modules_ from Angular _libraries_ using JavaScript import statements:
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
In the example of the simple root module above, the application module needs material from within that `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this.
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="ngmodule-imports" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
In this way you're using both the Angular and JavaScript module systems _together_.
|
||||
|
||||
It's easy to confuse the two systems because they share the common vocabulary of "imports" and "exports".
|
||||
@ -190,6 +226,8 @@ Hang in there. The confusion yields to clarity with time and experience.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Learn more from the [Angular modules](guide/ngmodule) page.
|
||||
|
||||
|
||||
@ -197,18 +235,20 @@ Learn more from the [Angular modules](guide/ngmodule) page.
|
||||
|
||||
|
||||
|
||||
<div class='l-hr'>
|
||||
---
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Components
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/hero-component.png" alt="Component" align="left" style="width:200px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/hero-component.png" alt="Component" align="left" style="width:200px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
A _component_ controls a patch of screen called a *view*.
|
||||
|
||||
For example, the following views are controlled by components:
|
||||
@ -220,7 +260,7 @@ For example, the following views are controlled by components:
|
||||
You define a component's application logic—what it does to support the view—inside a class.
|
||||
The class interacts with the view through an API of properties and methods.
|
||||
|
||||
<a id="component-code"></a>
|
||||
{@a component-code}
|
||||
For example, this `HeroListComponent` has a `heroes` property that returns an array of heroes
|
||||
that it acquires from a service.
|
||||
`HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list.
|
||||
@ -230,21 +270,25 @@ that it acquires from a service.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Angular creates, updates, and destroys components as the user moves through the application.
|
||||
Your app can take action at each moment in this lifecycle through optional [lifecycle hooks](guide/lifecycle-hooks), like `ngOnInit()` declared above.
|
||||
|
||||
|
||||
<div class='l-hr'>
|
||||
---
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/template.png" alt="Template" align="left" style="width:200px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/template.png" alt="Template" align="left" style="width:200px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
You define a component's view with its companion **template**. A template is a form of HTML
|
||||
that tells Angular how to render the component.
|
||||
|
||||
@ -252,10 +296,12 @@ A template looks like regular HTML, except for a few differences. Here is a
|
||||
template for our `HeroListComponent`:
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.html">
|
||||
<code-example path="architecture/src/app/hero-list.component.html" title="src/app/hero-list.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Although this template uses typical HTML elements like `<h2>` and `<p>`, it also has some differences. Code like `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<hero-detail>` uses Angular's [template syntax](guide/template-syntax).
|
||||
|
||||
|
||||
@ -268,25 +314,31 @@ The `HeroDetailComponent` is a **child** of the `HeroListComponent`.
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/component-tree.png" alt="Metadata" align="left" style="width:300px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/component-tree.png" alt="Metadata" align="left" style="width:300px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Notice how `<hero-detail>` rests comfortably among native HTML elements. Custom components mix seamlessly with native HTML in the same layouts.
|
||||
<br class="l-clear-both">
|
||||
|
||||
<div class='l-hr'>
|
||||
---
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Metadata
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/metadata.png" alt="Metadata" align="left" style="width:150px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/metadata.png" alt="Metadata" align="left" style="width:150px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
<p style="padding-top:10px">Metadata tells Angular how to process a class.</p>
|
||||
<br class="l-clear-both">[Looking back at the code](guide/architecture#component-code) for `HeroListComponent`, you can see that it's just a class.
|
||||
<br class="l-clear-both">
|
||||
|
||||
[Looking back at the code](guide/architecture#component-code) for `HeroListComponent`, you can see that it's just a class.
|
||||
There is no evidence of a framework, no "Angular" in it at all.
|
||||
|
||||
In fact, `HeroListComponent` really is *just a class*. It's not a component until you *tell Angular about it*.
|
||||
@ -301,39 +353,51 @@ Here's some metadata for `HeroListComponent`:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here is the `@Component` decorator, which identifies the class
|
||||
immediately below it as a component class.
|
||||
|
||||
|
||||
The `@Component` decorator takes a required configuration object with the
|
||||
information Angular needs to create and present the component and its view.
|
||||
|
||||
Here are a few of the most useful `@Component` configuration options:
|
||||
- `selector`: CSS selector that tells Angular to create and insert an instance of this component
|
||||
|
||||
|
||||
* `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.
|
||||
|
||||
- `templateUrl`: module-relative address of this component's HTML template, shown [above](guide/architecture#templates).
|
||||
- `providers`: array of **dependency injection providers** for services that the component requires.
|
||||
* `templateUrl`: module-relative address of this component's HTML template, shown [above](guide/architecture#templates).
|
||||
|
||||
|
||||
* `providers`: array of **dependency injection providers** for services that the component requires.
|
||||
This is one way to tell Angular that the component's constructor requires a `HeroService`
|
||||
so it can get the list of heroes to display.
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/template-metadata-component.png" alt="Metadata" align="left" style="height:200px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/template-metadata-component.png" alt="Metadata" align="left" style="height:200px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The metadata in the `@Component` tells Angular where to get the major building blocks you specify for the component.
|
||||
|
||||
The template, metadata, and component together describe a view.
|
||||
|
||||
Apply other metadata decorators in a similar fashion to guide Angular behavior.
|
||||
`@Injectable`, `@Input`, and `@Output` are a few of the more popular decorators.<br class="l-clear-both">The architectural takeaway is that you must add metadata to your code
|
||||
`@Injectable`, `@Input`, and `@Output` are a few of the more popular decorators.<br class="l-clear-both">
|
||||
|
||||
The architectural takeaway is that you must add metadata to your code
|
||||
so that Angular knows what to do.
|
||||
|
||||
|
||||
<div class='l-hr'>
|
||||
---
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Data binding
|
||||
@ -342,27 +406,33 @@ into actions and value updates. Writing such push/pull logic by hand is tedious,
|
||||
read as any experienced jQuery programmer can attest.
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/databinding.png" alt="Data Binding" style="width:220px; float:left; margin-left:-40px;margin-right:20px"> </img>
|
||||
<img src="assets/images/devguide/architecture/databinding.png" alt="Data Binding" style="width:220px; float:left; margin-left:-40px;margin-right:20px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Angular supports **data binding**,
|
||||
a mechanism for coordinating parts of a template with parts of a component.
|
||||
Add binding markup to the template HTML to tell Angular how to connect both sides.
|
||||
|
||||
As the diagram shows, there are four forms of data binding syntax. Each form has a direction — to the DOM, from the DOM, or in both directions.<br class="l-clear-both">The `HeroListComponent` [example](guide/architecture#templates) template has three forms:
|
||||
As the diagram shows, there are four forms of data binding syntax. Each form has a direction — to the DOM, from the DOM, or in both directions.<br class="l-clear-both">
|
||||
|
||||
The `HeroListComponent` [example](guide/architecture#templates) template has three forms:
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.1.html" linenums="false" title="src/app/hero-list.component.html (binding)" region="binding">
|
||||
|
||||
</code-example>
|
||||
|
||||
* The `{{hero.name}}` [*interpolation*](guide/displaying-data)
|
||||
|
||||
|
||||
* The `{{hero.name}}` [*interpolation*](guide/displaying-data#interpolation)
|
||||
displays the component's `hero.name` property value within the `<li>` element.
|
||||
|
||||
* The `[hero]` [*property binding*](guide/template-syntax) passes the value of `selectedHero` from
|
||||
* The `[hero]` [*property binding*](guide/template-syntax#property-binding) passes the value of `selectedHero` from
|
||||
the parent `HeroListComponent` to the `hero` property of the child `HeroDetailComponent`.
|
||||
|
||||
* The `(click)` [*event binding*](guide/user-input) calls the component's `selectHero` method when the user clicks a hero's name.
|
||||
* The `(click)` [*event binding*](guide/user-input#click) calls the component's `selectHero` method when the user clicks a hero's name.
|
||||
|
||||
**Two-way data binding** is an important fourth form
|
||||
that combines property and event binding in a single notation, using the `ngModel` directive.
|
||||
@ -373,6 +443,8 @@ Here's an example from the `HeroDetailComponent` template:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
In two-way binding, a data property value flows to the input box from the component as with property binding.
|
||||
The user's changes also flow back to the component, resetting the property to the latest value,
|
||||
as with event binding.
|
||||
@ -382,29 +454,35 @@ from the root of the application component tree through all child components.
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/component-databinding.png" alt="Data Binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/component-databinding.png" alt="Data Binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Data binding plays an important role in communication
|
||||
between a template and its component.<br class="l-clear-both">
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/parent-child-binding.png" alt="Parent/Child binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/parent-child-binding.png" alt="Parent/Child binding" style="float:left; width:300px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Data binding is also important for communication between parent and child components.<br class="l-clear-both">
|
||||
|
||||
<div class='l-hr'>
|
||||
---
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Directives
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/directive.png" alt="Parent child" style="float:left; width:150px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/directive.png" alt="Parent child" style="float:left; width:150px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Angular templates are *dynamic*. When Angular renders them, it transforms the DOM
|
||||
according to the instructions given by **directives**.
|
||||
|
||||
@ -416,11 +494,15 @@ a `@Component` decorator is actually a `@Directive` decorator extended with temp
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
While **a component is technically a directive**,
|
||||
components are so distinctive and central to Angular applications that this architectural overview separates components from directives.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Two *other* kinds of directives exist: _structural_ and _attribute_ directives.
|
||||
|
||||
They tend to appear within an element tag as attributes do,
|
||||
@ -435,8 +517,12 @@ The [example template](guide/architecture#templates) uses two built-in structura
|
||||
|
||||
</code-example>
|
||||
|
||||
* [`*ngFor`](guide/displaying-data) tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
||||
* [`*ngIf`](guide/displaying-data) includes the `HeroDetail` component only if a selected hero exists.
|
||||
|
||||
|
||||
* [`*ngFor`](guide/displaying-data#ngFor) tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
||||
* [`*ngIf`](guide/displaying-data#ngIf) includes the `HeroDetail` component only if a selected hero exists.
|
||||
|
||||
|
||||
**Attribute** directives alter the appearance or behavior of an existing element.
|
||||
In templates they look like regular HTML attributes, hence the name.
|
||||
|
||||
@ -450,31 +536,38 @@ by setting its display value property and responding to change events.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Angular has a few more directives that either alter the layout structure
|
||||
(for example, [ngSwitch](guide/template-syntax))
|
||||
(for example, [ngSwitch](guide/template-syntax#ngSwitch))
|
||||
or modify aspects of DOM elements and components
|
||||
(for example, [ngStyle](guide/template-syntax) and [ngClass](guide/template-syntax)).
|
||||
(for example, [ngStyle](guide/template-syntax#ngStyle) and [ngClass](guide/template-syntax#ngClass)).
|
||||
|
||||
Of course, you can also write your own directives. Components such as
|
||||
`HeroListComponent` are one kind of custom directive.
|
||||
<!-- PENDING: link to where to learn more about other kinds! -->
|
||||
|
||||
|
||||
<div class='l-hr'>
|
||||
---
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Services
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/service.png" alt="Service" style="float:left; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/service.png" alt="Service" style="float:left; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
_Service_ is a broad category encompassing any value, function, or feature that your application needs.
|
||||
|
||||
Almost anything can be a service.
|
||||
A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.<br class="l-clear-both">Examples include:
|
||||
A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.<br class="l-clear-both">
|
||||
|
||||
Examples include:
|
||||
|
||||
* logging service
|
||||
* data service
|
||||
* message bus
|
||||
@ -493,6 +586,8 @@ Here's an example of a service class that logs to the browser console:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here's a `HeroService` that uses a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to fetch heroes.
|
||||
The `HeroService` depends on the `Logger` service and another `BackendService` that handles the server communication grunt work.
|
||||
|
||||
@ -501,6 +596,8 @@ The `HeroService` depends on the `Logger` service and another `BackendService` t
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Services are everywhere.
|
||||
|
||||
Component classes should be lean. They don't fetch data from the server,
|
||||
@ -519,20 +616,24 @@ Angular does help you *follow* these principles by making it easy to factor your
|
||||
application logic into services and make those services available to components through *dependency injection*.
|
||||
|
||||
|
||||
<div class='l-hr'>
|
||||
---
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Dependency injection
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/dependency-injection.png" alt="Service" style="float:left; width:200px; margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/architecture/dependency-injection.png" alt="Service" style="float:left; width:200px; margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
_Dependency injection_ is a way to supply a new instance of a class
|
||||
with the fully-formed dependencies it requires. Most dependencies are services.
|
||||
Angular uses dependency injection to provide new components with the services they need.<br class="l-clear-both">Angular can tell which services a component needs by looking at the types of its constructor parameters.
|
||||
Angular uses dependency injection to provide new components with the services they need.<br class="l-clear-both">
|
||||
|
||||
Angular can tell which services a component needs by looking at the types of its constructor parameters.
|
||||
For example, the constructor of your `HeroListComponent` needs a `HeroService`:
|
||||
|
||||
|
||||
@ -540,6 +641,8 @@ For example, the constructor of your `HeroListComponent` needs a `HeroService`:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
When Angular creates a component, it first asks an **injector** for
|
||||
the services that the component requires.
|
||||
|
||||
@ -553,13 +656,17 @@ This is *dependency injection*.
|
||||
The process of `HeroService` injection looks a bit like this:
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/architecture/injector-injects.png" alt="Service"> </img>
|
||||
<img src="assets/images/devguide/architecture/injector-injects.png" alt="Service"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
If the injector doesn't have a `HeroService`, how does it know how to make one?
|
||||
|
||||
In brief, you must have previously registered a **provider** of the `HeroService` with the injector.
|
||||
A provider is something that can create or return a service, typically the service class itself.
|
||||
|
||||
|
||||
You can register providers in modules or in components.
|
||||
|
||||
In general, add providers to the [root module](guide/architecture#module) so that
|
||||
@ -570,6 +677,8 @@ the same instance of a service is available everywhere.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Alternatively, register at a component level in the `providers` property of the `@Component` metadata:
|
||||
|
||||
|
||||
@ -577,6 +686,8 @@ Alternatively, register at a component level in the `providers` property of the
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Registering at a component level means you get a new instance of the
|
||||
service with each new instance of that component.
|
||||
|
||||
@ -596,9 +707,9 @@ Points to remember about dependency injection:
|
||||
* Register *providers* with injectors.
|
||||
|
||||
|
||||
<div class='l-hr'>
|
||||
---
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Wrap up
|
||||
@ -645,5 +756,7 @@ by implementing the lifecycle hook interfaces.
|
||||
|
||||
> [**Router**](guide/router): Navigate from page to page within the client
|
||||
application and never leave the browser.
|
||||
|
||||
|
||||
> [**Testing**](guide/testing): Run unit tests on your application parts as they interact with the Angular framework
|
||||
using the _Angular Testing Platform_.
|
@ -6,6 +6,8 @@ Attribute directives attach behavior to elements.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
|
||||
An **Attribute** directive changes the appearance or behavior of a DOM element.
|
||||
|
||||
# Contents
|
||||
@ -19,6 +21,8 @@ An **Attribute** directive changes the appearance or behavior of a DOM element.
|
||||
|
||||
Try the <live-example title="Attribute Directive example"></live-example>.
|
||||
|
||||
|
||||
|
||||
## Directives overview
|
||||
|
||||
There are three kinds of directives in Angular:
|
||||
@ -31,14 +35,16 @@ There are three kinds of directives in Angular:
|
||||
You saw a component for the first time in the [QuickStart](quickstart) guide.
|
||||
|
||||
*Structural Directives* change the structure of the view.
|
||||
Two examples are [NgFor](guide/template-syntax) and [NgIf](guide/template-syntax).
|
||||
Two examples are [NgFor](guide/template-syntax#ngFor) and [NgIf](guide/template-syntax#ngIf).
|
||||
Learn about them in the [Structural Directives](guide/structural-directives) guide.
|
||||
|
||||
*Attribute directives* are used as attributes of elements.
|
||||
The built-in [NgStyle](guide/template-syntax) directive in the
|
||||
The built-in [NgStyle](guide/template-syntax#ngStyle) directive in the
|
||||
[Template Syntax](guide/template-syntax) guide, for example,
|
||||
can change several element styles at the same time.
|
||||
|
||||
|
||||
|
||||
## Build a simple attribute directive
|
||||
|
||||
An attribute directive minimally requires building a controller class annotated with
|
||||
@ -55,6 +61,8 @@ when the user hovers over that element. You can apply it like this:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### Write the directive code
|
||||
|
||||
Follow the [setup](guide/setup) instructions for creating a new local project
|
||||
@ -63,10 +71,12 @@ named <code>attribute-directives</code>.
|
||||
Create the following source file in the indicated folder:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.1.ts">
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.1.ts" title="src/app/highlight.directive.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `import` statement specifies symbols from the Angular `core`:
|
||||
|
||||
1. `Directive` provides the functionality of the `@Directive` decorator.
|
||||
@ -76,6 +86,8 @@ so the code can access the DOM element.
|
||||
|
||||
Next, the `@Directive` decorator function contains the directive metadata in a configuration object
|
||||
as an argument.
|
||||
|
||||
|
||||
`@Directive` requires a CSS selector to identify
|
||||
the HTML in the template that is associated with the directive.
|
||||
The [CSS selector for an attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors)
|
||||
@ -86,6 +98,8 @@ Angular locates all elements in the template that have an attribute named `myHig
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
### Why not call it "highlight"?
|
||||
|
||||
Though *highlight* is a more concise name than *myHighlight* and would work,
|
||||
@ -100,6 +114,8 @@ For a simple demo, the short prefix, `my`, helps distinguish your custom directi
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
After the `@Directive` metadata comes the directive's controller class,
|
||||
called `HighlightDirective`, which contains the logic for the directive.
|
||||
Exporting `HighlightDirective` makes it accessible to other components.
|
||||
@ -110,6 +126,8 @@ into the constructor.
|
||||
`ElementRef` is a service that grants direct access to the DOM element
|
||||
through its `nativeElement` property.
|
||||
|
||||
|
||||
|
||||
## Apply the attribute directive
|
||||
|
||||
To use the new `HighlightDirective`, create a template that
|
||||
@ -120,37 +138,45 @@ Put the template in its own <code>app.component.html</code>
|
||||
file that looks like this:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html">
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" title="src/app/app.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now reference this template in the `AppComponent`:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.ts">
|
||||
<code-example path="attribute-directives/src/app/app.component.ts" title="src/app/app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Next, add an `import` statement to fetch the `Highlight` directive and
|
||||
add that class to the `declarations` NgModule metadata. This way Angular
|
||||
recognizes the directive when it encounters `myHighlight` in the template.
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.module.ts">
|
||||
<code-example path="attribute-directives/src/app/app.module.ts" title="src/app/app.module.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now when the app runs, the `myHighlight` directive highlights the paragraph text.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/attribute-directives/first-highlight.png" alt="First Highlight"> </img>
|
||||
<img src="assets/images/devguide/attribute-directives/first-highlight.png" alt="First Highlight"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
### Your directive isn't working?
|
||||
|
||||
Did you remember to add the directive to the `declarations` attribute of `@NgModule`?
|
||||
@ -160,10 +186,12 @@ Open the console in the browser tools and look for an error like this:
|
||||
|
||||
<code-example format="nocode">
|
||||
EXCEPTION: Template parse errors:
|
||||
Can't bind to 'myHighlight' since it isn't a known property of 'p'.
|
||||
Can't bind to 'myHighlight' since it isn't a known property of 'p'.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Angular detects that you're trying to bind to *something* but it can't find this directive
|
||||
in the module's `declarations` array.
|
||||
After specifying `HighlightDirective` in the `declarations` array,
|
||||
@ -172,11 +200,15 @@ Angular knows it can apply the directive to components declared in this module.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
To summarize, Angular found the `myHighlight` attribute on the `<p>` element.
|
||||
It created an instance of the `HighlightDirective` class and
|
||||
injected a reference to the `<p>` element into the directive's constructor
|
||||
which sets the `<p>` element's background style to yellow.
|
||||
|
||||
|
||||
|
||||
## Respond to user-initiated events
|
||||
|
||||
Currently, `myHighlight` simply sets an element color.
|
||||
@ -192,6 +224,8 @@ add the `Input` symbol as well because you'll need it soon.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Then add two eventhandlers that respond when the mouse enters or leaves,
|
||||
each adorned by the `HostListener` decorator.
|
||||
|
||||
@ -200,12 +234,16 @@ each adorned by the `HostListener` decorator.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `@HostListener` decorator lets you subscribe to events of the DOM
|
||||
element that hosts an attribute directive, the `<p>` in this case.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Of course you could reach into the DOM with standard JavaScript and and attach event listeners manually.
|
||||
There are at least three problems with _that_ approach:
|
||||
|
||||
@ -216,6 +254,8 @@ There are at least three problems with _that_ approach:
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The handlers delegate to a helper method that sets the color on the DOM element, `el`,
|
||||
which you declare and initialize in the constructor.
|
||||
|
||||
@ -224,22 +264,28 @@ which you declare and initialize in the constructor.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here's the updated directive in full:
|
||||
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts">
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" title="src/app/highlight.directive.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Run the app and confirm that the background color appears when
|
||||
the mouse hovers over the `p` and disappears as it moves out.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/attribute-directives/highlight-directive-anim.gif" alt="Second Highlight"> </img>
|
||||
<img src="assets/images/devguide/attribute-directives/highlight-directive-anim.gif" alt="Second Highlight"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
|
||||
## Pass values into the directive with an _@Input_ data binding
|
||||
|
||||
Currently the highlight color is hard-coded _within_ the directive. That's inflexible.
|
||||
@ -255,6 +301,8 @@ Start by adding a `highlightColor` property to the directive class like this:
|
||||
|
||||
|
||||
{@a input}
|
||||
|
||||
|
||||
### Binding to an _@Input_ property
|
||||
|
||||
Notice the `@Input` decorator. It adds metadata to the class that makes the directive's `highlightColor` property available for binding.
|
||||
@ -269,6 +317,8 @@ Try it by adding the following directive binding variations to the `AppComponent
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Add a `color` property to the `AppComponent`.
|
||||
|
||||
|
||||
@ -276,6 +326,8 @@ Add a `color` property to the `AppComponent`.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Let it control the highlight color with a property binding.
|
||||
|
||||
|
||||
@ -283,6 +335,8 @@ Let it control the highlight color with a property binding.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
That's good, but it would be nice to _simultaneously_ apply the directive and set the color _in the same attribute_ like this.
|
||||
|
||||
|
||||
@ -290,6 +344,8 @@ That's good, but it would be nice to _simultaneously_ apply the directive and se
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `[myHighlight]` attribute binding both applies the highlighting directive to the `<p>` element
|
||||
and sets the directive's highlight color with a property binding.
|
||||
You're re-using the directive's attribute selector (`[myHighlight]`) to do both jobs.
|
||||
@ -302,10 +358,14 @@ You'll have to rename the directive's `highlightColor` property to `myHighlight`
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
This is disagreeable. The word, `myHighlight`, is a terrible property name and it doesn't convey the property's intent.
|
||||
|
||||
|
||||
{@a input-alias}
|
||||
|
||||
|
||||
### Bind to an _@Input_ alias
|
||||
|
||||
Fortunately you can name the directive property whatever you want _and_ **_alias it_** for binding purposes.
|
||||
@ -317,6 +377,8 @@ Restore the original property name and specify the selector as the alias in the
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
_Inside_ the directive the property is known as `highlightColor`.
|
||||
_Outside_ the directive, where you bind to it, it's known as `myHighlight`.
|
||||
|
||||
@ -327,6 +389,8 @@ You get the best of both worlds: the property name you want and the binding synt
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it.
|
||||
If someone neglects to bind to `highlightColor`, highlight in red:
|
||||
|
||||
@ -335,6 +399,8 @@ If someone neglects to bind to `highlightColor`, highlight in red:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here's the latest version of the directive class.
|
||||
|
||||
|
||||
@ -342,6 +408,8 @@ Here's the latest version of the directive class.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
## Write a harness to try it
|
||||
|
||||
It may be difficult to imagine how this directive actually works.
|
||||
@ -355,6 +423,8 @@ Update <code>app.component.html</code> as follows:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Revise the `AppComponent.color` so that it has no initial value.
|
||||
|
||||
|
||||
@ -362,14 +432,18 @@ Revise the `AppComponent.color` so that it has no initial value.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here are the harness and directive in action.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/attribute-directives/highlight-directive-v2-anim.gif" alt="Highlight v.2"> </img>
|
||||
<img src="assets/images/devguide/attribute-directives/highlight-directive-v2-anim.gif" alt="Highlight v.2"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
|
||||
## Bind to a second property
|
||||
|
||||
This highlight directive has a single customizable property. In a real app, it may need more.
|
||||
@ -385,6 +459,8 @@ Add a second **input** property to `HighlightDirective` called `defaultColor`:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Revise the directive's `onMouseEnter` so that it first tries to highlight with the `highlightColor`,
|
||||
then with the `defaultColor`, and falls back to "red" if both properties are undefined.
|
||||
|
||||
@ -393,6 +469,8 @@ then with the `defaultColor`, and falls back to "red" if both properties are und
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
How do you bind to a second property when you're already binding to the `myHighlight` attribute name?
|
||||
|
||||
As with components, you can add as many directive property bindings as you need by stringing them along in the template.
|
||||
@ -404,6 +482,8 @@ and fall back to "violet" as the default color.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
|
||||
because you made it _public_ with the `@Input` decorator.
|
||||
|
||||
@ -411,18 +491,20 @@ Here's how the harness should work when you're done coding.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/attribute-directives/highlight-directive-final-anim.gif" alt="Final Highlight"> </img>
|
||||
<img src="assets/images/devguide/attribute-directives/highlight-directive-final-anim.gif" alt="Final Highlight"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
This page covered how to:
|
||||
|
||||
- [Build an **attribute directive**](guide/attribute-directives#write-directive) that modifies the behavior of an element.
|
||||
- [Apply the directive](guide/attribute-directives#apply-directive) to an element in a template.
|
||||
- [Respond to **events**](guide/attribute-directives#respond-to-user) that change the directive's behavior.
|
||||
- [**Bind** values to the directive](guide/attribute-directives#bindings).
|
||||
* [Build an **attribute directive**](guide/attribute-directives#write-directive) that modifies the behavior of an element.
|
||||
* [Apply the directive](guide/attribute-directives#apply-directive) to an element in a template.
|
||||
* [Respond to **events**](guide/attribute-directives#respond-to-user) that change the directive's behavior.
|
||||
* [**Bind** values to the directive](guide/attribute-directives#bindings).
|
||||
|
||||
The final source code follows:
|
||||
|
||||
@ -455,8 +537,12 @@ The final source code follows:
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
You can also experience and download the <live-example title="Attribute Directive example"></live-example>.
|
||||
|
||||
|
||||
|
||||
### Appendix: Why add _@Input_?
|
||||
|
||||
In this demo, the `hightlightColor` property is an ***input*** property of
|
||||
@ -467,6 +553,8 @@ the `HighlightDirective`. You've seen it applied without an alias:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You've seen it with an alias:
|
||||
|
||||
|
||||
@ -474,6 +562,8 @@ You've seen it with an alias:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Either way, the `@Input` decorator tells Angular that this property is
|
||||
_public_ and available for binding by a parent component.
|
||||
Without `@Input`, Angular refuses to bind to the property.
|
||||
@ -509,6 +599,8 @@ Now apply that reasoning to the following example:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
* The `color` property in the expression on the right belongs to the template's component.
|
||||
The template and its component trust each other.
|
||||
The `color` property doesn't require the `@Input` decorator.
|
||||
|
@ -6,6 +6,8 @@ Browser support and polyfills guide.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
|
||||
Angular supports most recent browsers. This includes the following specific versions:
|
||||
|
||||
|
||||
@ -197,6 +199,8 @@ Angular supports most recent browsers. This includes the following specific vers
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Angular's continuous integration process runs unit tests of the framework on all of these browsers for every pull request,
|
||||
using <a href="https://saucelabs.com/" target="_blank">SauceLabs</a> and
|
||||
<a href="https://www.browserstack.com" target="_blank">Browserstack</a>.
|
||||
@ -204,6 +208,8 @@ using <a href="https://saucelabs.com/" target="_blank">SauceLabs</a> and
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Polyfills #
|
||||
Angular is built on the latest standards of the web platform.
|
||||
Targeting such a wide range of browsers is challenging because they do not support all features of modern browsers.
|
||||
@ -211,10 +217,12 @@ Targeting such a wide range of browsers is challenging because they do not suppo
|
||||
You can compensate by loading polyfill scripts ("polyfills") on the host web page (`index.html`)
|
||||
that implement missing features in JavaScript.
|
||||
|
||||
<code-example path="quickstart/src/index.html" region="polyfills" linenums="false">
|
||||
<code-example path="quickstart/src/index.html" region="polyfills" title="quickstart/src/index.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
A particular browser may require at least one polyfill to run _any_ Angular application.
|
||||
You may need additional polyfills for specific features.
|
||||
|
||||
@ -223,6 +231,8 @@ The tables below can help you determine which polyfills to load, depending on th
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
The suggested polyfills are the ones that run full Angular applications.
|
||||
You may need additional polyfills to support features not covered by this list.
|
||||
Note that polyfills cannot magically transform an old, slow browser into a modern, fast one.
|
||||
@ -230,6 +240,8 @@ Note that polyfills cannot magically transform an old, slow browser into a moder
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
### Mandatory polyfills ##
|
||||
These are the polyfills required to run an Angular application on each supported browser:
|
||||
|
||||
@ -267,6 +279,8 @@ These are the polyfills required to run an Angular application on each supported
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[ES6](guide/browser-support#core-es6)
|
||||
</td>
|
||||
|
||||
@ -279,6 +293,8 @@ These are the polyfills required to run an Angular application on each supported
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[ES6<br>classList](guide/browser-support#classlist)
|
||||
|
||||
</td>
|
||||
@ -287,6 +303,8 @@ These are the polyfills required to run an Angular application on each supported
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
### Optional browser features to polyfill ##
|
||||
Some features of Angular may require additional polyfills.
|
||||
|
||||
@ -317,10 +335,12 @@ Here are the features which may require additional polyfills:
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
<td>
|
||||
<a href="./animations.html"> Animations </a>
|
||||
<a href="./animations.html">Animations</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[Web Animations](guide/browser-support#web-animations)
|
||||
</td>
|
||||
|
||||
@ -333,10 +353,12 @@ Here are the features which may require additional polyfills:
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
<td>
|
||||
<a href="../api/common/index/DatePipe-pipe.html" target="_blank"> Date </a> <span> , </span> <a href="../api/common/index/CurrencyPipe-pipe.html" target="_blank"> currency </a> <span> , </span> <a href="../api/common/index/DecimalPipe-pipe.html" target="_blank"> decimal </a> <span> and </span> <a href="../api/common/index/PercentPipe-pipe.html" target="_blank"> percent </a> <span> pipes </span>
|
||||
<a href="../api/common/index/DatePipe-pipe.html" target="_blank">Date</a> <span>, </span> <a href="../api/common/index/CurrencyPipe-pipe.html" target="_blank">currency</a> <span>, </span> <a href="../api/common/index/DecimalPipe-pipe.html" target="_blank">decimal</a> <span> and </span> <a href="../api/common/index/PercentPipe-pipe.html" target="_blank">percent</a> <span> pipes</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[Intl API](guide/browser-support#intl)
|
||||
</td>
|
||||
|
||||
@ -349,11 +371,13 @@ Here are the features which may require additional polyfills:
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
<td>
|
||||
<a href="../api/common/index/NgClass-directive.html" target="_blank"> NgClass </a> <span> on SVG elements </span>
|
||||
<a href="../api/common/index/NgClass-directive.html" target="_blank">NgClass</a> <span> on SVG elements</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
[classList](guide/browser-support#classlist)
|
||||
|
||||
|
||||
[classList](guide/browser-support#classlist)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -365,11 +389,13 @@ Here are the features which may require additional polyfills:
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
<td>
|
||||
<a href="./server-communication.html"> Http </a> <span> when sending and receiving binary data </span>
|
||||
<a href="./server-communication.html">Http</a> <span> when sending and receiving binary data</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
[Typed Array](guide/browser-support#typedarray) <br>[Blob](guide/browser-support#blob)<br>[FormData](guide/browser-support#formdata)
|
||||
|
||||
|
||||
[Typed Array](guide/browser-support#typedarray) <br>[Blob](guide/browser-support#blob)<br>[FormData](guide/browser-support#formdata)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -380,6 +406,8 @@ Here are the features which may require additional polyfills:
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
### Suggested polyfills ##
|
||||
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
|
||||
|
||||
@ -405,7 +433,7 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<a id='core-es6' href="https://github.com/zloirock/core-js" target="_blank"> ES6 </a>
|
||||
<a id='core-es6' href="https://github.com/zloirock/core-js" target="_blank">ES6</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -421,7 +449,7 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<a id='classlist' href="https://github.com/eligrey/classList.js" target="_blank"> classList </a>
|
||||
<a id='classlist' href="https://github.com/eligrey/classList.js" target="_blank">classList</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -437,7 +465,7 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<a id='intl' href="https://github.com/andyearnshaw/Intl.js" target="_blank"> Intl </a>
|
||||
<a id='intl' href="https://github.com/andyearnshaw/Intl.js" target="_blank">Intl</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -453,7 +481,7 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<a id='web-animations' href="https://github.com/web-animations/web-animations-js" target="_blank"> Web Animations </a>
|
||||
<a id='web-animations' href="https://github.com/web-animations/web-animations-js" target="_blank">Web Animations</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -469,7 +497,7 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<a id='typedarray' href="https://github.com/inexorabletash/polyfill/blob/master/typedarray.js" target="_blank"> Typed Array </a>
|
||||
<a id='typedarray' href="https://github.com/inexorabletash/polyfill/blob/master/typedarray.js" target="_blank">Typed Array</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -485,7 +513,7 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<a id='blob' href="https://github.com/eligrey/Blob.js" target="_blank"> Blob </a>
|
||||
<a id='blob' href="https://github.com/eligrey/Blob.js" target="_blank">Blob</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -501,7 +529,7 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<a id='formdata' href="https://github.com/francois2metz/html5-formdata" target="_blank"> FormData </a>
|
||||
<a id='formdata' href="https://github.com/francois2metz/html5-formdata" target="_blank">FormData</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -516,5 +544,7 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
\* Figures are for minified and gzipped code,
|
||||
computed with the <a href="http://closure-compiler.appspot.com/home" target="_blank">closure compiler</a>.
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,8 @@ Cookbook
|
||||
A collection of recipes for common Angular application scenarios.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
The *Cookbook* offers answers to common implementation questions.
|
||||
|
||||
Each cookbook page is a collection of recipes focused on a particular Angular feature or application challenge
|
||||
@ -13,11 +15,15 @@ such as data binding, cross-component interaction, and communicating with a remo
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The cookbook is just getting started. Many more recipes are on the way.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Each cookbook links to a live sample with every recipe included.
|
||||
|
||||
Recipes are deliberately brief and code-centric.
|
||||
|
@ -6,6 +6,8 @@ An annotated history of recent documentation improvements.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
|
||||
The Angular documentation is a living document with continuous improvements.
|
||||
This log calls attention to recent significant changes.
|
||||
|
||||
@ -41,6 +43,7 @@ Read about moving your existing project to this structure in
|
||||
the QuickStart repo update instructions</a>.
|
||||
|
||||
Notably:
|
||||
|
||||
* `app/main.ts` moved to `src/main.ts`.
|
||||
* `app/` moved to `src/app/`.
|
||||
* `index.html`, `styles.css` and `tsconfig.json` moved inside `src/`.
|
||||
@ -56,15 +59,18 @@ Remember also that you can use both techniques in the same app,
|
||||
choosing the approach that best fits each scenario.
|
||||
|
||||
## NEW: Deployment guide (2017-01-30)
|
||||
|
||||
The new [Deployment](guide/deployment) guide describes techniques for putting your application on a server.
|
||||
It includes important advice on optimizing for production.
|
||||
|
||||
## Hierarchical Dependency Injection: refreshed (2017-01-13)
|
||||
|
||||
[Hierarchical Dependency Injection](guide/hierarchical-dependency-injection) guide is significantly revised.
|
||||
Closes issue #3086.
|
||||
Revised samples are clearer and cover all topics discussed.
|
||||
|
||||
## Miscellaneous (2017-01-05)
|
||||
|
||||
* [Setup](guide/setup) guide:
|
||||
added (optional) instructions on how to remove _non-essential_ files.
|
||||
* No longer consolidate RxJS operator imports in `rxjs-extensions` file; each file should import what it needs.
|
||||
@ -72,28 +78,34 @@ added (optional) instructions on how to remove _non-essential_ files.
|
||||
* [Style Guide](guide/style-guide): copy edits and revised rules.
|
||||
|
||||
## Router: more detail (2016-12-21)
|
||||
|
||||
Added more information to the [Router](guide/router) guide
|
||||
including sections named outlets, wildcard routes, and preload strategies.
|
||||
|
||||
## HTTP: how to set default request headers (and other request options) (2016-12-14)
|
||||
|
||||
Added section on how to set default request headers (and other request options) to
|
||||
[HTTP](guide/server-communication) guide.
|
||||
[HTTP](guide/server-communication#override-default-request-options) guide.
|
||||
|
||||
## Testing: added component test plunkers (2016-12-02)
|
||||
|
||||
Added two plunkers that each test _one simple component_ so you can write a component test plunker of your own: <live-example name="setup" plnkr="quickstart-specs">one</live-example> for the QuickStart seed's `AppComponent` and <live-example name="testing" plnkr="banner-specs">another</live-example> for the Testing guide's `BannerComponent`.
|
||||
Linked to these plunkers in [Testing](guide/testing) and [Setup anatomy](guide/setup-systemjs-anatomy) guides.
|
||||
Linked to these plunkers in [Testing](guide/testing#live-examples) and [Setup anatomy](guide/setup-systemjs-anatomy) guides.
|
||||
|
||||
## Internationalization: pluralization and _select_ (2016-11-30)
|
||||
|
||||
The [Internationalization (i18n)](cookbook/i18n) guide explains how to handle pluralization and
|
||||
translation of alternative texts with `select`.
|
||||
The sample demonstrates these features too.
|
||||
|
||||
## Testing: karma file updates (2016-11-30)
|
||||
|
||||
* `karma.config` + `karma-test-shim` can handle multiple spec source paths;
|
||||
see quickstart issue: [angular/quickstart#294](https://github.com/angular/quickstart/issues/294).
|
||||
* Displays Jasmine Runner output in the karma-launched browser.
|
||||
|
||||
## QuickStart Rewrite (2016-11-18)
|
||||
|
||||
The QuickStart is completely rewritten so that it actually is quick.
|
||||
It references a minimal "Hello Angular" app running in Plunker.
|
||||
The new [Setup](guide/setup) page tells you how to install a local development environment
|
||||
@ -101,9 +113,11 @@ by downloading (or cloning) the QuickStart github repository.
|
||||
You are no longer asked to copy-and-paste code into setup files that were not explained anyway.
|
||||
|
||||
## Sync with Angular v.2.2.0 (2016-11-14)
|
||||
|
||||
Docs and code samples updated and tested with Angular v.2.2.0.
|
||||
|
||||
## UPDATE: NgUpgrade Guide for the AOT friendly _upgrade/static_ module (2016-11-14)
|
||||
|
||||
The updated [NgUpgrade Guide](guide/upgrade) guide covers the
|
||||
new AOT friendly `upgrade/static` module
|
||||
released in v.2.2.0, which is the recommended
|
||||
@ -111,15 +125,18 @@ facility for migrating from AngularJS to Angular.
|
||||
The documentation for the version prior to v.2.2.0 has been removed.
|
||||
|
||||
## ES6 described in "TypeScript to JavaScript" (2016-11-14)
|
||||
|
||||
The updated [TypeScript to JavaScript](cookbook/ts-to-js) cookbook
|
||||
now explains how to write apps in ES6/7
|
||||
by translating the common idioms in the TypeScript documentation examples
|
||||
(and elsewhere on the web) to ES6/7 and ES5.
|
||||
|
||||
## Sync with Angular v.2.1.1 (2016-10-21)
|
||||
|
||||
Docs and code samples updated and tested with Angular v.2.1.1.
|
||||
|
||||
## npm _@types_ packages replace _typings_ (2016-10-20)
|
||||
|
||||
Documentation samples now get TypeScript type information for 3rd party libraries
|
||||
from npm `@types` packages rather than with the _typings_ tooling.
|
||||
The `typings.json` file is gone.
|
||||
@ -129,34 +146,41 @@ The `package.json` installs `@types/angular` and several `@types/angular-...`
|
||||
packages in support of upgrade; these are not needed for pure Angular development.
|
||||
|
||||
## "Template Syntax" explains two-way data binding syntax (2016-10-20)
|
||||
|
||||
Demonstrates how to two-way data bind to a custom Angular component and
|
||||
re-explains `[(ngModel)]` in terms of the basic `[()]` syntax.
|
||||
|
||||
## BREAKING CHANGE: `in-memory-web-api` (v.0.1.11) delivered as esm umd (2016-10-19)
|
||||
|
||||
This change supports ES6 developers and aligns better with typical Angular libraries.
|
||||
It does not affect the module's API but it does affect how you load and import it.
|
||||
See the <a href="https://github.com/angular/in-memory-web-api/blob/master/CHANGELOG.md#0113-2016-10-20" target="_blank">change note</a>
|
||||
in the `in-memory-web-api` repo.
|
||||
|
||||
## "Router" _preload_ syntax and _:enter_/_:leave_ animations (2016-10-19)
|
||||
|
||||
The router can lazily _preload_ modules _after_ the app starts and
|
||||
_before_ the user navigates to them for improved perceived performance.
|
||||
|
||||
New `:enter` and `:leave` aliases make animation more natural.
|
||||
|
||||
## Sync with Angular v.2.1.0 (2016-10-12)
|
||||
|
||||
Docs and code samples updated and tested with Angular v.2.1.0.
|
||||
|
||||
## NEW "Ahead of time (AOT) Compilation" cookbook (2016-10-11)
|
||||
|
||||
The NEW [Ahead of time (AOT) Compilation](cookbook/aot-compiler) cookbook
|
||||
explains what AOT compilation is and why you'd want it.
|
||||
It demonstrates the basics with a QuickStart app
|
||||
followed by the more advanced considerations of compiling and bundling the Tour of Heroes.
|
||||
|
||||
## Sync with Angular v.2.0.2 (2016-10-6)
|
||||
|
||||
Docs and code samples updated and tested with Angular v.2.0.2.
|
||||
|
||||
## "Routing and Navigation" guide with the _Router Module_ (2016-10-5)
|
||||
|
||||
The [Routing and Navigation](guide/router) guide now locates route configuration
|
||||
in a _Routing Module_.
|
||||
The _Routing Module_ replaces the previous _routing object_ involving the `ModuleWithProviders`.
|
||||
|
@ -1,4 +1,6 @@
|
||||
@description
|
||||
|
||||
|
||||
Good tools make application development quicker and easier to maintain than
|
||||
if you did everything by hand.
|
||||
|
||||
@ -29,6 +31,8 @@ And you can also <a href="/resources/zips/cli-quickstart/cli-quickstart.zip">dow
|
||||
Step 1. Set up the Development Environment
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
You need to set up your development environment before you can do anything.
|
||||
|
||||
Install **[Node.js® and npm](https://nodejs.org/en/download/)**
|
||||
@ -36,12 +40,16 @@ if they are not already on your machine.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
**Verify that you are running at least node `6.9.x` and npm `3.x.x`**
|
||||
by running `node -v` and `npm -v` in a terminal/console window.
|
||||
Older versions produce errors, but newer versions are fine.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Then **install the [Angular CLI](https://github.com/angular/angular-cli)** globally.
|
||||
|
||||
|
||||
@ -57,7 +65,11 @@ Then **install the [Angular CLI](https://github.com/angular/angular-cli)** globa
|
||||
Step 2. Create a new project
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
Open a terminal window.
|
||||
|
||||
|
||||
Generate a new project and skeleton application by running the following commands:
|
||||
|
||||
|
||||
@ -70,6 +82,8 @@ Generate a new project and skeleton application by running the following command
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Patience please.
|
||||
It takes time to set up a new project, most of it spent installing npm packages.
|
||||
|
||||
@ -83,15 +97,19 @@ It takes time to set up a new project, most of it spent installing npm packages.
|
||||
Step 3: Serve the application
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
Go to the project directory and launch the server.
|
||||
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
cd my-app
|
||||
ng serve --open
|
||||
ng serve --open
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `ng serve` command launches the server, watches your files,
|
||||
and rebuilds the app as you make changes to those files.
|
||||
|
||||
@ -102,7 +120,7 @@ Your app greets you with a message:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/cli-quickstart/app-works.png' alt="The app works!"> </img>
|
||||
<img src='assets/images/devguide/cli-quickstart/app-works.png' alt="The app works!"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
@ -112,33 +130,43 @@ Your app greets you with a message:
|
||||
Step 4: Edit your first Angular component
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
The CLI created the first Angular component for you.
|
||||
This is the _root component_ and it is named `app-root`.
|
||||
You can find it in `./src/app/app.component.ts`.
|
||||
|
||||
|
||||
Open the component file and change the `title` property from _app works!_ to _My First Angular App_:
|
||||
|
||||
|
||||
<code-example path="cli-quickstart/src/app/app.component.ts" region="title" linenums="false">
|
||||
<code-example path="cli-quickstart/src/app/app.component.ts" region="title" title="src/app/app.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The browser reloads automatically with the revised title. That's nice, but it could look better.
|
||||
|
||||
Open `src/app/app.component.css` and give the component some style.
|
||||
|
||||
|
||||
<code-example path="cli-quickstart/src/app/app.component.css" linenums="false">
|
||||
<code-example path="cli-quickstart/src/app/app.component.css" title="src/app/app.component.css" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/cli-quickstart/my-first-app.png' alt="Output of QuickStart app"> </img>
|
||||
<img src='assets/images/devguide/cli-quickstart/my-first-app.png' alt="Output of QuickStart app"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Looking good!
|
||||
|
||||
|
||||
|
||||
## What's next?
|
||||
That's about all you'd expect to do in a "Hello, World" app.
|
||||
|
||||
@ -147,6 +175,8 @@ a small application that demonstrates the great things you can build with Angula
|
||||
|
||||
Or you can stick around a bit longer to learn about the files in your brand new project.
|
||||
|
||||
|
||||
|
||||
## Project file review
|
||||
|
||||
An Angular CLI project is the foundation for both quick experiments and enterprise solutions.
|
||||
@ -159,6 +189,8 @@ Whenever you want to know more about how Angular CLI works make sure to visit
|
||||
|
||||
Some of the generated files might be unfamiliar to you.
|
||||
|
||||
|
||||
|
||||
### The `src` folder
|
||||
Your app lives in the `src` folder.
|
||||
All Angular components, templates, styles, images, and anything else your app needs go here.
|
||||
@ -286,9 +318,11 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Defines the `AppComponent` along with an HTML template, CSS stylesheet, and a unit test.
|
||||
It is the **root** component of what will become a tree of nested components
|
||||
as the application evolves.
|
||||
It is the **root** component of what will become a tree of nested components
|
||||
as the application evolves.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -300,9 +334,11 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Defines `AppModule`, the [root module](guide/guide/appmodule) that tells Angular how to assemble the application.
|
||||
Right now it declares only the `AppComponent`.
|
||||
Soon there will be more components to declare.
|
||||
|
||||
|
||||
Defines `AppModule`, the [root module](guide/guide/appmodule "AppModule: the root module") that tells Angular how to assemble the application.
|
||||
Right now it declares only the `AppComponent`.
|
||||
Soon there will be more components to declare.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -314,8 +350,10 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
A folder where you can put images and anything else to be copied wholesale
|
||||
when you build your application.
|
||||
when you build your application.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -327,13 +365,15 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
This folder contains one file for each of your destination environments,
|
||||
each exporting simple configuration variables to use in your application.
|
||||
The files are replaced on-the-fly when you build your app.
|
||||
You might use a different API endpoint for development than you do for production
|
||||
or maybe different analytics tokens.
|
||||
You might even use some mock services.
|
||||
Either way, the CLI has you covered.
|
||||
each exporting simple configuration variables to use in your application.
|
||||
The files are replaced on-the-fly when you build your app.
|
||||
You might use a different API endpoint for development than you do for production
|
||||
or maybe different analytics tokens.
|
||||
You might even use some mock services.
|
||||
Either way, the CLI has you covered.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -345,8 +385,10 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Every site wants to look good on the bookmark bar.
|
||||
Get started with your very own Angular icon.
|
||||
Get started with your very own Angular icon.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -358,10 +400,12 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
The main HTML page that is served when someone visits your site.
|
||||
Most of the time you'll never need to edit it.
|
||||
The CLI automatically adds all `js` and `css` files when building your app so you
|
||||
never need to add any `<script>` or `<link>` tags here manually.
|
||||
Most of the time you'll never need to edit it.
|
||||
The CLI automatically adds all `js` and `css` files when building your app so you
|
||||
never need to add any `<script>` or `<link>` tags here manually.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -373,11 +417,13 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
The main entry point for your app.
|
||||
Compiles the application with the [JIT compiler](guide/glossary)
|
||||
and bootstraps the application's root module (`AppModule`) to run in the browser.
|
||||
You can also use the [AOT compiler](guide/glossary)
|
||||
without changing any code by passing in `--aot` to `ng build` or `ng serve`.
|
||||
Compiles the application with the [JIT compiler](guide/glossary#jit)
|
||||
and bootstraps the application's root module (`AppModule`) to run in the browser.
|
||||
You can also use the [AOT compiler](guide/glossary#ahead-of-time-aot-compilation)
|
||||
without changing any code by passing in `--aot` to `ng build` or `ng serve`.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -389,10 +435,12 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Different browsers have different levels of support of the web standards.
|
||||
Polyfills help normalize those differences.
|
||||
You should be pretty safe with `core-js` and `zone.js`, but be sure to check out
|
||||
the [Browser Support guide](guide/guide/browser-support) for more information.
|
||||
Polyfills help normalize those differences.
|
||||
You should be pretty safe with `core-js` and `zone.js`, but be sure to check out
|
||||
the [Browser Support guide](guide/guide/browser-support) for more information.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -404,9 +452,11 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Your global styles go here.
|
||||
Most of the time you'll want to have local styles in your components for easier maintenance,
|
||||
but styles that affect all of your app need to be in a central place.
|
||||
Most of the time you'll want to have local styles in your components for easier maintenance,
|
||||
but styles that affect all of your app need to be in a central place.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -418,9 +468,11 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
This is the main entry point for your unit tests.
|
||||
It has some custom configuration that might be unfamiliar, but it's not something you'll
|
||||
need to edit.
|
||||
It has some custom configuration that might be unfamiliar, but it's not something you'll
|
||||
need to edit.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -432,8 +484,10 @@ Any files outside of this folder are meant to support building your app.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
TypeScript compiler configuration for the Angular app (`tsconfig.app.json`)
|
||||
and for the unit tests (`tsconfig.spec.json`).
|
||||
and for the unit tests (`tsconfig.spec.json`).
|
||||
|
||||
|
||||
</td>
|
||||
@ -443,6 +497,8 @@ Any files outside of this folder are meant to support building your app.
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
### The root folder
|
||||
The `src/` folder is just one of the items inside the project's root folder.
|
||||
Other files help you build, test, maintain, document, and deploy the app.
|
||||
@ -554,10 +610,12 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Inside `e2e/` live the End-to-End tests.
|
||||
They shouldn't be inside `src/` because e2e tests are really a separate app that
|
||||
just so happens to test your main app.
|
||||
That's also why they have their own `tsconfig.e2e.json`.
|
||||
They shouldn't be inside `src/` because e2e tests are really a separate app that
|
||||
just so happens to test your main app.
|
||||
That's also why they have their own `tsconfig.e2e.json`.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -569,8 +627,10 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
`Node.js` creates this folder and puts all third party modules listed in
|
||||
`package.json` inside of it.
|
||||
`package.json` inside of it.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -582,10 +642,12 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Configuration for Angular CLI.
|
||||
In this file you can set several defaults and also configure what files are included
|
||||
when your project is build.
|
||||
Check out the official documentation if you want to know more.
|
||||
In this file you can set several defaults and also configure what files are included
|
||||
when your project is build.
|
||||
Check out the official documentation if you want to know more.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -597,10 +659,12 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Simple configuration for your editor to make sure everyone that uses your project
|
||||
has the same basic configuration.
|
||||
Most editors support an `.editorconfig` file.
|
||||
See http://editorconfig.org for more information.
|
||||
has the same basic configuration.
|
||||
Most editors support an `.editorconfig` file.
|
||||
See http://editorconfig.org for more information.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -612,6 +676,8 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Git configuration to make sure autogenerated files are not commited to source control.
|
||||
</td>
|
||||
|
||||
@ -624,8 +690,10 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Unit test configuration for the [Karma test runner](https://karma-runner.github.io),
|
||||
used when running `ng test`.
|
||||
used when running `ng test`.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -637,8 +705,10 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
`npm` configuration listing the third party packages your project uses.
|
||||
You can also add your own [custom scripts](https://docs.npmjs.com/misc/scripts) here.
|
||||
You can also add your own [custom scripts](https://docs.npmjs.com/misc/scripts) here.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -650,8 +720,10 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
End-to-end test configuration for [Protractor](http://www.protractortest.org/),
|
||||
used when running `ng e2e`.
|
||||
used when running `ng e2e`.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -663,9 +735,11 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Basic documentation for your project, pre-filled with CLI command information.
|
||||
Make sure to enhance it with project documentation so that anyone
|
||||
checking out the repo can build your app!
|
||||
Make sure to enhance it with project documentation so that anyone
|
||||
checking out the repo can build your app!
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -677,6 +751,8 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
TypeScript compiler configuration for your IDE to pick up and give you helpful tooling.
|
||||
</td>
|
||||
|
||||
@ -689,9 +765,11 @@ These files go in the root folder next to `src/`.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Linting configuration for [TSLint](https://palantir.github.io/tslint/) together with
|
||||
[Codelyzer](http://codelyzer.com/), used when running `ng lint`.
|
||||
Linting helps keep your code style consistent.
|
||||
[Codelyzer](http://codelyzer.com/), used when running `ng lint`.
|
||||
Linting helps keep your code style consistent.
|
||||
|
||||
</td>
|
||||
|
||||
@ -703,10 +781,12 @@ These files go in the root folder next to `src/`.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
### Next Step
|
||||
|
||||
If you're new to Angular, continue on the
|
||||
[learning path](guide/guide/learning-angular).
|
||||
[learning path](guide/guide/learning-angular "Angular learning path").
|
||||
You can skip the "Setup" step since you're already using the Angular CLI setup.
|
||||
|
||||
~~~
|
||||
|
@ -5,28 +5,38 @@ Component Interaction
|
||||
Share information between different directives and components.
|
||||
|
||||
@description
|
||||
<a id="top"></a>This cookbook contains recipes for common component communication scenarios
|
||||
in which two or more components share information.
|
||||
<a id="toc"></a># Contents
|
||||
{@a top}
|
||||
|
||||
This cookbook contains recipes for common component communication scenarios
|
||||
in which two or more components share information.
|
||||
{@a toc}
|
||||
|
||||
# Contents
|
||||
|
||||
* [Pass data from parent to child with input binding](guide/component-communication#parent-to-child)
|
||||
* [Intercept input property changes with a setter](guide/component-communication#parent-to-child-setter)
|
||||
* [Intercept input property changes with `ngOnChanges()`](guide/component-communication#parent-to-child-on-changes)
|
||||
* [Parent calls an `@ViewChild()`](guide/component-communication#parent-to-view-child)
|
||||
* [Parent and children communicate via a service](guide/component-communication#bidirectional-service)
|
||||
|
||||
|
||||
- [Pass data from parent to child with input binding](guide/component-communication#parent-to-child)
|
||||
- [Intercept input property changes with a setter](guide/component-communication#parent-to-child-setter)
|
||||
- [Intercept input property changes with `ngOnChanges()`](guide/component-communication#parent-to-child-on-changes)
|
||||
- [Parent calls an `@ViewChild()`](guide/component-communication#parent-to-view-child)
|
||||
- [Parent and children communicate via a service](guide/component-communication#bidirectional-service)
|
||||
|
||||
**See the <live-example name="cb-component-communication"></live-example>**.
|
||||
|
||||
<a id="parent-to-child"></a>## Pass data from parent to child with input binding
|
||||
{@a parent-to-child}
|
||||
|
||||
## Pass data from parent to child with input binding
|
||||
|
||||
`HeroChildComponent` has two ***input properties***,
|
||||
typically adorned with [@Input decorations](guide/template-syntax).
|
||||
typically adorned with [@Input decorations](guide/template-syntax#inputs-outputs).
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/hero-child.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/hero-child.component.ts" title="cb-component-communication/src/app/hero-child.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The second `@Input` aliases the child component property name `masterName` as `'master'`.
|
||||
|
||||
The `HeroParentComponent` nests the child `HeroChildComponent` inside an `*ngFor` repeater,
|
||||
@ -34,29 +44,37 @@ binding its `master` string property to the child's `master` alias,
|
||||
and each iteration's `hero` instance to the child's `hero` property.
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/hero-parent.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/hero-parent.component.ts" title="cb-component-communication/src/app/hero-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The running application displays three heroes:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/component-communication/parent-to-child.png" alt="Parent-to-child"> </img>
|
||||
<img src="assets/images/cookbooks/component-communication/parent-to-child.png" alt="Parent-to-child"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
### Test it
|
||||
|
||||
E2E test that all children were instantiated and displayed as expected:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child">
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child" title="cb-component-communication/e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
<a id="parent-to-child-setter"></a>## Intercept input property changes with a setter
|
||||
{@a parent-to-child-setter}
|
||||
|
||||
## Intercept input property changes with a setter
|
||||
|
||||
Use an input property setter to intercept and act upon a value from the parent.
|
||||
|
||||
@ -64,122 +82,154 @@ The setter of the `name` input property in the child `NameChildComponent`
|
||||
trims the whitespace from a name and replaces an empty value with default text.
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/name-child.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/name-child.component.ts" title="cb-component-communication/src/app/name-child.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here's the `NameParentComponent` demonstrating name variations including a name with all spaces:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/name-parent.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/name-parent.component.ts" title="cb-component-communication/src/app/name-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/component-communication/setter.png" alt="Parent-to-child-setter"> </img>
|
||||
<img src="assets/images/cookbooks/component-communication/setter.png" alt="Parent-to-child-setter"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
### Test it
|
||||
|
||||
E2E tests of input property setter with empty and non-empty names:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child-setter">
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child-setter" title="cb-component-communication/e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
<a id="parent-to-child-on-changes"></a>## Intercept input property changes with *ngOnChanges()*
|
||||
{@a parent-to-child-on-changes}
|
||||
|
||||
## Intercept input property changes with *ngOnChanges()*
|
||||
|
||||
Detect and act upon changes to input property values with the `ngOnChanges()` method of the `OnChanges` lifecycle hook interface.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
You may prefer this approach to the property setter when watching multiple, interacting input properties.
|
||||
|
||||
Learn about `ngOnChanges()` in the [LifeCycle Hooks](guide/lifecycle-hooks) chapter.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
This `VersionChildComponent` detects changes to the `major` and `minor` input properties and composes a log message reporting these changes:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/version-child.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/version-child.component.ts" title="cb-component-communication/src/app/version-child.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `VersionParentComponent` supplies the `minor` and `major` values and binds buttons to methods that change them.
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/version-parent.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/version-parent.component.ts" title="cb-component-communication/src/app/version-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here's the output of a button-pushing sequence:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/component-communication/parent-to-child-on-changes.gif" alt="Parent-to-child-onchanges"> </img>
|
||||
<img src="assets/images/cookbooks/component-communication/parent-to-child-on-changes.gif" alt="Parent-to-child-onchanges"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
### Test it
|
||||
|
||||
Test that ***both*** input properties are set initially and that button clicks trigger
|
||||
the expected `ngOnChanges` calls and values:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child-onchanges">
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="parent-to-child-onchanges" title="cb-component-communication/e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
<a id="child-to-parent"></a>## Parent listens for child event
|
||||
{@a child-to-parent}
|
||||
|
||||
## Parent listens for child event
|
||||
|
||||
The child component exposes an `EventEmitter` property with which it `emits` events when something happens.
|
||||
The parent binds to that event property and reacts to those events.
|
||||
|
||||
The child's `EventEmitter` property is an ***output property***,
|
||||
typically adorned with an [@Output decoration](guide/template-syntax)
|
||||
typically adorned with an [@Output decoration](guide/template-syntax#inputs-outputs)
|
||||
as seen in this `VoterComponent`:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/voter.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/voter.component.ts" title="cb-component-communication/src/app/voter.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Clicking a button triggers emission of a `true` or `false`, the boolean *payload*.
|
||||
|
||||
The parent `VoteTakerComponent` binds an event handler called `onVoted()` that responds to the child event
|
||||
payload `$event` and updates a counter.
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/votetaker.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/votetaker.component.ts" title="cb-component-communication/src/app/votetaker.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The framework passes the event argument—represented by `$event`—to the handler method,
|
||||
and the method processes it:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/component-communication/child-to-parent.gif" alt="Child-to-parent"> </img>
|
||||
<img src="assets/images/cookbooks/component-communication/child-to-parent.gif" alt="Child-to-parent"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
### Test it
|
||||
|
||||
Test that clicking the *Agree* and *Disagree* buttons update the appropriate counters:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="child-to-parent">
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="child-to-parent" title="cb-component-communication/e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
|
||||
|
||||
## Parent interacts with child via *local variable*
|
||||
|
||||
A parent component cannot use data binding to read child properties
|
||||
@ -188,22 +238,26 @@ by creating a template reference variable for the child element
|
||||
and then reference that variable *within the parent template*
|
||||
as seen in the following example.
|
||||
|
||||
<a id="countdown-timer-example"></a>
|
||||
{@a countdown-timer-example}
|
||||
The following is a child `CountdownTimerComponent` that repeatedly counts down to zero and launches a rocket.
|
||||
It has `start` and `stop` methods that control the clock and it displays a
|
||||
countdown status message in its own template.
|
||||
|
||||
<code-example path="cb-component-communication/src/app/countdown-timer.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/countdown-timer.component.ts" title="cb-component-communication/src/app/countdown-timer.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `CountdownLocalVarParentComponent` that hosts the timer component is as follows:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/countdown-parent.component.ts" region="lv">
|
||||
<code-example path="cb-component-communication/src/app/countdown-parent.component.ts" region="lv" title="cb-component-communication/src/app/countdown-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The parent component cannot data bind to the child's
|
||||
`start` and `stop` methods nor to its `seconds` property.
|
||||
|
||||
@ -218,12 +272,14 @@ Here we see the parent and child working together.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/component-communication/countdown-timer-anim.gif" alt="countdown timer"> </img>
|
||||
<img src="assets/images/cookbooks/component-communication/countdown-timer-anim.gif" alt="countdown timer"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
{@a countdown-tests}
|
||||
|
||||
|
||||
### Test it
|
||||
|
||||
Test that the seconds displayed in the parent template
|
||||
@ -231,13 +287,17 @@ match the seconds displayed in the child's status message.
|
||||
Test also that clicking the *Stop* button pauses the countdown timer:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="countdown-timer-tests">
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="countdown-timer-tests" title="cb-component-communication/e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
<a id="parent-to-view-child"></a>## Parent calls an _@ViewChild()_
|
||||
{@a parent-to-view-child}
|
||||
|
||||
## Parent calls an _@ViewChild()_
|
||||
|
||||
The *local variable* approach is simple and easy. But it is limited because
|
||||
the parent-child wiring must be done entirely within the parent template.
|
||||
@ -256,17 +316,23 @@ The child [CountdownTimerComponent](guide/component-communication#countdown-time
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The switch from the *local variable* to the *ViewChild* technique
|
||||
is solely for the purpose of demonstration.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Here is the parent, `CountdownViewChildParentComponent`:
|
||||
|
||||
<code-example path="cb-component-communication/src/app/countdown-parent.component.ts" region="vc">
|
||||
<code-example path="cb-component-communication/src/app/countdown-parent.component.ts" region="vc" title="cb-component-communication/src/app/countdown-parent.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
It takes a bit more work to get the child view into the parent component *class*.
|
||||
|
||||
First, you have to import references to the `ViewChild` decorator and the `AfterViewInit` lifecycle hook.
|
||||
@ -293,9 +359,13 @@ Use `setTimeout()` to wait one tick and then revise the `seconds()` method so
|
||||
that it takes future values from the timer component.
|
||||
|
||||
### Test it
|
||||
Use [the same countdown timer tests](guide/component-communication#countdown-tests) as before.[Back to top](guide/component-communication#top)
|
||||
Use [the same countdown timer tests](guide/component-communication#countdown-tests) as before.
|
||||
|
||||
<a id="bidirectional-service"></a>## Parent and children communicate via a service
|
||||
[Back to top](guide/component-communication#top)
|
||||
|
||||
{@a bidirectional-service}
|
||||
|
||||
## Parent and children communicate via a service
|
||||
|
||||
A parent component and its children share a service whose interface enables bi-directional communication
|
||||
*within the family*.
|
||||
@ -306,23 +376,27 @@ Components outside this component subtree have no access to the service or their
|
||||
This `MissionService` connects the `MissionControlComponent` to multiple `AstronautComponent` children.
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/mission.service.ts">
|
||||
<code-example path="cb-component-communication/src/app/mission.service.ts" title="cb-component-communication/src/app/mission.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `MissionControlComponent` both provides the instance of the service that it shares with its children
|
||||
(through the `providers` metadata array) and injects that instance into itself through its constructor:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/missioncontrol.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/missioncontrol.component.ts" title="cb-component-communication/src/app/missioncontrol.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `AstronautComponent` also injects the service in its constructor.
|
||||
Each `AstronautComponent` is a child of the `MissionControlComponent` and therefore receives its parent's service instance:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/src/app/astronaut.component.ts">
|
||||
<code-example path="cb-component-communication/src/app/astronaut.component.ts" title="cb-component-communication/src/app/astronaut.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -330,6 +404,8 @@ Each `AstronautComponent` is a child of the `MissionControlComponent` and theref
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Notice that this example captures the `subscription` and `unsubscribe()` when the `AstronautComponent` is destroyed.
|
||||
This is a memory-leak guard step. There is no actual risk in this app because the
|
||||
lifetime of a `AstronautComponent` is the same as the lifetime of the app itself.
|
||||
@ -340,23 +416,29 @@ it controls the lifetime of the `MissionService`.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The *History* log demonstrates that messages travel in both directions between
|
||||
the parent `MissionControlComponent` and the `AstronautComponent` children,
|
||||
facilitated by the service:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/component-communication/bidirectional-service.gif" alt="bidirectional-service"> </img>
|
||||
<img src="assets/images/cookbooks/component-communication/bidirectional-service.gif" alt="bidirectional-service"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
### Test it
|
||||
|
||||
Tests click buttons of both the parent `MissionControlComponent` and the `AstronautComponent` children
|
||||
and verify that the history meets expectations:
|
||||
|
||||
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="bidirectional-service">
|
||||
<code-example path="cb-component-communication/e2e-spec.ts" region="bidirectional-service" title="cb-component-communication/e2e-spec.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/component-communication#top)
|
@ -6,6 +6,8 @@ Learn how to apply CSS styles to components.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
|
||||
Angular applications are styled with standard CSS. That means you can apply
|
||||
everything you know about CSS stylesheets, selectors, rules, and media queries
|
||||
directly to Angular applications.
|
||||
@ -26,6 +28,8 @@ This page describes how to load and apply these component styles.
|
||||
|
||||
You can run the <live-example></live-example> in Plunker and download the code from there.
|
||||
|
||||
|
||||
|
||||
## Using component styles
|
||||
|
||||
For every Angular component you write, you may define not only an HTML template,
|
||||
@ -37,10 +41,12 @@ The `styles` property takes an array of strings that contain CSS code.
|
||||
Usually you give it one string, as in the following example:
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts" title="src/app/hero-app.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The selectors you put into a component's styles apply only within the template
|
||||
of that component. The `h1` selector in the preceding example applies only to the `<h1>` tag
|
||||
in the template of `HeroAppComponent`. Any `<h1>` elements elsewhere in
|
||||
@ -60,6 +66,8 @@ This is a big improvement in modularity compared to how CSS traditionally works.
|
||||
|
||||
{@a special-selectors}
|
||||
|
||||
|
||||
|
||||
## Special selectors
|
||||
|
||||
Component styles have a few special *selectors* from the world of shadow DOM style scoping
|
||||
@ -73,10 +81,12 @@ Use the `:host` pseudo-class selector to target styles in the element that *host
|
||||
targeting elements *inside* the component's template).
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="host" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="host" title="src/app/hero-details.component.css" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `:host` selector is the only way to target the host element. You can't reach
|
||||
the host element from inside the component with other selectors because it's not part of the
|
||||
component's own template. The host element is in a parent component's template.
|
||||
@ -87,10 +97,12 @@ including another selector inside parentheses after `:host`.
|
||||
The next example targets the host element again, but only when it also has the `active` CSS class.
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostfunction" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostfunction" title="src/app/hero-details.component.css" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### :host-context
|
||||
|
||||
Sometimes it's useful to apply styles based on some condition *outside* of a component's view.
|
||||
@ -105,10 +117,12 @@ The following example applies a `background-color` style to all `<h2>` elements
|
||||
if some ancestor element has the CSS class `theme-light`.
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostcontext" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="hostcontext" title="src/app/hero-details.component.css" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### /deep/
|
||||
|
||||
Component styles normally apply only to the HTML in the component's own template.
|
||||
@ -120,15 +134,19 @@ children and content children of the component.
|
||||
The following example targets all `<h3>` elements, from the host element down
|
||||
through this component to all of its child elements in the DOM.
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="deep" linenums="false">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="deep" title="src/app/hero-details.component.css" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `/deep/` selector also has the alias `>>>`. You can use either interchangeably.
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
Use the `/deep/` and `>>>` selectors only with *emulated* view encapsulation.
|
||||
Emulated is the default and most commonly used view encapsulation. For more information, see the
|
||||
[Controlling view encapsulation](guide/component-styles#view-encapsulation) section.
|
||||
@ -140,9 +158,12 @@ Emulated is the default and most commonly used view encapsulation. For more info
|
||||
|
||||
{@a loading-styles}
|
||||
|
||||
|
||||
|
||||
## Loading styles into components
|
||||
|
||||
There are several ways to add styles to a component:
|
||||
|
||||
* By setting `styles` or `styleUrls` metadata.
|
||||
* Inline in the template HTML.
|
||||
* With CSS imports.
|
||||
@ -155,17 +176,19 @@ You can add a `styles` array property to the `@Component` decorator.
|
||||
Each string in the array (usually just one string) defines the CSS.
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts">
|
||||
<code-example path="component-styles/src/app/hero-app.component.ts" title="src/app/hero-app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### Style URLs in metadata
|
||||
|
||||
You can load styles from external CSS files by adding a `styleUrls` attribute
|
||||
into a component's `@Component` decorator:
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.ts" region="styleurls">
|
||||
<code-example path="component-styles/src/app/hero-details.component.ts" region="styleurls" title="src/app/hero-details.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -173,6 +196,8 @@ into a component's `@Component` decorator:
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
The URL is relative to the *application root*, which is usually the
|
||||
location of the `index.html` web page that hosts the application.
|
||||
The style file URL is *not* relative to the component file.
|
||||
@ -186,6 +211,8 @@ To specify a URL relative to the component file, see [Appendix 2](guide/componen
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
If you use module bundlers like Webpack, you can also use the `styles` attribute
|
||||
to load styles from external files at build time. You could write:
|
||||
|
||||
@ -200,16 +227,20 @@ For information on loading CSS in this manner, refer to the module bundler's doc
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
### Template inline styles
|
||||
|
||||
You can embed styles directly into the HTML template by putting them
|
||||
inside `<style>` tags.
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-controls.component.ts" region="inlinestyles">
|
||||
<code-example path="component-styles/src/app/hero-controls.component.ts" region="inlinestyles" title="src/app/hero-controls.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### Template link tags
|
||||
|
||||
You can also embed `<link>` tags into the component's HTML template.
|
||||
@ -218,19 +249,23 @@ As with `styleUrls`, the link tag's `href` URL is relative to the
|
||||
application root, not the component file.
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-team.component.ts" region="stylelink">
|
||||
<code-example path="component-styles/src/app/hero-team.component.ts" region="stylelink" title="src/app/hero-team.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### CSS @imports
|
||||
|
||||
You can also import CSS files into the CSS files using the standard CSS `@import` rule.
|
||||
For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import)
|
||||
on the [MDN](https://developer.mozilla.org) site.
|
||||
|
||||
|
||||
In this case, the URL is relative to the CSS file into which you're importing.
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="import">
|
||||
<code-example path="component-styles/src/app/hero-details.component.css" region="import" title="src/app/hero-details.component.css (excerpt)">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -238,6 +273,8 @@ In this case, the URL is relative to the CSS file into which you're importing.
|
||||
|
||||
{@a view-encapsulation}
|
||||
|
||||
|
||||
|
||||
## Controlling view encapsulation: native, emulated, and none
|
||||
|
||||
As discussed earlier, component CSS styles are encapsulated into the component's view and don't
|
||||
@ -252,9 +289,11 @@ Choose from the following modes:
|
||||
on the [MDN](https://developer.mozilla.org) site)
|
||||
to attach a shadow DOM to the component's host element, and then puts the component
|
||||
view inside that shadow DOM. The component's styles are included within the shadow DOM.
|
||||
|
||||
* `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing
|
||||
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
||||
For details, see [Appendix 1](guide/component-styles#inspect-generated-css).
|
||||
|
||||
* `None` means that Angular does no view encapsulation.
|
||||
Angular adds the CSS to the global styles.
|
||||
The scoping rules, isolations, and protections discussed earlier don't apply.
|
||||
@ -263,10 +302,12 @@ Choose from the following modes:
|
||||
To set the components encapsulation mode, use the `encapsulation` property in the component metadata:
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" linenums="false">
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" title="src/app/quest-summary.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
`Native` view encapsulation only works on browsers that have native support
|
||||
for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the
|
||||
[Can I use](http://caniuse.com) site). The support is still limited,
|
||||
@ -276,6 +317,8 @@ in most cases.
|
||||
|
||||
{@a inspect-generated-css}
|
||||
|
||||
|
||||
|
||||
## Appendix 1: Inspecting the CSS generated in emulated view encapsulation
|
||||
|
||||
When using emulated view encapsulation, Angular preprocesses
|
||||
@ -288,15 +331,18 @@ attached to it:
|
||||
|
||||
<code-example format="">
|
||||
<hero-details _nghost-pmm-5>
|
||||
<h2 _ngcontent-pmm-5>Mister Fantastic</h2>
|
||||
<hero-team _ngcontent-pmm-5 _nghost-pmm-6>
|
||||
<h3 _ngcontent-pmm-6>Team</h3>
|
||||
</hero-team>
|
||||
</hero-detail>
|
||||
<h2 _ngcontent-pmm-5>Mister Fantastic</h2>
|
||||
<hero-team _ngcontent-pmm-5 _nghost-pmm-6>
|
||||
<h3 _ngcontent-pmm-6>Team</h3>
|
||||
</hero-team>
|
||||
</hero-detail>
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
There are two kinds of generated attributes:
|
||||
|
||||
* An element that would be a shadow DOM host in native encapsulation has a
|
||||
generated `_nghost` attribute. This is typically the case for component host elements.
|
||||
* An element within a component's view has a `_ngcontent` attribute
|
||||
@ -309,17 +355,19 @@ by the generated component styles, which are in the `<head>` section of the DOM:
|
||||
|
||||
<code-example format="">
|
||||
[_nghost-pmm-5] {
|
||||
display: block;
|
||||
border: 1px solid black;
|
||||
}
|
||||
display: block;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
h3[_ngcontent-pmm-6] {
|
||||
background-color: white;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
h3[_ngcontent-pmm-6] {
|
||||
background-color: white;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
These styles are post-processed so that each selector is augmented
|
||||
with `_nghost` or `_ngcontent` attribute selectors.
|
||||
These extra selectors enable the scoping rules described in this page.
|
||||
@ -327,24 +375,30 @@ These extra selectors enable the scoping rules described in this page.
|
||||
|
||||
{@a relative-urls}
|
||||
|
||||
|
||||
|
||||
## Appendix 2: Loading styles with relative URLs
|
||||
|
||||
It's common practice to split a component's code, HTML, and CSS into three separate files in the same directory:
|
||||
|
||||
<code-example format="nocode">
|
||||
quest-summary.component.ts
|
||||
quest-summary.component.html
|
||||
quest-summary.component.css
|
||||
quest-summary.component.html
|
||||
quest-summary.component.css
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You include the template and CSS files by setting the `templateUrl` and `styleUrls` metadata properties respectively.
|
||||
Because these files are co-located with the component,
|
||||
it would be nice to refer to them by name without also having to specify a path back to the root of the application.
|
||||
|
||||
|
||||
You can use a relative URL by prefixing your filenames with `./`:
|
||||
|
||||
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts">
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts" title="src/app/quest-summary.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -6,53 +6,67 @@ Angular's dependency injection system creates and delivers dependent services "j
|
||||
|
||||
@description
|
||||
|
||||
|
||||
|
||||
**Dependency injection** is an important application design pattern.
|
||||
Angular has its own dependency injection framework, and
|
||||
you really can't build an Angular application without it.
|
||||
It's used so widely that almost everyone just calls it _DI_.
|
||||
|
||||
This page covers what DI is, why it's so useful,
|
||||
and [how to use it](guide/dependency-injection#angular-di) in an Angular app.# Contents
|
||||
and [how to use it](guide/dependency-injection#angular-di) in an Angular app.
|
||||
|
||||
- [Why dependency injection?](guide/dependency-injection#why-di)
|
||||
- [Angular dependency injection](guide/dependency-injection#angular-dependency-injection)
|
||||
- [Configuring the injector](guide/dependency-injection#injector-config)
|
||||
- [Registering providers in an `NgModule`](guide/dependency-injection#register-providers-ngmodule)
|
||||
- [Registering providers in a component](guide/dependency-injection#register-providers-component)
|
||||
- [When to use `NgModule` versus an application component](guide/dependency-injection#ngmodule-vs-comp)
|
||||
- [Preparing the `HeroListComponent` for injection](guide/dependency-injection#prep-for-injection)
|
||||
- [Implicit injector creation](guide/dependency-injection#di-metadata)
|
||||
- [Singleton services](guide/dependency-injection#singleton-services)
|
||||
- [Testing the component](guide/dependency-injection#testing-the-component)
|
||||
- [When the service needs a service](guide/dependency-injection#service-needs-service)
|
||||
- [Why `@Injectable()`?](guide/dependency-injection#injectable)
|
||||
- [Creating and registering a logger service](guide/dependency-injection#logger-service)
|
||||
- [Injector providers](guide/dependency-injection#injector-providers)
|
||||
- [The `Provider` class and `provide` object literal](guide/dependency-injection#provide)
|
||||
- [Alternative class providers](guide/dependency-injection#class-provider)
|
||||
- [Class provider with dependencies](guide/dependency-injection#class-provider-dependencies)
|
||||
- [Aliased class providers](guide/dependency-injection#aliased-class-providers)
|
||||
- [Value providers](guide/dependency-injection#value-provider)
|
||||
- [Factory providers](guide/dependency-injection#factory-provider)
|
||||
- [Dependency injection tokens](guide/dependency-injection#dependency-injection-tokens)
|
||||
- [Non-class dependencies](guide/dependency-injection#non-class-dependencies)
|
||||
- [`OpaqueToken`](guide/dependency-injection#opaquetoken)
|
||||
- [Optional dependencies](guide/dependency-injection#optional)
|
||||
- [Summary](guide/dependency-injection#summary)
|
||||
- [Appendix: Working with injectors directly](guide/dependency-injection#explicit-injector)
|
||||
# Contents
|
||||
|
||||
* [Why dependency injection?](guide/dependency-injection#why-di)
|
||||
* [Angular dependency injection](guide/dependency-injection#angular-dependency-injection)
|
||||
|
||||
* [Configuring the injector](guide/dependency-injection#injector-config)
|
||||
* [Registering providers in an `NgModule`](guide/dependency-injection#register-providers-ngmodule)
|
||||
* [Registering providers in a component](guide/dependency-injection#register-providers-component)
|
||||
* [When to use `NgModule` versus an application component](guide/dependency-injection#ngmodule-vs-comp)
|
||||
* [Preparing the `HeroListComponent` for injection](guide/dependency-injection#prep-for-injection)
|
||||
* [Implicit injector creation](guide/dependency-injection#di-metadata)
|
||||
* [Singleton services](guide/dependency-injection#singleton-services)
|
||||
* [Testing the component](guide/dependency-injection#testing-the-component)
|
||||
* [When the service needs a service](guide/dependency-injection#service-needs-service)
|
||||
* [Why `@Injectable()`?](guide/dependency-injection#injectable)
|
||||
|
||||
* [Creating and registering a logger service](guide/dependency-injection#logger-service)
|
||||
* [Injector providers](guide/dependency-injection#injector-providers)
|
||||
|
||||
* [The `Provider` class and `provide` object literal](guide/dependency-injection#provide)
|
||||
* [Alternative class providers](guide/dependency-injection#class-provider)
|
||||
* [Class provider with dependencies](guide/dependency-injection#class-provider-dependencies)
|
||||
* [Aliased class providers](guide/dependency-injection#aliased-class-providers)
|
||||
* [Value providers](guide/dependency-injection#value-provider)
|
||||
* [Factory providers](guide/dependency-injection#factory-provider)
|
||||
|
||||
* [Dependency injection tokens](guide/dependency-injection#dependency-injection-tokens)
|
||||
|
||||
* [Non-class dependencies](guide/dependency-injection#non-class-dependencies)
|
||||
* [`InjectionToken`](guide/dependency-injection#injection-token)
|
||||
|
||||
* [Optional dependencies](guide/dependency-injection#optional)
|
||||
* [Summary](guide/dependency-injection#summary)
|
||||
* [Appendix: Working with injectors directly](guide/dependency-injection#explicit-injector)
|
||||
|
||||
Run the <live-example></live-example>.
|
||||
|
||||
|
||||
|
||||
## Why dependency injection?
|
||||
|
||||
To understand why dependency injection is so important, consider an example without it.
|
||||
Imagine writing the following code:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-no-di.ts" region="car">
|
||||
<code-example path="dependency-injection/src/app/car/car-no-di.ts" region="car" title="src/app/car/car.ts (without DI)">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `Car` class creates everything it needs inside its constructor.
|
||||
What's the problem?
|
||||
The problem is that the `Car` class is brittle, inflexible, and hard to test.
|
||||
@ -95,7 +109,7 @@ When you can't control the dependencies, a class becomes difficult to test.
|
||||
|
||||
How can you make `Car` more robust, flexible, and testable?
|
||||
|
||||
<a id="ctor-injection"></a>
|
||||
{@a ctor-injection}
|
||||
That's super easy. Change the `Car` constructor to a version with DI:
|
||||
|
||||
|
||||
@ -111,6 +125,8 @@ That's super easy. Change the `Car` constructor to a version with DI:
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
See what happened? The definition of the dependencies are
|
||||
now in the constructor.
|
||||
The `Car` class no longer creates an `engine` or `tires`.
|
||||
@ -119,12 +135,16 @@ It just consumes them.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
This example leverages TypeScript's constructor syntax for declaring
|
||||
parameters and properties simultaneously.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Now you can create a car by passing the engine and tires to the constructor.
|
||||
|
||||
|
||||
@ -132,6 +152,8 @@ Now you can create a car by passing the engine and tires to the constructor.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
How cool is that?
|
||||
The definition of the `engine` and `tire` dependencies are
|
||||
decoupled from the `Car` class.
|
||||
@ -143,6 +165,8 @@ Now, if someone extends the `Engine` class, that is not `Car`'s problem.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
||||
something like this:
|
||||
|
||||
@ -151,12 +175,16 @@ something like this:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The critical point is this: the `Car` class did not have to change.
|
||||
You'll take care of the consumer's problem shortly.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The `Car` class is much easier to test now because you are in complete control
|
||||
of its dependencies.
|
||||
You can pass mocks to the constructor that do exactly what you want them to do
|
||||
@ -167,6 +195,8 @@ during each test:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
**You just learned what dependency injection is**.
|
||||
|
||||
It's a coding pattern in which a class receives its dependencies from external
|
||||
@ -181,10 +211,12 @@ You need something that takes care of assembling these parts.
|
||||
You _could_ write a giant class to do that:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-factory.ts">
|
||||
<code-example path="dependency-injection/src/app/car/car-factory.ts" title="src/app/car/car-factory.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
It's not so bad now with only three creation methods.
|
||||
But maintaining it will be hairy as the application grows.
|
||||
This factory is going to become a huge spiderweb of
|
||||
@ -200,10 +232,12 @@ You register some classes with this injector, and it figures out how to create t
|
||||
When you need a `Car`, you simply ask the injector to get it for you and you're good to go.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-injector.ts" region="injector-call" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/car/car-injector.ts" region="injector-call" title="src/app/car/car-injector.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
|
||||
The consumer knows nothing about creating a `Car`.
|
||||
You don't have a gigantic factory class to maintain.
|
||||
@ -214,6 +248,8 @@ This is what a **dependency injection framework** is all about.
|
||||
Now that you know what dependency injection is and appreciate its benefits,
|
||||
read on to see how it is implemented in Angular.
|
||||
|
||||
|
||||
|
||||
## Angular dependency injection
|
||||
|
||||
Angular ships with its own dependency injection framework. This framework can also be used
|
||||
@ -244,10 +280,14 @@ that from the [The Tour of Heroes](tutorial/).
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
The `HeroesComponent` is the root component of the *Heroes* feature area.
|
||||
It governs all the child components of this area.
|
||||
This stripped down version has only one child, `HeroListComponent`,
|
||||
which displays a list of heroes.
|
||||
|
||||
|
||||
Right now `HeroListComponent` gets heroes from `HEROES`, an in-memory collection
|
||||
defined in another file.
|
||||
That may suffice in the early stages of development, but it's far from ideal.
|
||||
@ -260,6 +300,8 @@ It's better to make a service that hides how the app gets hero data.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Given that the service is a
|
||||
[separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns),
|
||||
consider writing the service code in its own file.
|
||||
@ -268,18 +310,24 @@ See [this note](guide/dependency-injection#one-class-per-file) for details.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The following `HeroService` exposes a `getHeroes` method that returns
|
||||
the same mock data as before, but none of its consumers need to know that.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.1.ts">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.1.ts" title="src/app/heroes/hero.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The `@Injectable()` decorator above the service class is
|
||||
covered [shortly](guide/dependency-injection#injectable).
|
||||
|
||||
@ -290,6 +338,8 @@ covered [shortly](guide/dependency-injection#injectable).
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Of course, this isn't a real service.
|
||||
If the app were actually getting data from a remote server, the API would have to be
|
||||
asynchronous, perhaps returning a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||
@ -299,6 +349,8 @@ This is important in general, but not in this example.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
A service is nothing more than a class in Angular.
|
||||
It remains nothing more than a class until you register it with an Angular injector.
|
||||
|
||||
@ -310,6 +362,8 @@ It remains nothing more than a class until you register it with an Angular injec
|
||||
|
||||
|
||||
{@a injector-config}
|
||||
|
||||
|
||||
### Configuring the injector
|
||||
|
||||
You don't have to create an Angular injector.
|
||||
@ -320,13 +374,19 @@ Angular creates an application-wide injector for you during the bootstrap proces
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You do have to configure the injector by registering the **providers**
|
||||
that create the services the application requires.
|
||||
This guide explains what [providers](guide/dependency-injection#providers) are later.
|
||||
|
||||
|
||||
You can either register a provider within an [NgModule](guide/ngmodule) or in application components.
|
||||
|
||||
|
||||
{@a register-providers-ngmodule}
|
||||
|
||||
|
||||
### Registering providers in an _NgModule_
|
||||
Here's the `AppModule` that registers two providers, `UserService` and an `APP_CONFIG` provider,
|
||||
in its `providers` array.
|
||||
@ -336,24 +396,30 @@ in its `providers` array.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Because the `HeroService` is used _only_ within the `HeroesComponent`
|
||||
and its subcomponents, the top-level `HeroesComponent` is the ideal
|
||||
place to register it.
|
||||
|
||||
|
||||
{@a register-providers-component}
|
||||
|
||||
|
||||
### Registering providers in a component
|
||||
|
||||
Here's a revised `HeroesComponent` that registers the `HeroService` in its `providers` array.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/heroes.component.1.ts" region="full" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/heroes/heroes.component.1.ts" region="full" title="src/app/heroes/heroes.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a ngmodule-vs-comp}
|
||||
|
||||
|
||||
### When to use _NgModule_ versus an application component
|
||||
|
||||
On the one hand, a provider in an `NgModule` is registered in the root injector. That means that every provider
|
||||
@ -371,8 +437,10 @@ the `HeroesComponent`.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Also see *"Should I add app-wide providers to the root `AppModule` or
|
||||
the root `AppComponent`?"* in the [NgModule FAQ](cookbook/ngmodule-faq).
|
||||
the root `AppComponent`?"* in the [NgModule FAQ](cookbook/ngmodule-faq#q-root-component-or-module).
|
||||
|
||||
|
||||
~~~
|
||||
@ -380,6 +448,8 @@ the root `AppComponent`?"* in the [NgModule FAQ](cookbook/ngmodule-faq).
|
||||
|
||||
|
||||
{@a prep-for-injection}
|
||||
|
||||
|
||||
### Preparing the _HeroListComponent_ for injection
|
||||
|
||||
The `HeroListComponent` should get heroes from the injected `HeroService`.
|
||||
@ -404,15 +474,19 @@ It's a small change:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
#### Focus on the constructor
|
||||
|
||||
Adding a parameter to the constructor isn't all that's happening here.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero-list.component.2.ts" region="ctor" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero-list.component.2.ts" region="ctor" title="src/app/heroes/hero-list.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Note that the constructor parameter has the type `HeroService`, and that
|
||||
the `HeroListComponent` class has an `@Component` decorator
|
||||
(scroll up to confirm that fact).
|
||||
@ -430,6 +504,8 @@ Angular injector to inject an instance of
|
||||
|
||||
|
||||
{@a di-metadata}
|
||||
|
||||
|
||||
### Implicit injector creation
|
||||
|
||||
You saw how to use an injector to create a new
|
||||
@ -438,10 +514,12 @@ You _could_ create such an injector
|
||||
explicitly:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-injector.ts" region="injector-create-and-call" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/car/car-injector.ts" region="injector-create-and-call" title="src/app/car/car-injector.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You won't find code like that in the Tour of Heroes or any of the other
|
||||
documentation samples.
|
||||
You *could* write code that [explicitly creates an injector](guide/dependency-injection#explicit-injector) if you *had* to,
|
||||
@ -453,6 +531,8 @@ If you let Angular do its job, you'll enjoy the benefits of automated dependency
|
||||
|
||||
|
||||
{@a singleton-services}
|
||||
|
||||
|
||||
### Singleton services
|
||||
|
||||
Dependencies are singletons within the scope of an injector.
|
||||
@ -465,6 +545,8 @@ For more information, see [Hierarchical Injectors](guide/hierarchical-dependency
|
||||
|
||||
|
||||
{@a testing-the-component}
|
||||
|
||||
|
||||
### Testing the component
|
||||
|
||||
Earlier you saw that designing a class for dependency injection makes the class easier to test.
|
||||
@ -474,7 +556,7 @@ For example, you can create a new `HeroListComponent` with a mock service that y
|
||||
under test:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/test.component.ts" region="spec" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/test.component.ts" region="spec" title="src/app/test.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -482,6 +564,8 @@ under test:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Learn more in [Testing](guide/testing).
|
||||
|
||||
|
||||
@ -490,6 +574,8 @@ Learn more in [Testing](guide/testing).
|
||||
|
||||
|
||||
{@a service-needs-service}
|
||||
|
||||
|
||||
### When the service needs a service
|
||||
|
||||
The `HeroService` is very simple. It doesn't have any dependencies of its own.
|
||||
@ -514,11 +600,15 @@ Here is the revision compared to the original.
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`.
|
||||
You call that property within the `getHeroes()` method when anyone asks for heroes.
|
||||
|
||||
|
||||
{@a injectable}
|
||||
|
||||
|
||||
### Why _@Injectable()_?
|
||||
|
||||
**<a href="../api/core/index/Injectable-decorator.html">@Injectable()</a>** marks a class as available to an
|
||||
@ -529,6 +619,8 @@ error when trying to instantiate a class that is not marked as
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
As it happens, you could have omitted `@Injectable()` from the first
|
||||
version of `HeroService` because it had no injected parameters.
|
||||
But you must have it now that the service has an injected dependency.
|
||||
@ -548,6 +640,8 @@ in order to inject a `Logger`.
|
||||
Suggestion: add @Injectable() to every service class
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
Consider adding `@Injectable()` to every service class, even those that don't have dependencies
|
||||
and, therefore, do not technically require it. Here's why:
|
||||
|
||||
@ -568,6 +662,8 @@ and, therefore, do not technically require it. Here's why:
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Injectors are also responsible for instantiating components
|
||||
like `HeroesComponent`. So why doesn't `HeroesComponent` have
|
||||
`@Injectable()`?
|
||||
@ -582,6 +678,8 @@ identify a class as a target for instantiation by an injector.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
At runtime, injectors can read class metadata in the transpiled JavaScript code
|
||||
and use the constructor parameter type information
|
||||
to determine what things to inject.
|
||||
@ -610,6 +708,8 @@ to make the intent clear.
|
||||
Always include the parentheses
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
Always write `@Injectable()`, not just `@Injectable`.
|
||||
The application will fail mysteriously if you forget the parentheses.
|
||||
|
||||
@ -617,19 +717,24 @@ The application will fail mysteriously if you forget the parentheses.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Creating and registering a logger service
|
||||
|
||||
Inject a logger into `HeroService` in two steps:
|
||||
|
||||
1. Create the logger service.
|
||||
1. Register it with the application.
|
||||
|
||||
The logger service is quite simple:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/logger.service.ts">
|
||||
<code-example path="dependency-injection/src/app/logger.service.ts" title="src/app/logger.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You're likely to need the same logger service everywhere in your application,
|
||||
so put it in the project's `app` folder and
|
||||
register it in the `providers` array of the application module, `AppModule`.
|
||||
@ -639,6 +744,8 @@ register it in the `providers` array of the application module, `AppModule`.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
If you forget to register the logger, Angular throws an exception when it first looks for the logger:
|
||||
|
||||
<code-example format="nocode">
|
||||
@ -646,6 +753,8 @@ If you forget to register the logger, Angular throws an exception when it first
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
That's Angular telling you that the dependency injector couldn't find the *provider* for the logger.
|
||||
It needed that provider to create a `Logger` to inject into a new
|
||||
`HeroService`, which it needed to
|
||||
@ -653,6 +762,8 @@ create and inject into a new `HeroListComponent`.
|
||||
|
||||
The chain of creations started with the `Logger` provider. *Providers* are the subject of the next section.
|
||||
|
||||
|
||||
|
||||
## Injector providers
|
||||
|
||||
A provider *provides* the concrete, runtime version of a dependency value.
|
||||
@ -664,10 +775,12 @@ You must register a service *provider* with the injector, or it won't know how t
|
||||
Earlier you registered the `Logger` service in the `providers` array of the metadata for the `AppModule` like this:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger" title="src/app/providers.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
There are many ways to *provide* something that looks and behaves like a `Logger`.
|
||||
The `Logger` class itself is an obvious and natural provider.
|
||||
But it's not the only way.
|
||||
@ -684,22 +797,30 @@ What matters is that the injector has a provider to go to when it needs a `Logge
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### The *Provider* class and _provide_ object literal
|
||||
|
||||
|
||||
You wrote the `providers` array like this:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-1">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-1" title="src/app/providers.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
This is actually a shorthand expression for a provider registration
|
||||
using a _provider_ object literal with two properties:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-3">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-3" title="src/app/providers.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The first is the [token](guide/dependency-injection#token) that serves as the key for both locating a dependency value
|
||||
and registering the provider.
|
||||
|
||||
@ -712,6 +833,8 @@ There are many ways to create dependency values just as there are many ways to w
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### Alternative class providers
|
||||
|
||||
Occasionally you'll ask a different class to provide the service.
|
||||
@ -719,33 +842,39 @@ The following code tells the injector
|
||||
to return a `BetterLogger` when something asks for the `Logger`.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-4">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-4" title="src/app/providers.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a class-provider-dependencies}
|
||||
|
||||
|
||||
### Class provider with dependencies
|
||||
Maybe an `EvenBetterLogger` could display the user name in the log message.
|
||||
This logger gets the user from the injected `UserService`,
|
||||
which is also injected at the application level.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="EvenBetterLogger" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="EvenBetterLogger" title="src/app/providers.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Configure it like `BetterLogger`.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-5" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-5" title="src/app/providers.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a aliased-class-providers}
|
||||
|
||||
|
||||
### Aliased class providers
|
||||
|
||||
Suppose an old component depends upon an `OldLogger` class.
|
||||
@ -763,10 +892,12 @@ You certainly do not want two different `NewLogger` instances in your app.
|
||||
Unfortunately, that's what you get if you try to alias `OldLogger` to `NewLogger` with `useClass`.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6a" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6a" title="src/app/providers.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The solution: alias with the `useExisting` option.
|
||||
|
||||
|
||||
@ -777,14 +908,20 @@ The solution: alias with the `useExisting` option.
|
||||
|
||||
|
||||
{@a value-provider}
|
||||
|
||||
|
||||
### Value providers
|
||||
|
||||
|
||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="silent-logger" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="silent-logger" title="src/app/providers.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Then you register a provider with the `useValue` option,
|
||||
which makes this object play the logger role.
|
||||
|
||||
@ -793,15 +930,19 @@ which makes this object play the logger role.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
See more `useValue` examples in the
|
||||
[Non-class dependencies](guide/dependency-injection#non-class-dependencies) and
|
||||
[OpaqueToken](guide/dependency-injection#opaquetoken) sections.
|
||||
[InjectionToken](guide/dependency-injection#injection-token) sections.
|
||||
|
||||
|
||||
<div id='factory-provider'>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### Factory providers
|
||||
|
||||
Sometimes you need to create the dependent value dynamically,
|
||||
@ -824,30 +965,36 @@ as when you log in a different user.
|
||||
Unlike `EvenBetterLogger`, you can't inject the `UserService` into the `HeroService`.
|
||||
The `HeroService` won't have direct access to the user information to decide
|
||||
who is authorized and who is not.
|
||||
|
||||
|
||||
Instead, the `HeroService` constructor takes a boolean flag to control display of secret heroes.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.ts" region="internals" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.ts" region="internals" title="src/app/heroes/hero.service.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You can inject the `Logger`, but you can't inject the boolean `isAuthorized`.
|
||||
You'll have to take over the creation of new instances of this `HeroService` with a factory provider.
|
||||
|
||||
A factory provider needs a factory function:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="factory" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="factory" title="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Although the `HeroService` has no access to the `UserService`, the factory function does.
|
||||
|
||||
You inject both the `Logger` and the `UserService` into the factory provider
|
||||
and let the injector pass them along to the factory function:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="provider" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero.service.provider.ts" region="provider" title="src/app/heroes/hero.service.provider.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -855,6 +1002,8 @@ and let the injector pass them along to the factory function:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The `useFactory` field tells Angular that the provider is a factory function
|
||||
whose implementation is the `heroServiceFactory`.
|
||||
|
||||
@ -865,6 +1014,8 @@ The injector resolves these tokens and injects the corresponding services into t
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Notice that you captured the factory provider in an exported variable, `heroServiceProvider`.
|
||||
This extra step makes the factory provider reusable.
|
||||
You can register the `HeroService` with this variable wherever you need it.
|
||||
@ -887,6 +1038,8 @@ Here you see the new and the old implementation side-by-side:
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
|
||||
## Dependency injection tokens
|
||||
|
||||
When you register a provider with an injector, you associate that provider with a dependency injection token.
|
||||
@ -898,48 +1051,58 @@ the class *type* served as its own lookup key.
|
||||
Here you get a `HeroService` directly from the injector by supplying the `HeroService` type as the token:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/injector.component.ts" region="get-hero-service" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/injector.component.ts" region="get-hero-service" title="src/app/injector.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You have similar good fortune when you write a constructor that requires an injected class-based dependency.
|
||||
When you define a constructor parameter with the `HeroService` class type,
|
||||
Angular knows to inject the
|
||||
service associated with that `HeroService` class token:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/heroes/hero-list.component.ts" region="ctor-signature">
|
||||
<code-example path="dependency-injection/src/app/heroes/hero-list.component.ts" region="ctor-signature" title="src/app/heroes/hero-list.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
This is especially convenient when you consider that most dependency values are provided by classes.
|
||||
|
||||
|
||||
{@a non-class-dependencies}
|
||||
|
||||
|
||||
### Non-class dependencies
|
||||
|
||||
<p>
|
||||
What if the dependency value isn't a class? Sometimes the thing you want to inject is a
|
||||
span string, function, or object.
|
||||
span string, function, or object.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p>
|
||||
Applications often define configuration objects with lots of small facts
|
||||
(like the title of the application or the address of a web API endpoint)
|
||||
but these configuration objects aren't always instances of a class.
|
||||
They can be object literals such as this one:
|
||||
(like the title of the application or the address of a web API endpoint)
|
||||
but these configuration objects aren't always instances of a class.
|
||||
They can be object literals such as this one:
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="config" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="config" title="src/app/app-config.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
What if you'd like to make this configuration object available for injection?
|
||||
You know you can register an object with a [value provider](guide/dependency-injection#value-provider).
|
||||
|
||||
|
||||
But what should you use as the token?
|
||||
You don't have a class to serve as a token.
|
||||
There is no `AppConfig` class.
|
||||
@ -947,21 +1110,25 @@ There is no `AppConfig` class.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
### TypeScript interfaces aren't valid tokens
|
||||
|
||||
The `HERO_DI_CONFIG` constant has an interface, `AppConfig`. Unfortunately, you
|
||||
cannot use a TypeScript interface as a token:
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9-interface" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9-interface" title="src/app/providers.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="provider-9-ctor-interface" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="provider-9-ctor-interface" title="src/app/providers.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
That seems strange if you're used to dependency injection in strongly typed languages, where
|
||||
an interface is the preferred dependency lookup key.
|
||||
|
||||
@ -974,30 +1141,39 @@ There is no interface type information left for Angular to find at runtime.
|
||||
|
||||
|
||||
|
||||
{@a opaquetoken}
|
||||
### _OpaqueToken_
|
||||
{@a injection-token}
|
||||
|
||||
|
||||
### _InjectionToken_
|
||||
|
||||
One solution to choosing a provider token for non-class dependencies is
|
||||
to define and use an <a href="../api/core/index/OpaqueToken-class.html"><b>OpaqueToken</b></a>.
|
||||
The definition looks like this:
|
||||
to define and use an <a href="../api/core/index/InjectionToken-class.html"><b>InjectionToken</b></a>.
|
||||
The definition of such a token looks like this:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="token" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/app.config.ts" region="token" title="src/app/app.config.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Register the dependency provider using the `OpaqueToken` object:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9" linenums="false">
|
||||
The type parameter, while optional, conveys the dependency's type to developers and tooling.
|
||||
The token description is another developer aid.
|
||||
|
||||
Register the dependency provider using the `InjectionToken` object:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9" title="src/app/providers.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now you can inject the configuration object into any constructor that needs it, with
|
||||
the help of an `@Inject` decorator:
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/app.component.2.ts" region="ctor" linenums="false">
|
||||
<code-example path="dependency-injection/src/app/app.component.2.ts" region="ctor" title="src/app/app.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1005,12 +1181,16 @@ the help of an `@Inject` decorator:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Although the `AppConfig` interface plays no role in dependency injection,
|
||||
it supports typing of the configuration object within the class.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Aternatively, you can provide and inject the configuration object in an ngModule like `AppModule`.
|
||||
|
||||
+makeExcerpt('src/app/app.module.ts','ngmodule-providers')
|
||||
@ -1020,6 +1200,8 @@ Aternatively, you can provide and inject the configuration object in an ngModule
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Optional dependencies
|
||||
|
||||
The `HeroService` *requires* a `Logger`, but what if it could get by without
|
||||
@ -1038,10 +1220,14 @@ constructor argument with `@Optional()`:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
When using `@Optional()`, your code must be prepared for a null value. If you
|
||||
don't register a `logger` somewhere up the line, the injector will set the
|
||||
value of `logger` to null.
|
||||
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
You learned the basics of Angular dependency injection in this page.
|
||||
@ -1054,16 +1240,20 @@ You can learn more about its advanced features, beginning with its support for
|
||||
nested injectors, in
|
||||
[Hierarchical Dependency Injection](guide/hierarchical-dependency-injection).
|
||||
|
||||
|
||||
|
||||
## Appendix: Working with injectors directly
|
||||
|
||||
Developers rarely work directly with an injector, but
|
||||
here's an `InjectorComponent` that does.
|
||||
|
||||
|
||||
<code-example path="dependency-injection/src/app/injector.component.ts" region="injector">
|
||||
<code-example path="dependency-injection/src/app/injector.component.ts" region="injector" title="src/app/injector.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
An `Injector` is itself an injectable service.
|
||||
|
||||
In this example, Angular injects the component's own `Injector` into the component's constructor.
|
||||
@ -1079,6 +1269,8 @@ is not found. Angular can't find the service if it's not registered with this or
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The technique is an example of the
|
||||
[service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern).
|
||||
|
||||
@ -1096,6 +1288,8 @@ must acquire services generically and dynamically.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Appendix: Why have one class per file
|
||||
|
||||
Having multiple classes in the same file is confusing and best avoided.
|
||||
@ -1110,6 +1304,8 @@ you'll get a runtime null reference error.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
You actually can define the component first with the help of the `forwardRef()` method as explained
|
||||
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
||||
But why flirt with trouble?
|
||||
|
@ -5,10 +5,14 @@ Deployment
|
||||
Learn how to deploy your Angular app.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
This page describes tools and techniques for deploy and optimize your Angular application.
|
||||
|
||||
|
||||
{@a toc}
|
||||
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Overview](guide/deployment#overview)
|
||||
@ -30,6 +34,8 @@ This page describes tools and techniques for deploy and optimize your Angular ap
|
||||
|
||||
{@a overview}
|
||||
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
This guide describes techniques for preparing and deploying an Angular application to a server running remotely.
|
||||
@ -39,10 +45,10 @@ The techniques progress from _easy but suboptimal_ to _more optimal and more inv
|
||||
|
||||
* [_Ahead of Time_ compilation (AOT)](guide/deployment#aot "AOT Compilation") is the first of
|
||||
[several optimization strategies](guide/deployment#optimize).
|
||||
You'll also want to read the [detailed instructions in the AOT Cookbook](cookbook/aot-compiler).
|
||||
You'll also want to read the [detailed instructions in the AOT Cookbook](cookbook/aot-compiler "AOT Cookbook").
|
||||
|
||||
* [Webpack](guide/deployment#webpack "Webpack Optimization") is a popular general purpose packaging tool with a rich ecosystem, including plugins for AOT.
|
||||
The Angular [webpack guide](guide/webpack) can get you started and
|
||||
The Angular [webpack guide](guide/webpack "Webpack: an introduction") can get you started and
|
||||
_this_ page provides additional optimization advice, but you'll probably have to learn more about webpack on your own.
|
||||
|
||||
* The [Angular configuration](guide/deployment#angular-configuration "Angular configuration") section calls attention to
|
||||
@ -54,6 +60,8 @@ server-side changes that may be necessary, _no matter how you deploy the applica
|
||||
|
||||
|
||||
{@a dev-deploy}
|
||||
|
||||
|
||||
## Simplest deployment possible
|
||||
|
||||
The simplest way to deploy the app is to publish it to a web server
|
||||
@ -82,6 +90,8 @@ That's the simplest deployment you can do.
|
||||
|
||||
~~~ {.alert.is-helpful}
|
||||
|
||||
|
||||
|
||||
This is _not_ a production deployment. It's not optimized and it won't be fast for users.
|
||||
It might be good enough for sharing your progress and ideas internally with managers, teammates, and other stakeholders.
|
||||
Be sure to read about [optimizing for production](guide/deployment#optimize "Optimizing for production") below.
|
||||
@ -93,6 +103,8 @@ Be sure to read about [optimizing for production](guide/deployment#optimize "Opt
|
||||
|
||||
|
||||
{@a node-modules}
|
||||
|
||||
|
||||
### Load npm package files from the web (SystemJS)
|
||||
|
||||
The `node_modules` folder of _npm packages_ contains much more code
|
||||
@ -113,6 +125,8 @@ with versions that load from the web. It might look like this.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
(2) Replace the `systemjs.config.js` script with a script that
|
||||
loads `systemjs.config.server.js`.
|
||||
|
||||
@ -120,6 +134,8 @@ loads `systemjs.config.server.js`.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
(3) Add `systemjs.config.server.js` (shown in the code sample below) to the `src/` folder.
|
||||
This alternative version configures _SystemJS_ to load _UMD_ versions of Angular
|
||||
(and other third-party packages) from the web.
|
||||
@ -134,6 +150,8 @@ Notice the `paths` key:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
In the standard SystemJS config, the `npm` path points to the `node_modules/`.
|
||||
In this server config, it points to
|
||||
<a href="https://unpkg.com/" target="_blank" title="unpkg.com">https://unpkg.com</a>,
|
||||
@ -183,9 +201,11 @@ The following trivial router sample app shows these changes.
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
Practice with this sample before attempting these techniques on your application.
|
||||
|
||||
1. Follow the [setup instructions](guide/setup) for creating a new project
|
||||
1. Follow the [setup instructions](guide/setup "Angular QuickStart setup") for creating a new project
|
||||
named <code>simple-deployment</code>.
|
||||
|
||||
1. Add the "Simple deployment" sample files shown above.
|
||||
@ -205,6 +225,8 @@ When you have that working, try the same process on your application.
|
||||
|
||||
{@a optimize}
|
||||
|
||||
|
||||
|
||||
## Optimize for production
|
||||
|
||||
Although deploying directly from the development environment works, it's far from optimal.
|
||||
@ -226,14 +248,14 @@ Does it matter? That depends upon business and technical factors you must evalua
|
||||
|
||||
If it _does_ matter, there are tools and techniques to reduce the number of requests and the size of responses.
|
||||
|
||||
- Ahead-of-Time (AOT) Compilation: pre-compiles Angular component templates.
|
||||
- Bundling: concatenates modules into a single file (bundle).
|
||||
- Inlining: pulls template html and css into the components.
|
||||
- Minification: removes excess whitespace, comments, and optional tokens.
|
||||
- Uglification: rewrites code to use short, cryptic variable and function names.
|
||||
- Dead code elimination: removes unreferenced modules and unused code.
|
||||
- Pruned libraries: drop unused libraries and pare others down to the features you need.
|
||||
- Performance measurement: focus on optimizations that make a measurable difference.
|
||||
* Ahead-of-Time (AOT) Compilation: pre-compiles Angular component templates.
|
||||
* Bundling: concatenates modules into a single file (bundle).
|
||||
* Inlining: pulls template html and css into the components.
|
||||
* Minification: removes excess whitespace, comments, and optional tokens.
|
||||
* Uglification: rewrites code to use short, cryptic variable and function names.
|
||||
* Dead code elimination: removes unreferenced modules and unused code.
|
||||
* Pruned libraries: drop unused libraries and pare others down to the features you need.
|
||||
* Performance measurement: focus on optimizations that make a measurable difference.
|
||||
|
||||
Each tool does something different.
|
||||
They work best in combination and are mutually reinforcing.
|
||||
@ -244,28 +266,33 @@ building for production is a single step.
|
||||
|
||||
|
||||
{@a aot}
|
||||
|
||||
|
||||
### Ahead-of-Time (AOT) compilation
|
||||
|
||||
The Angular _Ahead-of-Time_ compiler pre-compiles application components and their templates
|
||||
during the build process.
|
||||
|
||||
Apps compiled with AOT launch faster for several reasons.
|
||||
|
||||
* Application components execute immediately, without client-side compilation.
|
||||
* Templates are embedded as code within their components so there is no client-side request for template files.
|
||||
* You don't download the Angular compiler, which is pretty big on its own.
|
||||
* The compiler discards unused Angular directives that a tree-shaking tool can then exclude.
|
||||
|
||||
Learn more about AOT Compilation in the [AOT Cookbook](cookbook/aot-compiler)
|
||||
Learn more about AOT Compilation in the [AOT Cookbook](cookbook/aot-compiler "AOT Cookbook")
|
||||
which describes running the AOT compiler from the command line
|
||||
and using [_rollup_](guide/deployment#rollup) for bundling, minification, uglification and tree shaking.
|
||||
|
||||
|
||||
{@a webpack}
|
||||
|
||||
|
||||
### Webpack (and AOT)
|
||||
|
||||
<a href="https://webpack.js.org/" target="_blank" title="Webpack 2">Webpack 2</a> is another
|
||||
great option for inlining templates and style-sheets, for bundling, minifying, and uglifying the application.
|
||||
The "[Webpack: an introduction](guide/webpack)" guide will get you started
|
||||
The "[Webpack: an introduction](guide/webpack "Webpack: an introduction")" guide will get you started
|
||||
using webpack with Angular.
|
||||
|
||||
Consider configuring _Webpack_ with the official
|
||||
@ -277,6 +304,8 @@ and performs AOT compilation — without any changes to the source code.
|
||||
|
||||
|
||||
{@a rollup}
|
||||
|
||||
|
||||
### Dead code elimination with _rollup_
|
||||
|
||||
Any code that you don't call is _dead code_.
|
||||
@ -294,6 +323,8 @@ this post</a> by rollup-creator, Rich Harris.
|
||||
|
||||
|
||||
{@a prune}
|
||||
|
||||
|
||||
### Pruned libraries
|
||||
|
||||
Don't count on automation to remove all dead code.
|
||||
@ -307,6 +338,8 @@ Other libraries let you import features _a la carte_.
|
||||
|
||||
|
||||
{@a measure}
|
||||
|
||||
|
||||
### Measure performance first
|
||||
|
||||
You can make better decisions about what to optimize and how when you have a clear and accurate understanding of
|
||||
@ -325,15 +358,19 @@ that can also help verify that your deployment was successful.
|
||||
|
||||
{@a angular-configuration}
|
||||
|
||||
|
||||
|
||||
## Angular configuration
|
||||
|
||||
Angular configuration can make the difference between whether the app launches quickly or doesn't load at all.
|
||||
|
||||
|
||||
{@a base-tag}
|
||||
|
||||
|
||||
### The `base` tag
|
||||
|
||||
The HTML [_<base href="..."/>_](https://angular.io/docs/ts/latest/guide/router.html#!#base-href)
|
||||
The HTML [_<base href="..."/>_](https://angular.io/docs/ts/latest/guide/router.html#!)
|
||||
specifies a base path for resolving relative URLs to assets such as images, scripts, and style sheets.
|
||||
For example, given the `<base href="/my/app/">`, the browser resolves a URL such as `some/place/foo.jpg`
|
||||
into a server request for `my/app/some/place/foo.jpg`.
|
||||
@ -342,10 +379,14 @@ During navigation, the Angular router uses the _base href_ as the base path to c
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
See also the [*APP_BASE_HREF*](api/common/index/APP_BASE_HREF-let) alternative.
|
||||
|
||||
|
||||
See also the [*APP_BASE_HREF*](api/common/index/APP_BASE_HREF-let "API: APP_BASE_HREF") alternative.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
In development, you typically start the server in the folder that holds `index.html`.
|
||||
That's the root folder and you'd add `<base href="/">` near the top of `index.html` because `/` is the root of the app.
|
||||
|
||||
@ -358,6 +399,8 @@ for the missing files. Look at where it _tried_ to find those files and adjust t
|
||||
|
||||
|
||||
{@a enable-prod-mode}
|
||||
|
||||
|
||||
### Enable production mode
|
||||
|
||||
Angular apps run in development mode by default, as you can see by the following message on the browser
|
||||
@ -368,26 +411,30 @@ console:
|
||||
Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Switching to production mode can make it run faster by disabling development specific checks such as the dual change detection cycles.
|
||||
|
||||
To enable [production mode](api/core/index/enableProdMode-function) when running remotely, add the following code to the `main.ts`.
|
||||
|
||||
|
||||
<code-example path="deployment/src/main.ts" region="enableProdMode" linenums="false">
|
||||
<code-example path="deployment/src/main.ts" region="enableProdMode" title="src/main.ts (enableProdMode)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a lazy-loading}
|
||||
|
||||
|
||||
### Lazy loading
|
||||
|
||||
You can dramatically reduce launch time by only loading the application modules that
|
||||
absolutely must be present when the app starts.
|
||||
|
||||
Configure the Angular Router to defer loading of all other modules (and their associated code), either by
|
||||
[waiting until the app has launched](guide/router)
|
||||
or by [_lazy loading_](guide/router)
|
||||
[waiting until the app has launched](guide/router#preloading "Preloading")
|
||||
or by [_lazy loading_](guide/router#asynchronous-routing "Lazy loading")
|
||||
them on demand.
|
||||
|
||||
#### Don't eagerly import something from a lazy loaded module
|
||||
@ -411,12 +458,16 @@ automatically recognizes lazy loaded `NgModules` and creates separate bundles fo
|
||||
|
||||
{@a server-configuration}
|
||||
|
||||
|
||||
|
||||
## Server configuration
|
||||
|
||||
This section covers changes you may have make to the server or to files deployed to the server.
|
||||
|
||||
|
||||
{@a fallback}
|
||||
|
||||
|
||||
### Routed apps must fallback to `index.html`
|
||||
|
||||
Angular apps are perfect candidates for serving with a simple static HTML server.
|
||||
@ -428,6 +479,8 @@ to return the application's host page (`index.html`) when asked for a file that
|
||||
|
||||
|
||||
{@a deep-link}
|
||||
|
||||
|
||||
A routed application should support "deep links".
|
||||
A _deep link_ is a URL that specifies a path to a component inside the app.
|
||||
For example, `http://www.mysite.com/heroes/42` is a _deep link_ to the hero detail page
|
||||
@ -453,41 +506,46 @@ The list is by no means exhaustive, but should provide you with a good starting
|
||||
|
||||
#### Development servers
|
||||
|
||||
- [Lite-Server](https://github.com/johnpapa/lite-server): the default dev server installed with the
|
||||
* [Lite-Server](https://github.com/johnpapa/lite-server): the default dev server installed with the
|
||||
[Quickstart repo](https://github.com/angular/quickstart) is pre-configured to fallback to `index.html`.
|
||||
|
||||
- [Webpack-Dev-Server](https://github.com/webpack/webpack-dev-server): setup the
|
||||
* [Webpack-Dev-Server](https://github.com/webpack/webpack-dev-server): setup the
|
||||
`historyApiFallback` entry in the dev server options as follows:
|
||||
|
||||
|
||||
<code-example>
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
|
||||
}
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
#### Production servers
|
||||
|
||||
- [Apache](https://httpd.apache.org/): add a
|
||||
* [Apache](https://httpd.apache.org/): add a
|
||||
[rewrite rule](http://httpd.apache.org/docs/current/mod/mod_rewrite.html)
|
||||
to the `.htaccess` file as show
|
||||
[here](https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/):
|
||||
|
||||
|
||||
<code-example format=".">
|
||||
RewriteEngine On
|
||||
# If an existing asset or directory is requested go to it as it is
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
|
||||
RewriteRule ^ - [L]
|
||||
# If an existing asset or directory is requested go to it as it is
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# If the requested resource doesn't exist, use index.html
|
||||
RewriteRule ^ /index.html
|
||||
# If the requested resource doesn't exist, use index.html
|
||||
RewriteRule ^ /index.html
|
||||
|
||||
</code-example>
|
||||
|
||||
- [NGinx](http://nginx.org/): use `try_files`, as described in
|
||||
|
||||
|
||||
* [NGinx](http://nginx.org/): use `try_files`, as described in
|
||||
[Front Controller Pattern Web Apps](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#front-controller-pattern-web-apps),
|
||||
modified to serve `index.html`:
|
||||
|
||||
@ -497,28 +555,32 @@ modified to serve `index.html`:
|
||||
|
||||
</code-example>
|
||||
|
||||
- [IIS](https://www.iis.net/): add a rewrite rule to `web.config`, similar to the one shown
|
||||
|
||||
|
||||
* [IIS](https://www.iis.net/): add a rewrite rule to `web.config`, similar to the one shown
|
||||
[here](http://stackoverflow.com/a/26152011/2116927):
|
||||
|
||||
<code-example format="." escape="html">
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="Angular Routes" stopProcessing="true">
|
||||
<match url=".*" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="/" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
<code-example format='.'>
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="Angular Routes" stopProcessing="true">
|
||||
<match url=".*" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="/src/" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
|
||||
</code-example>
|
||||
|
||||
- [GitHub Pages](https://pages.github.com/): you can't
|
||||
|
||||
|
||||
* [GitHub Pages](https://pages.github.com/): you can't
|
||||
[directly configure](https://github.com/isaacs/github/issues/408)
|
||||
the GitHub Pages server, but you can add a 404 page.
|
||||
Copy `index.html` into `404.html`.
|
||||
@ -528,15 +590,15 @@ It's also a good idea to
|
||||
and to
|
||||
[create a `.nojekyll` file](https://www.bennadel.com/blog/3181-including-node-modules-and-vendors-folders-in-your-github-pages-site.htm)
|
||||
|
||||
- [Firebase hosting](https://firebase.google.com/docs/hosting/): add a
|
||||
* [Firebase hosting](https://firebase.google.com/docs/hosting/): add a
|
||||
[rewrite rule](https://firebase.google.com/docs/hosting/url-redirects-rewrites#section-rewrites).
|
||||
|
||||
|
||||
<code-example format=".">
|
||||
"rewrites": [ {
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
} ]
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
} ]
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -544,6 +606,8 @@ and to
|
||||
|
||||
{@a cors}
|
||||
|
||||
|
||||
|
||||
### Requesting services from a different server (CORS)
|
||||
|
||||
Angular developers may encounter a
|
||||
@ -560,6 +624,8 @@ Read about how to enable CORS for specific servers at
|
||||
|
||||
{@a next-steps}
|
||||
|
||||
|
||||
|
||||
## Next steps
|
||||
If you want to go beyond the [simple _copy-deploy_](guide/deployment#dev-deploy "Simplest deployment possible") approach,
|
||||
read the [AOT Cookbook](cookbook/aot-compiler) next.
|
||||
read the [AOT Cookbook](cookbook/aot-compiler "AOT Cookbook") next.
|
@ -6,6 +6,8 @@ Property binding helps show app data in the UI.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
|
||||
You can display data by binding controls in an HTML template to properties of an Angular component.
|
||||
|
||||
In this page, you'll create a component with a list of heroes.
|
||||
@ -16,9 +18,11 @@ The final UI looks like this:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/displaying-data/final.png" alt="Final UI"> </img>
|
||||
<img src="assets/images/devguide/displaying-data/final.png" alt="Final UI"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
# Contents
|
||||
|
||||
* [Showing component properties with interpolation](guide/displaying-data#interpolation).
|
||||
@ -28,6 +32,8 @@ The final UI looks like this:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The <live-example></live-example> demonstrates all of the syntax and code
|
||||
snippets described in this page.
|
||||
|
||||
@ -35,6 +41,8 @@ snippets described in this page.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Showing component properties with interpolation
|
||||
The easiest way to display a component property
|
||||
is to bind the property name through interpolation.
|
||||
@ -49,10 +57,12 @@ changing the template and the body of the component.
|
||||
When you're done, it should look like this:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app.component.1.ts">
|
||||
<code-example path="displaying-data/src/app/app.component.1.ts" title="src/app/app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You added two properties to the formerly empty component: `title` and `myHero`.
|
||||
|
||||
The revised template displays the two component properties using double curly brace
|
||||
@ -67,6 +77,8 @@ interpolation:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
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 (`'`)—allows you to compose a string over several lines, which makes the
|
||||
@ -75,6 +87,8 @@ HTML more readable.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Angular automatically pulls the value of the `title` and `myHero` properties from the component and
|
||||
inserts those values into the browser. Angular updates the display
|
||||
when these properties change.
|
||||
@ -82,12 +96,16 @@ when these properties change.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
More precisely, the redisplay occurs after some kind of asynchronous event related to
|
||||
the view, such as a keystroke, a timer completion, or a response to an HTTP request.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Notice that you don't call **new** to create an instance of the `AppComponent` class.
|
||||
Angular is creating an instance for you. How?
|
||||
|
||||
@ -99,6 +117,8 @@ That element is a placeholder in the body of your `index.html` file:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
When you bootstrap with the `AppComponent` class (in <code>main.ts</code>), 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.
|
||||
@ -106,10 +126,14 @@ inside the `<my-app>` tag.
|
||||
Now run the app. It should display the title and hero name:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero"> </img>
|
||||
<img src="assets/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The next few sections review some of the coding choices in the app.
|
||||
|
||||
|
||||
## Template inline or template file?
|
||||
|
||||
You can store your component's template in one of two places.
|
||||
@ -123,6 +147,8 @@ Here the app uses inline HTML because the template is small and the demo
|
||||
is simpler without the additional HTML file.
|
||||
|
||||
In either style, the template data bindings have the same access to the component's properties.
|
||||
|
||||
|
||||
## Constructor or variable initialization?
|
||||
|
||||
Although this example uses variable assignment to initialize the components, you can instead declare and initialize the properties using a constructor:
|
||||
@ -132,8 +158,12 @@ Although this example uses variable assignment to initialize the components, you
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
This app uses more terse "variable assignment" style simply for brevity.
|
||||
|
||||
|
||||
|
||||
## Showing an array property with ***ngFor**
|
||||
|
||||
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.
|
||||
@ -143,6 +173,8 @@ To display a list of heroes, begin by adding an array of hero names to the compo
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now use the Angular `ngFor` directive in the template to display
|
||||
each item in the `heroes` list.
|
||||
|
||||
@ -151,6 +183,8 @@ each item in the `heroes` list.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
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":
|
||||
@ -164,15 +198,19 @@ It marks that `<li>` element (and its children) as the "repeater template":
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
Don't forget the leading asterisk (\*) in `*ngFor`. It is an essential part of the syntax.
|
||||
For more information, see the [Template Syntax](guide/template-syntax) page.
|
||||
For more information, see the [Template Syntax](guide/template-syntax#ngFor) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Notice the `hero` in the `ngFor` double-quoted instruction;
|
||||
it is an example of a template input variable. Read
|
||||
more about template input variables in the [microsyntax](guide/template-syntax) section of
|
||||
more about template input variables in the [microsyntax](guide/template-syntax#microsyntax) section of
|
||||
the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
Angular duplicates the `<li>` for each item in the list, setting the `hero` variable
|
||||
@ -182,19 +220,25 @@ context for the interpolation in the double curly braces.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
In this case, `ngFor` is displaying an array, but `ngFor` can
|
||||
repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) object.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Now the heroes appear in an unordered list.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/displaying-data/hero-names-list.png" alt="After ngfor"> </img>
|
||||
<img src="assets/images/devguide/displaying-data/hero-names-list.png" alt="After ngfor"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
|
||||
## Creating a class for the data
|
||||
|
||||
The app's code defines the data directly inside the component, which isn't best practice.
|
||||
@ -213,6 +257,8 @@ Create a new file in the `app` folder called `hero.ts` with the following code:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You've defined a class with a constructor and two properties: `id` and `name`.
|
||||
|
||||
It might not look like the class has properties, but it does.
|
||||
@ -225,11 +271,16 @@ Consider the first parameter:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
That brief syntax does a lot:
|
||||
|
||||
* Declares a constructor parameter and its type.
|
||||
* Declares a public property of the same name.
|
||||
* Initializes that property with the corresponding argument when creating an instance of the class.
|
||||
|
||||
|
||||
|
||||
## Using the Hero class
|
||||
|
||||
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ array
|
||||
@ -240,6 +291,8 @@ of `Hero` objects:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Next, update the template.
|
||||
At the moment it displays the hero's `id` and `name`.
|
||||
Fix that to display only the hero's `name` property.
|
||||
@ -249,8 +302,12 @@ Fix that to display only the hero's `name` property.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The display looks the same, but the code is clearer.
|
||||
|
||||
|
||||
|
||||
## Conditional display with NgIf
|
||||
|
||||
Sometimes an app needs to display a view or a portion of a view only under specific circumstances.
|
||||
@ -269,39 +326,50 @@ To see it in action, add the following paragraph at the bottom of the template:
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
Don't forget the leading asterisk (\*) in `*ngIf`. It is an essential part of the syntax.
|
||||
Read more about `ngIf` and `*` in the [ngIf section](guide/template-syntax) of the [Template Syntax](guide/template-syntax) page.
|
||||
Read more about `ngIf` and `*` in the [ngIf section](guide/template-syntax#ngIf) of the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The template expression inside the double quotes,
|
||||
`*ngIf="heros.length > 3"`, looks and behaves much like TypeScript.
|
||||
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](guide/template-syntax) section of the
|
||||
see the [template expressions](guide/template-syntax#template-expressions) section of the
|
||||
[Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
~~~ {.alert.is-helpful}
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Try it out. Because the array has four items, the message should appear.
|
||||
Go back into <code>app.component.ts"</code> and delete or comment out one of the elements from the hero array.
|
||||
The browser should refresh automatically and the message should disappear.
|
||||
|
||||
|
||||
|
||||
## Summary
|
||||
Now you know how to use:
|
||||
- **Interpolation** with double curly braces to display a component property.
|
||||
- **ngFor** to display an array of items.
|
||||
- A TypeScript class to shape the **model data** for your component and display properties of that model.
|
||||
- **ngIf** to conditionally display a chunk of HTML based on a boolean expression.
|
||||
|
||||
* **Interpolation** with double curly braces to display a component property.
|
||||
* **ngFor** to display an array of items.
|
||||
* A TypeScript class to shape the **model data** for your component and display properties of that model.
|
||||
* **ngIf** to conditionally display a chunk of HTML based on a boolean expression.
|
||||
|
||||
Here's the final code:
|
||||
|
||||
|
@ -5,114 +5,188 @@ Dynamic Component Loader
|
||||
Load components dynamically.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
Component templates are not always fixed. An application may need to load new components at runtime.
|
||||
|
||||
In this cookbook we show how to use `ComponentFactoryResolver` to add components dynamically.
|
||||
This cookbook shows you how to use `ComponentFactoryResolver` to add components dynamically.
|
||||
|
||||
<a id="toc"></a>## Table of contents
|
||||
{@a toc}
|
||||
|
||||
[Dynamic Component Loading](guide/dynamic-component-loader#dynamic-loading)
|
||||
# Contents
|
||||
|
||||
[Where to load the component](guide/dynamic-component-loader#where-to-load)
|
||||
* [Dynamic component loading](guide/dynamic-component-loader#dynamic-loading)
|
||||
* [The directive](guide/dynamic-component-loader#directive)
|
||||
* [Loading components](guide/dynamic-component-loader#loading-components)
|
||||
|
||||
[Loading components](guide/dynamic-component-loader#loading-components)
|
||||
* [Resolving Components](guide/dynamic-component-loader#resolving-components)
|
||||
* [Selector References](guide/dynamic-component-loader#selector-references)
|
||||
|
||||
<a id="dynamic-loading"></a>## Dynamic Component Loading
|
||||
* [A common _AdComponent_ interface](guide/dynamic-component-loader#common-interface)
|
||||
* [Final ad banner](guide/dynamic-component-loader#final-ad-banner)
|
||||
|
||||
|
||||
See the <live-example name="cb-dynamic-component-loader"></live-example>
|
||||
of the code in this cookbook.
|
||||
|
||||
{@a dynamic-loading}
|
||||
|
||||
## Dynamic component loading
|
||||
|
||||
The following example shows how to build a dynamic ad banner.
|
||||
|
||||
The hero agency is planning an ad campaign with several different ads cycling through the banner.
|
||||
The hero agency is planning an ad campaign with several different
|
||||
ads cycling through the banner. New ad components are added
|
||||
frequently by several different teams. This makes it impractical
|
||||
to use a template with a static component structure.
|
||||
|
||||
New ad components are added frequently by several different teams. This makes it impractical to use a template with a static component structure.
|
||||
Instead, you need a way to load a new component without a fixed
|
||||
reference to the component in the ad banner's template.
|
||||
|
||||
Instead we need a way to load a new component without a fixed reference to the component in the ad banner's template.
|
||||
|
||||
Angular comes with its own API for loading components dynamically. In the following sections you will learn how to use it.
|
||||
Angular comes with its own API for loading components dynamically.
|
||||
|
||||
|
||||
<a id="where-to-load"></a>## Where to load the component
|
||||
{@a directive}
|
||||
|
||||
Before components can be added we have to define an anchor point to mark where components can be inserted dynamically.
|
||||
## The directive
|
||||
|
||||
The ad banner uses a helper directive called `AdDirective` to mark valid insertion points in the template.
|
||||
Before you can add components you have to define an anchor point
|
||||
to tell Angular where to insert components.
|
||||
|
||||
The ad banner uses a helper directive called `AdDirective` to
|
||||
mark valid insertion points in the template.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-component-loader/src/app/ad.directive.ts" linenums="false">
|
||||
<code-example path="cb-dynamic-component-loader/src/app/ad.directive.ts" title="src/app/ad.directive.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
`AdDirective` injects `ViewContainerRef` to gain access to the view container of the element that will become the host of the dynamically added component.
|
||||
|
||||
<a id="loading-components"></a>## Loading components
|
||||
|
||||
The next step is to implement the ad banner. Most of the implementation is in `AdBannerComponent`.
|
||||
|
||||
We start by adding a `template` element with the `AdDirective` directive applied.
|
||||
|
||||
|
||||
<code-tabs>
|
||||
`AdDirective` injects `ViewContainerRef` to gain access to the view
|
||||
container of the element that will host the dynamically added component.
|
||||
|
||||
<code-pane title="ad-banner.component.ts" path="cb-dynamic-component-loader/src/app/ad-banner.component.ts">
|
||||
In the `@Directive` decorator, notice the selector name, `ad-host`;
|
||||
that's what you use to apply the directive to the element.
|
||||
The next section shows you how.
|
||||
|
||||
</code-pane>
|
||||
{@a loading-components}
|
||||
|
||||
<code-pane title="ad.service.ts" path="cb-dynamic-component-loader/src/app/ad.service.ts">
|
||||
## Loading components
|
||||
|
||||
</code-pane>
|
||||
Most of the ad banner implementation is in `ad-banner.component.ts`.
|
||||
To keep things simple in this example, the HTML is in the `@Component`
|
||||
decorator's `template` property as a template string.
|
||||
|
||||
<code-pane title="ad-item.ts" path="cb-dynamic-component-loader/src/app/ad-item.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app.module.ts" path="cb-dynamic-component-loader/src/app/app.module.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app.component" path="cb-dynamic-component-loader/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
The `template` element decorated with the `ad-host` directive marks where dynamically loaded components will be added.
|
||||
|
||||
Using a `template` element is recommended since it doesn't render any additional output.
|
||||
The `<ng-template>` element is where you apply the directive you just made.
|
||||
To apply the `AdDirective`, recall the selector from `ad.directive.ts`,
|
||||
`ad-host`. Apply that to `<ng-template>` without the square brackets. Now Angular knows
|
||||
where to dynamically load components.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-component-loader/src/app/ad-banner.component.ts" region="ad-host" linenums="false">
|
||||
<code-example path="cb-dynamic-component-loader/src/app/ad-banner.component.ts" region="ad-host" title="src/app/ad-banner.component.ts (template)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
### Resolving Components
|
||||
|
||||
`AdBanner` takes an array of `AdItem` objects as input. `AdItem` objects specify the type of component to load and any data to bind to the component.
|
||||
|
||||
The ad components making up the ad campaign are returned from `AdService`.
|
||||
|
||||
Passing an array of components to `AdBannerComponent` allows for a dynamic list of ads without static elements in the template.
|
||||
|
||||
`AdBannerComponent` cycles through the array of `AdItems` and loads the corresponding components on an interval. Every 3 seconds a new component is loaded.
|
||||
|
||||
`ComponentFactoryResolver` is used to resolve a `ComponentFactory` for each specific component. The component factory is need to create an instance of the component.
|
||||
|
||||
`ComponentFactories` are generated by the Angular compiler.
|
||||
|
||||
Generally the compiler will generate a component factory for any component referenced in a template.
|
||||
|
||||
With dynamically loaded components there are no selector references in the templates since components are loaded at runtime. In order to ensure that the compiler will still generate a factory, dynamically loaded components have to be added to their `NgModule`'s `entryComponents` array.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-component-loader/src/app/app.module.ts" region="entry-components" linenums="false">
|
||||
The `<ng-template>` element is a good choice for dynamic components
|
||||
because it doesn't render any additional output.
|
||||
|
||||
|
||||
{@a resolving-components}
|
||||
|
||||
|
||||
### Resolving components
|
||||
|
||||
Take a closer look at the methods in `ad-banner.component.ts`.
|
||||
|
||||
`AdBannerComponent` takes an array of `AdItem` objects as input,
|
||||
which ultimately comes from `AdService`. `AdItem` objects specify
|
||||
the type of component to load and any data to bind to the
|
||||
component.`AdService` returns the actual ads making up the ad campaign.
|
||||
|
||||
Passing an array of components to `AdBannerComponent` allows for a
|
||||
dynamic list of ads without static elements in the template.
|
||||
|
||||
With its `getAds()` method, `AdBannerComponent` cycles through the array of `AdItems`
|
||||
and loads a new component every 3 seconds by calling `loadComponent()`.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-component-loader/src/app/ad-banner.component.ts" region="class" title="src/app/ad-banner.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Components are added to the template by calling `createComponent` on the `ViewContainerRef` reference.
|
||||
|
||||
`createComponent` returns a reference to the loaded component. The component reference can be used to pass input data or call methods to interact with the component.
|
||||
|
||||
In the Ad banner, all components implement a common `AdComponent` interface to standardize the api for passing data to the components.
|
||||
The `loadComponent()` method is doing a lot of the heavy lifting here.
|
||||
Take it step by step. First, it picks an ad.
|
||||
|
||||
Two sample components and the `AdComponent` interface are shown below:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
**How _loadComponent()_ chooses an ad**
|
||||
|
||||
The `loadComponent()` method chooses an ad using some math.
|
||||
|
||||
First, it sets the `currentAddIndex` by taking whatever it
|
||||
currently is plus one, dividing that by the length of the `AdItem` array, and
|
||||
using the _remainder_ as the new `currentAddIndex` value. Then, it uses that
|
||||
value to select an `adItem` from the array.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
After `loadComponent()` selects an ad, it uses `ComponentFactoryResolver`
|
||||
to resolve a `ComponentFactory` for each specific component.
|
||||
The `ComponentFactory` then creates an instance of each component.
|
||||
|
||||
Next, you're targeting the `viewContainerRef` that
|
||||
exists on this specific instance of the component. How do you know it's
|
||||
this specific instance? Because it's referring to `adHost` and `adHost` is the
|
||||
directive you set up earlier to tell Angular where to insert dynamic components.
|
||||
|
||||
As you may recall, `AdDirective` injects `ViewContainerRef` into its constructor.
|
||||
This is how the directive accesses the element that you want to use to host the dynamic component.
|
||||
|
||||
To add the component to the template, you call `createComponent()` on `ViewContainerRef`.
|
||||
|
||||
The `createComponent()` method returns a reference to the loaded component.
|
||||
Use that reference to interact with the component by assigning to its properties or calling its methods.
|
||||
|
||||
|
||||
{@a selector-references}
|
||||
|
||||
|
||||
#### Selector references
|
||||
|
||||
Generally, the Angular compiler generates a `ComponentFactory`
|
||||
for any component referenced in a template. However, there are
|
||||
no selector references in the templates for
|
||||
dynamically loaded components since they load at runtime.
|
||||
|
||||
To ensure that the compiler still generates a factory,
|
||||
add dynamically loaded components to the `NgModule`'s `entryComponents` array:
|
||||
|
||||
<code-example path="cb-dynamic-component-loader/src/app/app.module.ts" region="entry-components" title="src/app/app.module.ts (entry components)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a common-interface}
|
||||
|
||||
|
||||
### A common _AdComponent_ interface
|
||||
|
||||
In the ad banner, all components implement a common `AdComponent` interface to
|
||||
standardize the API for passing data to the components.
|
||||
|
||||
Here are two sample components and the `AdComponent` interface for reference:
|
||||
|
||||
|
||||
<code-tabs>
|
||||
@ -131,9 +205,18 @@ Two sample components and the `AdComponent` interface are shown below:
|
||||
|
||||
</code-tabs>
|
||||
|
||||
The final ad banner looks like this:
|
||||
|
||||
|
||||
{@a final-ad-baner}
|
||||
|
||||
|
||||
### Final ad banner
|
||||
The final ad banner looks like this:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/dynamic-component-loader/ads.gif" alt="Ads"> </img>
|
||||
<img src="assets/images/cookbooks/dynamic-component-loader/ads.gif" alt="Ads"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
See the <live-example name="cb-dynamic-component-loader"></live-example>.
|
@ -5,6 +5,8 @@ Dynamic Forms
|
||||
Render dynamic forms with FormGroup.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
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.
|
||||
@ -19,7 +21,9 @@ 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>## Table of contents
|
||||
{@a toc}
|
||||
|
||||
## Table of contents
|
||||
|
||||
[Bootstrap](guide/dynamic-form#bootstrap)
|
||||
|
||||
@ -30,9 +34,13 @@ We can create the forms on the fly *without changing our application code*.
|
||||
[Questionnaire Metadata](guide/dynamic-form#questionnaire-metadata)
|
||||
|
||||
[Dynamic Template](guide/dynamic-form#dynamic-template)
|
||||
|
||||
|
||||
**See the <live-example name="cb-dynamic-form"></live-example>**.
|
||||
|
||||
<a id="bootstrap"></a>## Bootstrap
|
||||
{@a bootstrap}
|
||||
|
||||
## Bootstrap
|
||||
|
||||
We start by creating an `NgModule` called `AppModule`.
|
||||
|
||||
@ -56,7 +64,9 @@ We bootstrap our `AppModule` in main.ts.
|
||||
</code-tabs>
|
||||
|
||||
|
||||
<a id="object-model"></a>## Question Model
|
||||
{@a object-model}
|
||||
|
||||
## Question Model
|
||||
|
||||
The next 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.
|
||||
@ -65,37 +75,47 @@ The "question" is the most fundamental object in the model.
|
||||
We have created `QuestionBase` as the most fundamental question class.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/question-base.ts">
|
||||
<code-example path="cb-dynamic-form/src/app/question-base.ts" title="src/app/question-base.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/question-textbox.ts" linenums="false">
|
||||
<code-example path="cb-dynamic-form/src/app/question-textbox.ts" title="src/app/question-textbox.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
`DropdownQuestion` presents a list of choices in a select box.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/question-dropdown.ts" linenums="false">
|
||||
<code-example path="cb-dynamic-form/src/app/question-dropdown.ts" title="src/app/question-dropdown.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Next we have defined `QuestionControlService`, a simple service for transforming our questions to a `FormGroup`.
|
||||
In a nutshell, the form group consumes the metadata from the question model and allows us to specify default values and validation rules.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/question-control.service.ts" linenums="false">
|
||||
<code-example path="cb-dynamic-form/src/app/question-control.service.ts" title="src/app/question-control.service.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
<a id="form-component"></a>## Question form components
|
||||
{@a form-component}
|
||||
|
||||
## Question form components
|
||||
Now that we have defined the complete model we are ready to create components to represent the dynamic form.
|
||||
|
||||
|
||||
`DynamicFormComponent` is the entry point and the main container for the form.
|
||||
|
||||
<code-tabs>
|
||||
@ -110,6 +130,8 @@ Now that we have defined the complete model we are ready to create components to
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
@ -127,6 +149,8 @@ the component responsible for rendering the details of each _individual_ questio
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
@ -135,7 +159,11 @@ In both components we're relying on Angular's **formGroup** to connect the temp
|
||||
underlying control objects, populated from the question model with display and validation rules.
|
||||
|
||||
`formControlName` and `formGroup` are directives defined in `ReactiveFormsModule`. Our templates can access these directives directly since we imported `ReactiveFormsModule` from `AppModule`.
|
||||
<a id="questionnaire-metadata"></a>## Questionnaire data`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
|
||||
{@a questionnaire-metadata}
|
||||
|
||||
## Questionnaire data
|
||||
|
||||
`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.
|
||||
@ -144,18 +172,22 @@ underlying control objects, populated from the question model with display and v
|
||||
Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/question.service.ts">
|
||||
<code-example path="cb-dynamic-form/src/app/question.service.ts" title="src/app/question.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Finally, we display an instance of the form in the `AppComponent` shell.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/app.component.ts">
|
||||
<code-example path="cb-dynamic-form/src/app/app.component.ts" title="app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
<a id="dynamic-template"></a>## Dynamic Template
|
||||
{@a dynamic-template}
|
||||
|
||||
## 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`.
|
||||
|
||||
@ -169,10 +201,14 @@ 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.
|
||||
|
||||
|
||||
The final form looks like this:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/dynamic-form/dynamic-form.png" alt="Dynamic-Form"> </img>
|
||||
<img src="assets/images/cookbooks/dynamic-form/dynamic-form.png" alt="Dynamic-Form"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/dynamic-form#top)
|
@ -8,6 +8,8 @@ Validate user's form entries.
|
||||
|
||||
|
||||
{@a top}
|
||||
|
||||
|
||||
Improve overall data quality by validating user input for accuracy and completeness.
|
||||
|
||||
This cookbook shows how to validate user input in the UI and display useful validation messages
|
||||
@ -15,6 +17,8 @@ using first the template-driven forms and then the reactive forms approach.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Read more about these choices in the [Forms](guide/forms)
|
||||
and the [Reactive Forms](guide/reactive-forms) guides.
|
||||
|
||||
@ -24,25 +28,36 @@ and the [Reactive Forms](guide/reactive-forms) guides.
|
||||
|
||||
|
||||
{@a toc}
|
||||
|
||||
|
||||
## Contents
|
||||
|
||||
* [Simple template-driven forms](guide/form-validation#template1)
|
||||
* [Template-driven forms with validation messages in code](guide/form-validation#template2)
|
||||
- [Component Class](guide/form-validation#component-class)
|
||||
- [The benefits of messages in code](guide/form-validation#improvement)
|
||||
- [`FormModule` and template-driven forms](guide/form-validation#formmodule)
|
||||
|
||||
* [Component Class](guide/form-validation#component-class)
|
||||
* [The benefits of messages in code](guide/form-validation#improvement)
|
||||
* [`FormModule` and template-driven forms](guide/form-validation#formmodule)
|
||||
|
||||
* [Reactive forms with validation in code](guide/form-validation#reactive)
|
||||
- [Switch to the `ReactiveFormsModule`](guide/form-validation#reactive-forms-module)
|
||||
- [Component template](guide/form-validation#reactive-component-template)
|
||||
- [Component class](guide/form-validation#reactive-component-class)
|
||||
- [`FormBuilder` declaration](guide/form-validation#formbuilder)
|
||||
- [Committing hero value changes](guide/form-validation#committing-changes)
|
||||
|
||||
* [Switch to the `ReactiveFormsModule`](guide/form-validation#reactive-forms-module)
|
||||
* [Component template](guide/form-validation#reactive-component-template)
|
||||
* [Component class](guide/form-validation#reactive-component-class)
|
||||
|
||||
* [`FormBuilder` declaration](guide/form-validation#formbuilder)
|
||||
* [Committing hero value changes](guide/form-validation#committing-changes)
|
||||
|
||||
* [Custom validation](guide/form-validation#custom-validation)
|
||||
- [Custom validation directive](guide/form-validation#custom-validation-directive)
|
||||
|
||||
* [Custom validation directive](guide/form-validation#custom-validation-directive)
|
||||
|
||||
* [Testing considerations](guide/form-validation#testing)
|
||||
|
||||
|
||||
{@a live-example}
|
||||
|
||||
|
||||
**Try the live example to see and download the full cookbook source code.**
|
||||
|
||||
<live-example name="cb-form-validation" embedded=true img="cookbooks/form-validation/plunker.png">
|
||||
@ -53,6 +68,8 @@ and the [Reactive Forms](guide/reactive-forms) guides.
|
||||
|
||||
|
||||
{@a template1}
|
||||
|
||||
|
||||
## Simple template-driven forms
|
||||
|
||||
In the template-driven approach, you arrange
|
||||
@ -72,27 +89,30 @@ In this first template validation example,
|
||||
notice the HTML that reads the control state and updates the display appropriately.
|
||||
Here's an excerpt from the template HTML for a single input control bound to the hero name:
|
||||
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.html" region="name-with-error-msg" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.html" region="name-with-error-msg" title="template/hero-form-template1.component.html (Hero name)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Note the following:
|
||||
- The `<input>` element carries the HTML validation attributes: `required`, `minlength`, and `maxlength`.
|
||||
|
||||
- The `name` attribute of the input is set to `"name"` so Angular can track this input element and associate it
|
||||
|
||||
Note the following:
|
||||
|
||||
* The `<input>` element carries the HTML validation attributes: `required`, `minlength`, and `maxlength`.
|
||||
|
||||
* The `name` attribute of the input is set to `"name"` so Angular can track this input element and associate it
|
||||
with an Angular form control called `name` in its internal control model.
|
||||
|
||||
- The `[(ngModel)]` directive allows two-way data binding between the input box to the `hero.name` property.
|
||||
* The `[(ngModel)]` directive allows two-way data binding between the input box to the `hero.name` property.
|
||||
|
||||
- The template variable (`#name`) has the value `"ngModel"` (always `ngModel`).
|
||||
* The template variable (`#name`) has the value `"ngModel"` (always `ngModel`).
|
||||
This gives you a reference to the Angular `NgModel` directive
|
||||
associated with this control that you can use _in the template_
|
||||
to check for control states such as `valid` and `dirty`.
|
||||
|
||||
- The `*ngIf` on the `<div>` element reveals a set of nested message `divs` but only if there are "name" errors and
|
||||
* The `*ngIf` on the `<div>` element reveals a set of nested message `divs` but only if there are "name" errors and
|
||||
the control is either `dirty` or `touched`.
|
||||
|
||||
- Each nested `<div>` can present a custom message for one of the possible validation errors.
|
||||
* Each nested `<div>` can present a custom message for one of the possible validation errors.
|
||||
There are messages for `required`, `minlength`, and `maxlength`.
|
||||
|
||||
The full template repeats this kind of layout for each data entry control on the form.
|
||||
@ -102,6 +122,8 @@ The full template repeats this kind of layout for each data entry control on the
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
#### Why check _dirty_ and _touched_?
|
||||
|
||||
The app shouldn't show errors for a new hero before the user has had a chance to edit the value.
|
||||
@ -111,14 +133,18 @@ Learn about `dirty` and `touched` in the [Forms](guide/forms) guide.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The component class manages the hero model used in the data binding
|
||||
as well as other code to support the view.
|
||||
|
||||
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.ts" region="class">
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.ts" region="class" title="template/hero-form-template1.component.ts (class)">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Use this template-driven validation technique when working with static forms with simple, standard validation rules.
|
||||
|
||||
Here are the complete files for the first version of `HeroFormTemplateCompononent` in the template-driven approach:
|
||||
@ -140,6 +166,8 @@ Here are the complete files for the first version of `HeroFormTemplateCompononen
|
||||
|
||||
|
||||
{@a template2}
|
||||
|
||||
|
||||
## Template-driven forms with validation messages in code
|
||||
|
||||
While the layout is straightforward,
|
||||
@ -171,20 +199,25 @@ Here's the hero name again, excerpted from the revised template
|
||||
|
||||
</code-tabs>
|
||||
|
||||
The `<input>` element HTML is almost the same. There are noteworthy differences:
|
||||
- The hard-code error message `<divs>` are gone.
|
||||
|
||||
- There's a new attribute, `forbiddenName`, that is actually a custom validation directive.
|
||||
|
||||
The `<input>` element HTML is almost the same. There are noteworthy differences:
|
||||
|
||||
* The hard-code error message `<divs>` are gone.
|
||||
|
||||
* There's a new attribute, `forbiddenName`, that is actually a custom validation directive.
|
||||
It invalidates the control if the user enters "bob" in the name `<input>`([try it](guide/form-validation#live-example)).
|
||||
See the [custom validation](guide/form-validation#custom-validation) section later in this cookbook for more information
|
||||
on custom validation directives.
|
||||
|
||||
- The `#name` template variable is gone because the app no longer refers to the Angular control for this element.
|
||||
* The `#name` template variable is gone because the app no longer refers to the Angular control for this element.
|
||||
|
||||
- Binding to the new `formErrors.name` property is sufficent to display all name validation error messages.
|
||||
* Binding to the new `formErrors.name` property is sufficent to display all name validation error messages.
|
||||
|
||||
|
||||
{@a component-class}
|
||||
|
||||
|
||||
### Component class
|
||||
The original component code for Template 1 stayed the same; however,
|
||||
Template 2 requires some changes in the component. This section covers the code
|
||||
@ -196,36 +229,42 @@ The first step is to acquire the form control that Angular created from the temp
|
||||
Look back at the top of the component template at the
|
||||
`#heroForm` template variable in the `<form>` element:
|
||||
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.html" region="form-tag" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template1.component.html" region="form-tag" title="template/hero-form-template1.component.html (form tag)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `heroForm` variable is a reference to the control model that Angular derived from the template.
|
||||
Tell Angular to inject that model into the component class's `currentForm` property using a `@ViewChild` query:
|
||||
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="view-child" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="view-child" title="template/hero-form-template2.component.ts (heroForm)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Some observations:
|
||||
|
||||
- Angular `@ViewChild` queries for a template variable when you pass it
|
||||
* Angular `@ViewChild` queries for a template variable when you pass it
|
||||
the name of that variable as a string (`'heroForm'` in this case).
|
||||
|
||||
- The `heroForm` object changes several times during the life of the component, most notably when you add a new hero.
|
||||
* The `heroForm` object changes several times during the life of the component, most notably when you add a new hero.
|
||||
Periodically inspecting it reveals these changes.
|
||||
|
||||
- Angular calls the `ngAfterViewChecked` [lifecycle hook method](guide/lifecycle-hooks)
|
||||
* Angular calls the `ngAfterViewChecked` [lifecycle hook method](guide/lifecycle-hooks#afterview)
|
||||
when anything changes in the view.
|
||||
That's the right time to see if there's a new `heroForm` object.
|
||||
|
||||
- When there _is_ a new `heroForm` model, `formChanged()` subscribes to its `valueChanges` _Observable_ property.
|
||||
* When there _is_ a new `heroForm` model, `formChanged()` subscribes to its `valueChanges` _Observable_ property.
|
||||
The `onValueChanged` handler looks for validation errors after every keystroke.
|
||||
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="handler" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="handler" title="template/hero-form-template2.component.ts (handler)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `onValueChanged` handler interprets user data entry.
|
||||
The `data` object passed into the handler contains the current element values.
|
||||
The handler ignores them. Instead, it iterates over the fields of the component's `formErrors` object.
|
||||
@ -235,22 +274,26 @@ Only two hero properties have validation rules, `name` and `power`.
|
||||
The messages are empty strings when the hero data are valid.
|
||||
|
||||
For each field, the `onValueChanged` handler does the following:
|
||||
- Clears the prior error message, if any.
|
||||
- Acquires the field's corresponding Angular form control.
|
||||
- If such a control exists _and_ it's been changed ("dirty")
|
||||
* Clears the prior error message, if any.
|
||||
* Acquires the field's corresponding Angular form control.
|
||||
* If such a control exists _and_ it's been changed ("dirty")
|
||||
_and_ it's invalid, the handler composes a consolidated error message for all of the control's errors.
|
||||
|
||||
Next, the component needs some error messages of course—a set for each validated property with
|
||||
one message per validation rule:
|
||||
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="messages" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.ts" region="messages" title="template/hero-form-template2.component.ts (messages)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now every time the user makes a change, the `onValueChanged` handler checks for validation errors and produces messages accordingly.
|
||||
|
||||
|
||||
{@a improvement}
|
||||
|
||||
|
||||
### The benefits of messages in code
|
||||
|
||||
Clearly the template got substantially smaller while the component code got substantially larger.
|
||||
@ -276,6 +319,8 @@ In short, there are more opportunities to improve message handling now that text
|
||||
|
||||
|
||||
{@a formmodule}
|
||||
|
||||
|
||||
### _FormModule_ and template-driven forms
|
||||
|
||||
Angular has two different forms modules—`FormsModule` and
|
||||
@ -287,7 +332,7 @@ You've been reviewing the "Template-driven" approach which requires the `FormsMo
|
||||
Here's how you imported it in the `HeroFormTemplateModule`.
|
||||
|
||||
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template.module.ts" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template.module.ts" title="template/hero-form-template.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -295,6 +340,8 @@ Here's how you imported it in the `HeroFormTemplateModule`.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
This guide hasn't talked about the `SharedModule` or its `SubmittedComponent` which appears at the bottom of every
|
||||
form template in this cookbook.
|
||||
|
||||
@ -307,6 +354,8 @@ They're not germane to the validation story. Look at the [live example](guide/fo
|
||||
|
||||
|
||||
{@a reactive}
|
||||
|
||||
|
||||
## Reactive forms with validation in code
|
||||
|
||||
In the template-driven approach, you markup the template with form elements, validation attributes,
|
||||
@ -321,6 +370,7 @@ At runtime, Angular binds the template elements to your control model based on y
|
||||
This approach requires a bit more effort. *You have to write the control model and manage it*.
|
||||
|
||||
This allows you to do the following:
|
||||
|
||||
* Add, change, and remove validation functions on the fly.
|
||||
* Manipulate the control model dynamically from within the component.
|
||||
* [Test](guide/form-validation#testing) validation and control logic with isolated unit tests.
|
||||
@ -329,19 +379,25 @@ The following cookbook sample re-writes the hero form in _reactive forms_ style.
|
||||
|
||||
|
||||
{@a reactive-forms-module}
|
||||
|
||||
|
||||
### Switch to the _ReactiveFormsModule_
|
||||
The reactive forms classes and directives come from the Angular `ReactiveFormsModule`, not the `FormsModule`.
|
||||
The application module for the reactive forms feature in this sample looks like this:
|
||||
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.module.ts" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.module.ts" title="src/app/reactive/hero-form-reactive.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The reactive forms feature module and component are in the `src/app/reactive` folder.
|
||||
Focus on the `HeroFormReactiveComponent` there, starting with its template.
|
||||
|
||||
|
||||
{@a reactive-component-template}
|
||||
|
||||
|
||||
### Component template
|
||||
|
||||
Begin by changing the `<form>` tag so that it binds the Angular `formGroup` directive in the template
|
||||
@ -349,10 +405,12 @@ to the `heroForm` property in the component class.
|
||||
The `heroForm` is the control model that the component class builds and maintains.
|
||||
|
||||
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.html" region="form-tag" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.html" region="form-tag" title="cb-form-validation/src/app/reactive/hero-form-reactive.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Next, modify the template HTML elements to match the _reactive forms_ style.
|
||||
Here is the "name" portion of the template again, revised for reactive forms and compared with the template-driven version:
|
||||
|
||||
@ -368,16 +426,20 @@ Here is the "name" portion of the template again, revised for reactive forms and
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
Key changes are:
|
||||
- The validation attributes are gone (except `required`) because
|
||||
* The validation attributes are gone (except `required`) because
|
||||
validating happens in code.
|
||||
|
||||
- `required` remains, not for validation purposes (that's in the code),
|
||||
* `required` remains, not for validation purposes (that's in the code),
|
||||
but rather for css styling and accessibility.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A future version of reactive forms will add the `required` HTML validation attribute to the DOM element
|
||||
(and perhaps the `aria-required` attribute) when the control has the `required` validator function.
|
||||
|
||||
@ -387,16 +449,20 @@ to the control model, as you'll see below.
|
||||
|
||||
~~~
|
||||
|
||||
- The `formControlName` replaces the `name` attribute; it serves the same
|
||||
|
||||
|
||||
* The `formControlName` replaces the `name` attribute; it serves the same
|
||||
purpose of correlating the input with the Angular form control.
|
||||
|
||||
- The two-way `[(ngModel)]` binding is gone.
|
||||
* The two-way `[(ngModel)]` binding is gone.
|
||||
The reactive approach does not use data binding to move data into and out of the form controls.
|
||||
That's all in code.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The retreat from data binding is a principle of the reactive paradigm rather than a technical limitation.
|
||||
|
||||
~~~
|
||||
@ -404,6 +470,8 @@ The retreat from data binding is a principle of the reactive paradigm rather tha
|
||||
|
||||
|
||||
{@a reactive-component-class}
|
||||
|
||||
|
||||
### Component class
|
||||
|
||||
The component class is now responsible for defining and managing the form control model.
|
||||
@ -426,24 +494,32 @@ Here's the section of code devoted to that process, paired with the template-dri
|
||||
|
||||
</code-tabs>
|
||||
|
||||
- Inject `FormBuilder` in a constructor.
|
||||
|
||||
- Call a `buildForm` method in the `ngOnInit` [lifecycle hook method](guide/lifecycle-hooks)
|
||||
|
||||
* Inject `FormBuilder` in a constructor.
|
||||
|
||||
* Call a `buildForm` method in the `ngOnInit` [lifecycle hook method](guide/lifecycle-hooks#hooks-overview)
|
||||
because that's when you'll have the hero data. Call it again in the `addHero` method.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A real app would retrieve the hero asynchronously from a data service, a task best performed in the `ngOnInit` hook.
|
||||
|
||||
~~~
|
||||
|
||||
- The `buildForm` method uses the `FormBuilder`, `fb`, to declare the form control model.
|
||||
|
||||
|
||||
* The `buildForm` method uses the `FormBuilder`, `fb`, to declare the form control model.
|
||||
Then it attaches the same `onValueChanged` handler (there's a one line difference)
|
||||
to the form's `valueChanges` event and calls it immediately
|
||||
to set error messages for the new control model.
|
||||
|
||||
|
||||
{@a formbuilder}
|
||||
|
||||
|
||||
#### _FormBuilder_ declaration
|
||||
The `FormBuilder` declaration object specifies the three controls of the sample's hero form.
|
||||
|
||||
@ -460,7 +536,9 @@ discussed in a separate [section below](guide/form-validation#custom-validation)
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
Learn more about `FormBuilder` in the [Introduction to FormBuilder](guide/reactive-forms) section of Reactive Forms guide.
|
||||
|
||||
|
||||
Learn more about `FormBuilder` in the [Introduction to FormBuilder](guide/reactive-forms#formbuilder) section of Reactive Forms guide.
|
||||
|
||||
|
||||
~~~
|
||||
@ -468,6 +546,8 @@ Learn more about `FormBuilder` in the [Introduction to FormBuilder](guide/reacti
|
||||
|
||||
|
||||
{@a committing-changes}
|
||||
|
||||
|
||||
#### Committing hero value changes
|
||||
|
||||
In two-way data binding, the user's changes flow automatically from the controls back to the data model properties.
|
||||
@ -475,12 +555,13 @@ Reactive forms do not use data binding to update data model properties.
|
||||
The developer decides _when and how_ to update the data model from control values.
|
||||
|
||||
This sample updates the model twice:
|
||||
|
||||
1. When the user submits the form.
|
||||
1. When the user adds a new hero.
|
||||
|
||||
The `onSubmit()` method simply replaces the `hero` object with the combined values of the form:
|
||||
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="on-submit" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="on-submit" title="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -488,17 +569,23 @@ The `onSubmit()` method simply replaces the `hero` object with the combined valu
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
This example is lucky in that the `heroForm.value` properties _just happen_ to
|
||||
correspond _exactly_ to the hero data object properties.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The `addHero()` method discards pending changes and creates a brand new `hero` model object.
|
||||
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="add-hero" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="add-hero" title="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Then it calls `buildForm()` again which replaces the previous `heroForm` control model with a new one.
|
||||
The `<form>` tag's `[formGroup]` binding refreshes the page with the new control model.
|
||||
|
||||
@ -524,6 +611,8 @@ Here's the complete reactive component file, compared to the two template-driven
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Run the [live example](guide/form-validation#live-example) to see how the reactive form behaves,
|
||||
and to compare all of the files in this cookbook sample.
|
||||
|
||||
@ -534,6 +623,8 @@ and to compare all of the files in this cookbook sample.
|
||||
|
||||
|
||||
{@a custom-validation}
|
||||
|
||||
|
||||
## Custom validation
|
||||
This cookbook sample has a custom `forbiddenNamevalidator()` function that's applied to both the
|
||||
template-driven and the reactive form controls. It's in the `src/app/shared` folder
|
||||
@ -541,10 +632,12 @@ and declared in the `SharedModule`.
|
||||
|
||||
Here's the `forbiddenNamevalidator()` function:
|
||||
|
||||
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator" title="shared/forbidden-name.directive.ts (forbiddenNameValidator)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name
|
||||
and returns a validator function.
|
||||
|
||||
@ -560,40 +653,52 @@ and whose value is an arbitrary dictionary of values that you could insert into
|
||||
|
||||
|
||||
{@a custom-validation-directive}
|
||||
|
||||
|
||||
### Custom validation directive
|
||||
In the reactive forms component, the `'name'` control's validator function list
|
||||
has a `forbiddenNameValidator` at the bottom.
|
||||
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="name-validators" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/reactive/hero-form-reactive.component.ts" region="name-validators" title="reactive/hero-form-reactive.component.ts (name validators)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
In the _template-driven_ example, the `<input>` has the selector (`forbiddenName`)
|
||||
of a custom _attribute directive_, which rejects "bob".
|
||||
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.html" region="name-input" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/template/hero-form-template2.component.html" region="name-input" title="template/hero-form-template2.component.html (name input)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The corresponding `ForbiddenValidatorDirective` is a wrapper around the `forbiddenNameValidator`.
|
||||
|
||||
Angular `forms` recognizes the directive's role in the validation process because the directive registers itself
|
||||
with the `NG_VALIDATORS` provider, a provider with an extensible collection of validation directives.
|
||||
|
||||
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="directive-providers" linenums="false">
|
||||
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="directive-providers" title="shared/forbidden-name.directive.ts (providers)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here is the rest of the directive to help you get an idea of how it all comes together:
|
||||
|
||||
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="directive">
|
||||
<code-example path="cb-form-validation/src/app/shared/forbidden-name.directive.ts" region="directive" title="shared/forbidden-name.directive.ts (directive)">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
If you are familiar with Angular validations, you may have noticed
|
||||
that the custom validation directive is instantiated with `useExisting`
|
||||
rather than `useClass`. The registered validator must be _this instance_ of
|
||||
@ -611,8 +716,12 @@ This time, when you type “bob”, there's no "bob" error message.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
For more information on attaching behavior to elements,
|
||||
see [Attribute Directives](guide/attribute-directives).
|
||||
|
||||
@ -623,6 +732,8 @@ see [Attribute Directives](guide/attribute-directives).
|
||||
|
||||
|
||||
{@a testing}
|
||||
|
||||
|
||||
## Testing Considerations
|
||||
|
||||
You can write _isolated unit tests_ of validation and control logic in _Reactive Forms_.
|
||||
|
@ -5,6 +5,8 @@ Forms
|
||||
A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
Forms are the mainstay of business applications.
|
||||
You use forms to log in, submit a help request, place an order, book a flight,
|
||||
schedule a meeting, and perform countless other data-entry tasks.
|
||||
@ -18,15 +20,17 @@ which you'll learn about on this page.
|
||||
|
||||
This page shows you how to build a simple form from scratch. Along the way you'll learn how to:
|
||||
|
||||
- Build an Angular form with a component and template.
|
||||
- Use `ngModel` to create two-way data bindings for reading and writing input-control values.
|
||||
- Track state changes and the validity of form controls.
|
||||
- Provide visual feedback using special CSS classes that track the state of the controls.
|
||||
- Display validation errors to users and enable/disable form controls.
|
||||
- Share information across HTML elements using template reference variables.
|
||||
* Build an Angular form with a component and template.
|
||||
* Use `ngModel` to create two-way data bindings for reading and writing input-control values.
|
||||
* Track state changes and the validity of form controls.
|
||||
* Provide visual feedback using special CSS classes that track the state of the controls.
|
||||
* Display validation errors to users and enable/disable form controls.
|
||||
* Share information across HTML elements using template reference variables.
|
||||
|
||||
You can run the <live-example></live-example> in Plunker and download the code from there.
|
||||
|
||||
|
||||
|
||||
## Template-driven forms
|
||||
|
||||
You can build forms by writing templates in the Angular [template syntax](guide/template-syntax) with
|
||||
@ -35,12 +39,16 @@ the form-specific directives and techniques described in this page.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
You can also use a reactive (or model-driven) approach to build forms.
|
||||
However, this page focuses on template-driven forms.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
You can build almost any form with an Angular template—login forms, contact forms, and pretty much any business form.
|
||||
You can lay out the controls creatively, bind them to data, specify validation rules and display validation errors,
|
||||
conditionally enable or disable specific controls, trigger built-in visual feedback, and much more.
|
||||
@ -52,9 +60,11 @@ You'll learn to build a template-driven form that looks like this:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/forms/hero-form-1.png" width="400px" alt="Clean Form"> </img>
|
||||
<img src="assets/images/devguide/forms/hero-form-1.png" width="400px" alt="Clean Form"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The *Hero Employment Agency* uses this form to maintain personal information about heroes.
|
||||
Every hero needs a job. It's the company mission to match the right hero with the right crisis.
|
||||
|
||||
@ -64,19 +74,25 @@ If you delete the hero name, the form displays a validation error in an attentio
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/forms/hero-form-2.png" width="400px" alt="Invalid, Name Required"> </img>
|
||||
<img src="assets/images/devguide/forms/hero-form-2.png" width="400px" alt="Invalid, Name Required"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Note that the *Submit* button is disabled, and the "required" bar to the left of the input control changes from green to red.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
You can customize the colors and location of the "required" bar with standard CSS.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
You'll build this form in small steps:
|
||||
|
||||
1. Create the `Hero` model class.
|
||||
@ -88,6 +104,8 @@ You'll build this form in small steps:
|
||||
1. Show and hide validation-error messages.
|
||||
1. Handle form submission with *ngSubmit*.
|
||||
1. Disable the form’s *Submit* button until the form is valid.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Follow the [setup](guide/setup) instructions for creating a new project
|
||||
@ -105,10 +123,12 @@ and one optional field (`alterEgo`).
|
||||
In the `app` directory, create the following file with the given content:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero.ts">
|
||||
<code-example path="forms/src/app/hero.ts" title="src/app/hero.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
It's an anemic model with few requirements and no behavior. Perfect for the demo.
|
||||
|
||||
The TypeScript compiler generates a public field for each `public` constructor parameter and
|
||||
@ -124,6 +144,8 @@ You can create a new hero like this:
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
## Create a form component
|
||||
|
||||
An Angular form has two parts: an HTML-based _template_ and a component _class_
|
||||
@ -137,21 +159,25 @@ Create the following file with the given content:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
There’s nothing special about this component, nothing form-specific,
|
||||
nothing to distinguish it from any component you've written before.
|
||||
|
||||
Understanding this component requires only the Angular concepts covered in previous pages.
|
||||
|
||||
- The code imports the Angular core library and the `Hero` model you just created.
|
||||
- The `@Component` selector value of "hero-form" means you can drop this form in a parent template with a `<hero-form>` tag.
|
||||
- The `templateUrl` property points to a separate file for the template HTML.
|
||||
- You defined dummy data for `model` and `powers`, as befits a demo.
|
||||
* The code imports the Angular core library and the `Hero` model you just created.
|
||||
* The `@Component` selector value of "hero-form" means you can drop this form in a parent template with a `<hero-form>` tag.
|
||||
* The `templateUrl` property points to a separate file for the template HTML.
|
||||
* You defined dummy data for `model` and `powers`, as befits a demo.
|
||||
|
||||
Down the road, you can inject a data service to get and save real data
|
||||
or perhaps expose these properties as inputs and outputs
|
||||
(see [Input and output properties](guide/template-syntax) on the
|
||||
(see [Input and output properties](guide/template-syntax#inputs-outputs) on the
|
||||
[Template Syntax](guide/template-syntax) page) for binding to a
|
||||
parent component. This is not a concern now and these future changes won't affect the form.
|
||||
- You added a `diagnostic` property to return a JSON representation of the model.
|
||||
|
||||
* You added a `diagnostic` property to return a JSON representation of the model.
|
||||
It'll help you see what you're doing during development; you've left yourself a cleanup note to discard it later.
|
||||
|
||||
### Why the separate template file?
|
||||
@ -167,6 +193,8 @@ so it's usually best to put the HTML template in a separate file.
|
||||
You'll write that template file in a moment. First,
|
||||
revise the `app.module.ts` and `app.component.ts` to make use of the new `HeroFormComponent`.
|
||||
|
||||
|
||||
|
||||
## Revise *app.module.ts*
|
||||
|
||||
`app.module.ts` defines the application's root module. In it you identify the external modules you'll use in the application
|
||||
@ -177,14 +205,18 @@ Because template-driven forms are in their own module, you need to add the `Form
|
||||
|
||||
Replace the contents of the "QuickStart" version with the following:
|
||||
|
||||
<code-example path="forms/src/app/app.module.ts">
|
||||
<code-example path="forms/src/app/app.module.ts" title="src/app/app.module.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
There are three changes:
|
||||
|
||||
1. You import `FormsModule` and the new `HeroFormComponent`.
|
||||
@ -202,6 +234,8 @@ the `HeroFormComponent` component visible throughout this module.
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
If a component, directive, or pipe belongs to a module in the `imports` array, _don't_ re-declare it in the `declarations` array.
|
||||
If you wrote it and it should belong to this module, _do_ declare it in the `declarations` array.
|
||||
|
||||
@ -209,6 +243,8 @@ If you wrote it and it should belong to this module, _do_ declare it in th
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Revise *app.component.ts*
|
||||
|
||||
`AppComponent` is the application's root component. It will host the new `HeroFormComponent`.
|
||||
@ -216,14 +252,18 @@ If you wrote it and it should belong to this module, _do_ declare it in th
|
||||
Replace the contents of the "QuickStart" version with the following:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/app.component.ts">
|
||||
<code-example path="forms/src/app/app.component.ts" title="src/app/app.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
There are only two changes.
|
||||
The `template` is simply the new element tag identified by the component's `selector` property.
|
||||
This displays the hero form when the application component is loaded.
|
||||
@ -233,15 +273,19 @@ You've also dropped the `name` field from the class body.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Create an initial HTML form template
|
||||
|
||||
Create the template file with the following contents:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="start">
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="start" title="src/app/hero-form.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The language is simply HTML5. You're presenting two of the `Hero` fields, `name` and `alterEgo`, and
|
||||
opening them up for user input in input boxes.
|
||||
|
||||
@ -265,12 +309,16 @@ Bootstrap gives the form a little style.
|
||||
Angular forms don't require a style library
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
|
||||
the styles of any external library. Angular apps can use any CSS library or none at all.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
To add the stylesheet, open `index.html` and add the following link to the `<head>`:
|
||||
|
||||
|
||||
@ -279,6 +327,8 @@ To add the stylesheet, open `index.html` and add the following link to the `<hea
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
## Add powers with _*ngFor_
|
||||
|
||||
The hero must choose one superpower from a fixed list of agency-approved powers.
|
||||
@ -295,19 +345,25 @@ Add the following HTML *immediately below* the *Alter Ego* group:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
This code repeats the `<option>` tag for each power in the list of powers.
|
||||
The `pow` template input variable is a different power in each iteration;
|
||||
you display its name using the interpolation syntax.
|
||||
|
||||
|
||||
|
||||
## Two-way data binding with _ngModel_
|
||||
|
||||
Running the app right now would be disappointing.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/forms/hero-form-3.png" width="400px" alt="Early form with no binding"> </img>
|
||||
<img src="assets/images/devguide/forms/hero-form-3.png" width="400px" alt="Early form with no binding"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
You don't see hero data because you're not binding to the `Hero` yet.
|
||||
You know how to do that from earlier pages.
|
||||
[Displaying Data](guide/displaying-data) teaches property binding.
|
||||
@ -331,6 +387,8 @@ Find the `<input>` tag for *Name* and update it like this:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
You added a diagnostic interpolation after the input tag
|
||||
so you can see what you're doing.
|
||||
You left yourself a note to throw it away when you're done.
|
||||
@ -338,6 +396,8 @@ You left yourself a note to throw it away when you're done.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Focus on the binding syntax: `[(ngModel)]="..."`.
|
||||
|
||||
If you ran the app now and started typing in the *Name* input box,
|
||||
@ -347,23 +407,29 @@ At some point it might look like this:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/forms/ng-model-in-action.png" width="400px" alt="ngModel in action"> </img>
|
||||
<img src="assets/images/devguide/forms/ng-model-in-action.png" width="400px" alt="ngModel in action"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The diagnostic is evidence that values really are flowing from the input box to the model and
|
||||
back again.
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
That's *two-way data binding*.
|
||||
For more information, see
|
||||
[Two-way binding with NgModel](guide/template-syntax) on the
|
||||
[Two-way binding with NgModel](guide/template-syntax#ngModel) on the
|
||||
the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Notice that you also added a `name` attribute to the `<input>` tag and set it to "name",
|
||||
which makes sense for the hero's name. Any unique value will do, but using a descriptive name is helpful.
|
||||
Defining a `name` attribute is a requirement when using `[(ngModel)]` in combination with a form.
|
||||
@ -371,6 +437,8 @@ Defining a `name` attribute is a requirement when using `[(ngModel)]` in combina
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Internally, Angular creates `FormControl` instances and
|
||||
registers them with an `NgForm` directive that Angular attached to the `<form>` tag.
|
||||
Each `FormControl` is registered under the name you assigned to the `name` attribute.
|
||||
@ -379,6 +447,8 @@ Read more in [The NgForm directive](guide/forms#ngForm), later in this page.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Add similar `[(ngModel)]` bindings and `name` attributes to *Alter Ego* and *Hero Power*.
|
||||
You'll ditch the input box binding message
|
||||
and add a new binding (at the top) to the component's `diagnostic` property.
|
||||
@ -395,25 +465,33 @@ After revision, the core of the form should look like this:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
- Each input element has an `id` property that is used by the `label` element's `for` attribute
|
||||
|
||||
|
||||
* Each input element has an `id` property that is used by the `label` element's `for` attribute
|
||||
to match the label to its input control.
|
||||
- Each input element has a `name` property that is required by Angular forms to register the control with the form.
|
||||
* Each input element has a `name` property that is required by Angular forms to register the control with the form.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
If you run the app now and change every hero model property, the form might display like this:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/forms/ng-model-in-action-2.png" width="400px" alt="ngModel in action"> </img>
|
||||
<img src="assets/images/devguide/forms/ng-model-in-action-2.png" width="400px" alt="ngModel in action"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The diagnostic near the top of the form
|
||||
confirms that all of your changes are reflected in the model.
|
||||
|
||||
*Delete* the `{{diagnostic}}` binding at the top as it has served its purpose.
|
||||
|
||||
|
||||
|
||||
## Track control state and validity with _ngModel_
|
||||
|
||||
Using `ngModel` in a form gives you more than just two-way data binding. It also tells
|
||||
@ -491,7 +569,9 @@ You can leverage those class names to change the appearance of the control.
|
||||
|
||||
</table>
|
||||
|
||||
Temporarily add a [template reference variable](guide/template-syntax) named `spy`
|
||||
|
||||
|
||||
Temporarily add a [template reference variable](guide/template-syntax#ref-vars) named `spy`
|
||||
to the _Name_ `<input>` tag and use it to display the input's CSS classes.
|
||||
|
||||
|
||||
@ -499,6 +579,8 @@ to the _Name_ `<input>` tag and use it to display the input's CSS classes.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now run the app and look at the _Name_ input box.
|
||||
Follow these steps *precisely*:
|
||||
|
||||
@ -511,22 +593,28 @@ The actions and effects are as follows:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/forms/control-state-transitions-anim.gif" alt="Control State Transition"> </img>
|
||||
<img src="assets/images/devguide/forms/control-state-transitions-anim.gif" alt="Control State Transition"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
You should see the following transitions and class names:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/forms/ng-control-class-changes.png" width="500px" alt="Control state transitions"> </img>
|
||||
<img src="assets/images/devguide/forms/ng-control-class-changes.png" width="500px" alt="Control state transitions"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The `ng-valid`/`ng-invalid` pair is the most interesting, because you want to send a
|
||||
strong visual signal when the values are invalid. You also want to mark required fields.
|
||||
To create such visual feedback, add definitions for the `ng-*` CSS classes.
|
||||
|
||||
*Delete* the `#spy` template reference variable and the `TODO` as they have served their purpose.
|
||||
|
||||
|
||||
|
||||
## Add custom CSS for visual feedback
|
||||
|
||||
You can mark required fields and invalid data at the same time with a colored bar
|
||||
@ -534,17 +622,21 @@ on the left of the input box:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/forms/validity-required-indicator.png" width="400px" alt="Invalid Form"> </img>
|
||||
<img src="assets/images/devguide/forms/validity-required-indicator.png" width="400px" alt="Invalid Form"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
You achieve this effect by adding these class definitions to a new `forms.css` file
|
||||
that you add to the project as a sibling to `index.html`:
|
||||
|
||||
|
||||
<code-example path="forms/src/forms.css">
|
||||
<code-example path="forms/src/forms.css" title="src/forms.css">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Update the `<head>` of `index.html` to include this style sheet:
|
||||
|
||||
|
||||
@ -552,6 +644,8 @@ Update the `<head>` of `index.html` to include this style sheet:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
## Show and hide validation error messages
|
||||
|
||||
You can improve the form. The _Name_ input box is required and clearing it turns the bar red.
|
||||
@ -562,12 +656,15 @@ When the user deletes the name, the form should look like this:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/forms/name-required-error.png" width="400px" alt="Name required"> </img>
|
||||
<img src="assets/images/devguide/forms/name-required-error.png" width="400px" alt="Name required"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
To achieve this effect, extend the `<input>` tag with the following:
|
||||
- A [template reference variable](guide/template-syntax).
|
||||
- The "*is required*" message in a nearby `<div>`, which you'll display only if the control is invalid.
|
||||
|
||||
* A [template reference variable](guide/template-syntax#ref-vars).
|
||||
* The "*is required*" message in a nearby `<div>`, which you'll display only if the control is invalid.
|
||||
|
||||
Here's an example of an error message added to the _name_ input box:
|
||||
|
||||
@ -576,12 +673,16 @@ Here's an example of an error message added to the _name_ input box:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You need a template reference variable to access the input box's Angular control from within the template.
|
||||
Here you created a variable called `name` and gave it the value "ngModel".
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Why "ngModel"?
|
||||
A directive's [exportAs](api/core/index/Directive-decorator) property
|
||||
tells Angular how to link the reference variable to the directive.
|
||||
@ -590,6 +691,8 @@ You set `name` to `ngModel` because the `ngModel` directive's `exportAs` propert
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
You control visibility of the name error message by binding properties of the `name`
|
||||
control to the message `<div>` element's `hidden` property.
|
||||
|
||||
@ -598,6 +701,8 @@ control to the message `<div>` element's `hidden` property.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
In this example, you hide the message when the control is valid or pristine;
|
||||
"pristine" means the user hasn't changed the value since it was displayed in this form.
|
||||
|
||||
@ -621,16 +726,18 @@ Now you'll add a new hero in this form.
|
||||
Place a *New Hero* button at the bottom of the form and bind its click event to a `newHero` component method.
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="new-hero-button-no-reset">
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="new-hero-button-no-reset" title="src/app/hero-form.component.html (New Hero button)">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.ts" region="new-hero" linenums="false">
|
||||
<code-example path="forms/src/app/hero-form.component.ts" region="new-hero" title="src/app/hero-form.component.ts (New Hero method)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Run the application again, click the *New Hero* button, and the form clears.
|
||||
The *required* bars to the left of the input box are red, indicating invalid `name` and `power` properties.
|
||||
That's understandable as these are required fields.
|
||||
@ -649,12 +756,16 @@ You have to clear all of the flags imperatively, which you can do
|
||||
by calling the form's `reset()` method after calling the `newHero()` method.
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="new-hero-button-form-reset">
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="new-hero-button-form-reset" title="src/app/hero-form.component.html (Reset the form)">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now clicking "New Hero" resets both the form and its control flags.
|
||||
|
||||
|
||||
|
||||
## Submit the form with _ngSubmit_
|
||||
|
||||
The user should be able to submit this form after filling it in.
|
||||
@ -667,10 +778,12 @@ To make it useful, bind the form's `ngSubmit` event property
|
||||
to the hero form component's `onSubmit()` method:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="forms/ts/src/app/hero-form.component.html (ngSubmit)" region="ngSubmit">
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (ngSubmit)" region="ngSubmit">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You added something extra at the end. You defined a
|
||||
template reference variable, `#heroForm`, and initialized it with the value "ngForm".
|
||||
|
||||
@ -679,6 +792,8 @@ The variable `heroForm` is now a reference to the `NgForm` directive that govern
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
### The _NgForm_ directive
|
||||
|
||||
What `NgForm` directive?
|
||||
@ -695,6 +810,8 @@ control* is valid.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
You'll bind the form's overall validity via
|
||||
the `heroForm` variable to the button's `disabled` property
|
||||
using an event binding. Here's the code:
|
||||
@ -704,6 +821,8 @@ using an event binding. Here's the code:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
If you run the application now, you find that the button is enabled—although
|
||||
it doesn't do anything useful yet.
|
||||
|
||||
@ -719,6 +838,8 @@ For you, it was as simple as this:
|
||||
1. Define a template reference variable on the (enhanced) form element.
|
||||
2. Refer to that variable in a button many lines away.
|
||||
|
||||
|
||||
|
||||
## Toggle two form regions (extra credit)
|
||||
|
||||
Submitting the form isn't terribly dramatic at the moment.
|
||||
@ -726,6 +847,8 @@ Submitting the form isn't terribly dramatic at the moment.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
An unsurprising observation for a demo. To be honest,
|
||||
jazzing it up won't teach you anything new about forms.
|
||||
But this is an opportunity to exercise some of your newly won
|
||||
@ -735,6 +858,8 @@ If you aren't interested, skip to this page's conclusion.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
For a more strikingly visual effect,
|
||||
hide the data entry area and display something else.
|
||||
|
||||
@ -746,6 +871,8 @@ its `hidden` property to the `HeroFormComponent.submitted` property.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The main form is visible from the start because the
|
||||
`submitted` property is false until you submit the form,
|
||||
as this fragment from the `HeroFormComponent` shows:
|
||||
@ -755,6 +882,8 @@ as this fragment from the `HeroFormComponent` shows:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
When you click the *Submit* button, the `submitted` flag becomes true and the form disappears
|
||||
as planned.
|
||||
|
||||
@ -766,6 +895,8 @@ Add the following HTML below the `<div>` wrapper you just wrote:
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
There's the hero again, displayed read-only with interpolation bindings.
|
||||
This `<div>` appears only while the component is in the submitted state.
|
||||
|
||||
@ -774,20 +905,22 @@ that clears the `submitted` flag.
|
||||
|
||||
When you click the *Edit* button, this block disappears and the editable form reappears.
|
||||
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
The Angular form discussed in this page takes advantage of the following
|
||||
framework features to provide support for data modification, validation, and more:
|
||||
|
||||
- An Angular HTML form template.
|
||||
- A form component class with a `@Component` decorator.
|
||||
- Handling form submission by binding to the `NgForm.ngSubmit` event property.
|
||||
- Template-reference variables such as `#heroForm` and `#name`.
|
||||
- `[(ngModel)]` syntax for two-way data binding.
|
||||
- The use of `name` attributes for validation and form-element change tracking.
|
||||
- The reference variable’s `valid` property on input controls to check if a control is valid and show/hide error messages.
|
||||
- Controlling the *Submit* button's enabled state by binding to `NgForm` validity.
|
||||
- Custom CSS classes that provide visual feedback to users about invalid controls.
|
||||
* An Angular HTML form template.
|
||||
* A form component class with a `@Component` decorator.
|
||||
* Handling form submission by binding to the `NgForm.ngSubmit` event property.
|
||||
* Template-reference variables such as `#heroForm` and `#name`.
|
||||
* `[(ngModel)]` syntax for two-way data binding.
|
||||
* The use of `name` attributes for validation and form-element change tracking.
|
||||
* The reference variable’s `valid` property on input controls to check if a control is valid and show/hide error messages.
|
||||
* Controlling the *Submit* button's enabled state by binding to `NgForm` validity.
|
||||
* Custom CSS classes that provide visual feedback to users about invalid controls.
|
||||
|
||||
The final project folder structure should look like this:
|
||||
|
||||
@ -848,6 +981,8 @@ The final project folder structure should look like this:
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
|
||||
|
||||
Here’s the code for the final version of the application:
|
||||
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
@description
|
||||
|
||||
|
||||
|
||||
Angular has its own vocabulary.
|
||||
Most Angular terms are common English words
|
||||
with a specific meaning within the Angular system.
|
||||
@ -15,10 +17,14 @@ unexpected definitions.
|
||||
|
||||
|
||||
{@a aot}
|
||||
|
||||
|
||||
## Ahead-of-time (AOT) compilation
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
You can compile Angular applications at build time.
|
||||
By compiling your application using the compiler-cli, `ngc`, you can bootstrap directly
|
||||
to a module factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
|
||||
@ -27,10 +33,14 @@ Ahead-of-time compiled applications also benefit from decreased load time and in
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Angular module
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Helps you organize an application into cohesive blocks of functionality.
|
||||
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
|
||||
|
||||
@ -42,10 +52,14 @@ For details and examples, see the [Angular Modules (NgModule)](guide/ngmodule) p
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Annotation
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
In practice, a synonym for [Decoration](guide/glossary#decorator).
|
||||
|
||||
|
||||
@ -57,10 +71,14 @@ In practice, a synonym for [Decoration](guide/glossary#decorator).
|
||||
|
||||
|
||||
{@a attribute-directives}
|
||||
|
||||
|
||||
## Attribute directives
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A category of [directive](guide/glossary#directive) that can listen to and modify the behavior of
|
||||
other HTML elements, attributes, properties, and components. They are usually represented
|
||||
as HTML attributes, hence the name.
|
||||
@ -73,10 +91,14 @@ Learn about them in the [_Attribute Directives_](guide/attribute-directives) gui
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Barrel
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A way to *roll up exports* from several ES2015 modules into a single convenient ES2015 module.
|
||||
The barrel itself is an ES2015 module file that re-exports *selected* exports of other ES2015 modules.
|
||||
|
||||
@ -84,42 +106,52 @@ For example, imagine three ES2015 modules in a `heroes` folder:
|
||||
|
||||
<code-example>
|
||||
// heroes/hero.component.ts
|
||||
export class HeroComponent {}
|
||||
export class HeroComponent {}
|
||||
|
||||
// heroes/hero.model.ts
|
||||
export class Hero {}
|
||||
// heroes/hero.model.ts
|
||||
export class Hero {}
|
||||
|
||||
// heroes/hero.service.ts
|
||||
export class HeroService {}
|
||||
// heroes/hero.service.ts
|
||||
export class HeroService {}
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Without a barrel, a consumer needs three import statements:
|
||||
|
||||
<code-example>
|
||||
import { HeroComponent } from '../heroes/hero.component.ts';
|
||||
import { Hero } from '../heroes/hero.model.ts';
|
||||
import { HeroService } from '../heroes/hero.service.ts';
|
||||
import { Hero } from '../heroes/hero.model.ts';
|
||||
import { HeroService } from '../heroes/hero.service.ts';
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You can add a barrel to the `heroes` folder (called `index`, by convention) that exports all of these items:
|
||||
|
||||
<code-example>
|
||||
export * from './hero.model.ts'; // re-export all of its exports
|
||||
export * from './hero.service.ts'; // re-export all of its exports
|
||||
export { HeroComponent } from './hero.component.ts'; // re-export the named thing
|
||||
export * from './hero.service.ts'; // re-export all of its exports
|
||||
export { HeroComponent } from './hero.component.ts'; // re-export the named thing
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now a consumer can import what it needs from the barrel.
|
||||
|
||||
<code-example>
|
||||
import { Hero, HeroService } from '../heroes'; // index is implied
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The Angular [scoped packages](guide/glossary#scoped-package) each have a barrel named `index`.
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
You can often achieve the same result using [Angular modules](guide/glossary#angular-module) instead.
|
||||
|
||||
|
||||
@ -129,10 +161,14 @@ You can often achieve the same result using [Angular modules](guide/glossary#ang
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Binding
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Usually refers to [data binding](guide/glossary#data-binding) and the act of
|
||||
binding an HTML object property to a data object property.
|
||||
|
||||
@ -142,10 +178,14 @@ between a "token"—also referred to as a "key"—and a dependency [prov
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Bootstrap
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
|
||||
Bootstrapping identifies an application's top level "root" [component](guide/glossary#component),
|
||||
which is the first component that is loaded for the application.
|
||||
@ -157,10 +197,14 @@ You can bootstrap multiple apps in the same `index.html`, each app with its own
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## camelCase
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The practice of writing compound words or phrases such that each word or abbreviation begins with a capital letter
|
||||
_except the first letter, which is lowercase_.
|
||||
|
||||
@ -175,10 +219,14 @@ In Angular documentation, "camelCase" always means *lower camel case*.
|
||||
|
||||
|
||||
{@a component}
|
||||
|
||||
|
||||
## Component
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
An Angular class responsible for exposing data to a [view](guide/glossary#view) and handling most of the view’s display and user-interaction logic.
|
||||
|
||||
The *component* is one of the most important building blocks in the Angular system.
|
||||
@ -196,10 +244,14 @@ the component in the role of "controller" or "view model".
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## dash-case
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The practice of writing compound words or phrases such that each word is separated by a dash or hyphen (`-`).
|
||||
This form is also known as kebab-case.
|
||||
|
||||
@ -210,10 +262,14 @@ spelled in dash-case.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Data binding
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Applications display data values to a user and respond to user
|
||||
actions (such as clicks, touches, and keystrokes).
|
||||
|
||||
@ -227,13 +283,14 @@ Angular has a rich data-binding framework with a variety of data-binding
|
||||
operations and supporting declaration syntax.
|
||||
|
||||
Read about the following forms of binding in the [Template Syntax](guide/template-syntax) page:
|
||||
* [Interpolation](guide/template-syntax).
|
||||
* [Property binding](guide/template-syntax).
|
||||
* [Event binding](guide/template-syntax).
|
||||
* [Attribute binding](guide/template-syntax).
|
||||
* [Class binding](guide/template-syntax).
|
||||
* [Style binding](guide/template-syntax).
|
||||
* [Two-way data binding with ngModel](guide/template-syntax).
|
||||
|
||||
* [Interpolation](guide/template-syntax#interpolation).
|
||||
* [Property binding](guide/template-syntax#property-binding).
|
||||
* [Event binding](guide/template-syntax#event-binding).
|
||||
* [Attribute binding](guide/template-syntax#attribute-binding).
|
||||
* [Class binding](guide/template-syntax#class-binding).
|
||||
* [Style binding](guide/template-syntax#style-binding).
|
||||
* [Two-way data binding with ngModel](guide/template-syntax#ngModel).
|
||||
|
||||
|
||||
~~~
|
||||
@ -244,10 +301,14 @@ operations and supporting declaration syntax.
|
||||
|
||||
|
||||
{@a decoration}
|
||||
|
||||
|
||||
## Decorator | decoration
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A *function* that adds metadata to a class, its members (properties, methods) and function arguments.
|
||||
|
||||
Decorators are a JavaScript language [feature](https://github.com/wycats/javascript-decorators), implemented in TypeScript and proposed for ES2016 (also known as ES7).
|
||||
@ -272,6 +333,8 @@ classes that follow it in the file.
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
Always include parentheses `()` when applying a decorator.
|
||||
|
||||
|
||||
@ -281,10 +344,14 @@ Always include parentheses `()` when applying a decorator.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Dependency injection
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A design pattern and mechanism
|
||||
for creating and delivering parts of an application to other
|
||||
parts of an application that request them.
|
||||
@ -311,7 +378,7 @@ into other application parts where and when needed.
|
||||
At the core, an [`injector`](guide/glossary#injector) returns dependency values on request.
|
||||
The expression `injector.get(token)` returns the value associated with the given token.
|
||||
|
||||
A token is an Angular type (`OpaqueToken`). You rarely need to work with tokens directly; most
|
||||
A token is an Angular type (`InjectionToken`). You rarely need to work with tokens directly; most
|
||||
methods accept a class name (`Foo`) or a string ("foo") and Angular converts it
|
||||
to a token. When you write `injector.get(Foo)`, the injector returns
|
||||
the value associated with the token for the `Foo` class, typically an instance of `Foo` itself.
|
||||
@ -343,10 +410,14 @@ Read more in the [Dependency Injection](guide/dependency-injection) page.
|
||||
|
||||
|
||||
{@a directives}
|
||||
|
||||
|
||||
## Directive
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
An Angular class responsible for creating, reshaping, and interacting with HTML elements
|
||||
in the browser DOM. The directive is Angular's most fundamental feature.
|
||||
|
||||
@ -380,10 +451,14 @@ elements and their children.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## ECMAScript
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
|
||||
|
||||
The latest approved version of JavaScript is
|
||||
@ -401,26 +476,38 @@ Angular developers can write in ES5 directly.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## ES2015
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## ES5
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Short hand for [ECMAScript](guide/glossary#ecmascript) 5, the version of JavaScript run by most modern browsers.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## ES6
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
||||
|
||||
|
||||
@ -436,10 +523,14 @@ Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
||||
|
||||
{@a H}
|
||||
|
||||
|
||||
|
||||
## Injector
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
An object in the Angular [dependency-injection system](guide/glossary#dependency-injection)
|
||||
that can find a named dependency in its cache or create a dependency
|
||||
with a registered [provider](guide/glossary#provider).
|
||||
@ -447,24 +538,32 @@ with a registered [provider](guide/glossary#provider).
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Input
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A directive property that can be the *target* of a
|
||||
[property binding](guide/template-syntax) (explained in detail in the [Template Syntax](guide/template-syntax) page).
|
||||
[property binding](guide/template-syntax#property-binding) (explained in detail in the [Template Syntax](guide/template-syntax) page).
|
||||
Data values flow *into* this property from the data source identified
|
||||
in the template expression to the right of the equal sign.
|
||||
|
||||
See the [Input and output properties](guide/template-syntax) section of the [Template Syntax](guide/template-syntax) page.
|
||||
See the [Input and output properties](guide/template-syntax#inputs-outputs) section of the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Interpolation
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A form of [property data binding](guide/glossary#data-binding) in which a
|
||||
[template expression](guide/glossary#template-expression) between double-curly braces
|
||||
renders as text. That text may be concatenated with neighboring text
|
||||
@ -477,7 +576,9 @@ or displayed between element tags, as in this example.
|
||||
|
||||
</code-example>
|
||||
|
||||
Read more about [interpolation](guide/template-syntax) in the
|
||||
|
||||
|
||||
Read more about [interpolation](guide/template-syntax#interpolation) in the
|
||||
[Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
@ -487,10 +588,14 @@ Read more about [interpolation](guide/template-syntax) in the
|
||||
|
||||
|
||||
{@a jit}
|
||||
|
||||
|
||||
## Just-in-time (JIT) compilation
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A bootstrapping method of compiling components and modules in the browser
|
||||
and launching the application dynamically. Just-in-time mode is a good choice during development.
|
||||
Consider using the [ahead-of-time](guide/glossary#aot) mode for production apps.
|
||||
@ -499,20 +604,28 @@ Consider using the [ahead-of-time](guide/glossary#aot) mode for production apps.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## kebab-case
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
See [dash-case](guide/glossary#dash-case).
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Lifecycle hooks
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
[Directives](guide/glossary#directive) and [components](guide/glossary#component) have a lifecycle
|
||||
managed by Angular as it creates, updates, and destroys them.
|
||||
|
||||
@ -523,6 +636,7 @@ Each interface has a single hook method whose name is the interface name prefixe
|
||||
For example, the `OnInit` interface has a hook method named `ngOnInit`.
|
||||
|
||||
Angular calls these hook methods in the following order:
|
||||
|
||||
* `ngOnChanges`: when an [input](guide/glossary#input)/[output](guide/glossary#output) binding value changes.
|
||||
* `ngOnInit`: after the first `ngOnChanges`.
|
||||
* `ngDoCheck`: developer's custom change detection.
|
||||
@ -538,6 +652,8 @@ Read more in the [Lifecycle Hooks](guide/lifecycle-hooks) page.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Module
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
@ -546,14 +662,19 @@ Read more in the [Lifecycle Hooks](guide/lifecycle-hooks) page.
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
Angular has the following types of modules:
|
||||
- [Angular modules](guide/glossary#angular-module).
|
||||
|
||||
* [Angular modules](guide/glossary#angular-module).
|
||||
For details and examples, see the [Angular Modules](guide/ngmodule) page.
|
||||
- ES2015 modules, as described in this section.
|
||||
* ES2015 modules, as described in this section.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
A cohesive block of code dedicated to a single purpose.
|
||||
|
||||
Angular apps are modular.
|
||||
@ -585,10 +706,14 @@ You rarely access Angular feature modules directly. You usually import them from
|
||||
|
||||
{@a N}
|
||||
|
||||
|
||||
|
||||
## Observable
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
An array whose items arrive asynchronously over time.
|
||||
Observables help you manage asynchronous data, such as data coming from a backend service.
|
||||
Observables are used within Angular itself, including Angular's event system and its HTTP client service.
|
||||
@ -599,26 +724,34 @@ Observables are a proposed feature for ES2016, the next version of JavaScript.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Output
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A directive property that can be the *target* of event binding
|
||||
(read more in the [event binding](guide/template-syntax)
|
||||
(read more in the [event binding](guide/template-syntax#event-binding)
|
||||
section of the [Template Syntax](guide/template-syntax) page).
|
||||
Events stream *out* of this property to the receiver identified
|
||||
in the template expression to the right of the equal sign.
|
||||
|
||||
See the [Input and output properties](guide/template-syntax) section of the [Template Syntax](guide/template-syntax) page.
|
||||
See the [Input and output properties](guide/template-syntax#inputs-outputs) section of the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## PascalCase
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The practice of writing individual words, compound words, or phrases such that each word or abbreviation begins with a capital letter.
|
||||
Class names are typically spelled in PascalCase. For example, `Person` and `HeroDetailComponent`.
|
||||
|
||||
@ -628,10 +761,14 @@ In this documentation, "PascalCase" means *upper camel case* and "camelCase" me
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Pipe
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
An Angular pipe is a function that transforms input values to output values for
|
||||
display in a [view](guide/glossary#view).
|
||||
Here's an example that uses the built-in `currency` pipe to display
|
||||
@ -643,16 +780,22 @@ a numeric value in the local currency.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You can also write your own custom pipes.
|
||||
Read more in the page on [pipes](guide/pipes).
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Provider
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A _provider_ creates a new instance of a dependency for the
|
||||
[dependency injection](guide/glossary#dependency-injection) system.
|
||||
It relates a lookup token to code—sometimes called a "recipe"—that can create a dependency value.
|
||||
@ -664,28 +807,37 @@ It relates a lookup token to code—sometimes called a "recipe"—that c
|
||||
|
||||
{@a Q}
|
||||
|
||||
|
||||
|
||||
## Reactive forms
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A technique for building Angular forms through code in a component.
|
||||
The alternative technique is [template-driven forms](guide/glossary#template-driven-forms).
|
||||
|
||||
When building reactive forms:
|
||||
- The "source of truth" is the component. The validation is defined using code in the component.
|
||||
- Each control is explicitly created in the component class with `new FormControl()` or with `FormBuilder`.
|
||||
- The template input elements do *not* use `ngModel`.
|
||||
- The associated Angular directives are all prefixed with `Form`, such as `FormGroup`, `FormControl`, and `FormControlName`.
|
||||
|
||||
* The "source of truth" is the component. The validation is defined using code in the component.
|
||||
* Each control is explicitly created in the component class with `new FormControl()` or with `FormBuilder`.
|
||||
* The template input elements do *not* use `ngModel`.
|
||||
* The associated Angular directives are all prefixed with `Form`, such as `FormGroup`, `FormControl`, and `FormControlName`.
|
||||
|
||||
Reactive forms are powerful, flexible, and a good choice for more complex data-entry form scenarios, such as dynamic generation of form controls.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Router
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Most applications consist of many screens or [views](guide/glossary#view).
|
||||
The user navigates among them by clicking links and buttons,
|
||||
and performing other similar actions that cause the application to
|
||||
@ -708,10 +860,14 @@ For more information, see the [Routing & Navigation](guide/router) page.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Router module
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A separate [Angular module](guide/glossary#angular-module) that provides the necessary service providers and directives for navigating through application views.
|
||||
|
||||
For more information, see the [Routing & Navigation](guide/router) page.
|
||||
@ -719,10 +875,14 @@ For more information, see the [Routing & Navigation](guide/router) page.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Routing component
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
An Angular [component](guide/glossary#component) with a `RouterOutlet` that displays views based on router navigations.
|
||||
|
||||
For more information, see the [Routing & Navigation](guide/router) page.
|
||||
@ -731,10 +891,14 @@ For more information, see the [Routing & Navigation](guide/router) page.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Scoped package
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A way to group related *npm* packages.
|
||||
Read more at the [npm-scope](https://docs.npmjs.com/misc/scope) page.
|
||||
|
||||
@ -746,7 +910,7 @@ The only difference, from a consumer perspective,
|
||||
is that the scoped package name begins with the Angular *scope name*, `@angular`.
|
||||
|
||||
|
||||
<code-example path="architecture/src/app/app.component.ts" linenums="false" title="architecture/ts/src/app/app.component.ts (import)" region="import">
|
||||
<code-example path="architecture/src/app/app.component.ts" linenums="false" title="architecture/src/app/app.component.ts (import)" region="import">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -754,10 +918,14 @@ is that the scoped package name begins with the Angular *scope name*, `@angular`
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Service
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
For data or logic that is not associated
|
||||
with a specific view or that you want to share across components, build services.
|
||||
|
||||
@ -778,10 +946,14 @@ For more information, see the [Services](guide/.ial/toh-pt4) page of the [Tour o
|
||||
|
||||
|
||||
{@a snake-case}
|
||||
|
||||
|
||||
## snake_case
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The practice of writing compound words or phrases such that an
|
||||
underscore (`_`) separates one word from the next. This form is also known as *underscore case*.
|
||||
|
||||
@ -794,10 +966,14 @@ underscore (`_`) separates one word from the next. This form is also known as *u
|
||||
|
||||
|
||||
{@a structural-directives}
|
||||
|
||||
|
||||
## Structural directives
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A category of [directive](guide/glossary#directive) that can
|
||||
shape or reshape HTML layout, typically by adding and removing elements in the DOM.
|
||||
The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples.
|
||||
@ -808,10 +984,14 @@ Read more in the [Structural Directives](guide/structural-directives) page.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A chunk of HTML that Angular uses to render a [view](guide/glossary#view) with
|
||||
the support and guidance of an Angular [directive](guide/glossary#directive),
|
||||
most notably a [component](guide/glossary#component).
|
||||
@ -819,18 +999,23 @@ most notably a [component](guide/glossary#component).
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Template-driven forms
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A technique for building Angular forms using HTML forms and input elements in the view.
|
||||
The alternate technique is [Reactive Forms](guide/glossary#reactive-forms).
|
||||
|
||||
When building template-driven forms:
|
||||
- The "source of truth" is the template. The validation is defined using attributes on the individual input elements.
|
||||
- [Two-way binding](guide/glossary#data-binding) with `ngModel` keeps the component model synchronized with the user's entry into the input elements.
|
||||
- Behind the scenes, Angular creates a new control for each input element, provided you have set up a `name` attribute and two-way binding for each input.
|
||||
- The associated Angular directives are all prefixed with `ng` such as `ngForm`, `ngModel`, and `ngModelGroup`.
|
||||
|
||||
* The "source of truth" is the template. The validation is defined using attributes on the individual input elements.
|
||||
* [Two-way binding](guide/glossary#data-binding) with `ngModel` keeps the component model synchronized with the user's entry into the input elements.
|
||||
* Behind the scenes, Angular creates a new control for each input element, provided you have set up a `name` attribute and two-way binding for each input.
|
||||
* The associated Angular directives are all prefixed with `ng` such as `ngForm`, `ngModel`, and `ngModelGroup`.
|
||||
|
||||
Template-driven forms are convenient, quick, and simple. They are a good choice for many basic data-entry form scenarios.
|
||||
|
||||
@ -840,34 +1025,46 @@ in the [Forms](guide/forms) page.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Template expression
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A TypeScript-like syntax that Angular evaluates within
|
||||
a [data binding](guide/glossary#data-binding).
|
||||
|
||||
Read about how to write template expressions
|
||||
in the [Template expressions](guide/template-syntax) section
|
||||
in the [Template expressions](guide/template-syntax#template-expressions) section
|
||||
of the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## Transpile
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The process of transforming code written in one form of JavaScript
|
||||
(such as TypeScript) into another form of JavaScript (such as [ES5](guide/glossary#es5)).
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## TypeScript
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A version of JavaScript that supports most [ECMAScript 2015](guide/glossary#es2015)
|
||||
language features such as [decorators](guide/glossary#decorator).
|
||||
|
||||
@ -888,10 +1085,14 @@ Read more about TypeScript at [typescriptlang.org](http://www.typescriptlang.org
|
||||
|
||||
{@a U}
|
||||
|
||||
|
||||
|
||||
## View
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A portion of the screen that displays information and responds
|
||||
to user actions such as clicks, mouse moves, and keystrokes.
|
||||
|
||||
@ -917,10 +1118,14 @@ under the control of a [router](guide/glossary#router).
|
||||
|
||||
{@a Y}
|
||||
|
||||
|
||||
|
||||
## Zone
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
A mechanism for encapsulating and intercepting
|
||||
a JavaScript application's asynchronous activity.
|
||||
|
||||
|
@ -6,6 +6,8 @@ Angular's hierarchical dependency injection system supports nested injectors in
|
||||
|
||||
@description
|
||||
|
||||
|
||||
|
||||
You learned the basics of Angular Dependency injection in the
|
||||
[Dependency Injection](guide/dependency-injection) guide.
|
||||
|
||||
@ -17,6 +19,8 @@ This guide explores this system and how to use it to your advantage.
|
||||
|
||||
Try the <live-example></live-example>.
|
||||
|
||||
|
||||
|
||||
## The injector tree
|
||||
|
||||
In the [Dependency Injection](guide/dependency-injection) guide,
|
||||
@ -30,6 +34,8 @@ The tree of components parallels the tree of injectors.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The component's injector may be a _proxy_ for an ancestor injector higher in the component tree.
|
||||
That's an implementation detail that improves efficiency.
|
||||
You won't notice the difference and
|
||||
@ -38,6 +44,8 @@ your mental model should be that every component has its own injector.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Consider this guide's variation on the Tour of Heroes application.
|
||||
At the top is the `AppComponent` which has some sub-components.
|
||||
One of them is the `HeroesListComponent`.
|
||||
@ -47,9 +55,11 @@ open simultaneously.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree" width="600"> </img>
|
||||
<img src="assets/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree" width="600"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
### Injector bubbling
|
||||
|
||||
When a component requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.
|
||||
@ -61,6 +71,8 @@ If it runs out of ancestors, Angular throws an error.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
You can cap the bubbling. An intermediate component can declare that it is the "host" component.
|
||||
The hunt for providers will climb no higher than the injector for that host component.
|
||||
This is a topic for another day.
|
||||
@ -68,6 +80,8 @@ This is a topic for another day.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
### Re-providing a service at different levels
|
||||
|
||||
You can re-register a provider for a particular dependency token at multiple levels of the injector tree.
|
||||
@ -81,6 +95,8 @@ It effectively "reconfigures" and "shadows" a provider at a higher level in the
|
||||
If you only specify providers at the top level (typically the root `AppModule`), the tree of injectors appears to be flat.
|
||||
All requests bubble up to the root <code>NgModule</code> injector that you configured with the `bootstrapModule` method.
|
||||
|
||||
|
||||
|
||||
## Component injectors
|
||||
|
||||
The ability to configure one or more providers at different levels opens up interesting and useful possibilities.
|
||||
@ -105,6 +121,8 @@ Instead, provide the `VillainsService` in the `providers` metadata of the `Villa
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
By providing `VillainsService` in the `VillainsListComponent` metadata and nowhere else,
|
||||
the service becomes available only in the `VillainsListComponent` and its sub-component tree.
|
||||
It's still a singleton, but it's a singleton that exist solely in the _villain_ domain.
|
||||
@ -131,9 +149,11 @@ Each tax return component has the following characteristics:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/dependency-injection/hid-heroes-anim.gif" width="400" alt="Heroes in action"> </img>
|
||||
<img src="assets/images/devguide/dependency-injection/hid-heroes-anim.gif" width="400" alt="Heroes in action"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
One might suppose that the `HeroTaxReturnComponent` has logic to manage and restore changes.
|
||||
That would be a pretty easy task for a simple hero tax return.
|
||||
In the real world, with a rich tax return data model, the change management would be tricky.
|
||||
@ -144,17 +164,21 @@ It caches a single `HeroTaxReturn`, tracks changes to that return, and can save
|
||||
It also delegates to the application-wide singleton `HeroService`, which it gets by injection.
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.service.ts">
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.service.ts" title="src/app/hero-tax-return.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here is the `HeroTaxReturnComponent` that makes use of it.
|
||||
|
||||
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts">
|
||||
<code-example path="hierarchical-dependency-injection/src/app/hero-tax-return.component.ts" title="src/app/hero-tax-return.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The _tax-return-to-edit_ arrives via the input property which is implemented with getters and setters.
|
||||
The setter initializes the component's own instance of the `HeroTaxReturnService` with the incoming return.
|
||||
The getter always returns what that service says is the current state of the hero.
|
||||
@ -172,6 +196,8 @@ Look closely at the metadata for the `HeroTaxReturnComponent`. Notice the `provi
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `HeroTaxReturnComponent` has its own provider of the `HeroTaxReturnService`.
|
||||
Recall that every component _instance_ has its own injector.
|
||||
Providing the service at the component level ensures that _every_ instance of the component gets its own, private instance of the service.
|
||||
@ -180,12 +206,16 @@ No tax return overwriting. No mess.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The rest of the scenario code relies on other Angular features and techniques that you can learn about elsewhere in the documentation.
|
||||
You can review it and download it from the <live-example></live-example>.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
### Scenario: specialized providers
|
||||
|
||||
Another reason to re-provide a service is to substitute a _more specialized_ implementation of that service,
|
||||
@ -204,9 +234,11 @@ Component (B) is the parent of another component (C) that defines its own, even
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/dependency-injection/car-components.png" alt="car components" width="220"> </img>
|
||||
<img src="assets/images/devguide/dependency-injection/car-components.png" alt="car components" width="220"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Behind the scenes, each component sets up its own injector with zero, one, or more providers defined for that component itself.
|
||||
|
||||
When you resolve an instance of `Car` at the deepest component (C),
|
||||
@ -215,13 +247,15 @@ its injector produces an instance of `Car` resolved by injector (C) with an `Eng
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/dependency-injection/injector-tree.png" alt="car injector tree" width="600"> </img>
|
||||
<img src="assets/images/devguide/dependency-injection/injector-tree.png" alt="car injector tree" width="600"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The code for this _cars_ scenario is in the `car.components.ts` and `car.services.ts` files of the sample
|
||||
which you can review and download from the <live-example></live-example>.
|
||||
|
||||
|
@ -8,6 +8,8 @@ Translate the app's template text into multiple languages.
|
||||
|
||||
|
||||
{@a top}
|
||||
|
||||
|
||||
Angular's _internationalization_ (_i18n_) tools help make your app available in multiple languages.
|
||||
|
||||
## Table of contents
|
||||
@ -20,9 +22,13 @@ Angular's _internationalization_ (_i18n_) tools help make your app available in
|
||||
* [Create a translation source file with the **_ng-xi18n_ extraction tool**](guide/i18n#ng-xi18n)
|
||||
* [Translate text messages](guide/i18n#translate)
|
||||
* [Merge the completed translation file into the app](guide/i18n#merge)
|
||||
|
||||
* [Merge with the JIT compiler](guide/i18n#jit)
|
||||
* [Internationalization with the AOT compiler](guide/i18n#aot)
|
||||
|
||||
* [Translation file maintenance and _id_ changes](guide/i18n#maintenance)
|
||||
|
||||
|
||||
**Try this** <live-example name="cb-i18n" title="i18n Example in Spanish">live example</live-example>
|
||||
of a JIT-compiled app, translated into Spanish.
|
||||
|
||||
@ -30,6 +36,8 @@ of a JIT-compiled app, translated into Spanish.
|
||||
|
||||
{@a angular-i18n}
|
||||
|
||||
|
||||
|
||||
## Angular and _i18n_ template translation
|
||||
|
||||
Application internationalization is a challenging, many-faceted effort that
|
||||
@ -43,12 +51,16 @@ into multiple languages.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Practitioners of _internationalization_ refer to a translatable text as a "_message_".
|
||||
This page uses the words "_text_" and "_message_" interchangably and in the combination, "_text message_".
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The _i18n_ template translation process has four phases:
|
||||
|
||||
1. Mark static text messages in your component templates for translation.
|
||||
@ -67,6 +79,8 @@ You need to build and deploy a separate version of the application for each supp
|
||||
|
||||
{@a i18n-attribute}
|
||||
|
||||
|
||||
|
||||
## Mark text with the _i18n_ attribute
|
||||
|
||||
The Angular `i18n` attribute is a marker for translatable content.
|
||||
@ -75,6 +89,8 @@ Place it on every element tag whose fixed text should be translated.
|
||||
|
||||
~~~ {.alert.is-helpful}
|
||||
|
||||
|
||||
|
||||
`i18n` is not an Angular _directive_.
|
||||
It's a custom _attribute_, recognized by Angular tools and compilers.
|
||||
After translation, the compiler removes it.
|
||||
@ -82,20 +98,26 @@ After translation, the compiler removes it.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
In the accompanying sample, an `<h1>` tag displays a simple English language greeting
|
||||
that you translate into Spanish:
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="greeting" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="greeting" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Add the `i18n` attribute to the tag to mark it for translation.
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-attribute" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-attribute" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### Help the translator with a _description_ and _intent_
|
||||
|
||||
In order to translate it accurately, the translator may
|
||||
@ -103,10 +125,12 @@ need a description of the message.
|
||||
Assign a description to the i18n attribute:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-attribute-desc" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-attribute-desc" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
In order to deliver a correct translation, the translator may need to
|
||||
know your _intent_—the true _meaning_ of the text
|
||||
within _this particular_ application context.
|
||||
@ -114,10 +138,12 @@ In front of the description, add some contextual meaning to the assigned string,
|
||||
separating it from the description with the `|` character (`<meaning>|<description>`):
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-attribute-meaning" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-attribute-meaning" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
While all appearances of a message with the _same_ meaning have the _same_ translation,
|
||||
a message with *a variety of possible meanings* could have different translations.
|
||||
The Angular extraction tool preserves both the _meaning_ and the _description_ in the translation source file
|
||||
@ -134,14 +160,16 @@ Here are two techniques to try.
|
||||
(1) Wrap the text in an `<ng-container>` element. The `<ng-container>` is never renderered:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-ng-container" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-ng-container" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
(2) Wrap the text in a pair of HTML comments:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-with-comment" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-with-comment" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -149,14 +177,18 @@ Here are two techniques to try.
|
||||
|
||||
|
||||
{@a translate-attributes}
|
||||
|
||||
|
||||
## Add _i18n-..._ translation attributes
|
||||
You've added an image to your template. You care about accessibility too so you add a `title` attribute:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-title" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="i18n-title" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `title` attribute needs to be translated.
|
||||
Angular i18n support has more translation attributes in the form,`i18n-x`, where `x` is the
|
||||
name of the attribute to translate.
|
||||
@ -164,15 +196,19 @@ name of the attribute to translate.
|
||||
To translate the `title` on the `img` tag from the previous example, write:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-title-translate" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-title-translate" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You can also assign a meaning and a description with the `i18n-x="<meaning>|<description>"` syntax.
|
||||
|
||||
|
||||
|
||||
{@a cardinality}
|
||||
|
||||
|
||||
## Handle singular and plural
|
||||
|
||||
Different languages have different pluralization rules.
|
||||
@ -184,10 +220,12 @@ Other languages might express the _cardinality_ differently.
|
||||
Here's how you could mark up the component template to display the phrase appropriate to the number of wolves:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-plural" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-plural" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
* The first parameter is the key. It is bound to the component property (`wolves`)
|
||||
that determines the number of wolves.
|
||||
* The second parameter identifies this as a `plural` translation type.
|
||||
@ -195,6 +233,7 @@ that determines the number of wolves.
|
||||
categories and their matching values.
|
||||
|
||||
Pluralization categories include:
|
||||
|
||||
* =0
|
||||
* =1
|
||||
* =5
|
||||
@ -203,6 +242,7 @@ Pluralization categories include:
|
||||
|
||||
Put the default _English_ translation in braces (`{}`) next to the pluralization category.
|
||||
* When you're talking about one wolf, you could write `=1 {one wolf}`.
|
||||
|
||||
* For zero wolves, you could write `=0 {no wolves}`.
|
||||
* For two wolves, you could write `=2 {two wolves}`.
|
||||
|
||||
@ -213,6 +253,8 @@ and write something like: `other {a wolf pack}`.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
This syntax conforms to the
|
||||
<a href="http://userguide.icu-project.org/formatparse/messages" target="_blank" title="ICU Message Format">ICU Message Format</a>
|
||||
that derives from the
|
||||
@ -226,6 +268,8 @@ which specifies the
|
||||
|
||||
|
||||
{@a select}
|
||||
|
||||
|
||||
## Select among alternative texts
|
||||
The application displays different text depending upon whether the hero is male or female.
|
||||
These text alternatives require translation too.
|
||||
@ -240,7 +284,7 @@ property, which outputs either an "m" or an "f".
|
||||
The message maps those values to the appropriate translation:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-select" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-select" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -248,6 +292,8 @@ The message maps those values to the appropriate translation:
|
||||
|
||||
{@a ng-xi18n}
|
||||
|
||||
|
||||
|
||||
## Create a translation source file with the _ng-xi18n_ tool
|
||||
|
||||
Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
|
||||
@ -262,6 +308,8 @@ If you haven't already installed the CLI and its `platform-server` peer dependen
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Open a terminal window at the root of the application project and enter the `ng-xi18n` command:
|
||||
|
||||
|
||||
@ -274,16 +322,22 @@ Open a terminal window at the root of the application project and enter the `ng-
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Windows users may have to quote the command like this: `"./node_modules/.bin/ng-xi18n"`
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
By default, the tool generates a translation file named **`messages.xlf`** in the
|
||||
<a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">XML Localisation Interchange File Format (XLIFF, version 1.2)</a>.
|
||||
|
||||
|
||||
{@a other-formats}
|
||||
|
||||
|
||||
### Other translation formats
|
||||
|
||||
You can generate a file named **`messages.xmb`** in the
|
||||
@ -296,10 +350,14 @@ by adding the `--i18nFormat=xmb` flag.
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
This sample sticks with the _XLIFF_ format.
|
||||
|
||||
|
||||
{@a ng-xi18n-options}
|
||||
|
||||
|
||||
### Other options
|
||||
You may have to specify additional options.
|
||||
For example, if the `tsconfig.json` TypeScript configuration
|
||||
@ -308,7 +366,7 @@ you must identify the path to it with the `-p` option:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
./node_modules/.bin/ng-xi18n -p path/to/tsconfig.json
|
||||
./node_modules/.bin/ng-xi18n --i18nFormat=xmb -p path/to/tsconfig.json
|
||||
./node_modules/.bin/ng-xi18n --i18nFormat=xmb -p path/to/tsconfig.json
|
||||
|
||||
|
||||
</code-example>
|
||||
@ -316,6 +374,8 @@ you must identify the path to it with the `-p` option:
|
||||
|
||||
|
||||
{@a npm-i18n-script}
|
||||
|
||||
|
||||
### Add an _npm_ script for convenience
|
||||
|
||||
Consider adding a convenience shortcut to the `scripts` section of the `package.json`
|
||||
@ -323,25 +383,31 @@ to make the command easier to remember and run:
|
||||
|
||||
<code-example format='.' language='sh'>
|
||||
"scripts": {
|
||||
"i18n": "ng-xi18n",
|
||||
...
|
||||
}
|
||||
"i18n": "ng-xi18n",
|
||||
...
|
||||
}
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now you can issue command variations such as these:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
npm run i18n
|
||||
npm run i18n -- -p path/to/tsconfig.json
|
||||
npm run i18n -- --i18nFormat=xmb -p path/to/tsconfig.json
|
||||
npm run i18n -- -p path/to/tsconfig.json
|
||||
npm run i18n -- --i18nFormat=xmb -p path/to/tsconfig.json
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Note the `--` flag before the options.
|
||||
It tells _npm_ to pass every flag thereafter to `ng-xi18n`.
|
||||
|
||||
|
||||
{@a translate}
|
||||
|
||||
|
||||
|
||||
## Translate text messages
|
||||
|
||||
The `ng-xi18n` command generates a translation source file
|
||||
@ -352,6 +418,8 @@ files. The cookbook sample creates a Spanish translation file.
|
||||
|
||||
|
||||
{@a localization-folder}
|
||||
|
||||
|
||||
### Create a localization folder
|
||||
|
||||
You will probably translate into more than one other language so it's a good idea
|
||||
@ -362,11 +430,15 @@ One approach is to dedicate a folder to localization and store related assets
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Localization and internationalization are
|
||||
<a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" target="_blank">different but closely related terms</a>.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
This cookbook follows that suggestion. It has a `locale` folder under the `src/`.
|
||||
Assets within the folder carry a filename extension that matches a language-culture code from a
|
||||
<a href="https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx" target="_blank">well-known codeset</a>.
|
||||
@ -384,16 +456,18 @@ This sample file is easy to translate without a special editor or knowledge of S
|
||||
Open `messages.es.xlf` and find the first `<trans-unit>` section:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-hello" linenums="false">
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-hello" title="src/locale/messages.es.xlf (<trans-unit>)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
This XML element represents the translation of the `<h1>` greeting tag you marked with the `i18n` attribute.
|
||||
|
||||
Using the _source_, _description_, and _meaning_ elements to guide your translation,
|
||||
replace the `<target/>` tag with the Spanish greeting:
|
||||
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-hello" linenums="false">
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-hello" title="src/locale/messages.es.xlf (<trans-unit>, after translation)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -401,6 +475,8 @@ replace the `<target/>` tag with the Spanish greeting:
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
|
||||
|
||||
Note that the tool generates the `id`. **Don't touch it.**
|
||||
Its value depends on the content of the message and its assigned meaning.
|
||||
Change either factor and the `id` changes as well.
|
||||
@ -409,16 +485,20 @@ See the **[translation file maintenance discussion](guide/i18n#maintenance)**.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Translate the other text nodes the same way:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-other-nodes" linenums="false">
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-other-nodes" title="src/locale/messages.es.xlf (<trans-unit>)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a translate-plural-select}
|
||||
|
||||
|
||||
## Translate _plural_ and _select_
|
||||
Translating _plural_ and _select_ messages is a little tricky.
|
||||
|
||||
@ -429,22 +509,28 @@ However, the `XMB` format does support the ICU rules.
|
||||
|
||||
You'll just have to look for them in relation to other translation units that you recognize from elsewhere in the source template.
|
||||
In this example, you know the translation unit for the `select` must be just below the translation unit for the logo.
|
||||
|
||||
|
||||
### Translate _plural_
|
||||
To translate a `plural`, translate its ICU format match values:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-plural" linenums="false">
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-plural" title="src/locale/messages.es.xlf (<trans-unit>)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### Translate _select_
|
||||
The `select` behaves a little differently. Here again is the ICU format message in the component template:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-select" linenums="false">
|
||||
<code-example path="cb-i18n/src/app/app.component.html" region="i18n-select" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The extraction tool broke that into _two_ translation units.
|
||||
|
||||
The first unit contains the text that was _outside_ the `select`.
|
||||
@ -452,21 +538,25 @@ In place of the `select` is a placeholder, `<x id="ICU">`, that represents the `
|
||||
Translate the text and leave the placeholder where it is.
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translate-select-1" linenums="false">
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translate-select-1" title="src/locale/messages.es.xlf (<trans-unit>)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The second translation unit, immediately below the first one, contains the `select` message. Translate that.
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translate-select-2" linenums="false">
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translate-select-2" title="src/locale/messages.es.xlf (<trans-unit>)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here they are together, after translation:
|
||||
|
||||
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-select" linenums="false">
|
||||
<code-example path="cb-i18n/src/locale/messages.es.xlf.html" region="translated-select" title="src/locale/messages.es.xlf (<trans-unit>)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -476,6 +566,8 @@ Here they are together, after translation:
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
The entire template translation is complete. It's
|
||||
time to incorporate that translation into the application.
|
||||
|
||||
@ -484,6 +576,8 @@ time to incorporate that translation into the application.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### The app before translation
|
||||
|
||||
When the previous steps finish, the sample app _and_ its translation file are as follows:
|
||||
@ -517,6 +611,8 @@ When the previous steps finish, the sample app _and_ its translation file are as
|
||||
|
||||
{@a merge}
|
||||
|
||||
|
||||
|
||||
## Merge the completed translation file into the app
|
||||
|
||||
To merge the translated text into component templates,
|
||||
@ -525,6 +621,7 @@ The process is the same whether the file is in `.xlf` format or
|
||||
in another format (`.xlif` and `.xtb`) that Angular understands.
|
||||
|
||||
You provide the Angular compiler with three new pieces of information:
|
||||
|
||||
* the translation file
|
||||
* the translation file format
|
||||
* the <a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">_Locale ID_</a>
|
||||
@ -539,6 +636,8 @@ the JIT (_Just-in-Time_) compiler or the AOT (_Ahead-of-Time_) compiler.
|
||||
|
||||
{@a jit}
|
||||
|
||||
|
||||
|
||||
### Merge with the JIT compiler
|
||||
|
||||
The JIT compiler compiles the application in the browser as the application loads.
|
||||
@ -551,15 +650,19 @@ Translation with the JIT compiler is a dynamic process of:
|
||||
|
||||
Open `index.html` and revise the launch script as follows:
|
||||
|
||||
<code-example path="cb-i18n/src/index.html" region="i18n" linenums="false">
|
||||
<code-example path="cb-i18n/src/index.html" region="i18n" title="index.html (launch script)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
In this sample, the user's language is hardcoded as a global `document.locale` variable
|
||||
in the `index.html`.
|
||||
|
||||
|
||||
{@a text-plugin}
|
||||
|
||||
|
||||
### SystemJS Text plugin
|
||||
|
||||
Notice the SystemJS mapping of `text` to a `systemjs-text-plugin.js`.
|
||||
@ -570,10 +673,12 @@ You'll need it to import the language translation file.
|
||||
SystemJS doesn't ship with a raw text plugin but it's easy to add.
|
||||
Create the following `systemjs-text-plugin.js` in the `src/` folder:
|
||||
|
||||
<code-example path="cb-i18n/src/systemjs-text-plugin.js" linenums="false">
|
||||
<code-example path="cb-i18n/src/systemjs-text-plugin.js" title="src/systemjs-text-plugin.js" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
### Create translation providers
|
||||
|
||||
Three providers tell the JIT compiler how to translate the template texts for a particular language
|
||||
@ -587,10 +692,12 @@ The `getTranslationProviders` function in the following `src/app/i18n-providers.
|
||||
creates those providers based on the user's _locale_
|
||||
and the corresponding translation file:
|
||||
|
||||
<code-example path="cb-i18n/src/app/i18n-providers.ts">
|
||||
<code-example path="cb-i18n/src/app/i18n-providers.ts" title="src/app/i18n-providers.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
1. It gets the locale from the global `document.locale` variable that was set in `index.html`.
|
||||
|
||||
1. If there is no locale or the language is U.S. English (`en-US`), there is no need to translate.
|
||||
@ -616,10 +723,12 @@ You'll create an _options_ object with the translation providers from `getTransl
|
||||
and pass it to `bootstrapModule`.
|
||||
Open the `src/main.ts` and modify the bootstrap code as follows:
|
||||
|
||||
<code-example path="cb-i18n/src/main.ts" linenums="false">
|
||||
<code-example path="cb-i18n/src/main.ts" title="src/main.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Notice that it waits for the `getTranslationProviders` promise to resolve before
|
||||
bootstrapping the app.
|
||||
|
||||
@ -629,6 +738,8 @@ more languages.
|
||||
|
||||
{@a aot}
|
||||
|
||||
|
||||
|
||||
### _Internationalize_ with the AOT compiler
|
||||
|
||||
The JIT compiler translates the application into the target language
|
||||
@ -657,6 +768,7 @@ Next, issue an `ngc` compile command for each supported language (including Engl
|
||||
The result is a separate version of the application for each language.
|
||||
|
||||
Tell AOT how to translate by adding three options to the `ngc` command:
|
||||
|
||||
* `--i18nFile`: the path to the translation file
|
||||
* `--locale`: the name of the locale
|
||||
* `--i18nFormat`: the format of the localization file
|
||||
@ -672,6 +784,8 @@ For this sample, the Spanish language command would be
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Windows users may have to quote the command:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
@ -686,6 +800,8 @@ Windows users may have to quote the command:
|
||||
|
||||
|
||||
{@a maintenance}
|
||||
|
||||
|
||||
## Translation file maintenance and _id_ changes
|
||||
|
||||
As the application evolves, you will change the _i18n_ markup
|
||||
|
@ -6,6 +6,8 @@ How to read and use this documentation.
|
||||
|
||||
@description
|
||||
|
||||
|
||||
|
||||
This page describes the Angular documentation at a high level.
|
||||
If you're new to Angular, you may want to visit "[Learning Angular](guide/learning-angular)" first.
|
||||
|
||||
@ -33,8 +35,10 @@ a collection of pages devoted to that theme.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
A first taste of Angular<span if-docs="ts"> with zero installation.
|
||||
Run "Hello World" in an online code editor and start playing with live code</span>.
|
||||
Run "Hello World" in an online code editor and start playing with live code</span>.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -46,9 +50,11 @@ a collection of pages devoted to that theme.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Learn the Angular basics (you're already here!) like the setup for local development,
|
||||
displaying data and accepting user input, injecting application services into components,
|
||||
and building simple forms.
|
||||
displaying data and accepting user input, injecting application services into components,
|
||||
and building simple forms.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -60,6 +66,8 @@ a collection of pages devoted to that theme.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Authoritative details about each of the Angular libraries.
|
||||
</td>
|
||||
|
||||
@ -72,8 +80,10 @@ a collection of pages devoted to that theme.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
A step-by-step, immersive approach to learning Angular that
|
||||
introduces the major features of Angular in an application context.
|
||||
introduces the major features of Angular in an application context.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -85,6 +95,8 @@ a collection of pages devoted to that theme.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
In-depth analysis of Angular features and development practices.
|
||||
</td>
|
||||
|
||||
@ -97,6 +109,8 @@ a collection of pages devoted to that theme.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Recipes for specific application challenges, mostly code snippets with a minimum of exposition.
|
||||
|
||||
</td>
|
||||
@ -105,6 +119,8 @@ a collection of pages devoted to that theme.
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
A few early pages are written as tutorials and are clearly marked as such.
|
||||
The rest of the pages highlight key points in code rather than explain each step necessary to build the sample.
|
||||
You can always get the full source through the #{_liveLink}s.
|
||||
|
@ -9,15 +9,17 @@ A suggested path through the documentation for Angular newcomers.
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/intro/people.png" width="200px" height="152px" alt="Us" align="left" style="margin-left:-40px;margin-right:10px"> </img>
|
||||
<img src="assets/images/devguide/intro/people.png" width="200px" height="152px" alt="Us" align="left" style="margin-left:-40px;margin-right:10px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Everyone learns differently.
|
||||
You don't have to read the documentation straight through. Most pages stand on their own.
|
||||
Those new to Angular may wish to follow this popular learning path.
|
||||
<br class="l-clear-left">
|
||||
|
||||
1. [Setup](guide/setup) for local Angular development, if you haven't already done so.
|
||||
1. [Setup](guide/setup "Setup locally withe Quickstart seed") for local Angular development, if you haven't already done so.
|
||||
|
||||
1. Take the [*Tour of Heroes* tutorial](tutorial "Tour of Heroes").
|
||||
|
||||
@ -25,7 +27,7 @@ Those new to Angular may wish to follow this popular learning path.
|
||||
to a full-featured example that demonstrates the essential characteristics of a professional application:
|
||||
a sensible project structure, data binding, master/detail, services, dependency injection, navigation, and remote data access.
|
||||
|
||||
1. <a id="architecture"></a>Read the [Architecture](guide/architecture) overview for the big picture.
|
||||
1. {@a architecture}Read the [Architecture](guide/architecture) overview for the big picture.
|
||||
|
||||
1. [The Root Module](guide/appmodule) introduces the `NgModule` class that tells Angular how to compile and run your application.
|
||||
|
||||
@ -45,10 +47,12 @@ After reading the above sections, feel free to skip around among the other pages
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
### Next Step
|
||||
|
||||
Try the [tutorial](tutorial "Tour of Heroes") if you're ready to start coding or
|
||||
visit the [Architecture](guide/architecture) page if you prefer to learn the basic concepts first.
|
||||
visit the [Architecture](guide/architecture "Basic Concepts") page if you prefer to learn the basic concepts first.
|
||||
|
||||
~~~
|
||||
|
||||
|
@ -9,9 +9,11 @@ Angular calls lifecycle hook methods on directives and components as it creates,
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="assets/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px"> </img>
|
||||
<img src="assets/images/devguide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:30px"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
A component has a lifecycle managed by Angular.
|
||||
|
||||
Angular creates it, renders it, creates and renders its children,
|
||||
@ -22,7 +24,10 @@ that provide visibility into these key life moments and the ability to act when
|
||||
|
||||
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
|
||||
<br class="l-clear-both">
|
||||
|
||||
|
||||
## Contents
|
||||
|
||||
* [Component lifecycle hooks overview](guide/lifecycle-hooks#hooks-overview)
|
||||
* [Lifecycle sequence](guide/lifecycle-hooks#hooks-purpose-timing)
|
||||
* [Interfaces are optional (technically)](guide/lifecycle-hooks#interface-optional)
|
||||
@ -30,21 +35,30 @@ A directive has the same set of lifecycle hooks, minus the hooks that are specif
|
||||
* [Lifecycle examples](guide/lifecycle-hooks#the-sample)
|
||||
* [Peek-a-boo: all hooks](guide/lifecycle-hooks#peek-a-boo)
|
||||
* [Spying OnInit and OnDestroy](guide/lifecycle-hooks#spy)
|
||||
|
||||
* [OnInit](guide/lifecycle-hooks#oninit)
|
||||
* [OnDestroy](guide/lifecycle-hooks#ondestroy)
|
||||
|
||||
* [OnChanges](guide/lifecycle-hooks#onchanges)
|
||||
* [DoCheck](guide/lifecycle-hooks#docheck)
|
||||
* [AfterView](guide/lifecycle-hooks#afterview)
|
||||
|
||||
* [Abide by the unidirectional data flow rule](guide/lifecycle-hooks#wait-a-tick)
|
||||
|
||||
* [AfterContent](guide/lifecycle-hooks#aftercontent)
|
||||
|
||||
* [Content projection](guide/lifecycle-hooks#content-projection)
|
||||
* [AfterContent hooks](guide/lifecycle-hooks#aftercontent-hooks)
|
||||
* [No unidirectional flow worries with _AfterContent_](guide/lifecycle-hooks#no-unidirectional-flow-worries)
|
||||
|
||||
|
||||
Try the <live-example></live-example>.
|
||||
|
||||
|
||||
{@a hooks-overview}
|
||||
|
||||
|
||||
|
||||
## Component lifecycle hooks overview
|
||||
Directive and component instances have a lifecycle
|
||||
as Angular creates, updates, and destroys them.
|
||||
@ -55,16 +69,20 @@ Each interface has a single hook method whose name is the interface name prefixe
|
||||
For example, the `OnInit` interface has a hook method named `ngOnInit()`
|
||||
that Angular calls shortly after creating the component:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/peek-a-boo.component.ts" region="ngOnInit" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/peek-a-boo.component.ts" region="ngOnInit" title="peek-a-boo.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
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*.
|
||||
|
||||
|
||||
{@a hooks-purpose-timing}
|
||||
|
||||
|
||||
|
||||
## Lifecycle sequence
|
||||
*After* creating a component/directive by calling its constructor, Angular
|
||||
calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
@ -98,10 +116,12 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Respond when Angular (re)sets data-bound input properties.
|
||||
The method receives a `SimpleChanges` object of current and previous property values.
|
||||
|
||||
Called before `ngOnInit()` and whenever one or more data-bound input properties change.
|
||||
|
||||
Respond when Angular (re)sets data-bound input properties.
|
||||
The method receives a `SimpleChanges` object of current and previous property values.
|
||||
|
||||
Called before `ngOnInit()` and whenever one or more data-bound input properties change.
|
||||
|
||||
</td>
|
||||
|
||||
@ -114,10 +134,12 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Initialize the directive/component after Angular first displays the data-bound properties
|
||||
and sets the directive/component's input properties.
|
||||
|
||||
Called _once_, after the _first_ `ngOnChanges()`.
|
||||
|
||||
Initialize the directive/component after Angular first displays the data-bound properties
|
||||
and sets the directive/component's input properties.
|
||||
|
||||
Called _once_, after the _first_ `ngOnChanges()`.
|
||||
|
||||
</td>
|
||||
|
||||
@ -130,9 +152,11 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Detect and act upon changes that Angular can't or won't detect on its own.
|
||||
|
||||
Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`.
|
||||
Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`.
|
||||
|
||||
</td>
|
||||
|
||||
@ -145,11 +169,13 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Respond after Angular projects external content into the component's view.
|
||||
|
||||
Called _once_ after the first `ngDoCheck()`.
|
||||
Called _once_ after the first `ngDoCheck()`.
|
||||
|
||||
_A component-only hook_.
|
||||
_A component-only hook_.
|
||||
|
||||
</td>
|
||||
|
||||
@ -162,11 +188,13 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Respond after Angular checks the content projected into the component.
|
||||
|
||||
Called after the `ngAfterContentInit()` and every subsequent `ngDoCheck()`.
|
||||
Called after the `ngAfterContentInit()` and every subsequent `ngDoCheck()`.
|
||||
|
||||
_A component-only hook_.
|
||||
_A component-only hook_.
|
||||
|
||||
</td>
|
||||
|
||||
@ -179,11 +207,13 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Respond after Angular initializes the component's views and child views.
|
||||
|
||||
Called _once_ after the first `ngAfterContentChecked()`.
|
||||
Called _once_ after the first `ngAfterContentChecked()`.
|
||||
|
||||
_A component-only hook_.
|
||||
_A component-only hook_.
|
||||
|
||||
</td>
|
||||
|
||||
@ -196,11 +226,13 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Respond after Angular checks the component's views and child views.
|
||||
|
||||
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked()`.
|
||||
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked()`.
|
||||
|
||||
_A component-only hook_.
|
||||
_A component-only hook_.
|
||||
|
||||
</td>
|
||||
|
||||
@ -213,10 +245,12 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Cleanup just before Angular destroys the directive/component.
|
||||
Unsubscribe Observables and detach event handlers to avoid memory leaks.
|
||||
|
||||
Called _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.
|
||||
|
||||
Called _just before_ Angular destroys the directive/component.
|
||||
|
||||
</td>
|
||||
|
||||
@ -228,7 +262,9 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
|
||||
{@a interface-optional}
|
||||
|
||||
## Interfaces are optional (technically)
|
||||
|
||||
|
||||
## Interfaces are optional (technically)
|
||||
|
||||
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
||||
The JavaScript language doesn't have interfaces.
|
||||
@ -246,12 +282,18 @@ in order to benefit from strong typing and editor tooling.
|
||||
|
||||
{@a other-lifecycle-hooks}
|
||||
|
||||
|
||||
|
||||
## Other Angular lifecycle hooks
|
||||
|
||||
Other Angular sub-systems may have their own lifecycle hooks apart from these component hooks.
|
||||
|
||||
|
||||
3rd party libraries might implement their hooks as well in order to give developers more
|
||||
control over how these libraries are used.
|
||||
|
||||
|
||||
|
||||
## Lifecycle examples
|
||||
|
||||
The <live-example></live-example>
|
||||
@ -293,8 +335,10 @@ Here's a brief description of each exercise:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Demonstrates every lifecycle hook.
|
||||
Each hook method writes to the on-screen log.
|
||||
Each hook method writes to the on-screen log.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -306,12 +350,14 @@ Here's a brief description of each exercise:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Directives have lifecycle hooks too.
|
||||
A `SpyDirective` can log when the element it spies upon is
|
||||
created or destroyed using the `ngOnInit` and `ngOnDestroy` hooks.
|
||||
|
||||
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
||||
managed by the parent `SpyComponent`.
|
||||
|
||||
Directives have lifecycle hooks too.
|
||||
A `SpyDirective` can log when the element it spies upon is
|
||||
created or destroyed using the `ngOnInit` and `ngOnDestroy` hooks.
|
||||
|
||||
This example applies the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
||||
managed by the parent `SpyComponent`.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -323,9 +369,11 @@ Here's a brief description of each exercise:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
See how Angular calls the `ngOnChanges()` hook with a `changes` object
|
||||
every time one of the component input properties changes.
|
||||
Shows how to interpret the `changes` object.
|
||||
every time one of the component input properties changes.
|
||||
Shows how to interpret the `changes` object.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -337,8 +385,10 @@ Here's a brief description of each exercise:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Implements an `ngDoCheck()` method with custom change detection.
|
||||
See how often Angular calls this hook and watch it post changes to a log.
|
||||
See how often Angular calls this hook and watch it post changes to a log.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -350,8 +400,10 @@ Here's a brief description of each exercise:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Shows what Angular means by a *view*.
|
||||
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
|
||||
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -363,9 +415,11 @@ Here's a brief description of each exercise:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
Shows how to project external content into a component and
|
||||
how to distinguish projected content from a component's view children.
|
||||
Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks.
|
||||
how to distinguish projected content from a component's view children.
|
||||
Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -377,13 +431,15 @@ Here's a brief description of each exercise:
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Demonstrates a combination of a component and a directive
|
||||
each with its own hooks.
|
||||
|
||||
In this example, a `CounterComponent` logs a change (via `ngOnChanges`)
|
||||
every time the parent component increments its input counter property.
|
||||
Meanwhile, the `SpyDirective` from the previous example is applied
|
||||
to the `CounterComponent` log where it watches log entries being created and destroyed.
|
||||
|
||||
Demonstrates a combination of a component and a directive
|
||||
each with its own hooks.
|
||||
|
||||
In this example, a `CounterComponent` logs a change (via `ngOnChanges`)
|
||||
every time the parent component increments its input counter property.
|
||||
Meanwhile, the `SpyDirective` from the previous example is applied
|
||||
to the `CounterComponent` log where it watches log entries being created and destroyed.
|
||||
|
||||
</td>
|
||||
|
||||
@ -391,11 +447,15 @@ Here's a brief description of each exercise:
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
The remainder of this page discusses selected exercises in further detail.
|
||||
|
||||
|
||||
{@a peek-a-boo}
|
||||
|
||||
|
||||
|
||||
## Peek-a-boo: all hooks
|
||||
The `PeekABooComponent` demonstrates all of the hooks in one component.
|
||||
|
||||
@ -405,9 +465,11 @@ The peek-a-boo exists to show how Angular calls the hooks in the expected order.
|
||||
This snapshot reflects the state of the log after the user clicked the *Create...* button and then the *Destroy...* button.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/devguide/lifecycle-hooks/peek-a-boo.png" alt="Peek-a-boo"> </img>
|
||||
<img src="assets/images/devguide/lifecycle-hooks/peek-a-boo.png" alt="Peek-a-boo"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The sequence of log messages follows the prescribed hook calling order:
|
||||
`OnChanges`, `OnInit`, `DoCheck` (3x), `AfterContentInit`, `AfterContentChecked` (3x),
|
||||
`AfterViewInit`, `AfterViewChecked` (3x), and `OnDestroy`.
|
||||
@ -415,11 +477,15 @@ The sequence of log messages follows the prescribed hook calling order:
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The constructor isn't an Angular hook *per se*.
|
||||
The log confirms that input properties (the `name` property in this case) have no assigned values at construction.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
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 *often*. Keep the logic in these hooks as lean as possible!
|
||||
@ -429,6 +495,8 @@ The next examples focus on hook details.
|
||||
|
||||
{@a spy}
|
||||
|
||||
|
||||
|
||||
## Spying *OnInit* and *OnDestroy*
|
||||
|
||||
Go undercover with these two spy hooks to discover when an element is initialized or destroyed.
|
||||
@ -439,6 +507,8 @@ The heroes will never know they're being watched.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Kidding aside, pay attention to two key points:
|
||||
|
||||
1. Angular calls hook methods for *directives* as well as components.<br><br>
|
||||
@ -452,30 +522,38 @@ But you can watch both with a directive.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The sneaky spy directive is simple, consisting almost entirely of `ngOnInit()` and `ngOnDestroy()` hooks
|
||||
that log messages to the parent via an injected `LoggerService`.
|
||||
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/spy.directive.ts" region="spy-directive" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/spy.directive.ts" region="spy-directive" title="src/app/spy.directive.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
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 it is attached to the repeated hero `<div>`:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/spy.component.html" region="template" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/spy.component.html" region="template" title="src/app/spy.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Each spy's birth and death marks the birth and death of the attached hero `<div>`
|
||||
with an entry in the *Hook Log* as seen here:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/spy-directive.gif' alt="Spy Directive"> </img>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/spy-directive.gif' alt="Spy Directive"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Adding a hero results in a new hero `<div>`. The spy's `ngOnInit()` logs that event.
|
||||
|
||||
The *Reset* button clears the `heroes` list.
|
||||
@ -485,9 +563,12 @@ The spy's `ngOnDestroy()` method reports its last moments.
|
||||
The `ngOnInit()` and `ngOnDestroy()` methods have more vital roles to play in real applications.
|
||||
|
||||
{@a oninit}
|
||||
|
||||
|
||||
### _OnInit()_
|
||||
|
||||
Use `ngOnInit()` for two main reasons:
|
||||
|
||||
1. To perform complex initializations shortly after construction.
|
||||
1. To set up the component after Angular sets the input properties.
|
||||
|
||||
@ -495,6 +576,8 @@ Experienced developers agree that components should be cheap and safe to constru
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
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.
|
||||
@ -502,13 +585,15 @@ you should avoid complex constructor logic.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
An `ngOnInit()` is a good place for a component to fetch its initial data. The
|
||||
[Tour of Heroes Tutorial](tutorial/toh-pt4) and [HTTP Client](guide/server-communication)
|
||||
[Tour of Heroes Tutorial](tutorial/toh-pt4#oninit) and [HTTP Client](guide/server-communication#oninit)
|
||||
guides show how.
|
||||
|
||||
|
||||
@ -518,17 +603,23 @@ They'll have been set when `ngOnInit()` runs.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
You can count on Angular to call the `ngOnInit()` method _soon_ after creating the component.
|
||||
That's where the heavy initialization logic belongs.
|
||||
|
||||
|
||||
{@a ondestroy}
|
||||
|
||||
|
||||
### _OnDestroy()_
|
||||
|
||||
Put cleanup logic in `ngOnDestroy()`, the logic that *must* run before Angular destroys the directive.
|
||||
@ -543,39 +634,49 @@ You risk memory leaks if you neglect to do so.
|
||||
|
||||
|
||||
{@a onchanges}
|
||||
|
||||
|
||||
## _OnChanges()_
|
||||
|
||||
Angular calls its `ngOnChanges()` method whenever it detects changes to ***input properties*** of the component (or directive).
|
||||
This example monitors the `OnChanges` hook.
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="ng-on-changes" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="ng-on-changes" title="on-changes.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `ngOnChanges()` method takes an object that maps each changed property name to a
|
||||
[SimpleChange](api/core/index/SimpleChange-class) object holding the current and previous property values.
|
||||
This hook iterates over the changed properties and logs them.
|
||||
|
||||
The example component, `OnChangesComponent`, has two input properties: `hero` and `power`.
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="inputs" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes.component.ts" region="inputs" title="src/app/on-changes.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The host `OnChangesParentComponent` binds to them like this:
|
||||
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes-parent.component.html" region="on-changes">
|
||||
<code-example path="lifecycle-hooks/src/app/on-changes-parent.component.html" region="on-changes" title="src/app/on-changes-parent.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Here's the sample in action as the user makes changes.
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/on-changes-anim.gif' alt="OnChanges"> </img>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/on-changes-anim.gif' alt="OnChanges"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
@ -588,30 +689,40 @@ The hero object *reference* didn't change so, from Angular's perspective, there
|
||||
|
||||
|
||||
{@a docheck}
|
||||
|
||||
|
||||
## _DoCheck()_
|
||||
Use the `DoCheck` hook to detect and act upon changes that Angular doesn't catch on its own.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
Use this method to detect a change that Angular overlooked.
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
The *DoCheck* sample extends the *OnChanges* sample with the following `ngDoCheck()` hook:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/do-check.component.ts" region="ng-do-check" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/do-check.component.ts" region="ng-do-check" title="DoCheckComponent (ngDoCheck)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
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:
|
||||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/do-check-anim.gif' alt="DoCheck"> </img>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/do-check-anim.gif' alt="DoCheck"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
@ -625,42 +736,52 @@ Clearly our implementation must be very lightweight or the user experience suffe
|
||||
|
||||
|
||||
{@a afterview}
|
||||
|
||||
|
||||
## AfterView
|
||||
The *AfterView* sample explores the `AfterViewInit()` and `AfterViewChecked()` hooks that Angular calls
|
||||
*after* it creates a component's child views.
|
||||
|
||||
Here's a child view that displays a hero's name in an `<input>`:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="child-view" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="child-view" title="ChildComponent" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `AfterViewComponent` displays this child view *within its template*:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="template" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="template" title="AfterViewComponent (template)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The following hooks take action based on changing values *within the child view*,
|
||||
which can only be reached by querying for the child view via the property decorated with
|
||||
[@ViewChild](api/core/index/ViewChild-decorator).
|
||||
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="hooks" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="hooks" title="AfterViewComponent (class excerpts)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a wait-a-tick}
|
||||
|
||||
|
||||
### Abide by the unidirectional data flow rule
|
||||
The `doSomething()` method updates the screen when the hero name exceeds 10 characters.
|
||||
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="do-something" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/after-view.component.ts" region="do-something" title="AfterViewComponent (doSomething)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Why does the `doSomething()` method wait a tick before updating `comment`?
|
||||
|
||||
Angular's unidirectional data flow rule forbids updates to the view *after* it has been composed.
|
||||
@ -669,24 +790,32 @@ Both of these hooks fire _after_ the component's view has been composed.
|
||||
Angular throws an error if the hook updates the component's data-bound `comment` property immediately (try it!).
|
||||
The `LoggerService.tick_then()` postpones the log update
|
||||
for one turn of the browser's JavaScript cycle and that's just long enough.
|
||||
|
||||
|
||||
Here's *AfterView* in action:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/after-view-anim.gif' alt="AfterView"> </img>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/after-view-anim.gif' alt="AfterView"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Notice that Angular frequently calls `AfterViewChecked()`, often when there are no changes of interest.
|
||||
Write lean hook methods to avoid performance problems.
|
||||
|
||||
|
||||
|
||||
{@a aftercontent}
|
||||
|
||||
|
||||
## AfterContent
|
||||
The *AfterContent* sample explores the `AfterContentInit()` and `AfterContentChecked()` hooks that Angular calls
|
||||
*after* Angular projects external content into the component.
|
||||
|
||||
|
||||
{@a content-projection}
|
||||
|
||||
|
||||
### Content projection
|
||||
*Content projection* is a way to import HTML content from outside the component and insert that content
|
||||
into the component's template in a designated spot.
|
||||
@ -694,51 +823,68 @@ into the component's template in a designated spot.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
AngularJS developers know this technique as *transclusion*.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
Consider this variation on the [previous _AfterView_](guide/lifecycle-hooks#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:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="parent-template" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="parent-template" title="AfterContentParentComponent (template excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Notice that the `<my-child>` tag is tucked between the `<after-content>` tags.
|
||||
Never put content between a component's element tags *unless you intend to project that content
|
||||
into the component*.
|
||||
|
||||
Now look at the component's template:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="template" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="template" title="AfterContentComponent (template)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The `<ng-content>` tag is a *placeholder* for the external content.
|
||||
It tells Angular where to insert that content.
|
||||
In this case, the projected content is the `<my-child>` from the parent.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/projected-child-view.png' width="230" alt="Projected Content"> </img>
|
||||
<img src='assets/images/devguide/lifecycle-hooks/projected-child-view.png' width="230" alt="Projected Content"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
|
||||
|
||||
The telltale signs of *content projection* are twofold:
|
||||
- HTML between component element tags.
|
||||
- The presence of `<ng-content>` tags in the component's template.
|
||||
|
||||
* HTML between component element tags.
|
||||
* The presence of `<ng-content>` tags in the component's template.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
{@a aftercontent-hooks}
|
||||
|
||||
|
||||
### AfterContent hooks
|
||||
|
||||
*AfterContent* hooks are similar to the *AfterView* hooks.
|
||||
The key difference is in the child component.
|
||||
|
||||
@ -753,13 +899,15 @@ which can only be reached by querying for them via the property decorated with
|
||||
[@ContentChild](api/core/index/ContentChild-decorator).
|
||||
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="hooks" linenums="false">
|
||||
<code-example path="lifecycle-hooks/src/app/after-content.component.ts" region="hooks" title="AfterContentComponent (class excerpts)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a no-unidirectional-flow-worries}
|
||||
|
||||
|
||||
### No unidirectional flow worries with _AfterContent_
|
||||
|
||||
This component's `doSomething()` method update's the component's data-bound `comment` property immediately.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user