From 8f178e5b74b5901783a5956bcda9bffa5758da7d Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 29 Sep 2015 12:59:56 -0700 Subject: [PATCH] docs(forms): Document the rest of the forms module. --- .../directives/abstract_control_directive.ts | 5 ++ .../directives/checkbox_value_accessor.ts | 2 +- .../forms/directives/control_container.ts | 10 ++- .../directives/control_value_accessor.ts | 14 ++++ .../directives/default_value_accessor.ts | 2 +- .../core/forms/directives/form_interface.ts | 27 +++++++ .../src/core/forms/directives/ng_control.ts | 6 +- .../core/forms/directives/ng_control_group.ts | 79 ++++++++++++------- .../src/core/forms/directives/validators.ts | 10 +++ .../angular2/src/core/forms/form_builder.ts | 62 +++++++-------- modules/angular2/src/core/forms/model.ts | 17 +++- modules/angular2/src/core/forms/validators.ts | 37 ++++++++- 12 files changed, 198 insertions(+), 73 deletions(-) diff --git a/modules/angular2/src/core/forms/directives/abstract_control_directive.ts b/modules/angular2/src/core/forms/directives/abstract_control_directive.ts index 05fe23bc0bc6e..85f898fef9d1c 100644 --- a/modules/angular2/src/core/forms/directives/abstract_control_directive.ts +++ b/modules/angular2/src/core/forms/directives/abstract_control_directive.ts @@ -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(); } diff --git a/modules/angular2/src/core/forms/directives/checkbox_value_accessor.ts b/modules/angular2/src/core/forms/directives/checkbox_value_accessor.ts index 7d6df788456b0..256b949531958 100644 --- a/modules/angular2/src/core/forms/directives/checkbox_value_accessor.ts +++ b/modules/angular2/src/core/forms/directives/checkbox_value_accessor.ts @@ -15,7 +15,7 @@ const CHECKBOX_VALUE_ACCESSOR = CONST_EXPR(new Provider( * * ### Example * ``` - * + * * ``` */ @Directive({ diff --git a/modules/angular2/src/core/forms/directives/control_container.ts b/modules/angular2/src/core/forms/directives/control_container.ts index ae37aab62a375..a84ad46430ee6 100644 --- a/modules/angular2/src/core/forms/directives/control_container.ts +++ b/modules/angular2/src/core/forms/directives/control_container.ts @@ -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; } } diff --git a/modules/angular2/src/core/forms/directives/control_value_accessor.ts b/modules/angular2/src/core/forms/directives/control_value_accessor.ts index 3ceba8f389dbb..9c0cf781da27e 100644 --- a/modules/angular2/src/core/forms/directives/control_value_accessor.ts +++ b/modules/angular2/src/core/forms/directives/control_value_accessor.ts @@ -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; } diff --git a/modules/angular2/src/core/forms/directives/default_value_accessor.ts b/modules/angular2/src/core/forms/directives/default_value_accessor.ts index 9c1d017a294e2..fa23a0870e72d 100644 --- a/modules/angular2/src/core/forms/directives/default_value_accessor.ts +++ b/modules/angular2/src/core/forms/directives/default_value_accessor.ts @@ -15,7 +15,7 @@ const DEFAULT_VALUE_ACCESSOR = CONST_EXPR(new Provider( * * ### Example * ``` - * + * * ``` */ @Directive({ diff --git a/modules/angular2/src/core/forms/directives/form_interface.ts b/modules/angular2/src/core/forms/directives/form_interface.ts index f23e26f7d3987..7e3f04651c810 100644 --- a/modules/angular2/src/core/forms/directives/form_interface.ts +++ b/modules/angular2/src/core/forms/directives/form_interface.ts @@ -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; } \ No newline at end of file diff --git a/modules/angular2/src/core/forms/directives/ng_control.ts b/modules/angular2/src/core/forms/directives/ng_control.ts index cc175b7bd821a..8ec16089a023c 100644 --- a/modules/angular2/src/core/forms/directives/ng_control.ts +++ b/modules/angular2/src/core/forms/directives/ng_control.ts @@ -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; diff --git a/modules/angular2/src/core/forms/directives/ng_control_group.ts b/modules/angular2/src/core/forms/directives/ng_control_group.ts index cec893902dd50..94ae2876ee674 100644 --- a/modules/angular2/src/core/forms/directives/ng_control_group.ts +++ b/modules/angular2/src/core/forms/directives/ng_control_group.ts @@ -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: ` - *
- *
- * Login - * Password - *
- *
Credentials are invalid
- * - *
- * Name - *
- * - *
- * `}) - * class SignupComp { - * onSignUp(value) { - * // value === { - * // personal: {name: 'some name'}, - * // credentials: {login: 'some login', password: 'some password'}} - * } + * selector: 'my-app', + * directives: [FORM_DIRECTIVES], + * }) + * @View({ + * template: ` + *
+ *

Angular2 Control & ControlGroup Example

+ *
+ *
+ *

Enter your name:

+ *

First:

+ *

Middle:

+ *

Last:

+ *
+ *

Name value:

+ *
{{valueOf(cgName)}}
+ *

Name is {{cgName?.control?.valid ? "valid" : "invalid"}}

+ *

What's your favorite food?

+ *

+ *

Form value

+ *
{{valueOf(f)}}
+ *
+ *
+ * `, + * 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); } diff --git a/modules/angular2/src/core/forms/directives/validators.ts b/modules/angular2/src/core/forms/directives/validators.ts index cfe33a44648c9..be1d7f8ab1553 100644 --- a/modules/angular2/src/core/forms/directives/validators.ts +++ b/modules/angular2/src/core/forms/directives/validators.ts @@ -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 + * + * ``` + * + * ``` + */ @Directive({ selector: '[required][ng-control],[required][ng-form-control],[required][ng-model]', providers: [REQUIRED_VALIDATOR] diff --git a/modules/angular2/src/core/forms/form_builder.ts b/modules/angular2/src/core/forms/form_builder.ts index 3553f750f37f5..f43b7832bb651 100644 --- a/modules/angular2/src/core/forms/form_builder.ts +++ b/modules/angular2/src/core/forms/form_builder.ts @@ -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: ` - *
- * Login - * - *
- * Password - * Confirm password + * + *

Login

+ *
+ *

Password

+ *

Confirm password

*
* + *

Form value:

+ *
{{value}}
* `, * 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] * }) * }); * } - * } - * - * 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}: * + * get value(): string { + * return JSON.stringify(this.loginForm.value, null, 2); + * } + * } * ``` - * 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)) { diff --git a/modules/angular2/src/core/forms/model.ts b/modules/angular2/src/core/forms/model.ts index 23a7b7a1d3e6b..8ecbb3afc2893 100644 --- a/modules/angular2/src/core/forms/model.ts +++ b/modules/angular2/src/core/forms/model.ts @@ -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; } diff --git a/modules/angular2/src/core/forms/validators.ts b/modules/angular2/src/core/forms/validators.ts index d93735fc6c297..bd5b4aaa2dd1a 100644 --- a/modules/angular2/src/core/forms/validators.ts +++ b/modules/angular2/src/core/forms/validators.ts @@ -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;