docs(forms): Document the rest of the forms module.

Closes #4437
This commit is contained in:
Alex Rickabaugh 2015-09-29 12:59:56 -07:00
parent 993b3d62de
commit ef23fe66a0
12 changed files with 198 additions and 73 deletions

View File

@ -2,6 +2,11 @@ import {AbstractControl} from '../model';
import {isPresent} from 'angular2/src/core/facade/lang';
import {unimplemented} from 'angular2/src/core/facade/exceptions';
/**
* Base class for control directives.
*
* Only used internally in the forms module.
*/
export abstract class AbstractControlDirective {
get control(): AbstractControl { return unimplemented(); }

View File

@ -15,7 +15,7 @@ const CHECKBOX_VALUE_ACCESSOR = CONST_EXPR(new Provider(
*
* ### Example
* ```
* <input type="checkbox" [ng-control]="rememberLogin">
* <input type="checkbox" ng-control="rememberLogin">
* ```
*/
@Directive({

View File

@ -2,12 +2,20 @@ import {Form} from './form_interface';
import {AbstractControlDirective} from './abstract_control_directive';
/**
* A directive that contains multiple {@link NgControl}.
* A directive that contains multiple {@link NgControl}s.
*
* Only used by the forms module.
*/
export class ControlContainer extends AbstractControlDirective {
name: string;
/**
* Get the form to which this container belongs.
*/
get formDirective(): Form { return null; }
/**
* Get the path to this container.
*/
get path(): string[] { return null; }
}

View File

@ -4,11 +4,25 @@ import {OpaqueToken} from 'angular2/src/core/di';
/**
* A bridge between a control and a native element.
*
* A `ControlValueAccessor` abstracts the operations of writing a new value to a
* DOM element representing an input control.
*
* Please see {@link DefaultValueAccessor} for more information.
*/
export interface ControlValueAccessor {
/**
* Write a new value to the element.
*/
writeValue(obj: any): void;
/**
* Set the function to be called when the control receives a change event.
*/
registerOnChange(fn: any): void;
/**
* Set the function to be called when the control receives a touch event.
*/
registerOnTouched(fn: any): void;
}

View File

@ -15,7 +15,7 @@ const DEFAULT_VALUE_ACCESSOR = CONST_EXPR(new Provider(
*
* ### Example
* ```
* <input type="text" [(ng-model)]="searchQuery">
* <input type="text" ng-control="searchQuery">
* ```
*/
@Directive({

View File

@ -8,11 +8,38 @@ import {Control, ControlGroup} from '../model';
* Only used by the forms module.
*/
export interface Form {
/**
* Add a control to this form.
*/
addControl(dir: NgControl): void;
/**
* Remove a control from this form.
*/
removeControl(dir: NgControl): void;
/**
* Look up the {@link Control} associated with a particular {@link NgControl}.
*/
getControl(dir: NgControl): Control;
/**
* Add a group of controls to this form.
*/
addControlGroup(dir: NgControlGroup): void;
/**
* Remove a group of controls from this form.
*/
removeControlGroup(dir: NgControlGroup): void;
/**
* Look up the {@link ControlGroup} associated with a particular {@link NgControlGroup}.
*/
getControlGroup(dir: NgControlGroup): ControlGroup;
/**
* Update the model for a particular control with a new value.
*/
updateModel(dir: NgControl, value: any): void;
}

View File

@ -5,11 +5,9 @@ import {unimplemented} from 'angular2/src/core/facade/exceptions';
/**
* A base class that all control directive extend.
* It binds a {@link Control} object to a DOM element.
*
* Used internally by Angular forms.
*/
// Cannot currently be abstract because it would contain
// an abstract method in the public API, and we cannot reflect
// on that in Dart due to https://github.com/dart-lang/sdk/issues/18721
// Also we don't have abstract setters, see https://github.com/Microsoft/TypeScript/issues/4669
export abstract class NgControl extends AbstractControlDirective {
name: string = null;
valueAccessor: ControlValueAccessor = null;

View File

@ -10,7 +10,7 @@ import {ControlGroup} from '../model';
import {Form} from './form_interface';
import {Validators, NG_VALIDATORS} from '../validators';
const controlGroupBinding =
const controlGroupProvider =
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgControlGroup)}));
/**
@ -18,42 +18,52 @@ const controlGroupBinding =
*
* This directive can only be used as a child of {@link NgForm} or {@link NgFormModel}.
*
* ### Example
* # Example ([live demo](http://plnkr.co/edit/7EJ11uGeaggViYM6T5nq?p=preview))
*
* In this example, we create the credentials and personal control groups.
* We can work with each group separately: check its validity, get its value, listen to its changes.
*
* ```
* ```typescript
* @Component({
* selector: "signup-comp",
* directives: [FORM_DIRECTIVES],
* template: `
* <form #f="form" (submit)='onSignUp(f.value)'>
* <div ng-control-group='credentials' #credentials="form">
* Login <input type='text' ng-control='login'>
* Password <input type='password' ng-control='password'>
* </div>
* <div *ng-if="!credentials.valid">Credentials are invalid</div>
*
* <div ng-control-group='personal'>
* Name <input type='text' ng-control='name'>
* </div>
* <button type='submit'>Sign Up!</button>
* </form>
* `})
* class SignupComp {
* onSignUp(value) {
* // value === {
* // personal: {name: 'some name'},
* // credentials: {login: 'some login', password: 'some password'}}
* }
* selector: 'my-app',
* directives: [FORM_DIRECTIVES],
* })
* @View({
* template: `
* <div>
* <h2>Angular2 Control &amp; ControlGroup Example</h2>
* <form #f="form">
* <div ng-control-group="name" #cg-name="form">
* <h3>Enter your name:</h3>
* <p>First: <input ng-control="first" required></p>
* <p>Middle: <input ng-control="middle"></p>
* <p>Last: <input ng-control="last" required></p>
* </div>
* <h3>Name value:</h3>
* <pre>{{valueOf(cgName)}}</pre>
* <p>Name is {{cgName?.control?.valid ? "valid" : "invalid"}}</p>
* <h3>What's your favorite food?</h3>
* <p><input ng-control="food"></p>
* <h3>Form value</h3>
* <pre>{{valueOf(f)}}</pre>
* </form>
* </div>
* `,
* directives: [FORM_DIRECTIVES]
* })
* export class App {
* valueOf(cg: NgControlGroup): string {
* if (cg.control == null) {
* return null;
* }
* return JSON.stringify(cg.control.value, null, 2);
* }
* }
* ```
*
* ```
* This example declares a control group for a user's name. The value and validation state of
* this group can be accessed separately from the overall form.
*/
@Directive({
selector: '[ng-control-group]',
bindings: [controlGroupBinding],
providers: [controlGroupProvider],
inputs: ['name: ng-control-group'],
exportAs: 'form'
})
@ -75,10 +85,19 @@ export class NgControlGroup extends ControlContainer implements OnInit,
onDestroy(): void { this.formDirective.removeControlGroup(this); }
/**
* Get the {@link ControlGroup} backing this binding.
*/
get control(): ControlGroup { return this.formDirective.getControlGroup(this); }
/**
* Get the path to this control group.
*/
get path(): string[] { return controlPath(this.name, this._parent); }
/**
* Get the {@link Form} to which this group belongs.
*/
get formDirective(): Form { return this._parent.formDirective; }
get validator(): Function { return Validators.compose(this._validators); }

View File

@ -29,6 +29,16 @@ export interface Validator { validate(c: modelModule.Control): {[key: string]: a
const REQUIRED_VALIDATOR =
CONST_EXPR(new Provider(NG_VALIDATORS, {useValue: Validators.required, multi: true}));
/**
* A Directive that adds the `required` validator to any controls marked with the
* `required` attribute, via the {@link NG_VALIDATORS} binding.
*
* # Example
*
* ```
* <input ng-control="fullName" required>
* ```
*/
@Directive({
selector: '[required][ng-control],[required][ng-form-control],[required][ng-model]',
providers: [REQUIRED_VALIDATOR]

View File

@ -7,63 +7,52 @@ import * as modelModule from './model';
/**
* Creates a form object from a user-specified configuration.
*
* ### Example
*
* ```
* import {Component, bootstrap} from 'angular2/angular2';
* import {FormBuilder, Validators, FORM_DIRECTIVES, ControlGroup} from 'angular2/core';
* ### Example ([live demo](http://plnkr.co/edit/ENgZo8EuIECZNensZCVr?p=preview))
*
* ```typescript
* @Component({
* selector: 'login-comp',
* viewProviders: [FormBuilder],
* selector: 'my-app',
* viewBindings: [FORM_BINDINGS]
* template: `
* <form [control-group]="loginForm">
* Login <input control="login">
*
* <div control-group="passwordRetry">
* Password <input type="password" control="password">
* Confirm password <input type="password" control="passwordConfirmation">
* <form [ng-form-model]="loginForm">
* <p>Login <input ng-control="login"></p>
* <div ng-control-group="passwordRetry">
* <p>Password <input type="password" ng-control="password"></p>
* <p>Confirm password <input type="password" ng-control="passwordConfirmation"></p>
* </div>
* </form>
* <h3>Form value:</h3>
* <pre>{{value}}</pre>
* `,
* directives: [FORM_DIRECTIVES]
* })
* class LoginComp {
* export class App {
* loginForm: ControlGroup;
*
* constructor(builder: FormBuilder) {
* this.loginForm = builder.group({
* login: ["", Validators.required],
*
* passwordRetry: builder.group({
* password: ["", Validators.required],
* passwordConfirmation: ["", Validators.required]
* })
* });
* }
*
* get value(): string {
* return JSON.stringify(this.loginForm.value, null, 2);
* }
* }
*
* bootstrap(LoginComp);
* ```
*
* This example creates a {@link ControlGroup} that consists of a `login` {@link Control}, and a
* nested {@link ControlGroup} that defines a `password` and a `passwordConfirmation`
* {@link Control}:
*
* ```
* var loginForm = builder.group({
* login: ["", Validators.required],
*
* passwordRetry: builder.group({
* password: ["", Validators.required],
* passwordConfirmation: ["", Validators.required]
* })
* });
*
* ```
*/
@Injectable()
export class FormBuilder {
/**
* Construct a new {@link ControlGroup} with the given map of configuration.
* Valid keys for the `extra` parameter map are `optionals` and `validator`.
*
* See the {@link ControlGroup} constructor for more details.
*/
group(controlsConfig: {[key: string]: any},
extra: {[key: string]: any} = null): modelModule.ControlGroup {
var controls = this._reduceControls(controlsConfig);
@ -77,6 +66,9 @@ export class FormBuilder {
}
}
/**
* Construct a new {@link Control} with the given `value` and `validator`.
*/
control(value: Object, validator: Function = null): modelModule.Control {
if (isPresent(validator)) {
return new modelModule.Control(value, validator);
@ -85,6 +77,10 @@ export class FormBuilder {
}
}
/**
* Construct an array of {@link Control}s from the given `controlsConfig` array of
* configuration, with the given optional `validator`.
*/
array(controlsConfig: any[], validator: Function = null): modelModule.ControlArray {
var controls = controlsConfig.map(c => this._createControl(c));
if (isPresent(validator)) {

View File

@ -291,23 +291,38 @@ export class ControlGroup extends AbstractControl {
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
}
/**
* Add a control to this group.
*/
addControl(name: string, control: AbstractControl): void {
this.controls[name] = control;
control.setParent(this);
}
/**
* Remove a control from this group.
*/
removeControl(name: string): void { StringMapWrapper.delete(this.controls, name); }
/**
* Mark the named control as non-optional.
*/
include(controlName: string): void {
StringMapWrapper.set(this._optionals, controlName, true);
this.updateValueAndValidity();
}
/**
* Mark the named control as optional.
*/
exclude(controlName: string): void {
StringMapWrapper.set(this._optionals, controlName, false);
this.updateValueAndValidity();
}
/**
* Check whether there is a control with the given name in the group.
*/
contains(controlName: string): boolean {
var c = StringMapWrapper.contains(this.controls, controlName);
return c && this._included(controlName);
@ -421,7 +436,7 @@ export class ControlArray extends AbstractControl {
}
/**
* Get the length of the control array.
* Length of the control array.
*/
get length(): number { return this.controls.length; }

View File

@ -5,22 +5,45 @@ import {OpaqueToken} from 'angular2/src/core/di';
import * as modelModule from './model';
/**
* Providers for validators to be used for {@link Control}s in a form.
*
* Provide this using `multi: true` to add validators.
*
* ### Example
*
* ```typescript
* var providers = [
* new Provider(NG_VALIDATORS, {useValue: myValidator, multi: true})
* ];
* ```
*/
export const NG_VALIDATORS: OpaqueToken = CONST_EXPR(new OpaqueToken("NgValidators"));
/**
* Provides a set of validators used by form controls.
*
* ### Example
* A validator is a function that processes a {@link Control} or collection of
* controls and returns a {@link StringMap} of errors. A null map means that
* validation has passed.
*
* ```
* # Example
*
* ```typescript
* var loginControl = new Control("", Validators.required)
* ```
*/
export class Validators {
/**
* Validator that requires controls to have a non-empty value.
*/
static required(control: modelModule.Control): {[key: string]: boolean} {
return isBlank(control.value) || control.value == "" ? {"required": true} : null;
}
/**
* Validator that requires controls to have a value of a minimum length.
*/
static minLength(minLength: number): Function {
return (control: modelModule.Control): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
@ -31,6 +54,9 @@ export class Validators {
};
}
/**
* Validator that requires controls to have a value of a maximum length.
*/
static maxLength(maxLength: number): Function {
return (control: modelModule.Control): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
@ -41,8 +67,15 @@ export class Validators {
};
}
/**
* No-op validator.
*/
static nullValidator(c: any): {[key: string]: boolean} { return null; }
/**
* Compose multiple validators into a single function that returns the union
* of the individual error maps.
*/
static compose(validators: Function[]): Function {
if (isBlank(validators)) return Validators.nullValidator;