Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
b1f8eb14c8 | |||
f983d1cc50 | |||
12857922c5 | |||
dbdf9f76be | |||
d5eaf4de3c | |||
2d6126ec1b | |||
259f6bf638 | |||
03d549be05 | |||
bc4b4b5b55 | |||
00f2055c59 | |||
91efc7f70b | |||
8b1a6b1f24 | |||
b732fb935b | |||
0e2d962acb | |||
7acbc19b94 | |||
c69eda8f60 | |||
210ff78f4d | |||
96766f7336 | |||
52b50c4b6c | |||
398690d47c | |||
dc01fb167b | |||
7c26c06495 | |||
82dc7fa628 | |||
3101e89cf5 | |||
79c3e1b968 | |||
c96967b235 | |||
422cd0b665 | |||
eea2039bce | |||
612f508dff | |||
aa3d75ba83 | |||
4cc6abbbf8 | |||
222758bed3 | |||
516107fd04 | |||
bd70aece52 | |||
2ca6bdd110 | |||
2aefac841f | |||
ec496c2fda | |||
346dbcf24a | |||
48843a9f47 | |||
8f2fb02048 | |||
89e4262188 | |||
424a323316 | |||
28985cb2de | |||
5d1cd57787 |
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,3 +1,17 @@
|
||||
<a name="5.0.2"></a>
|
||||
## [5.0.2](https://github.com/angular/angular/compare/5.0.1...5.0.2) (2017-11-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** ensure final state() styles are applied within @.disabled animations ([#20267](https://github.com/angular/angular/issues/20267)) ([8b1a6b1](https://github.com/angular/angular/commit/8b1a6b1)), closes [#20266](https://github.com/angular/angular/issues/20266)
|
||||
* **compiler:** fix corner cases in shadow CSS ([5d1cd57](https://github.com/angular/angular/commit/5d1cd57))
|
||||
* **compiler:** recognize @NgModule with a redundant @Injectable ([#20320](https://github.com/angular/angular/issues/20320)) ([4cc6abb](https://github.com/angular/angular/commit/4cc6abb))
|
||||
* **compiler:** show explanatory text in template errors ([#20313](https://github.com/angular/angular/issues/20313)) ([424a323](https://github.com/angular/angular/commit/424a323))
|
||||
* **router:** 'merge' queryParamHandling strategy should be able to remove query params ([#19733](https://github.com/angular/angular/issues/19733)) ([b732fb9](https://github.com/angular/angular/commit/b732fb9)), closes [#18463](https://github.com/angular/angular/issues/18463) [#17202](https://github.com/angular/angular/issues/17202)
|
||||
|
||||
|
||||
|
||||
<a name="5.0.1"></a>
|
||||
## [5.0.1](https://github.com/angular/angular/compare/5.0.0...5.0.1) (2017-11-08)
|
||||
|
||||
|
62
aio/content/examples/ajs-quick-reference/.angular-cli.1.json
Normal file
62
aio/content/examples/ajs-quick-reference/.angular-cli.1.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"project": {
|
||||
"name": "angular.io-example"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.ico"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
// #docregion styles
|
||||
"styles": [
|
||||
"styles.css"
|
||||
],
|
||||
// #enddocregion styles
|
||||
"scripts": [],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
{
|
||||
"project": "src/tsconfig.app.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "src/tsconfig.spec.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "css",
|
||||
"component": {}
|
||||
}
|
||||
}
|
@ -5,9 +5,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>AngularJS to Angular Quick Reference</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- #docregion style -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<!-- #enddocregion style -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -4,7 +4,7 @@
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!app/*.[1,2,3].*"
|
||||
"!app/*.[0,1,2,3].*"
|
||||
],
|
||||
"tags": ["attribute", "directive"]
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
// #docregion
|
||||
import { Directive } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[appHighlight]'
|
||||
})
|
||||
export class HighlightDirective {
|
||||
constructor() { }
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
/* tslint:disable:no-unused-variable */
|
||||
// #docregion
|
||||
import { Directive, ElementRef, Input } from '@angular/core';
|
||||
import { Directive, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive({ selector: '[appHighlight]' })
|
||||
@Directive({
|
||||
selector: '[appHighlight]'
|
||||
})
|
||||
export class HighlightDirective {
|
||||
constructor(el: ElementRef) {
|
||||
el.nativeElement.style.backgroundColor = 'yellow';
|
||||
|
@ -1,7 +1,10 @@
|
||||
/* tslint:disable:no-unused-variable member-ordering */
|
||||
// #docplaster
|
||||
// #docregion imports,
|
||||
import { Directive, ElementRef, HostListener } from '@angular/core';
|
||||
// #enddocregion imports,
|
||||
import { Input } from '@angular/core';
|
||||
// #docregion
|
||||
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[appHighlight]'
|
||||
@ -35,7 +38,7 @@ export class HighlightDirective {
|
||||
// #enddocregion color
|
||||
|
||||
// #docregion color-2
|
||||
@Input() myHighlight: string;
|
||||
@Input() appHighlight: string;
|
||||
// #enddocregion color-2
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* tslint:disable:member-ordering */
|
||||
// #docregion
|
||||
// #docregion, imports
|
||||
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
|
||||
// #enddocregion imports
|
||||
|
||||
@Directive({
|
||||
selector: '[appHighlight]'
|
||||
|
@ -1,7 +1,5 @@
|
||||
/* tslint:disable:member-ordering */
|
||||
// #docregion imports,
|
||||
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
|
||||
// #enddocregion imports
|
||||
|
||||
@Directive({
|
||||
selector: '[appHighlight]'
|
||||
|
@ -4,7 +4,8 @@
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
"!**/*.[0,1,2].*",
|
||||
"**/dummy.module.ts"
|
||||
],
|
||||
"tags": ["dependency", "di"]
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
/*
|
||||
Must put this interface in its own file instead of app.config.ts
|
||||
or else TypeScript gives a (bogus) warning:
|
||||
WARNING in ./src/app/... .ts
|
||||
"export 'AppConfig' was not found in './app.config'
|
||||
*/
|
||||
export interface AppConfig {
|
||||
apiEndpoint: string;
|
||||
title: string;
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
// Early versions
|
||||
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
@ -1,7 +1,6 @@
|
||||
// #docregion
|
||||
// #docregion imports
|
||||
import { Component } from '@angular/core';
|
||||
import { Inject } from '@angular/core';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
import { APP_CONFIG, AppConfig } from './app.config';
|
||||
// #enddocregion imports
|
||||
@ -23,3 +22,5 @@ export class AppComponent {
|
||||
}
|
||||
// #enddocregion ctor
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
import { APP_CONFIG, AppConfig } from './app.config';
|
||||
import { Logger } from './logger.service';
|
||||
import { UserService } from './user.service';
|
||||
// #enddocregion imports
|
||||
|
||||
@ -23,8 +22,7 @@ import { UserService } from './user.service';
|
||||
<app-heroes id="authorized" *ngIf="isAuthorized"></app-heroes>
|
||||
<app-heroes id="unauthorized" *ngIf="!isAuthorized"></app-heroes>
|
||||
<app-providers></app-providers>
|
||||
`,
|
||||
providers: [Logger]
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
title: string;
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { AppConfig } from './app-config';
|
||||
export { AppConfig } from './app-config';
|
||||
|
||||
// #docregion token
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');
|
||||
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
|
||||
// #enddocregion token
|
||||
|
||||
// #docregion config
|
||||
export interface AppConfig {
|
||||
apiEndpoint: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const HERO_DI_CONFIG: AppConfig = {
|
||||
apiEndpoint: 'api.heroes.com',
|
||||
title: 'Dependency Injection'
|
||||
|
@ -1,32 +1,24 @@
|
||||
// #docplaster
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { APP_CONFIG, HERO_DI_CONFIG } from './app.config';
|
||||
import { AppComponent } from './app.component';
|
||||
import { CarComponent } from './car/car.component';
|
||||
import { HeroesComponent } from './heroes/heroes.component';
|
||||
import { HeroListComponent } from './heroes/hero-list.component';
|
||||
import { InjectorComponent } from './injector.component';
|
||||
import { Logger } from './logger.service';
|
||||
import { TestComponent } from './test.component';
|
||||
import { APP_CONFIG, HERO_DI_CONFIG } from './app.config';
|
||||
import { UserService } from './user.service';
|
||||
import {
|
||||
ProvidersComponent,
|
||||
Provider1Component,
|
||||
Provider3Component,
|
||||
Provider4Component,
|
||||
Provider5Component,
|
||||
Provider6aComponent,
|
||||
Provider6bComponent,
|
||||
Provider7Component,
|
||||
Provider8Component,
|
||||
Provider9Component,
|
||||
Provider10Component,
|
||||
} from './providers.component';
|
||||
|
||||
import { ProvidersModule } from './providers.module';
|
||||
|
||||
// #docregion ngmodule
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
BrowserModule,
|
||||
ProvidersModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
@ -35,26 +27,19 @@ import {
|
||||
// #enddocregion ngmodule
|
||||
HeroListComponent,
|
||||
InjectorComponent,
|
||||
TestComponent,
|
||||
ProvidersComponent,
|
||||
Provider1Component,
|
||||
Provider3Component,
|
||||
Provider4Component,
|
||||
Provider5Component,
|
||||
Provider6aComponent,
|
||||
Provider6bComponent,
|
||||
Provider7Component,
|
||||
Provider8Component,
|
||||
Provider9Component,
|
||||
Provider10Component,
|
||||
TestComponent
|
||||
// #docregion ngmodule
|
||||
],
|
||||
// #docregion ngmodule-providers
|
||||
// #docregion providers, providers-2
|
||||
providers: [
|
||||
// #enddocregion providers
|
||||
Logger,
|
||||
// #docregion providers
|
||||
UserService,
|
||||
{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
|
||||
],
|
||||
// #enddocregion ngmodule-providers
|
||||
// #enddocregion providers, providers-2
|
||||
exports: [ CarComponent, HeroesComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -0,0 +1,25 @@
|
||||
|
||||
/// Dummy modules to satisfy Angular Language Service
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
////////
|
||||
|
||||
import { AppComponent as AppComponent1 } from './app.component.1';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, AppModule ],
|
||||
declarations: [ AppComponent1 ]
|
||||
})
|
||||
export class DummyModule1 {}
|
||||
|
||||
/////////
|
||||
|
||||
import { AppComponent as AppComponent2 } from './app.component.2';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, AppModule ],
|
||||
declarations: [ AppComponent2 ]
|
||||
})
|
||||
export class DummyModule2 {}
|
@ -0,0 +1,35 @@
|
||||
|
||||
/// Dummy modules to satisfy Angular Language Service
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
////////
|
||||
|
||||
import { HeroListComponent as HeroListComponent1 } from './hero-list.component.1';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule ],
|
||||
declarations: [ HeroListComponent1 ],
|
||||
exports: [ HeroListComponent1 ]
|
||||
})
|
||||
export class DummyModule1 {}
|
||||
|
||||
/////////
|
||||
|
||||
import { HeroListComponent as HeroListComponent2 } from './hero-list.component.2';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule ],
|
||||
declarations: [ HeroListComponent2 ]
|
||||
})
|
||||
export class DummyModule2 {}
|
||||
|
||||
/////////
|
||||
|
||||
import { HeroesComponent as HeroesComponent1 } from './heroes.component.1';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, DummyModule1 ],
|
||||
declarations: [ HeroesComponent1 ]
|
||||
})
|
||||
export class DummyModule3 {}
|
@ -1,16 +1,17 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list',
|
||||
template: `
|
||||
<div *ngFor="let hero of heroes">
|
||||
{{hero.id}} - {{hero.name}}
|
||||
</div>
|
||||
<div *ngFor="let hero of heroes">
|
||||
{{hero.id}} - {{hero.name}}
|
||||
</div>
|
||||
`
|
||||
})
|
||||
// #docregion class
|
||||
export class HeroListComponent {
|
||||
heroes = HEROES;
|
||||
}
|
||||
// #enddocregion class
|
||||
|
@ -1,7 +1,6 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
// #enddocregion
|
||||
import { HeroService } from './hero.service.1';
|
||||
@ -15,9 +14,9 @@ import { HeroService } from './hero.service';
|
||||
@Component({
|
||||
selector: 'app-hero-list',
|
||||
template: `
|
||||
<div *ngFor="let hero of heroes">
|
||||
{{hero.id}} - {{hero.name}}
|
||||
</div>
|
||||
<div *ngFor="let hero of heroes">
|
||||
{{hero.id}} - {{hero.name}}
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class HeroListComponent {
|
||||
|
@ -1,17 +1,16 @@
|
||||
/* tslint:disable:one-line */
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list',
|
||||
template: `
|
||||
<div *ngFor="let hero of heroes">
|
||||
{{hero.id}} - {{hero.name}}
|
||||
({{hero.isSecret ? 'secret' : 'public'}})
|
||||
</div>
|
||||
<div *ngFor="let hero of heroes">
|
||||
{{hero.id}} - {{hero.name}}
|
||||
({{hero.isSecret ? 'secret' : 'public'}})
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class HeroListComponent {
|
||||
|
@ -0,0 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
constructor() { }
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Injectable()
|
||||
|
@ -1,6 +1,5 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
import { Logger } from '../logger.service';
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
import { Logger } from '../logger.service';
|
||||
|
||||
|
@ -1,21 +1,18 @@
|
||||
// #docplaster
|
||||
// #docregion full, v1
|
||||
import { Component } from '@angular/core';
|
||||
// #docregion, v1
|
||||
import { Component } from '@angular/core';
|
||||
// #enddocregion v1
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
import { HeroService } from './hero.service';
|
||||
// #enddocregion full
|
||||
|
||||
// #docregion full, v1
|
||||
|
||||
// #docregion v1
|
||||
@Component({
|
||||
selector: 'app-heroes',
|
||||
// #enddocregion v1
|
||||
providers: [HeroService],
|
||||
providers: [ HeroService ],
|
||||
// #docregion v1
|
||||
template: `
|
||||
<h2>Heroes</h2>
|
||||
<app-hero-list></app-hero-list>
|
||||
<h2>Heroes</h2>
|
||||
<app-hero-list></app-hero-list>
|
||||
`
|
||||
})
|
||||
export class HeroesComponent { }
|
||||
|
@ -1,14 +1,13 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { heroServiceProvider } from './hero.service.provider';
|
||||
|
||||
@Component({
|
||||
selector: 'app-heroes',
|
||||
providers: [ heroServiceProvider ],
|
||||
template: `
|
||||
<h2>Heroes</h2>
|
||||
<app-hero-list></app-hero-list>
|
||||
`,
|
||||
providers: [heroServiceProvider]
|
||||
<h2>Heroes</h2>
|
||||
<app-hero-list></app-hero-list>
|
||||
`
|
||||
})
|
||||
export class HeroesComponent { }
|
||||
|
@ -1,19 +1,21 @@
|
||||
/* tslint:disable:one-line:check-open-brace*/
|
||||
// Examples of provider arrays
|
||||
// #docplaster
|
||||
/*
|
||||
* A collection of demo components showing different ways to provide services
|
||||
* in @Component metadata
|
||||
*/
|
||||
import { Component, Inject, Injectable, OnInit } from '@angular/core';
|
||||
|
||||
import { APP_CONFIG, AppConfig,
|
||||
HERO_DI_CONFIG } from './app.config';
|
||||
import {
|
||||
APP_CONFIG,
|
||||
AppConfig,
|
||||
HERO_DI_CONFIG } from './app.config';
|
||||
|
||||
import { HeroService } from './heroes/hero.service';
|
||||
import { heroServiceProvider } from './heroes/hero.service.provider';
|
||||
import { Logger } from './logger.service';
|
||||
import { UserService } from './user.service';
|
||||
import { HeroService } from './heroes/hero.service';
|
||||
import { heroServiceProvider } from './heroes/hero.service.provider';
|
||||
import { Logger } from './logger.service';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
let template = '{{log}}';
|
||||
const template = '{{log}}';
|
||||
|
||||
//////////////////////////////////////////
|
||||
@Component({
|
||||
selector: 'provider-1',
|
||||
template: template,
|
||||
@ -30,6 +32,7 @@ export class Provider1Component {
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
@Component({
|
||||
selector: 'provider-3',
|
||||
template: template,
|
||||
@ -47,7 +50,7 @@ export class Provider3Component {
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
class BetterLogger extends Logger {}
|
||||
export class BetterLogger extends Logger {}
|
||||
|
||||
@Component({
|
||||
selector: 'provider-4',
|
||||
@ -66,9 +69,10 @@ export class Provider4Component {
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
// #docregion EvenBetterLogger
|
||||
@Injectable()
|
||||
class EvenBetterLogger extends Logger {
|
||||
export class EvenBetterLogger extends Logger {
|
||||
constructor(private userService: UserService) { super(); }
|
||||
|
||||
log(message: string) {
|
||||
@ -96,8 +100,10 @@ export class Provider5Component {
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
class NewLogger extends Logger {}
|
||||
class OldLogger {
|
||||
|
||||
export class NewLogger extends Logger {}
|
||||
|
||||
export class OldLogger {
|
||||
logs: string[] = [];
|
||||
log(message: string) {
|
||||
throw new Error('Should not call the old logger!');
|
||||
@ -149,11 +155,14 @@ export class Provider6bComponent {
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
// #docregion silent-logger
|
||||
// An object in the shape of the logger service
|
||||
let silentLogger = {
|
||||
export function SilentLoggerFn() {}
|
||||
|
||||
const silentLogger = {
|
||||
logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
|
||||
log: () => {}
|
||||
log: SilentLoggerFn
|
||||
};
|
||||
// #enddocregion silent-logger
|
||||
|
||||
@ -172,6 +181,7 @@ export class Provider7Component {
|
||||
this.log = logger.logs[0];
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
|
||||
@Component({
|
||||
@ -189,6 +199,7 @@ export class Provider8Component {
|
||||
}
|
||||
|
||||
/////////////////
|
||||
|
||||
@Component({
|
||||
selector: 'provider-9',
|
||||
template: template,
|
||||
@ -218,6 +229,7 @@ export class Provider9Component implements OnInit {
|
||||
this.log = 'APP_CONFIG Application title is ' + this.config.title;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Sample providers 1 to 7 illustrate a required logger dependency.
|
||||
// Optional logger, can be null
|
||||
@ -248,6 +260,7 @@ export class Provider10Component implements OnInit {
|
||||
}
|
||||
|
||||
/////////////////
|
||||
|
||||
@Component({
|
||||
selector: 'app-providers',
|
||||
template: `
|
||||
|
@ -0,0 +1,33 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import {
|
||||
Provider1Component,
|
||||
Provider3Component,
|
||||
Provider4Component,
|
||||
Provider5Component,
|
||||
Provider6aComponent,
|
||||
Provider6bComponent,
|
||||
Provider7Component,
|
||||
Provider8Component,
|
||||
Provider9Component,
|
||||
Provider10Component,
|
||||
ProvidersComponent,
|
||||
} from './providers.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
Provider1Component,
|
||||
Provider3Component,
|
||||
Provider4Component,
|
||||
Provider5Component,
|
||||
Provider6aComponent,
|
||||
Provider6bComponent,
|
||||
Provider7Component,
|
||||
Provider8Component,
|
||||
Provider9Component,
|
||||
Provider10Component,
|
||||
ProvidersComponent,
|
||||
],
|
||||
exports: [ ProvidersComponent ]
|
||||
})
|
||||
export class ProvidersModule {}
|
@ -2,10 +2,11 @@
|
||||
// Simulate a simple test
|
||||
// Reader should look to the testing chapter for the real thing
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { HeroService } from './heroes/hero.service';
|
||||
import { HeroListComponent } from './heroes/hero-list.component';
|
||||
import { Hero } from './heroes/hero';
|
||||
import { HeroService } from './heroes/hero.service';
|
||||
import { HeroListComponent } from './heroes/hero-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tests',
|
||||
@ -22,12 +23,13 @@ export class TestComponent {
|
||||
function runTests() {
|
||||
|
||||
// #docregion spec
|
||||
let expectedHeroes = [{name: 'A'}, {name: 'B'}]
|
||||
let mockService = <HeroService> {getHeroes: () => expectedHeroes }
|
||||
const expectedHeroes = [{name: 'A'}, {name: 'B'}]
|
||||
const mockService = <HeroService> {getHeroes: () => expectedHeroes }
|
||||
|
||||
it('should have heroes when HeroListComponent created', () => {
|
||||
let hlc = new HeroListComponent(mockService);
|
||||
expect(hlc.heroes.length).toEqual(expectedHeroes.length);
|
||||
// Pass the mock to the constructor as the Angular injector would
|
||||
const component = new HeroListComponent(mockService);
|
||||
expect(component.heroes.length).toEqual(expectedHeroes.length);
|
||||
});
|
||||
// #enddocregion spec
|
||||
|
||||
|
1
aio/content/examples/forms/src/app/app.component.html
Normal file
1
aio/content/examples/forms/src/app/app.component.html
Normal file
@ -0,0 +1 @@
|
||||
<app-hero-form></app-hero-form>
|
@ -3,6 +3,7 @@ import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: '<app-hero-form></app-hero-form>'
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent { }
|
||||
|
@ -4,7 +4,7 @@ import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { HeroFormComponent } from './hero-form.component';
|
||||
import { HeroFormComponent } from './hero-form/hero-form.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -15,6 +15,7 @@ import { HeroFormComponent } from './hero-form.component';
|
||||
AppComponent,
|
||||
HeroFormComponent
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -2,11 +2,12 @@
|
||||
// #docregion , v1, final
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { Hero } from '../hero';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-form',
|
||||
templateUrl: './hero-form.component.html'
|
||||
templateUrl: './hero-form.component.html',
|
||||
styleUrls: ['./hero-form.component.css']
|
||||
})
|
||||
export class HeroFormComponent {
|
||||
|
1
aio/content/examples/forms/src/styles.1.css
Normal file
1
aio/content/examples/forms/src/styles.1.css
Normal file
@ -0,0 +1 @@
|
||||
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
|
3
aio/content/examples/reactive-forms/debug.log
Normal file
3
aio/content/examples/reactive-forms/debug.log
Normal file
@ -0,0 +1,3 @@
|
||||
[1030/162525.401:ERROR:process_reader_win.cc(123)] NtOpenThread: {Acceso denegado} Un proceso ha solicitado acceso a un objeto, pero no se le han concedido esos derechos de acceso. (0xc0000022)
|
||||
[1030/162525.402:ERROR:exception_snapshot_win.cc(87)] thread ID 26896 not found in process
|
||||
[1030/162525.402:WARNING:crash_report_exception_handler.cc(62)] ProcessSnapshotWin::Initialize failed
|
@ -0,0 +1,4 @@
|
||||
<div class="container">
|
||||
<h1>Reactive Forms</h1>
|
||||
<app-hero-detail></app-hero-detail>
|
||||
</div>
|
@ -1,12 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<div class="container">
|
||||
<h1>Reactive Forms</h1>
|
||||
<app-hero-detail></app-hero-detail>
|
||||
</div>`
|
||||
})
|
||||
export class AppComponent { }
|
@ -0,0 +1,4 @@
|
||||
<div class="container">
|
||||
<h1>Reactive Forms</h1>
|
||||
<app-hero-list></app-hero-list>
|
||||
</div>
|
@ -3,10 +3,7 @@ import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<div class="container">
|
||||
<h1>Reactive Forms</h1>
|
||||
<app-hero-list></app-hero-list>
|
||||
</div>`
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent { }
|
||||
|
@ -6,9 +6,9 @@ import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import module
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component'; // <-- #1 import component
|
||||
import { HeroDetailComponent } from './hero-detail/hero-detail.component'; // <-- #1 import component
|
||||
// #enddocregion v1
|
||||
import { HeroListComponent } from './hero-list.component';
|
||||
import { HeroListComponent } from './hero-list/hero-list.component';
|
||||
|
||||
import { HeroService } from './hero.service'; // <-- #1 import service
|
||||
// #docregion v1
|
||||
@ -20,7 +20,7 @@ import { HeroService } from './hero.service'; // <-- #1 import service
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeroDetailComponent, // <-- #3 declare app component
|
||||
HeroDetailComponent,
|
||||
// #enddocregion v1
|
||||
HeroListComponent
|
||||
// #docregion v1
|
||||
|
@ -4,14 +4,14 @@ import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
import { DemoComponent } from './demo.component';
|
||||
import { HeroDetailComponent1 } from './hero-detail-1.component';
|
||||
import { HeroDetailComponent2 } from './hero-detail-2.component';
|
||||
import { HeroDetailComponent3 } from './hero-detail-3.component';
|
||||
import { HeroDetailComponent4 } from './hero-detail-4.component';
|
||||
import { HeroDetailComponent5 } from './hero-detail-5.component';
|
||||
import { HeroDetailComponent6 } from './hero-detail-6.component';
|
||||
import { HeroDetailComponent7 } from './hero-detail-7.component';
|
||||
import { HeroDetailComponent8 } from './hero-detail-8.component';
|
||||
import { HeroDetailComponent1 } from './hero-detail/hero-detail-1.component';
|
||||
import { HeroDetailComponent2 } from './hero-detail/hero-detail-2.component';
|
||||
import { HeroDetailComponent3 } from './hero-detail/hero-detail-3.component';
|
||||
import { HeroDetailComponent4 } from './hero-detail/hero-detail-4.component';
|
||||
import { HeroDetailComponent5 } from './hero-detail/hero-detail-5.component';
|
||||
import { HeroDetailComponent6 } from './hero-detail/hero-detail-6.component';
|
||||
import { HeroDetailComponent7 } from './hero-detail/hero-detail-7.component';
|
||||
import { HeroDetailComponent8 } from './hero-detail/hero-detail-8.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -1,8 +1,9 @@
|
||||
/* tslint:disable:component-class-suffix */
|
||||
// #docregion imports
|
||||
import { Component } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
// #enddocregion
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
// #docregion import
|
||||
import { FormControl } from '@angular/forms';
|
||||
// #enddocregion import
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-detail-1',
|
@ -3,7 +3,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
import { states } from './data-model';
|
||||
import { states } from '../data-model';
|
||||
// #enddocregion imports
|
||||
|
||||
@Component({
|
@ -2,7 +2,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
import { states } from './data-model';
|
||||
import { states } from '../data-model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-detail-5',
|
@ -5,7 +5,7 @@ import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
// #docregion import-hero
|
||||
import { Hero, states } from './data-model';
|
||||
import { Hero, states } from '../data-model';
|
||||
// #enddocregion import-hero
|
||||
|
||||
////////// 6 ////////////////////
|
@ -5,7 +5,7 @@ import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
// #docregion import-address
|
||||
import { Address, Hero, states } from './data-model';
|
||||
import { Address, Hero, states } from '../data-model';
|
||||
// #enddocregion import-address
|
||||
|
||||
// #enddocregion imports
|
@ -3,7 +3,7 @@
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
import { Address, Hero, states } from './data-model';
|
||||
import { Address, Hero, states } from '../data-model';
|
||||
// #enddocregion imports
|
||||
|
||||
@Component({
|
@ -3,17 +3,16 @@
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
|
||||
|
||||
import { Address, Hero, states } from './data-model';
|
||||
import { Address, Hero, states } from '../data-model';
|
||||
// #docregion import-service
|
||||
import { HeroService } from './hero.service';
|
||||
import { HeroService } from '../hero.service';
|
||||
// #enddocregion import-service
|
||||
|
||||
// #docregion metadata
|
||||
@Component({
|
||||
selector: 'app-hero-detail',
|
||||
templateUrl: './hero-detail.component.html'
|
||||
templateUrl: './hero-detail.component.html',
|
||||
styleUrls: ['./hero-detail.component.css']
|
||||
})
|
||||
// #enddocregion metadata
|
||||
export class HeroDetailComponent implements OnChanges {
|
||||
@Input() hero: Hero;
|
||||
|
@ -3,12 +3,13 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/finally';
|
||||
|
||||
import { Hero } from './data-model';
|
||||
import { HeroService } from './hero.service';
|
||||
import { Hero } from '../data-model';
|
||||
import { HeroService } from '../hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-list',
|
||||
templateUrl: './hero-list.component.html'
|
||||
templateUrl: './hero-list.component.html',
|
||||
styleUrls: ['./hero-list.component.css']
|
||||
})
|
||||
export class HeroListComponent implements OnInit {
|
||||
heroes: Observable<Hero[]>;
|
@ -5,10 +5,8 @@
|
||||
<title>Hero Form</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<!-- #docregion bootstrap -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
|
||||
<!-- #enddocregion bootstrap -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
|
@ -5,9 +5,7 @@
|
||||
<title>Hero Form</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- #docregion bootstrap -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
|
||||
<!-- #enddocregion bootstrap -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
1
aio/content/examples/reactive-forms/src/styles.1.css
Normal file
1
aio/content/examples/reactive-forms/src/styles.1.css
Normal file
@ -0,0 +1 @@
|
||||
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
|
@ -3,7 +3,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
// #docregion import-observable
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
// #enddocregion import-observable
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
@ -1,7 +1,7 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
// #docregion downgrade-injectable
|
||||
declare var angular: angular.IAngularStatic;
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
// #enddocregion activatedroute
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
// #docregion
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SpyLocation } from '@angular/common/testing';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
// #enddocregion activatedroute
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
// #docregion routestuff
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SpyLocation } from '@angular/common/testing';
|
||||
|
||||
|
@ -212,8 +212,6 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
To launch the app in code, explicitly bootstrap the application's root module (`AppModule`)
|
||||
in `main.ts`
|
||||
and the application's root component (`AppComponent`) in `app.module.ts`.
|
||||
|
||||
For more information see the [Setup](guide/setup) page.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -1254,13 +1252,12 @@ also encapsulate a style sheet within a specific component.
|
||||
<td>
|
||||
|
||||
|
||||
### Link tag
|
||||
|
||||
<code-example hideCopy path="ajs-quick-reference/src/index.html" region="style" linenums="false"></code-example>
|
||||
### Styles configuration
|
||||
<code-example hideCopy path="ajs-quick-reference/.angular-cli.1.json" region="styles" linenums="false">
|
||||
|
||||
|
||||
In Angular, you can continue to use the link tag to define the styles for your application in the `index.html` file.
|
||||
But now you can also encapsulate styles for your components.
|
||||
With the Angular CLI, you can configure your global styles in the `.angular-cli.json` file.
|
||||
You can rename the extension to `.scss` to use sass.
|
||||
|
||||
### StyleUrls
|
||||
In Angular, you can use the `styles` or `styleUrls` property of the `@Component` metadata to define
|
||||
|
@ -127,7 +127,7 @@ You can think of `.metadata.json` as a diagram of the overall structure of a dec
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
Angular's [schema.ts](https://github.com/angular/angular/blob/master/packages/tsc-wrapped/src/schema.ts)
|
||||
Angular's [schema.ts](https://github.com/angular/angular/blob/master/packages/compiler-cli/src/metadata/schema.ts)
|
||||
describes the JSON format as a collection of TypeScript interfaces.
|
||||
|
||||
</div>
|
||||
|
@ -33,7 +33,7 @@ An attribute directive minimally requires building a controller class annotated
|
||||
the attribute.
|
||||
The controller class implements the desired directive behavior.
|
||||
|
||||
This page demonstrates building a simple _myHighlight_ attribute
|
||||
This page demonstrates building a simple _appHighlight_ attribute
|
||||
directive to set an element's background color
|
||||
when the user hovers over that element. You can apply it like this:
|
||||
|
||||
@ -43,106 +43,84 @@ when the user hovers over that element. You can apply it like this:
|
||||
|
||||
### Write the directive code
|
||||
|
||||
Follow the [setup](guide/setup) instructions for creating a new local project
|
||||
named <code>attribute-directives</code>.
|
||||
Create the directive class file in a terminal window with this CLI command.
|
||||
|
||||
Create the following source file in the indicated folder:
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng generate directive highlight
|
||||
</code-example>
|
||||
|
||||
<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.
|
||||
1. `ElementRef` [injects](guide/dependency-injection) into the directive's constructor
|
||||
so the code can access the DOM element.
|
||||
1. `Input` allows data to flow from the binding expression into the directive.
|
||||
|
||||
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)
|
||||
is the attribute name in square brackets.
|
||||
Here, the directive's selector is `[myHighlight]`.
|
||||
Angular locates all elements in the template that have an attribute named `myHighlight`.
|
||||
The CLI creates `src/app/highlight.directive.ts`, a corresponding test file (`.../spec.ts`, and _declares_ the directive class in the root `AppModule`.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
### Why not call it "highlight"?
|
||||
_Directives_ must be declared in [Angular Modules](guide/ngmodule) in the same manner as _components_.
|
||||
|
||||
Though *highlight* is a more concise name than *myHighlight* and would work,
|
||||
a best practice is to prefix selector names to ensure
|
||||
</div >
|
||||
|
||||
The generated `src/app/highlight.directive.ts` is as follows:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.0.ts" title="src/app/highlight.directive.ts"></code-example>
|
||||
|
||||
The imported `Directive` symbol provides the Angular the `@Directive` decorator.
|
||||
|
||||
The `@Directive` decorator's lone configuration property specifies the directive's
|
||||
[CSS attribute selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors), `[appHighlight]`.
|
||||
|
||||
It's the brackets (`[]`) that make it an attribute selector.
|
||||
Angular locates each element in the template that has an attribute named `appHighlight` and applies the logic of this directive to that element.
|
||||
|
||||
The _attribute selector_ pattern explains the name of this kind of directive.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
#### Why not "highlight"?
|
||||
|
||||
Though *highlight* would be a more concise selector than *appHighlight* and it would work,
|
||||
the best practice is to prefix selector names to ensure
|
||||
they don't conflict with standard HTML attributes.
|
||||
This also reduces the risk of colliding with third-party directive names.
|
||||
The CLI added the `app` prefix for you.
|
||||
|
||||
Make sure you do **not** prefix the `highlight` directive name with **`ng`** because
|
||||
that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose.
|
||||
For a simple demo, the short prefix, `my`, helps distinguish your custom directive.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
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.
|
||||
called `HighlightDirective`, which contains the (currently empty) logic for the directive.
|
||||
Exporting `HighlightDirective` makes the directive accessible.
|
||||
|
||||
Angular creates a new instance of the directive's controller class for
|
||||
each matching element, injecting an Angular `ElementRef`
|
||||
into the constructor.
|
||||
`ElementRef` is a service that grants direct access to the DOM element
|
||||
Now edit the generated `src/app/highlight.directive.ts` to look as follows:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.1.ts" title="src/app/highlight.directive.ts"></code-example>
|
||||
|
||||
The `import` statement specifies an additional `ElementRef` symbol from the Angular `core` library:
|
||||
|
||||
You use the `ElementRef`in the directive's constructor
|
||||
to [inject](guide/dependency-injection) a reference to the host DOM element,
|
||||
the element to which you applied `appHighlight`.
|
||||
|
||||
`ElementRef` grants direct access to the host DOM element
|
||||
through its `nativeElement` property.
|
||||
|
||||
This first implementation sets the background color of the host element to yellow.
|
||||
|
||||
{@a apply-directive}
|
||||
|
||||
## Apply the attribute directive
|
||||
|
||||
To use the new `HighlightDirective`, create a template that
|
||||
applies the directive as an attribute to a paragraph (`<p>`) element.
|
||||
In Angular terms, the `<p>` element is the attribute **host**.
|
||||
To use the new `HighlightDirective`, add a paragraph (`<p>`) element to the template of the root `AppComponent` and apply the directive as an attribute.
|
||||
|
||||
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" title="src/app/app.component.html" region="applied"></code-example>
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.1.html" title="src/app/app.component.html"></code-example>
|
||||
Now run the application to see the `HighlightDirective` in action.
|
||||
|
||||
Now reference this template in the `AppComponent`:
|
||||
|
||||
<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" title="src/app/app.module.ts"></code-example>
|
||||
|
||||
Now when the app runs, the `myHighlight` directive highlights the paragraph text.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/attribute-directives/first-highlight.png" alt="First Highlight">
|
||||
</figure>
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
<h3 class="no-toc">Your directive isn't working?</h3>
|
||||
|
||||
Did you remember to add the directive to the `declarations` attribute of `@NgModule`?
|
||||
It is easy to forget!
|
||||
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'.
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng serve
|
||||
</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,
|
||||
Angular knows it can apply the directive to components declared in this module.
|
||||
|
||||
</div>
|
||||
|
||||
To summarize, Angular found the `myHighlight` attribute on the `<p>` element.
|
||||
To summarize, Angular found the `appHighlight` attribute on the **host** `<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.
|
||||
@ -151,15 +129,14 @@ which sets the `<p>` element's background style to yellow.
|
||||
|
||||
## Respond to user-initiated events
|
||||
|
||||
Currently, `myHighlight` simply sets an element color.
|
||||
Currently, `appHighlight` simply sets an element color.
|
||||
The directive could be more dynamic.
|
||||
It could detect when the user mouses into or out of the element
|
||||
and respond by setting or clearing the highlight color.
|
||||
|
||||
Begin by adding `HostListener` to the list of imported symbols;
|
||||
add the `Input` symbol as well because you'll need it soon.
|
||||
Begin by adding `HostListener` to the list of imported symbols.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
|
||||
|
||||
Then add two eventhandlers that respond when the mouse enters or leaves,
|
||||
each adorned by the `HostListener` decorator.
|
||||
@ -180,8 +157,10 @@ There are at least three problems with _that_ approach:
|
||||
|
||||
</div>
|
||||
|
||||
The handlers delegate to a helper method that sets the color on the DOM element, `el`,
|
||||
which you declare and initialize in the constructor.
|
||||
The handlers delegate to a helper method that sets the color on the host DOM element, `el`.
|
||||
|
||||
The helper method, `highlight`, was extracted from the constructor.
|
||||
The revised constructor simply declares the injected `el: ElementRef`.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (constructor)" region="ctor"></code-example>
|
||||
|
||||
@ -203,7 +182,10 @@ the mouse hovers over the `p` and disappears as it moves out.
|
||||
Currently the highlight color is hard-coded _within_ the directive. That's inflexible.
|
||||
In this section, you give the developer the power to set the highlight color while applying the directive.
|
||||
|
||||
Start by adding a `highlightColor` property to the directive class like this:
|
||||
Begin by adding `Input` to the list of symbols imported from `@angular/core`.
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
|
||||
|
||||
Add a `highlightColor` property to the directive class like this:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (highlightColor)" region="color"></code-example>
|
||||
|
||||
@ -232,16 +214,16 @@ That's good, but it would be nice to _simultaneously_ apply the directive and se
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color"></code-example>
|
||||
|
||||
The `[myHighlight]` attribute binding both applies the highlighting directive to the `<p>` element
|
||||
The `[appHighlight]` 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.
|
||||
You're re-using the directive's attribute selector (`[appHighlight]`) to do both jobs.
|
||||
That's a crisp, compact syntax.
|
||||
|
||||
You'll have to rename the directive's `highlightColor` property to `myHighlight` because that's now the color property binding name.
|
||||
You'll have to rename the directive's `highlightColor` property to `appHighlight` because that's now the color property binding name.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (renamed to match directive selector)" region="color-2"></code-example>
|
||||
|
||||
This is disagreeable. The word, `myHighlight`, is a terrible property name and it doesn't convey the property's intent.
|
||||
This is disagreeable. The word, `appHighlight`, is a terrible property name and it doesn't convey the property's intent.
|
||||
|
||||
{@a input-alias}
|
||||
|
||||
@ -254,14 +236,14 @@ Restore the original property name and specify the selector as the alias in the
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (color property with alias)" region="color"></code-example>
|
||||
|
||||
_Inside_ the directive the property is known as `highlightColor`.
|
||||
_Outside_ the directive, where you bind to it, it's known as `myHighlight`.
|
||||
_Outside_ the directive, where you bind to it, it's known as `appHighlight`.
|
||||
|
||||
You get the best of both worlds: the property name you want and the binding syntax you want:
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color"></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:
|
||||
Now that you're binding via the alias to the `highlightColor`, modify the `onMouseEnter()` method to use that property.
|
||||
If someone neglects to bind to `appHighlightColor`, highlight the host element in red:
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (mouse enter)" region="mouse-enter"></code-example>
|
||||
|
||||
@ -308,7 +290,7 @@ then with the `defaultColor`, and falls back to "red" if both properties are und
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (mouse-enter)" region="mouse-enter"></code-example>
|
||||
|
||||
How do you bind to a second property when you're already binding to the `myHighlight` attribute name?
|
||||
How do you bind to a second property when you're already binding to the `appHighlight` attribute name?
|
||||
|
||||
As with components, you can add as many directive property bindings as you need by stringing them along in the template.
|
||||
The developer should be able to write the following template HTML to both bind to the `AppComponent.color`
|
||||
@ -398,6 +380,6 @@ Now apply that reasoning to the following example:
|
||||
The template and its component trust each other.
|
||||
The `color` property doesn't require the `@Input` decorator.
|
||||
|
||||
* The `myHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`,
|
||||
* The `appHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`,
|
||||
not a property of the template's component. There are trust issues.
|
||||
Therefore, the directive property must carry the `@Input` decorator.
|
||||
|
@ -356,6 +356,7 @@ Use `setTimeout()` to wait one tick and then revise the `seconds()` method so
|
||||
that it takes future values from the timer component.
|
||||
|
||||
<h3 class="no-toc">Test it</h3>
|
||||
|
||||
Use [the same countdown timer tests](guide/component-interaction#countdown-tests) as before.
|
||||
|
||||
[Back to top](guide/component-interaction#top)
|
||||
|
@ -5,51 +5,6 @@ Dependency Injection is a powerful pattern for managing code dependencies.
|
||||
This cookbook explores many of the features of Dependency Injection (DI) in Angular.
|
||||
{@a toc}
|
||||
|
||||
<!--
|
||||
|
||||
# Contents
|
||||
|
||||
* [Application-wide dependencies](guide/dependency-injection-in-action#app-wide-dependencies)
|
||||
* [External module configuration](guide/dependency-injection-in-action#external-module-configuration)
|
||||
* [`@Injectable()` and nested service dependencies](guide/dependency-injection-in-action#nested-dependencies)
|
||||
|
||||
* [`@Injectable()`](guide/dependency-injection-in-action#injectable-1)
|
||||
|
||||
* [Limit service scope to a component subtree](guide/dependency-injection-in-action#service-scope)
|
||||
* [Multiple service instances (sandboxing)](guide/dependency-injection-in-action#multiple-service-instances)
|
||||
* [Qualify dependency lookup with `@Optional()` and `@Host()`](guide/dependency-injection-in-action#qualify-dependency-lookup)
|
||||
|
||||
* [Demonstration](guide/dependency-injection-in-action#demonstration)
|
||||
|
||||
* [Inject the component's DOM element](guide/dependency-injection-in-action#component-element)
|
||||
* [Define dependencies with providers](guide/dependency-injection-in-action#providers)
|
||||
|
||||
* [Defining providers](guide/dependency-injection-in-action#defining-providers)
|
||||
* [The *provide* object literal](guide/dependency-injection-in-action#provide)
|
||||
* [`useValue`—the *value provider*](guide/dependency-injection-in-action#usevalue)
|
||||
* [`useClass`—the *class provider*](guide/dependency-injection-in-action#useclass)
|
||||
* [`useExisting`—the *alias provider*](guide/dependency-injection-in-action#useexisting)
|
||||
* [`useFactory`—the *factory provider*](guide/dependency-injection-in-action#usefactory)
|
||||
|
||||
* [Provider token alternatives: the class-interface and `InjectionToken`](guide/dependency-injection-in-action#tokens)
|
||||
|
||||
* [class-interface](guide/dependency-injection-in-action#class-interface)
|
||||
* [`InjectionToken`](guide/dependency-injection-in-action#injection-token)
|
||||
|
||||
* [Inject into a derived class](guide/dependency-injection-in-action#di-inheritance)
|
||||
* [Find a parent component by injection](guide/dependency-injection-in-action#find-parent)
|
||||
|
||||
* [Find parent with a known component type](guide/dependency-injection-in-action#known-parent)
|
||||
* [Cannot find a parent by its base class](guide/dependency-injection-in-action#base-parent)
|
||||
* [Find a parent by its class-interface](guide/dependency-injection-in-action#class-interface-parent)
|
||||
* [Find a parent in a tree of parents with `@SkipSelf()`](guide/dependency-injection-in-action#parent-tree)
|
||||
* [The `Parent` class-interface](guide/dependency-injection-in-action#parent-token)
|
||||
* [A `provideParent()` helper function](guide/dependency-injection-in-action#provideparent)
|
||||
|
||||
* [Break circularities with a forward class reference (*forwardRef*)](guide/dependency-injection-in-action#forwardref)
|
||||
|
||||
-->
|
||||
|
||||
See the <live-example name="dependency-injection-in-action"></live-example>
|
||||
of the code in this cookbook.
|
||||
|
||||
@ -79,7 +34,7 @@ is all the registration you need.
|
||||
|
||||
A *provider* is something that can create or deliver a service.
|
||||
Angular creates a service instance from a class provider by using `new`.
|
||||
Read more about providers in the [Dependency Injection](guide/dependency-injection#injector-providers)
|
||||
Read more about providers in the [Dependency Injection](guide/dependency-injection#register-providers-ngmodule)
|
||||
guide.
|
||||
|
||||
</div>
|
||||
|
167
aio/content/guide/dependency-injection-pattern.md
Normal file
167
aio/content/guide/dependency-injection-pattern.md
Normal file
@ -0,0 +1,167 @@
|
||||
# The Dependency Injection pattern
|
||||
|
||||
**Dependency injection** is an important application design pattern.
|
||||
It's used so widely that almost everyone just calls it _DI_.
|
||||
|
||||
Angular has its own dependency injection framework, and
|
||||
you really can't build an Angular application without it.
|
||||
|
||||
This page covers what DI is and why it's useful.
|
||||
|
||||
When you've learned the general pattern, you're ready to turn to
|
||||
the [Angular Dependency Injection](guide/dependency-injection) guide to see how it works in an Angular app.
|
||||
|
||||
{@a why-di }
|
||||
|
||||
## 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" 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.
|
||||
|
||||
This `Car` needs an engine and tires. Instead of asking for them,
|
||||
the `Car` constructor instantiates its own copies from
|
||||
the very specific classes `Engine` and `Tires`.
|
||||
|
||||
What if the `Engine` class evolves and its constructor requires a parameter?
|
||||
That would break the `Car` class and it would stay broken until you rewrote it along the lines of
|
||||
`this.engine = new Engine(theNewParameter)`.
|
||||
The `Engine` constructor parameters weren't even a consideration when you first wrote `Car`.
|
||||
You may not anticipate them even now.
|
||||
But you'll *have* to start caring because
|
||||
when the definition of `Engine` changes, the `Car` class must change.
|
||||
That makes `Car` brittle.
|
||||
|
||||
What if you want to put a different brand of tires on your `Car`? Too bad.
|
||||
You're locked into whatever brand the `Tires` class creates. That makes the
|
||||
`Car` class inflexible.
|
||||
|
||||
Right now each new car gets its own `engine`. It can't share an `engine` with other cars.
|
||||
While that makes sense for an automobile engine,
|
||||
surely you can think of other dependencies that should be shared, such as the onboard
|
||||
wireless connection to the manufacturer's service center. This `Car` lacks the flexibility
|
||||
to share services that have been created previously for other consumers.
|
||||
|
||||
When you write tests for `Car` you're at the mercy of its hidden dependencies.
|
||||
Is it even possible to create a new `Engine` in a test environment?
|
||||
What does `Engine` depend upon? What does that dependency depend on?
|
||||
Will a new instance of `Engine` make an asynchronous call to the server?
|
||||
You certainly don't want that going on during tests.
|
||||
|
||||
What if the `Car` should flash a warning signal when tire pressure is low?
|
||||
How do you confirm that it actually does flash a warning
|
||||
if you can't swap in low-pressure tires during the test?
|
||||
|
||||
You have no control over the car's hidden dependencies.
|
||||
When you can't control the dependencies, a class becomes difficult to test.
|
||||
|
||||
How can you make `Car` more robust, flexible, and testable?
|
||||
|
||||
{@a ctor-injection}
|
||||
That's super easy. Change the `Car` constructor to a version with DI:
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/car/car.ts (excerpt with DI)" path="dependency-injection/src/app/car/car.ts" region="car-ctor">
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/car/car.ts (excerpt without DI)" path="dependency-injection/src/app/car/car-no-di.ts" region="car-ctor">
|
||||
</code-pane>
|
||||
|
||||
</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`.
|
||||
It just consumes them.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
This example leverages TypeScript's constructor syntax for declaring
|
||||
parameters and properties simultaneously.
|
||||
|
||||
</div>
|
||||
|
||||
Now you can create a car by passing the engine and tires to the constructor.
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-creations.ts" region="car-ctor-instantiation" linenums="false">
|
||||
</code-example>
|
||||
|
||||
How cool is that?
|
||||
The definition of the `engine` and `tire` dependencies are
|
||||
decoupled from the `Car` class.
|
||||
You can pass in any kind of `engine` or `tires` you like, as long as they
|
||||
conform to the general API requirements of an `engine` or `tires`.
|
||||
|
||||
Now, if someone extends the `Engine` class, that is not `Car`'s problem.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
||||
something like this:
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-creations.ts" region="car-ctor-instantiation-with-param" linenums="false">
|
||||
|
||||
</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.
|
||||
|
||||
</div>
|
||||
|
||||
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
|
||||
during each test:
|
||||
|
||||
<code-example path="dependency-injection/src/app/car/car-creations.ts" region="car-ctor-instantiation-with-mocks" linenums="false">
|
||||
</code-example>
|
||||
|
||||
**You just learned what dependency injection is**.
|
||||
|
||||
It's a coding pattern in which a class receives its dependencies from external
|
||||
sources rather than creating them itself.
|
||||
|
||||
Cool! But what about that poor consumer?
|
||||
Anyone who wants a `Car` must now
|
||||
create all three parts: the `Car`, `Engine`, and `Tires`.
|
||||
The `Car` class shed its problems at the consumer's expense.
|
||||
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" 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
|
||||
interdependent factory methods!
|
||||
|
||||
Wouldn't it be nice if you could simply list the things you want to build without
|
||||
having to define which dependency gets injected into what?
|
||||
|
||||
This is where the dependency injection framework comes into play.
|
||||
Imagine the framework had something called an _injector_.
|
||||
You register some classes with this injector, and it figures out how to create them.
|
||||
|
||||
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" 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.
|
||||
Both `Car` and consumer simply ask for what they need and the injector delivers.
|
||||
|
||||
This is what a **dependency injection framework** is all about.
|
||||
|
||||
Now that you know what dependency injection is and appreciate its benefits,
|
||||
turn to the [Angular Dependency Injection](guide/dependency-injection) guide to see how it is implemented in Angular.
|
File diff suppressed because it is too large
Load Diff
@ -31,9 +31,11 @@ The easiest way to display a component property
|
||||
is to bind the property name through interpolation.
|
||||
With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
|
||||
|
||||
Follow the [setup](guide/setup) instructions for creating a new project
|
||||
Follow the [quickstart](guide/quickstart) instructions for creating a new project
|
||||
named <code>displaying-data</code>.
|
||||
|
||||
Delete the <code>app.component.html</code> file. It is not needed for this example.
|
||||
|
||||
Then modify the <code>app.component.ts</code> file by
|
||||
changing the template and the body of the component.
|
||||
|
||||
@ -48,7 +50,7 @@ When you're done, it should look like this:
|
||||
|
||||
You added two properties to the formerly empty component: `title` and `myHero`.
|
||||
|
||||
The revised template displays the two component properties using double curly brace
|
||||
The template displays the two component properties using double curly brace
|
||||
interpolation:
|
||||
|
||||
|
||||
@ -92,7 +94,7 @@ the view, such as a keystroke, a timer completion, or a response to an HTTP requ
|
||||
Notice that you don't call **new** to create an instance of the `AppComponent` class.
|
||||
Angular is creating an instance for you. How?
|
||||
|
||||
The CSS `selector` in the `@Component` decorator specifies an element named `<my-app>`.
|
||||
The CSS `selector` in the `@Component` decorator specifies an element named `<app-root>`.
|
||||
That element is a placeholder in the body of your `index.html` file:
|
||||
|
||||
|
||||
@ -102,9 +104,9 @@ That element is a placeholder in the body of your `index.html` file:
|
||||
|
||||
|
||||
|
||||
When you bootstrap with the `AppComponent` class (in <code>main.ts</code>), Angular looks for a `<my-app>`
|
||||
When you bootstrap with the `AppComponent` class (in <code>main.ts</code>), Angular looks for a `<app-root>`
|
||||
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
|
||||
inside the `<my-app>` tag.
|
||||
inside the `<app-root>` tag.
|
||||
|
||||
Now run the app. It should display the title and hero name:
|
||||
|
||||
@ -131,13 +133,23 @@ is simpler without the additional HTML file.
|
||||
|
||||
In either style, the template data bindings have the same access to the component's properties.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
By default, the Angular CLI generates components with a template file. You can override that with:
|
||||
|
||||
<code-example hideCopy language="sh" class="code-shell">
|
||||
ng generate component hero -it
|
||||
</code-example>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## 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:
|
||||
Although this example uses variable assignment to initialize the components, you could instead declare and initialize the properties using a constructor:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/app-ctor.component.ts" linenums="false" title="src/app/app-ctor.component.ts (class)" region="class">
|
||||
<code-example path="displaying-data/src/app/app-ctor.component.ts" linenums="false" region="class">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -231,12 +243,16 @@ At the moment, the binding is to an array of strings.
|
||||
In real applications, most bindings are to more specialized objects.
|
||||
|
||||
To convert this binding to use specialized objects, turn the array
|
||||
of hero names into an array of `Hero` objects. For that you'll need a `Hero` class.
|
||||
of hero names into an array of `Hero` objects. For that you'll need a `Hero` class:
|
||||
|
||||
Create a new file in the `app` folder called `hero.ts` with the following code:
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng generate class hero
|
||||
</code-example>
|
||||
|
||||
With the following code:
|
||||
|
||||
|
||||
<code-example path="displaying-data/src/app/hero.ts" linenums="false" title="src/app/hero.ts (excerpt)">
|
||||
<code-example path="displaying-data/src/app/hero.ts" linenums="false" title="src/app/hero.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -40,7 +40,7 @@ Note the following:
|
||||
also carries a custom validator directive, `forbiddenName`. For more
|
||||
information, see [Custom validators](guide/form-validation#custom-validators) section.
|
||||
|
||||
* `#name="ngModel"` exports `NgModel` into a local variable callled `name`. `NgModel` mirrors many of the properties of its underlying
|
||||
* `#name="ngModel"` exports `NgModel` into a local variable called `name`. `NgModel` mirrors many of the properties of its underlying
|
||||
`FormControl` instance, so you can use this in the template to check for control states such as `valid` and `dirty`. For a full list of control properties, see the [AbstractControl](api/forms/AbstractControl)
|
||||
API reference.
|
||||
|
||||
|
@ -29,19 +29,13 @@ You can run the <live-example></live-example> in Plunker and download the code f
|
||||
You can build forms by writing templates in the Angular [template syntax](guide/template-syntax) with
|
||||
the form-specific directives and techniques described in this page.
|
||||
|
||||
|
||||
<div class="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 also use a reactive (or model-driven) approach to build forms.
|
||||
However, this page focuses on template-driven forms.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
@ -51,13 +45,10 @@ otherwise wrestle with yourself.
|
||||
|
||||
You'll learn to build a template-driven form that looks like this:
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/forms/hero-form-1.png" alt="Clean Form">
|
||||
</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.
|
||||
|
||||
@ -65,27 +56,18 @@ Two of the three fields on this form are required. Required fields have a green
|
||||
|
||||
If you delete the hero name, the form displays a validation error in an attention-grabbing style:
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/forms/hero-form-2.png" alt="Invalid, Name Required">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
Note that the *Submit* button is disabled, and the "required" bar to the left of the input control changes from green to red.
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
You can customize the colors and location of the "required" bar with standard CSS.
|
||||
|
||||
You can customize the colors and location of the "required" bar with standard CSS.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
You'll build this form in small steps:
|
||||
|
||||
1. Create the `Hero` model class.
|
||||
@ -98,11 +80,15 @@ You'll build this form in small steps:
|
||||
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
|
||||
named angular-forms.
|
||||
Create a new project named <code>angular-forms</code>:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
ng new angular-forms
|
||||
|
||||
</code-example>
|
||||
|
||||
## Create the Hero model class
|
||||
|
||||
@ -113,15 +99,20 @@ A model can be as simple as a "property bag" that holds facts about a thing of a
|
||||
That describes well the `Hero` class with its three required fields (`id`, `name`, `power`)
|
||||
and one optional field (`alterEgo`).
|
||||
|
||||
In the `app` directory, create the following file with the given content:
|
||||
Using the Angular CLI, generate a new class named `Hero`:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
ng generate class Hero
|
||||
|
||||
</code-example>
|
||||
|
||||
With this content:
|
||||
|
||||
<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
|
||||
@ -131,28 +122,29 @@ The `alterEgo` is optional, so the constructor lets you omit it; note the questi
|
||||
|
||||
You can create a new hero like this:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.ts" linenums="false" title="src/app/hero-form.component.ts (SkyDog)" region="SkyDog">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" region="SkyDog">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
## Create a form component
|
||||
|
||||
An Angular form has two parts: an HTML-based _template_ and a component _class_
|
||||
to handle data and user interactions programmatically.
|
||||
Begin with the class because it states, in brief, what the hero editor can do.
|
||||
|
||||
Create the following file with the given content:
|
||||
Using the Angular CLI, generate a new component named `HeroForm`:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.ts" linenums="false" title="src/app/hero-form.component.ts (v1)" region="v1">
|
||||
ng generate component HeroForm
|
||||
|
||||
</code-example>
|
||||
|
||||
With this content:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" title="src/app/hero-form/hero-form.component.ts (v1)" region="v1">
|
||||
|
||||
</code-example>
|
||||
|
||||
There’s nothing special about this component, nothing form-specific,
|
||||
nothing to distinguish it from any component you've written before.
|
||||
@ -173,21 +165,6 @@ parent component. This is not a concern now and these future changes won't affec
|
||||
* 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?
|
||||
|
||||
Why don't you write the template inline in the component file as you often do elsewhere?
|
||||
|
||||
There is no "right" answer for all occasions. Inline templates are useful when they are short.
|
||||
Most form templates aren't short. TypeScript and JavaScript files generally aren't the best place to
|
||||
write (or read) large stretches of HTML, and few editors help with files that have a mix of HTML and code.
|
||||
|
||||
Form templates tend to be large, even when displaying a small number of fields,
|
||||
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
|
||||
@ -196,89 +173,57 @@ and declare the components that belong to this module, such as the `HeroFormComp
|
||||
Because template-driven forms are in their own module, you need to add the `FormsModule` to the array of
|
||||
`imports` for the application module before you can use forms.
|
||||
|
||||
Replace the contents of the "QuickStart" version with the following:
|
||||
Update it with the following:
|
||||
|
||||
<code-example path="forms/src/app/app.module.ts" title="src/app/app.module.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
There are two changes:
|
||||
|
||||
1. You import `FormsModule`.
|
||||
|
||||
There are three changes:
|
||||
|
||||
1. You import `FormsModule` and the new `HeroFormComponent`.
|
||||
|
||||
1. You add the `FormsModule` to the list of `imports` defined in the `@NgModule` decorator. This gives the application
|
||||
access to all of the template-driven forms features, including `ngModel`.
|
||||
|
||||
1. You add the `HeroFormComponent` to the list of `declarations` defined in the `@NgModule` decorator. This makes
|
||||
the `HeroFormComponent` component visible throughout this module.
|
||||
|
||||
1. You add the `FormsModule` to the list of `imports` defined in the `@NgModule` decorator. This gives the application
|
||||
access to all of the template-driven forms features, including `ngModel`.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="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.
|
||||
|
||||
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.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
## Revise *app.component.ts*
|
||||
## Revise *app.component.html*
|
||||
|
||||
`AppComponent` is the application's root component. It will host the new `HeroFormComponent`.
|
||||
|
||||
Replace the contents of the "QuickStart" version with the following:
|
||||
Replace the contents of its template with the following:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/app.component.ts" title="src/app/app.component.ts">
|
||||
<code-example path="forms/src/app/app.component.html" title="src/app/app.component.html">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="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.
|
||||
You've also dropped the `name` field from the class body.
|
||||
|
||||
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.
|
||||
Don't forget to remove the `name` field from the class body as well.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
## Create an initial HTML form template
|
||||
|
||||
Create the template file with the following contents:
|
||||
Update the template file with the following contents:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" region="start" title="src/app/hero-form.component.html">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="start" title="src/app/hero-form/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.
|
||||
|
||||
@ -289,52 +234,34 @@ You added a *Submit* button at the bottom with some classes on it for styling.
|
||||
|
||||
*You're not using Angular yet*. There are no bindings or extra directives, just layout.
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
In template driven forms, if you've imported `FormsModule`, you don't have to do anything
|
||||
to the `<form>` tag in order to make use of `FormsModule`. Continue on to see how this works.
|
||||
|
||||
In template driven forms, if you've imported `FormsModule`, you don't have to do anything
|
||||
to the `<form>` tag in order to make use of `FormsModule`. Continue on to see how this works.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
The `container`, `form-group`, `form-control`, and `btn` classes
|
||||
come from [Twitter Bootstrap](http://getbootstrap.com/css/). These classes are purely cosmetic.
|
||||
Bootstrap gives the form a little style.
|
||||
|
||||
|
||||
<div class="callout is-important">
|
||||
|
||||
<header>
|
||||
Angular forms don't require a style library
|
||||
</header>
|
||||
|
||||
|
||||
<header>
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
</div>
|
||||
|
||||
To add the stylesheet, open `styles.css` and add the following import line at the top:
|
||||
|
||||
|
||||
To add the stylesheet, open `index.html` and add the following link to the `<head>`:
|
||||
|
||||
|
||||
<code-example path="forms/src/index.html" linenums="false" title="src/index.html (bootstrap)" region="bootstrap">
|
||||
<code-example path="forms/src/styles.1.css" linenums="false" title="src/styles.css">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
## Add powers with _*ngFor_
|
||||
|
||||
The hero must choose one superpower from a fixed list of agency-approved powers.
|
||||
@ -346,13 +273,10 @@ a technique seen previously in the [Displaying Data](guide/displaying-data) page
|
||||
|
||||
Add the following HTML *immediately below* the *Alter Ego* group:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (powers)" region="powers">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (powers)" region="powers">
|
||||
|
||||
</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.
|
||||
@ -363,13 +287,11 @@ you display its name using the interpolation syntax.
|
||||
|
||||
Running the app right now would be disappointing.
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/forms/hero-form-3.png" alt="Early form with no binding">
|
||||
</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.
|
||||
@ -384,113 +306,83 @@ makes binding the form to the model easy.
|
||||
|
||||
Find the `<input>` tag for *Name* and update it like this:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="ngModelName-1">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-1">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<div class="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.
|
||||
|
||||
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.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
Focus on the binding syntax: `[(ngModel)]="..."`.
|
||||
|
||||
You need one more addition to display the data. Declare
|
||||
a template variable for the form. Update the `<form>` tag with
|
||||
`#heroForm="ngForm"` as follows:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="template-variable">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="template-variable">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The variable `heroForm` is now a reference to the `NgForm` directive that governs the form as a whole.
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
{@a ngForm}
|
||||
{@a ngForm}
|
||||
|
||||
### The _NgForm_ directive
|
||||
### The _NgForm_ directive
|
||||
|
||||
What `NgForm` directive?
|
||||
You didn't add an [NgForm](api/forms/NgForm) directive.
|
||||
What `NgForm` directive?
|
||||
You didn't add an [NgForm](api/forms/NgForm) directive.
|
||||
|
||||
Angular did. Angular automatically creates and attaches an `NgForm` directive to the `<form>` tag.
|
||||
|
||||
The `NgForm` directive supplements the `form` element with additional features.
|
||||
It holds the controls you created for the elements with an `ngModel` directive
|
||||
and `name` attribute, and monitors their properties, including their validity.
|
||||
It also has its own `valid` property which is true only *if every contained
|
||||
control* is valid.
|
||||
Angular did. Angular automatically creates and attaches an `NgForm` directive to the `<form>` tag.
|
||||
|
||||
The `NgForm` directive supplements the `form` element with additional features.
|
||||
It holds the controls you created for the elements with an `ngModel` directive
|
||||
and `name` attribute, and monitors their properties, including their validity.
|
||||
It also has its own `valid` property which is true only *if every contained
|
||||
control* is valid.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
If you ran the app now and started typing in the *Name* input box,
|
||||
adding and deleting characters, you'd see them appear and disappear
|
||||
from the interpolated text.
|
||||
At some point it might look like this:
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/forms/ng-model-in-action.png" alt="ngModel in action">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
The diagnostic is evidence that values really are flowing from the input box to the model and
|
||||
back again.
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
That's *two-way data binding*.
|
||||
For more information, see
|
||||
[Two-way binding with NgModel](guide/template-syntax#ngModel) on the
|
||||
the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
That's *two-way data binding*.
|
||||
For more information, see
|
||||
[Two-way binding with NgModel](guide/template-syntax#ngModel) on the
|
||||
the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
<div class="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.
|
||||
Read more in the previous section, [The NgForm directive](guide/forms#ngForm).
|
||||
|
||||
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.
|
||||
Read more in the previous section, [The NgForm directive](guide/forms#ngForm).
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
@ -498,42 +390,29 @@ Then you can confirm that two-way data binding works *for the entire hero model*
|
||||
|
||||
After revision, the core of the form should look like this:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="ngModel-2">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModel-2">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
* 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 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.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
If you run the app now and change every hero model property, the form might display like this:
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/forms/ng-model-in-action-2.png" alt="ngModel in action">
|
||||
</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
|
||||
@ -542,7 +421,6 @@ you if the user touched the control, if the value changed, or if the value becam
|
||||
The *NgModel* directive doesn't just track state; it updates the control with special Angular CSS classes that reflect the state.
|
||||
You can leverage those class names to change the appearance of the control.
|
||||
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
@ -611,18 +489,13 @@ You can leverage those class names to change the appearance of the control.
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="ngModelName-2">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-2">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Now run the app and look at the _Name_ input box.
|
||||
Follow these steps *precisely*:
|
||||
|
||||
@ -633,61 +506,44 @@ Follow these steps *precisely*:
|
||||
|
||||
The actions and effects are as follows:
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/forms/control-state-transitions-anim.gif" alt="Control State Transition">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
You should see the following transitions and class names:
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/forms/ng-control-class-changes.png" alt="Control state transitions">
|
||||
</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
|
||||
on the left of the input box:
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/forms/validity-required-indicator.png" alt="Invalid Form">
|
||||
</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/assets/forms.css" title="src/assets/forms.css">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Update the `<head>` of `index.html` to include this style sheet:
|
||||
|
||||
|
||||
<code-example path="forms/src/index.html" linenums="false" title="src/index.html (styles)" region="styles">
|
||||
|
||||
</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.
|
||||
@ -696,13 +552,10 @@ Leverage the control's state to reveal a helpful message.
|
||||
|
||||
When the user deletes the name, the form should look like this:
|
||||
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/forms/name-required-error.png" alt="Name required">
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
To achieve this effect, extend the `<input>` tag with the following:
|
||||
|
||||
* A [template reference variable](guide/template-syntax#ref-vars).
|
||||
@ -710,41 +563,29 @@ To achieve this effect, extend the `<input>` tag with the following:
|
||||
|
||||
Here's an example of an error message added to the _name_ input box:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="name-with-error-msg">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="name-with-error-msg">
|
||||
|
||||
</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".
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Why "ngModel"?
|
||||
A directive's [exportAs](api/core/Directive) property
|
||||
tells Angular how to link the reference variable to the directive.
|
||||
You set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel".
|
||||
|
||||
Why "ngModel"?
|
||||
A directive's [exportAs](api/core/Directive) property
|
||||
tells Angular how to link the reference variable to the directive.
|
||||
You set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel".
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
You control visibility of the name error message by binding properties of the `name`
|
||||
control to the message `<div>` element's `hidden` property.
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (hidden-error-msg)" region="hidden-error-msg">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (hidden-error-msg)" region="hidden-error-msg">
|
||||
|
||||
</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.
|
||||
|
||||
@ -767,19 +608,14 @@ power to valid values.
|
||||
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" title="src/app/hero-form.component.html (New Hero button)">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-no-reset" title="src/app/hero-form/hero-form.component.html (New Hero button)">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<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 path="forms/src/app/hero-form/hero-form.component.ts" region="new-hero" title="src/app/hero-form/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.
|
||||
@ -797,17 +633,12 @@ Replacing the hero object *did not restore the pristine state* of the form contr
|
||||
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" title="src/app/hero-form.component.html (Reset the form)">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-form-reset" title="src/app/hero-form/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.
|
||||
@ -819,13 +650,10 @@ A "form submit" is useless at the moment.
|
||||
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="src/app/hero-form.component.html (ngSubmit)" region="ngSubmit">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (ngSubmit)" region="ngSubmit">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
You'd already defined a template reference variable,
|
||||
`#heroForm`, and initialized it with the value "ngForm".
|
||||
Now, use that variable to access the form with the Submit button.
|
||||
@ -835,13 +663,10 @@ 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:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (submit-button)" region="submit-button">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (submit-button)" region="submit-button">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
If you run the application now, you find that the button is enabled—although
|
||||
it doesn't do anything useful yet.
|
||||
|
||||
@ -857,65 +682,48 @@ 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.
|
||||
|
||||
|
||||
<div class="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
|
||||
binding skills.
|
||||
If you aren't interested, skip to this page's conclusion.
|
||||
|
||||
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
|
||||
binding skills.
|
||||
If you aren't interested, skip to this page's conclusion.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
For a more strikingly visual effect,
|
||||
hide the data entry area and display something else.
|
||||
|
||||
Wrap the form in a `<div>` and bind
|
||||
its `hidden` property to the `HeroFormComponent.submitted` property.
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="edit-div">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="edit-div">
|
||||
|
||||
</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:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.ts" linenums="false" title="src/app/hero-form.component.ts (submitted)" region="submitted">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" title="src/app/hero-form/hero-form.component.ts (submitted)" region="submitted">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
When you click the *Submit* button, the `submitted` flag becomes true and the form disappears
|
||||
as planned.
|
||||
|
||||
Now the app needs to show something else while the form is in the submitted state.
|
||||
Add the following HTML below the `<div>` wrapper you just wrote:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="submitted">
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="submitted">
|
||||
|
||||
</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.
|
||||
|
||||
@ -924,9 +732,7 @@ that clears the `submitted` flag.
|
||||
|
||||
When you click the *Edit* button, this block disappears and the editable form reappears.
|
||||
|
||||
|
||||
|
||||
## Conclusion
|
||||
## Summary
|
||||
|
||||
The Angular form discussed in this page takes advantage of the following
|
||||
framework features to provide support for data modification, validation, and more:
|
||||
@ -941,89 +747,15 @@ framework features to provide support for data modification, validation, and mor
|
||||
* 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:
|
||||
|
||||
|
||||
<div class='filetree'>
|
||||
|
||||
<div class='file'>
|
||||
angular-forms
|
||||
</div>
|
||||
|
||||
<div class='children'>
|
||||
|
||||
<div class='file'>
|
||||
src
|
||||
</div>
|
||||
|
||||
<div class='children'>
|
||||
|
||||
<div class='file'>
|
||||
app
|
||||
</div>
|
||||
|
||||
<div class='children'>
|
||||
|
||||
<div class='file'>
|
||||
app.component.ts
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
app.module.ts
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
hero.ts
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
hero-form.component.html
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
hero-form.component.ts
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
main.ts
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
tsconfig.json
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
index.html
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
node_modules ...
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
package.json
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
Here’s the code for the final version of the application:
|
||||
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="hero-form.component.ts" path="forms/src/app/hero-form.component.ts" region="final">
|
||||
<code-pane title="hero-form/hero-form.component.ts" path="forms/src/app/hero-form/hero-form.component.ts" region="final">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="hero-form.component.html" path="forms/src/app/hero-form.component.html" region="final">
|
||||
<code-pane title="hero-form/hero-form.component.html" path="forms/src/app/hero-form/hero-form.component.html" region="final">
|
||||
|
||||
</code-pane>
|
||||
|
||||
@ -1035,6 +767,10 @@ Here’s the code for the final version of the application:
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app.component.html" path="forms/src/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app.component.ts" path="forms/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
@ -1043,10 +779,6 @@ Here’s the code for the final version of the application:
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="index.html" path="forms/src/index.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="forms.css" path="forms/src/assets/forms.css">
|
||||
|
||||
</code-pane>
|
||||
|
@ -132,7 +132,6 @@ You launch an Angular application by "bootstrapping" it using the application ro
|
||||
|
||||
Bootstrapping identifies an application's top level "root" [component](guide/glossary#component),
|
||||
which is the first component that is loaded for the application.
|
||||
For more information, see the [Setup](guide/setup) page.
|
||||
|
||||
You can bootstrap multiple apps in the same `index.html`, each app with its own top-level root.
|
||||
|
||||
@ -150,6 +149,12 @@ camelCase is also known as *lower camel case* to distinguish it from *upper came
|
||||
In Angular documentation, "camelCase" always means *lower camel case*.
|
||||
|
||||
|
||||
## CLI
|
||||
|
||||
The Angular CLI is a `command line interface` tool that can create a project, add files, and perform a variety of ongoing development tasks such as testing, bundling, and deployment.
|
||||
|
||||
Learn more in the [Getting Started](guide/quickstart) guide.
|
||||
|
||||
{@a component}
|
||||
|
||||
|
||||
@ -496,7 +501,7 @@ is based on the [ES2015 module standard](http://www.2ality.com/2014/09/es6-modul
|
||||
An application that adheres to this standard requires a module loader to
|
||||
load modules on request and resolve inter-module dependencies.
|
||||
Angular doesn't include a module loader and doesn't have a preference
|
||||
for any particular third-party library (although most examples use SystemJS).
|
||||
for any particular third-party library.
|
||||
You can use any module library that conforms to the standard.
|
||||
|
||||
Modules are typically named after the file in which the exported thing is defined.
|
||||
|
@ -28,11 +28,11 @@ you already have projects running on your machine that use other versions of nod
|
||||
|
||||
Both `npm` and `yarn` install packages identified in a [**package.json**](https://docs.npmjs.com/files/package.json) file.
|
||||
|
||||
The CLI `ng new` command creates a default `packages.json` file for your project.
|
||||
This `packages.json` specifies _a starter set of packages_ that work well together and
|
||||
The CLI `ng new` command creates a default `package.json` file for your project.
|
||||
This `package.json` specifies _a starter set of packages_ that work well together and
|
||||
jointly support many common application scenarios.
|
||||
|
||||
You will add packages to `packages.json` as your application evolves.
|
||||
You will add packages to `package.json` as your application evolves.
|
||||
You may even remove some.
|
||||
|
||||
This guide focuses on the most important packages in the starter set.
|
||||
|
@ -128,11 +128,13 @@ Then you'll learn about the [Angular form classes](guide/reactive-forms#essentia
|
||||
|
||||
## Setup
|
||||
|
||||
Follow the steps in the [_Setup_ guide](guide/setup "Setup guide")
|
||||
for creating a new project folder (perhaps called `reactive-forms`)
|
||||
based on the _QuickStart seed_.
|
||||
Create a new project named <code>angular-reactive-forms</code>:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
ng new angular-reactive-forms
|
||||
|
||||
</code-example>
|
||||
|
||||
{@a data-model}
|
||||
|
||||
@ -140,15 +142,21 @@ based on the _QuickStart seed_.
|
||||
## Create a data model
|
||||
The focus of this guide is a reactive forms component that edits a hero.
|
||||
You'll need a `hero` class and some hero data.
|
||||
Create a new `data-model.ts` file in the `app` directory and copy the content below into it.
|
||||
|
||||
Using the CLI, generate a new class named `data-model`:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
ng generate class data-model
|
||||
|
||||
</code-example>
|
||||
|
||||
And copy the content below:
|
||||
|
||||
<code-example path="reactive-forms/src/app/data-model.ts" title="src/app/data-model.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The file exports two classes and two constants. The `Address`
|
||||
and `Hero` classes define the application _data model_.
|
||||
The `heroes` and `states` constants supply the test data.
|
||||
@ -159,32 +167,26 @@ The `heroes` and `states` constants supply the test data.
|
||||
|
||||
|
||||
## Create a _reactive forms_ component
|
||||
Make a new file called
|
||||
`hero-detail.component.ts` in the `app` directory and import these symbols:
|
||||
|
||||
Generate a new component named `HeroDetail`:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-1.component.ts" region="imports" title="src/app/hero-detail.component.ts" linenums="false">
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
ng generate component HeroDetail
|
||||
|
||||
</code-example>
|
||||
|
||||
And import:
|
||||
|
||||
|
||||
Now enter the `@Component` decorator that specifies the `HeroDetailComponent` metadata:
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="metadata" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-1.component.ts" region="import" title="src/app/hero-detail/hero-detail.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
Next, create an exported `HeroDetailComponent` class with a `FormControl`.
|
||||
Next, update the `HeroDetailComponent` class with a `FormControl`.
|
||||
`FormControl` is a directive that allows you to create and manage
|
||||
a `FormControl` instance directly.
|
||||
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-1.component.ts" region="v1" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-1.component.ts" region="v1" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -218,10 +220,10 @@ read the [Form Validation](guide/form-validation) guide.
|
||||
|
||||
## Create the template
|
||||
|
||||
Now create the component's template, `src/app/hero-detail.component.html`, with the following markup.
|
||||
Now update the component's template, with the following markup.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-1.component.html" region="simple-control" title="src/app/hero-detail.component.html" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-1.component.html" region="simple-control" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -255,13 +257,11 @@ It _styles_ the form but in no way impacts the logic of the form.
|
||||
The HeroDetailComponent template uses `formControlName`
|
||||
directive from the `ReactiveFormsModule`.
|
||||
|
||||
In this sample, you declare the `HeroDetailComponent` in the `AppModule`.
|
||||
Therefore, do the following three things in `app.module.ts`:
|
||||
Do the following two things in `app.module.ts`:
|
||||
|
||||
1. Use a JavaScript `import` statement to access
|
||||
the `ReactiveFormsModule` and the `HeroDetailComponent`.
|
||||
the `ReactiveFormsModule`.
|
||||
1. Add `ReactiveFormsModule` to the `AppModule`'s `imports` list.
|
||||
1. Add `HeroDetailComponent` to the declarations array.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/app.module.ts" region="v1" title="src/app/app.module.ts (excerpt)" linenums="false">
|
||||
@ -277,7 +277,7 @@ the `ReactiveFormsModule` and the `HeroDetailComponent`.
|
||||
## Display the _HeroDetailComponent_
|
||||
Revise the `AppComponent` template so it displays the `HeroDetailComponent`.
|
||||
|
||||
<code-example path="reactive-forms/src/app/app.component.1.ts" title="src/app/app.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/app.component.1.html" title="src/app/app.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -312,10 +312,10 @@ You'll learn more about these classes as you work through this guide.
|
||||
|
||||
### Style the app
|
||||
You used bootstrap CSS classes in the template HTML of both the `AppComponent` and the `HeroDetailComponent`.
|
||||
Add the `bootstrap` _CSS stylesheet_ to the head of `index.html`:
|
||||
Add the `bootstrap` _CSS stylesheet_ to the head of `styles.css`:
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/index.html" region="bootstrap" title="index.html" linenums="false">
|
||||
<code-example path="reactive-forms/src/styles.1.css" title="styles.css" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -340,7 +340,7 @@ This is simple to do. To add a `FormGroup`, add it to the imports section
|
||||
of `hero-detail.component.ts`:
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-2.component.ts" region="imports" title="src/app/hero-detail.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-2.component.ts" region="imports" title="src/app/hero-detail/hero-detail.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -349,7 +349,7 @@ of `hero-detail.component.ts`:
|
||||
In the class, wrap the `FormControl` in a `FormGroup` called `heroForm` as follows:
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-2.component.ts" region="v2" title="src/app/hero-detail.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-2.component.ts" region="v2" title="src/app/hero-detail/hero-detail.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -359,7 +359,7 @@ Now that you've made changes in the class, they need to be reflected in the
|
||||
template. Update `hero-detail.component.html` by replacing it with the following.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-2.component.html" region="basic-form" title="src/app/hero-detail.component.html" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-2.component.html" region="basic-form" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -418,7 +418,7 @@ To see the form model, add the following line after the
|
||||
closing `form` tag in the `hero-detail.component.html`:
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3.component.html" region="form-value-json" title="src/app/hero-detail.component.html" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-3.component.html" region="form-value-json" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -458,7 +458,7 @@ clutter by handling details of control creation for you.
|
||||
|
||||
To use `FormBuilder`, you need to import it into `hero-detail.component.ts`:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3a.component.ts" region="imports" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-3a.component.ts" region="imports" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -474,7 +474,7 @@ by following this plan:
|
||||
|
||||
The revised `HeroDetailComponent` looks like this:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3a.component.ts" region="v3a" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-3a.component.ts" region="v3a" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -497,7 +497,7 @@ demonstrates the simplicity of using `Validators.required` in reactive forms.
|
||||
|
||||
First, import the `Validators` symbol.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3.component.ts" region="imports" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-3.component.ts" region="imports" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -509,7 +509,7 @@ The first item is the initial value for `name`;
|
||||
the second is the required validator, `Validators.required`.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3.component.ts" region="required" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-3.component.ts" region="required" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -529,7 +529,7 @@ Configuring validation is harder in template-driven forms where you must wrap va
|
||||
Update the diagnostic message at the bottom of the template to display the form's validity status.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-3.component.html" region="form-value-json" title="src/app/hero-detail.component.html (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-3.component.html" region="form-value-json" title="src/app/hero-detail/hero-detail.component.html (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -564,7 +564,7 @@ A hero has an address, a super power and sometimes a sidekick too.
|
||||
The address has a state property. The user will select a state with a `<select>` box and you'll populate
|
||||
the `<option>` elements with states. So import `states` from `data-model.ts`.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-4.component.ts" region="imports" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-4.component.ts" region="imports" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -573,7 +573,7 @@ the `<option>` elements with states. So import `states` from `data-model.ts`.
|
||||
Declare the `states` property and add some address `FormControls` to the `heroForm` as follows.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-4.component.ts" region="v4" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-4.component.ts" region="v4" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -583,7 +583,7 @@ Then add corresponding markup in `hero-detail.component.html`
|
||||
within the `form` element.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-4.component.html" title="src/app/hero-detail.component.html" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-4.component.html" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -640,7 +640,7 @@ Let that be the parent `FormGroup`.
|
||||
Use `FormBuilder` again to create a child `FormGroup` that encapsulates the address controls;
|
||||
assign the result to a new `address` property of the parent `FormGroup`.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-5.component.ts" region="v5" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-5.component.ts" region="v5" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -657,7 +657,7 @@ To make this change visually obvious, slip in an `<h4>` header near the top with
|
||||
The new _address_ HTML looks like this:
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-5.component.html" region="add-group" title="src/app/hero-detail.component.html (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-5.component.html" region="add-group" title="src/app/hero-detail/hero-detail.component.html (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -691,7 +691,7 @@ page by adding the following to the template,
|
||||
immediately after the `{{form.value | json}}` interpolation as follows:
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-5.component.html" region="inspect-value" title="src/app/hero-detail.component.html" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-5.component.html" region="inspect-value" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -700,7 +700,7 @@ immediately after the `{{form.value | json}}` interpolation as follows:
|
||||
To get the state of a `FormControl` that’s inside a `FormGroup`, use dot notation to path to the control.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-5.component.html" region="inspect-child-control" title="src/app/hero-detail.component.html" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-5.component.html" region="inspect-child-control" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -851,7 +851,7 @@ Recall the definition of `Hero` in `data-model.ts`:
|
||||
Here, again, is the component's `FormGroup` definition.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-6.component.ts" region="hero-form-model" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-6.component.ts" region="hero-form-model" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -870,7 +870,7 @@ to the _form model_ with the `patchValue` and `setValue` methods.
|
||||
|
||||
Take a moment to refactor the _address_ `FormGroup` definition for brevity and clarity as follows:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="address-form-group" title="src/app/hero-detail-7.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-7.component.ts" region="address-form-group" title="src/app/hero-detail/hero-detail-7.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -878,7 +878,7 @@ Take a moment to refactor the _address_ `FormGroup` definition for brevity and c
|
||||
|
||||
Also be sure to update the import from `data-model` so you can reference the `Hero` and `Address` classes:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="import-address" title="src/app/hero-detail-7.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-7.component.ts" region="import-address" title="src/app/hero-detail/hero-detail-7.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -898,7 +898,7 @@ With **`setValue`**, you assign _every_ form control value _at once_
|
||||
by passing in a data object whose properties exactly match the _form model_ behind the `FormGroup`.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="set-value" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-7.component.ts" region="set-value" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -920,7 +920,7 @@ because its shape is similar to the component's `FormGroup` structure.
|
||||
You can only show the hero's first address and you must account for the possibility that the `hero` has no addresses at all.
|
||||
This explains the conditional setting of the `address` property in the data object argument:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="set-value-address" title="src/app/hero-detail-7.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-7.component.ts" region="set-value-address" title="src/app/hero-detail/hero-detail-7.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -932,7 +932,7 @@ by supplying an object of key/value pairs for just the controls of interest.
|
||||
|
||||
This example sets only the form's `name` control.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-6.component.ts" region="patch-value" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-6.component.ts" region="patch-value" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -953,7 +953,7 @@ When the user clicks on a hero, the list component passes the selected hero into
|
||||
by binding to its `hero` input property.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-list.component.1.html" title="hero-list.component.html (simplified)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-list/hero-list.component.1.html" title="hero-list.component.html (simplified)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -968,7 +968,7 @@ as the following steps demonstrate.
|
||||
First, import the `OnChanges` and `Input` symbols in `hero-detail.component.ts`.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-6.component.ts" region="import-input" title="src/app/hero-detail.component.ts (core imports)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-6.component.ts" region="import-input" title="src/app/hero-detail/hero-detail.component.ts (core imports)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -976,7 +976,7 @@ First, import the `OnChanges` and `Input` symbols in `hero-detail.component.ts`.
|
||||
|
||||
Add the `hero` input property.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-6.component.ts" region="hero" title="src/app/hero-detail-6.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-6.component.ts" region="hero" title="src/app/hero-detail/hero-detail-6.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -985,7 +985,7 @@ Add the `hero` input property.
|
||||
Add the `ngOnChanges` method to the class as follows:
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="ngOnChanges-1" title="src/app/hero-detail.component.ts (ngOnchanges)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-7.component.ts" region="ngOnChanges-1" title="src/app/hero-detail/hero-detail.component.ts (ngOnchanges)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -998,7 +998,7 @@ control values from the previous hero are cleared and
|
||||
status flags are restored to the _pristine_ state.
|
||||
You could call `reset` at the top of `ngOnChanges` like this.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="reset" title="src/app/hero-detail-7.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-7.component.ts" region="reset" title="src/app/hero-detail/hero-detail-7.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1008,7 +1008,7 @@ The `reset` method has an optional `state` value so you can reset the flags _and
|
||||
Internally, `reset` passes the argument to `setValue`.
|
||||
A little refactoring and `ngOnChanges` becomes this:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="ngOnChanges" title="src/app/hero-detail.component.ts (ngOnchanges - revised)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-7.component.ts" region="ngOnChanges" title="src/app/hero-detail/hero-detail.component.ts (ngOnchanges - revised)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1049,7 +1049,7 @@ The techniques involved are covered elsewhere in the documentation, including th
|
||||
[here](tutorial/toh-pt3 "ToH: Multiple Components") and [here](tutorial/toh-pt4 "ToH: Services").
|
||||
|
||||
If you're coding along with the steps in this reactive forms tutorial,
|
||||
create the pertinent files based on the
|
||||
generate the pertinent files based on the
|
||||
[source code displayed below](guide/reactive-forms#source-code "Reactive Forms source code").
|
||||
Notice that `hero-list.component.ts` imports `Observable` and `finally` while `hero.service.ts` imports `Observable`, `of`,
|
||||
and `delay` from `rxjs`.
|
||||
@ -1073,7 +1073,7 @@ An Angular `FormArray` can display an array of _address_ `FormGroups`.
|
||||
|
||||
To get access to the `FormArray` class, import it into `hero-detail.component.ts`:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="imports" title="src/app/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-8.component.ts" region="imports" title="src/app/hero-detail/hero-detail.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1093,7 +1093,7 @@ let the user add or modify addresses (removing addresses is your homework).
|
||||
You’ll need to redefine the form model in the `HeroDetailComponent` constructor,
|
||||
which currently only displays the first hero address in an _address_ `FormGroup`.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-7.component.ts" region="address-form-group" title="src/app/hero-detail-7.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-7.component.ts" region="address-form-group" title="src/app/hero-detail/hero-detail-7.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1105,7 +1105,7 @@ From the user's point of view, heroes don't have _addresses_.
|
||||
_Addresses_ are for mere mortals. Heroes have _secret lairs_!
|
||||
Replace the _address_ `FormGroup` definition with a _secretLairs_ `FormArray` definition:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="secretLairs-form-array" title="src/app/hero-detail-8.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-8.component.ts" region="secretLairs-form-array" title="src/app/hero-detail/hero-detail-8.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1139,7 +1139,7 @@ the parent `HeroListComponent` sets the `HeroDetailComponent.hero` input propert
|
||||
The following `setAddresses` method replaces the _secretLairs_ `FormArray` with a new `FormArray`,
|
||||
initialized by an array of hero address `FormGroups`.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="set-addresses" title="src/app/hero-detail-8.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-8.component.ts" region="set-addresses" title="src/app/hero-detail/hero-detail-8.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1156,7 +1156,7 @@ The `HeroDetailComponent` should be able to display, add, and remove items from
|
||||
Use the `FormGroup.get` method to acquire a reference to that `FormArray`.
|
||||
Wrap the expression in a `secretLairs` convenience property for clarity and re-use.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="get-secret-lairs" title="src/app/hero-detail.component.ts (secretLayers property)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-8.component.ts" region="get-secret-lairs" title="src/app/hero-detail/hero-detail.component.ts (secretLayers property)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1184,7 +1184,7 @@ You'll re-use that index to compose a unique label for each address.
|
||||
|
||||
Here's the skeleton for the _secret lairs_ section of the HTML template:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.html" region="form-array-skeleton" title="src/app/hero-detail.component.html (*ngFor)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-8.component.html" region="form-array-skeleton" title="src/app/hero-detail/hero-detail.component.html (*ngFor)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1192,7 +1192,7 @@ Here's the skeleton for the _secret lairs_ section of the HTML template:
|
||||
|
||||
Here's the complete template for the _secret lairs_ section:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.html" region="form-array" title="src/app/hero-detail.component.html (excerpt)">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-8.component.html" region="form-array" title="src/app/hero-detail/hero-detail.component.html (excerpt)">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1202,7 +1202,7 @@ Here's the complete template for the _secret lairs_ section:
|
||||
|
||||
Add an `addLair` method that gets the _secretLairs_ `FormArray` and appends a new _address_ `FormGroup` to it.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="add-lair" title="src/app/hero-detail.component.ts (addLair method)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-8.component.ts" region="add-lair" title="src/app/hero-detail/hero-detail.component.ts (addLair method)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1211,7 +1211,7 @@ Add an `addLair` method that gets the _secretLairs_ `FormArray` and appends a ne
|
||||
Place a button on the form so the user can add a new _secret lair_ and wire it to the component's `addLair` method.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.html" region="add-lair" title="src/app/hero-detail.component.html (addLair button)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-8.component.html" region="add-lair" title="src/app/hero-detail/hero-detail.component.html (addLair button)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1272,7 +1272,7 @@ You don't need to know much about RxJS `Observable` to monitor form control valu
|
||||
|
||||
Add the following method to log changes to the value of the _name_ `FormControl`.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="log-name-change" title="src/app/hero-detail.component.ts (logNameChange)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail.component.ts" region="log-name-change" title="src/app/hero-detail/hero-detail.component.ts (logNameChange)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1280,7 +1280,7 @@ Add the following method to log changes to the value of the _name_ `FormControl`
|
||||
|
||||
Call it in the constructor, after creating the form.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail-8.component.ts" region="ctor" title="src/app/hero-detail-8.component.ts" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-8.component.ts" region="ctor" title="src/app/hero-detail/hero-detail-8.component.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1289,7 +1289,7 @@ Call it in the constructor, after creating the form.
|
||||
The `logNameChange` method pushes name-change values into a `nameChangeLog` array.
|
||||
Display that array at the bottom of the component template with this `*ngFor` binding:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.html" region="name-change-log" title="src/app/hero-detail.component.html (Name change log)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail.component.html" region="name-change-log" title="src/app/hero-detail/hero-detail.component.html (Name change log)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1328,7 +1328,7 @@ In this sample application, when the user submits the form,
|
||||
the `HeroDetailComponent` will pass an instance of the hero _data model_
|
||||
to a save method on the injected `HeroService`.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="on-submit" title="src/app/hero-detail.component.ts (onSubmit)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail.component.ts" region="on-submit" title="src/app/hero-detail/hero-detail.component.ts (onSubmit)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1339,7 +1339,7 @@ So you create a new `hero` from a combination of original hero values (the `hero
|
||||
and deep copies of the changed form model values, using the `prepareSaveHero` helper.
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="prepare-save-hero" title="src/app/hero-detail.component.ts (prepareSaveHero)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail.component.ts" region="prepare-save-hero" title="src/app/hero-detail/hero-detail.component.ts (prepareSaveHero)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1368,7 +1368,7 @@ The user cancels changes and reverts the form to the original state by pressing
|
||||
|
||||
Reverting is easy. Simply re-execute the `ngOnChanges` method that built the _form model_ from the original, unchanged `hero` _data model_.
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.ts" region="revert" title="src/app/hero-detail.component.ts (revert)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail.component.ts" region="revert" title="src/app/hero-detail/hero-detail.component.ts (revert)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1377,7 +1377,7 @@ Reverting is easy. Simply re-execute the `ngOnChanges` method that built the _fo
|
||||
### Buttons
|
||||
Add the "Save" and "Revert" buttons near the top of the component's template:
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail.component.html" region="buttons" title="src/app/hero-detail.component.html (Save and Revert buttons)" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail.component.html" region="buttons" title="src/app/hero-detail/hero-detail.component.html (Save and Revert buttons)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -1395,9 +1395,7 @@ Try the <live-example plnkr="final" title="Reactive Forms (final) in Plunker"></
|
||||
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
This page covered:
|
||||
## Summary
|
||||
|
||||
* How to create a reactive form component and its corresponding template.
|
||||
* How to use `FormBuilder` to simplify coding a reactive form.
|
||||
@ -1417,6 +1415,10 @@ The key files of the final version are as follows:
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="src/app/app.component.html" path="reactive-forms/src/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/app.component.ts" path="reactive-forms/src/app/app.component.ts">
|
||||
|
||||
</code-pane>
|
||||
@ -1425,19 +1427,19 @@ The key files of the final version are as follows:
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/hero-detail.component.ts" path="reactive-forms/src/app/hero-detail.component.ts">
|
||||
<code-pane title="src/app/hero-detail/hero-detail.component.ts" path="reactive-forms/src/app/hero-detail/hero-detail.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/hero-detail.component.html" path="reactive-forms/src/app/hero-detail.component.html">
|
||||
<code-pane title="src/app/hero-detail/hero-detail.component.html" path="reactive-forms/src/app/hero-detail/hero-detail.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/hero-list.component.html" path="reactive-forms/src/app/hero-list.component.html">
|
||||
<code-pane title="src/app/hero-list/hero-list.component.html" path="reactive-forms/src/app/hero-list/hero-list.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="src/app/hero-list.component.ts" path="reactive-forms/src/app/hero-list.component.ts">
|
||||
<code-pane title="src/app/hero-list/hero-list.component.ts" path="reactive-forms/src/app/hero-list/hero-list.component.ts">
|
||||
|
||||
</code-pane>
|
||||
|
||||
|
@ -3492,7 +3492,7 @@ It will be there when the `CrisisDetailComponent` ask for it.
|
||||
|
||||
|
||||
|
||||
**Two critical points**
|
||||
**Three critical points**
|
||||
|
||||
1. The router's `Resolve` interface is optional.
|
||||
The `CrisisDetailResolver` doesn't inherit from a base class.
|
||||
|
@ -25,7 +25,7 @@ They shape or reshape the DOM's _structure_, typically by adding, removing, or m
|
||||
elements.
|
||||
|
||||
As with other directives, you apply a structural directive to a _host element_.
|
||||
The directive then does whatever it's supposed to do with that host element and its descendents.
|
||||
The directive then does whatever it's supposed to do with that host element and its descendants.
|
||||
|
||||
Structural directives are easy to recognize.
|
||||
An asterisk (*) precedes the directive attribute name as in this example.
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.5 KiB |
@ -25,7 +25,7 @@
|
||||
<!-- MAIN CONTENT -->
|
||||
<article>
|
||||
|
||||
<h1 class="no-toc" style="display: none"></h1>
|
||||
<h1 class="no-anchor no-toc" style="display: none"></h1>
|
||||
|
||||
<div class="home-rows">
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user