Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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(); }

Expand Down
Expand Up @@ -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({
Expand Down
10 changes: 9 additions & 1 deletion modules/angular2/src/core/forms/directives/control_container.ts
Expand Up @@ -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; }
}
Expand Up @@ -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;
}

Expand Down
Expand Up @@ -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({
Expand Down
27 changes: 27 additions & 0 deletions modules/angular2/src/core/forms/directives/form_interface.ts
Expand Up @@ -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;
}
6 changes: 2 additions & 4 deletions modules/angular2/src/core/forms/directives/ng_control.ts
Expand Up @@ -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;
Expand Down
79 changes: 49 additions & 30 deletions modules/angular2/src/core/forms/directives/ng_control_group.ts
Expand Up @@ -10,50 +10,60 @@ 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)}));

/**
* Creates and binds a control group to a DOM element.
*
* 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'
})
Expand All @@ -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); }
Expand Down
10 changes: 10 additions & 0 deletions modules/angular2/src/core/forms/directives/validators.ts
Expand Up @@ -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]
Expand Down
62 changes: 29 additions & 33 deletions modules/angular2/src/core/forms/form_builder.ts
Expand Up @@ -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]
* })
* });
* }
* }
*
* 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);
Expand All @@ -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);
Expand All @@ -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)) {
Expand Down