(docs) forms - code maintained by _examples
Includes template-syntax.jade clarification on attributes v properties
1
public/docs/_examples/forms/ts/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
src/**/*.js
|
22
public/docs/_examples/forms/ts/package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "angular2-getting-started",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"tsc": "tsc -p src -w",
|
||||||
|
"start": "live-server --open=src"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"angular2": "2.0.0-alpha.44",
|
||||||
|
"bootstrap": "^3.3.5",
|
||||||
|
"systemjs": "0.19.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"live-server": "^0.8.1",
|
||||||
|
"typescript": "^1.6.2"
|
||||||
|
}
|
||||||
|
}
|
12
public/docs/_examples/forms/ts/src/app/app.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// #docregion
|
||||||
|
import {bootstrap, Component} from 'angular2/angular2'
|
||||||
|
import {HeroFormComponent} from './hero-form.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: '<hero-form></hero-form>',
|
||||||
|
directives: [HeroFormComponent]
|
||||||
|
})
|
||||||
|
class AppComponent { }
|
||||||
|
|
||||||
|
bootstrap(AppComponent);
|
196
public/docs/_examples/forms/ts/src/app/hero-form.component.html
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<!--#docplaster
|
||||||
|
-->
|
||||||
|
<!-- #docregion final -->
|
||||||
|
<div class="container">
|
||||||
|
<!-- #docregion edit-div -->
|
||||||
|
<div [hidden]="submitted">
|
||||||
|
<h1>Hero Form</h1>
|
||||||
|
<!-- #docregion ng-submit -->
|
||||||
|
<form (ng-submit)="onSubmit()" #hf="form">
|
||||||
|
<!-- #enddocregion edit-div -->
|
||||||
|
<!-- #enddocregion ng-submit -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<!-- #docregion name-with-error-msg -->
|
||||||
|
<input type="text" class="form-control" required
|
||||||
|
[(ng-model)]="model.name"
|
||||||
|
ng-control="name" #name="form" >
|
||||||
|
<div [hidden]="name.valid" class="alert alert-danger">
|
||||||
|
Name is required
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion name-with-error-msg -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="alterEgo">Alter Ego</label>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
[(ng-model)]="model.alterEgo"
|
||||||
|
ng-control="alterEgo" >
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="power">Hero Power</label>
|
||||||
|
<select class="form-control" required
|
||||||
|
[(ng-model)]="model.power"
|
||||||
|
ng-control="power" #power="form" >
|
||||||
|
<option *ng-for="#p of powers" [value]="p">{{p}}</option>
|
||||||
|
</select>
|
||||||
|
<div [hidden]="power.valid" class="alert alert-danger">
|
||||||
|
Power is required
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- #docregion submit-button -->
|
||||||
|
<button type="submit" class="btn btn-default"
|
||||||
|
[disabled]="!hf.form.valid">Submit</button>
|
||||||
|
<!-- #enddocregion submit-button -->
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- #docregion submitted -->
|
||||||
|
<div [hidden]="!submitted">
|
||||||
|
<h2>You submitted the following:</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3">Name</div>
|
||||||
|
<div class="col-xs-9 pull-left">{{ model.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3">Alter Ego</div>
|
||||||
|
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3">Power</div>
|
||||||
|
<div class="col-xs-9 pull-left">{{ model.power }}</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<button class="btn btn-default" (click)="submitted=false">Edit</button>
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion submitted -->
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion final -->
|
||||||
|
|
||||||
|
<!-- ==================================================== -->
|
||||||
|
<div>
|
||||||
|
<form>
|
||||||
|
<!-- #docregion edit-div -->
|
||||||
|
|
||||||
|
<!-- ... all of the form ... -->
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion edit-div -->
|
||||||
|
|
||||||
|
<!-- ==================================================== -->
|
||||||
|
<hr>
|
||||||
|
<style>
|
||||||
|
.no-style .ng-valid {
|
||||||
|
border-left: 1px solid #CCC
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-style .ng-invalid {
|
||||||
|
border-left: 1px solid #CCC
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="no-style" style="margin-left: 4px">
|
||||||
|
<!-- #docregion start -->
|
||||||
|
<div class="container">
|
||||||
|
<h1>Hero Form</h1>
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="alterEgo">Alter Ego</label>
|
||||||
|
<input type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- #enddocregion start -->
|
||||||
|
<!-- #docregion powers -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="power">Hero Power</label>
|
||||||
|
<select class="form-control" required>
|
||||||
|
<option *ng-for="#p of powers" [value]="p">{{p}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- #enddocregion powers -->
|
||||||
|
<!-- #docregion start -->
|
||||||
|
<button type="submit" class="btn btn-default">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion start -->
|
||||||
|
<!-- #enddocregion phase1-->
|
||||||
|
|
||||||
|
<!-- ==================================================== -->
|
||||||
|
<hr>
|
||||||
|
<!-- #docregion phase2-->
|
||||||
|
<div class="container">
|
||||||
|
<h1>Hero Form</h1>
|
||||||
|
<form>
|
||||||
|
<!-- #docregion ng-model-2-->
|
||||||
|
{{diagnostic}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" class="form-control" required
|
||||||
|
[(ng-model)]="model.name" >
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="alterEgo">Alter Ego</label>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
[(ng-model)]="model.alterEgo">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="power">Hero Power</label>
|
||||||
|
<select class="form-control" required
|
||||||
|
[(ng-model)]="model.power" >
|
||||||
|
<option *ng-for="#p of powers" [value]="p">{{p}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- #enddocregion ng-model-2-->
|
||||||
|
<button type="submit" class="btn btn-default">Submit</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion phase2-->
|
||||||
|
|
||||||
|
<!-- EXTRA MATERIAL FOR DOCUMENTATION -->
|
||||||
|
<hr>
|
||||||
|
<!-- #docregion ng-model-1-->
|
||||||
|
<input type="text" class="form-control" required
|
||||||
|
[(ng-model)]="model.name" >
|
||||||
|
TODO: remove this: {{model.name}}
|
||||||
|
<!-- #enddocregion ng-model-1-->
|
||||||
|
<hr>
|
||||||
|
<!-- #docregion ng-model-3-->
|
||||||
|
<input type="text" class="form-control" required
|
||||||
|
[ng-model]="model.name"
|
||||||
|
(ng-model-change)="model.name = $event" >
|
||||||
|
TODO: remove this: {{model.name}}
|
||||||
|
<!-- #enddocregion ng-model-3-->
|
||||||
|
<hr>
|
||||||
|
<form>
|
||||||
|
<!-- #docregion ng-control-1 -->
|
||||||
|
<input type="text" class="form-control" required
|
||||||
|
[(ng-model)]="model.name"
|
||||||
|
ng-control="name" >
|
||||||
|
<!-- #enddocregion ng-control-1 -->
|
||||||
|
<hr>
|
||||||
|
<!-- #docregion ng-control-2 -->
|
||||||
|
<input type="text" class="form-control" required
|
||||||
|
[(ng-model)]="model.name"
|
||||||
|
ng-control="name" #spy >
|
||||||
|
TODO: remove this: {{spy.className}}
|
||||||
|
<!-- #enddocregion ng-control-2 -->
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<hr>
|
||||||
|
Name via form.controls = {{showFormControls(hf)}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,48 @@
|
|||||||
|
// #docplaster
|
||||||
|
// #docregion
|
||||||
|
// #docregion first, final
|
||||||
|
import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2';
|
||||||
|
|
||||||
|
import { Hero } from './hero';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hero-form',
|
||||||
|
templateUrl: 'app/hero-form.component.html',
|
||||||
|
// #docregion directives
|
||||||
|
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
|
||||||
|
// #enddocregion
|
||||||
|
})
|
||||||
|
export class HeroFormComponent {
|
||||||
|
|
||||||
|
powers = ['Really Smart', 'Super Flexible',
|
||||||
|
'Super Hot', 'Weather Changer'];
|
||||||
|
|
||||||
|
model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');
|
||||||
|
|
||||||
|
// #docregion submitted
|
||||||
|
submitted = false;
|
||||||
|
|
||||||
|
onSubmit() { this.submitted = true; }
|
||||||
|
// #enddocregion submitted
|
||||||
|
|
||||||
|
// #enddocregion final
|
||||||
|
// TODO: Remove this when we're done
|
||||||
|
get diagnostic() { return JSON.stringify(this.model); }
|
||||||
|
// #enddocregion first
|
||||||
|
|
||||||
|
|
||||||
|
//////// DO NOT SHOW IN DOCS ////////
|
||||||
|
|
||||||
|
// Reveal in html:
|
||||||
|
// AlterEgo via form.controls = {{showFormControls(hf)}}
|
||||||
|
showFormControls(form){
|
||||||
|
return form.controls.alterEgo &&
|
||||||
|
// #docregion form-controls
|
||||||
|
form.controls.name.value; // Dr. IQ
|
||||||
|
// #enddocregion form-controls
|
||||||
|
}
|
||||||
|
/////////////////////////////
|
||||||
|
|
||||||
|
// #docregion first, final
|
||||||
|
}
|
||||||
|
// #enddocregion first, final
|
11
public/docs/_examples/forms/ts/src/app/hero.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// #docregion
|
||||||
|
export class Hero {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public id: number,
|
||||||
|
public name: string,
|
||||||
|
public power: string,
|
||||||
|
public alterEgo?: string
|
||||||
|
) { }
|
||||||
|
|
||||||
|
}
|
30
public/docs/_examples/forms/ts/src/index.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- #docregion -->
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Hero Form</title>
|
||||||
|
<!-- #docregion bootstrap -->
|
||||||
|
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||||
|
<!-- #enddocregion bootstrap -->
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
|
||||||
|
<!-- #docregion libraries -->
|
||||||
|
<script src="../node_modules/systemjs/dist/system.src.js"></script>
|
||||||
|
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||||
|
<!-- #enddocregion libraries -->
|
||||||
|
<!-- #docregion systemjs -->
|
||||||
|
<script>
|
||||||
|
System.config({
|
||||||
|
packages: {'app': {defaultExtension: 'js'}}
|
||||||
|
});
|
||||||
|
System.import('app/app');
|
||||||
|
</script>
|
||||||
|
<!-- #enddocregion systemjs -->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<my-app>Loading...</my-app>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
9
public/docs/_examples/forms/ts/src/styles.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* #docregion */
|
||||||
|
.ng-valid[required] {
|
||||||
|
border-left: 5px solid #42A948; /* green */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ng-invalid {
|
||||||
|
border-left: 5px solid #a94442; /* red */
|
||||||
|
}
|
||||||
|
/* #enddocregion */
|
11
public/docs/_examples/forms/ts/src/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES5",
|
||||||
|
"module": "commonjs",
|
||||||
|
"sourceMap": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"noImplicitAny": false
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,12 @@
|
|||||||
"title": "User Input",
|
"title": "User Input",
|
||||||
"intro": "User input triggers DOM events. We listen to those events with EventBindings that funnel updated values back into our components and models."
|
"intro": "User input triggers DOM events. We listen to those events with EventBindings that funnel updated values back into our components and models."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"forms": {
|
||||||
|
"title": "Forms",
|
||||||
|
"intro": "A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors."
|
||||||
|
},
|
||||||
|
|
||||||
"pipes": {
|
"pipes": {
|
||||||
"title": "Pipes",
|
"title": "Pipes",
|
||||||
"intro": "Pipes transform displayed values within a template"
|
"intro": "Pipes transform displayed values within a template"
|
||||||
@ -28,11 +33,6 @@
|
|||||||
"title": "Template Syntax",
|
"title": "Template Syntax",
|
||||||
"intro": "How to write templates that display data and consume user events with the help of data binding."
|
"intro": "How to write templates that display data and consume user events with the help of data binding."
|
||||||
},
|
},
|
||||||
|
|
||||||
"forms": {
|
|
||||||
"title": "Angular 2 Forms",
|
|
||||||
"intro": "Learn about the different approaches we can take when building forms and see examples of them in action."
|
|
||||||
},
|
|
||||||
|
|
||||||
"dependency-injection": {
|
"dependency-injection": {
|
||||||
"title": "Dependency Injection",
|
"title": "Dependency Injection",
|
||||||
|
@ -215,7 +215,7 @@ table
|
|||||||
That’s “HTML Plus”.
|
That’s “HTML Plus”.
|
||||||
|
|
||||||
Now we are learning about data binding. At first glance it appears that we are setting attributes,
|
Now we are learning about data binding. At first glance it appears that we are setting attributes,
|
||||||
as when we bind button `disabled` to the component’s `isUnchanged` data property:
|
as when we bind button `disabled` to the component’s `isUnchanged` property:
|
||||||
```
|
```
|
||||||
<button [disabled]="isUnchanged">Save</button>
|
<button [disabled]="isUnchanged">Save</button>
|
||||||
```
|
```
|
||||||
@ -225,27 +225,52 @@ table
|
|||||||
|
|
||||||
Our intuition is wrong! Our everyday HTML mental model is misleading us.
|
Our intuition is wrong! Our everyday HTML mental model is misleading us.
|
||||||
In fact, once we start data binding, we are no longer working with HTML *attributes*. We aren't setting attributes.
|
In fact, once we start data binding, we are no longer working with HTML *attributes*. We aren't setting attributes.
|
||||||
We are setting the *properties* of elements, components, and directives.
|
We are setting the *properties* of DOM elements, Components, and Directives.
|
||||||
|
|
||||||
The disabled button example may help make this distinction clear.
|
.l-sub-section
|
||||||
If we were trying to disable a button in static HTML, we would add the `disabled` attribute to it.
|
:markdown
|
||||||
To enable the button, we'd omit or remove the `disabled` attribtue.
|
### HTML Attribute vs. DOM Property
|
||||||
```
|
|
||||||
<button>Enabled</button>
|
The distinction between an HTML attribute and a DOM property is crucial to understanding how Angular binding works.
|
||||||
<button disabled>Disabled</button>
|
|
||||||
```
|
**Attributes are defined by HTML. Properties are defined by DOM (the Document Object Model).**
|
||||||
We'd get a pretty surprising result if we tried to set the `disabled` attribute.
|
|
||||||
```
|
|
||||||
<button disabled=false>Still disabled<button>
|
|
||||||
```
|
|
||||||
Say what!?!? Setting the `disabled` attribute to false does not enable the button.
|
|
||||||
In fact, any mention of the `disabled` attribute disables the button, regardless of the value.
|
|
||||||
|
|
||||||
On the other hand, setting the button's `disabled` *property* via a data binding does exactly what we expect.
|
* A few HTML attributes have 1:1 mapping to properties. `id` is one example.
|
||||||
```
|
|
||||||
<button [disabled]="true">Disabled</button>
|
* Some HTML attributes don't have corresponding properties. `colspan` is one example.
|
||||||
<button [disabled]="false">Enabled</button>
|
|
||||||
```
|
* Some DOM properties don't have corresponding attributes. `textContent` is one example.
|
||||||
|
|
||||||
|
* Many HTML attributes appear to map to properties ... but not the way we think!
|
||||||
|
|
||||||
|
That last category can be especially confusing ... until we understand this general rule:
|
||||||
|
|
||||||
|
**Attributes *initialize* DOM properties and then they are done.
|
||||||
|
Property values may change; attribute values don't.**
|
||||||
|
|
||||||
|
For example, when the browser renders `<input type="text" value="Bob">`, it creates a
|
||||||
|
corresponding DOM node with a `value` property *initialized* to "Bob".
|
||||||
|
|
||||||
|
When the user enters "Sally" into the input box, the DOM element `value` *property* becomes "Sally".
|
||||||
|
But the HTML `value` *attribute* remains unchanged as we discover if we ask the input element
|
||||||
|
about that attribute: `input.getAttribute('value') // returns "Bob"`
|
||||||
|
|
||||||
|
The HTML attribute `value` specifies the *initial* value; the DOM `value` property is the *current* value.
|
||||||
|
|
||||||
|
The `disabled` attribute is another peculiar example. A button's `disabled` *property* is
|
||||||
|
`false` by default so the button is enabled.
|
||||||
|
When we add the `disabled` *attribute*, it's presence alone initializes the button's `disabled` *property* to `true`
|
||||||
|
so the button is disabled.
|
||||||
|
|
||||||
|
Adding and removing the `disabled` *attribute* disables and enables the button. The value of the *attribute* is irrelevant
|
||||||
|
which is why we cannot enable a button by writing `<button disabled="false">Still Disabled</button>`.
|
||||||
|
|
||||||
|
Setting the button's `disabled` *property* (e.g. with an Angular binding) disables or enables the button.
|
||||||
|
The value of the *property* matters.
|
||||||
|
|
||||||
|
**The HTML attribute and the DOM property are not the same thing even when they have the same name.**
|
||||||
|
|
||||||
|
:markdown
|
||||||
This is so important, we’ll say it again.
|
This is so important, we’ll say it again.
|
||||||
|
|
||||||
**Template binding works with *properties* and *events*, not *attributes*.**
|
**Template binding works with *properties* and *events*, not *attributes*.**
|
||||||
|
BIN
public/resources/images/devguide/forms/hf-1.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
public/resources/images/devguide/forms/hf-2.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
public/resources/images/devguide/forms/hf-3.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/resources/images/devguide/forms/hf-4.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
public/resources/images/devguide/forms/hf-5.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
public/resources/images/devguide/forms/name-required-error.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 62 KiB |
BIN
public/resources/images/devguide/forms/ng-model-in-action-2.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
public/resources/images/devguide/forms/ng-model-in-action.png
Normal file
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 28 KiB |