(docs) forms - code maintained by _examples
Includes template-syntax.jade clarification on attributes v properties
|
@ -0,0 +1 @@
|
|||
src/**/*.js
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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
|
|
@ -0,0 +1,11 @@
|
|||
// #docregion
|
||||
export class Hero {
|
||||
|
||||
constructor(
|
||||
public id: number,
|
||||
public name: string,
|
||||
public power: string,
|
||||
public alterEgo?: string
|
||||
) { }
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,9 @@
|
|||
/* #docregion */
|
||||
.ng-valid[required] {
|
||||
border-left: 5px solid #42A948; /* green */
|
||||
}
|
||||
|
||||
.ng-invalid {
|
||||
border-left: 5px solid #a94442; /* red */
|
||||
}
|
||||
/* #enddocregion */
|
|
@ -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",
|
||||
"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": {
|
||||
"title": "Pipes",
|
||||
"intro": "Pipes transform displayed values within a template"
|
||||
|
@ -28,11 +33,6 @@
|
|||
"title": "Template Syntax",
|
||||
"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": {
|
||||
"title": "Dependency Injection",
|
||||
|
|
|
@ -215,7 +215,7 @@ table
|
|||
That’s “HTML Plus”.
|
||||
|
||||
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>
|
||||
```
|
||||
|
@ -225,27 +225,52 @@ table
|
|||
|
||||
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.
|
||||
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.
|
||||
If we were trying to disable a button in static HTML, we would add the `disabled` attribute to it.
|
||||
To enable the button, we'd omit or remove the `disabled` attribtue.
|
||||
```
|
||||
<button>Enabled</button>
|
||||
<button disabled>Disabled</button>
|
||||
```
|
||||
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.
|
||||
.l-sub-section
|
||||
:markdown
|
||||
### HTML Attribute vs. DOM Property
|
||||
|
||||
The distinction between an HTML attribute and a DOM property is crucial to understanding how Angular binding works.
|
||||
|
||||
**Attributes are defined by HTML. Properties are defined by DOM (the Document Object Model).**
|
||||
|
||||
On the other hand, setting the button's `disabled` *property* via a data binding does exactly what we expect.
|
||||
```
|
||||
<button [disabled]="true">Disabled</button>
|
||||
<button [disabled]="false">Enabled</button>
|
||||
```
|
||||
* A few HTML attributes have 1:1 mapping to properties. `id` is one example.
|
||||
|
||||
* Some HTML attributes don't have corresponding properties. `colspan` is one example.
|
||||
|
||||
* 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.
|
||||
|
||||
**Template binding works with *properties* and *events*, not *attributes*.**
|
||||
|
|
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 28 KiB |