docs(forms): update API reference for common APIs and template-driven directives (#27033)

PR Close #27033
This commit is contained in:
Brandon Roberts 2018-11-09 14:11:21 -06:00 committed by Igor Minar
parent 2389a68ea3
commit c331fc6f0c
7 changed files with 290 additions and 91 deletions

View File

@ -26,7 +26,9 @@ function _isAndroid(): boolean {
}
/**
* Turn this mode on if you want form directives to buffer IME input until compositionend
* @description
* Provide this token to control if form directives buffer IME input until
* the "compositionend" event occurs.
* @publicApi
*/
export const COMPOSITION_BUFFER_MODE = new InjectionToken<boolean>('CompositionEventMode');

View File

@ -37,9 +37,15 @@ export const ngControlStatusHost = {
};
/**
* @description
* Directive automatically applied to Angular form controls that sets CSS classes
* based on control status. The following classes are applied as the properties
* become true:
* based on control status.
*
* @usageNotes
*
* ### CSS classes applied
*
* The following classes are applied as the properties become true:
*
* * ng-valid
* * ng-invalid
@ -49,8 +55,8 @@ export const ngControlStatusHost = {
* * ng-untouched
* * ng-touched
*
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
@Directive({selector: '[formControlName],[ngModel],[formControl]', host: ngControlStatusHost})
@ -59,11 +65,14 @@ export class NgControlStatus extends AbstractControlStatus {
}
/**
* @description
* Directive automatically applied to Angular form groups that sets CSS classes
* based on control status (valid/invalid/dirty/etc).
*
* @see `NgControlStatus`
*
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
@Directive({

View File

@ -27,34 +27,36 @@ const resolvedPromise = Promise.resolve(null);
/**
* @description
*
* Creates a top-level `FormGroup` instance and binds it to a form
* to track aggregate form value and validation status.
*
* As soon as you import the `FormsModule`, this directive becomes active by default on
* all `<form>` tags. You don't need to add a special selector.
*
* You can export the directive into a local template variable using `ngForm` as the key
* You optionally export the directive into a local template variable using `ngForm` as the key
* (ex: `#myForm="ngForm"`). This is optional, but useful. Many properties from the underlying
* `FormGroup` instance are duplicated on the directive itself, so a reference to it
* will give you access to the aggregate value and validity status of the form, as well as
* gives you access to the aggregate value and validity status of the form, as well as
* user interaction properties like `dirty` and `touched`.
*
* To register child controls with the form, you'll want to use `NgModel` with a
* `name` attribute. You can also use `NgModelGroup` if you'd like to create
* sub-groups within the form.
* To register child controls with the form, use `NgModel` with a `name`
* attribute. You may use `NgModelGroup` to create sub-groups within the form.
*
* You can listen to the directive's `ngSubmit` event to be notified when the user has
* triggered a form submission. The `ngSubmit` event will be emitted with the original form
* If necessary, listen to the directive's `ngSubmit` event to be notified when the user has
* triggered a form submission. The `ngSubmit` event emits the original form
* submission event.
*
* In template driven forms, all `<form>` tags are automatically tagged as `NgForm`.
* If you want to import the `FormsModule` but skip its usage in some forms,
* for example, to use native HTML5 validation, you can add `ngNoForm` and the `<form>`
* To import the `FormsModule` but skip its usage in some forms,
* for example, to use native HTML5 validation, add the `ngNoForm` and the `<form>`
* tags won't create an `NgForm` directive. In reactive forms, using `ngNoForm` is
* unnecessary because the `<form>` tags are inert. In that case, you would
* refrain from using the `formGroup` directive.
*
* @usageNotes
*
* ### Migrating from deprecated ngForm selector
*
* Support for using `ngForm` element selector has been deprecated in Angular v6 and will be removed
* in Angular v9.
*
@ -71,8 +73,23 @@ const resolvedPromise = Promise.resolve(null);
* <ng-form #myForm="ngForm">
* ```
*
* ### Listening for form submission
*
* The following example shows how to capture the form values from the "ngSubmit" event.
*
* {@example forms/ts/simpleForm/simple_form_example.ts region='Component'}
*
* ### Setting the update options
*
* The following example shows you how to change the "updateOn" option from its default using
* ngFormOptions.
*
* ```html
* <form [ngFormOptions]="{updateOn: 'blur'}">
* <input name="one" ngModel> <!-- this ngModel will update on blur -->
* </form>
* ```
*
* @ngModule FormsModule
* @publicApi
*/
@ -85,25 +102,33 @@ const resolvedPromise = Promise.resolve(null);
})
export class NgForm extends ControlContainer implements Form,
AfterViewInit {
/**
* @description
* Returns whether the form submission has been triggered.
*/
public readonly submitted: boolean = false;
private _directives: NgModel[] = [];
/**
* @description
* The `FormGroup` instance created for this form.
*/
form: FormGroup;
/**
* @description
* Event emitter for the "ngSubmit" event
*/
ngSubmit = new EventEmitter();
/**
* Options for the `NgForm` instance. Accepts the following properties:
* @description
* Tracks options for the `NgForm` instance.
*
* **updateOn**: Serves as the default `updateOn` value for all child `NgModels` below it
* (unless a child has explicitly set its own value for this in `ngModelOptions`).
* Potential values: `'change'` | `'blur'` | `'submit'`
*
* ```html
* <form [ngFormOptions]="{updateOn: 'blur'}">
* <input name="one" ngModel> <!-- this ngModel will update on blur -->
* </form>
* ```
* **updateOn**: Sets the default `updateOn` value for all child `NgModels` below it
* unless explicitly set by a child `NgModel` using `ngModelOptions`). Defaults to 'change'.
* Possible values: `'change'` | `'blur'` | `'submit'`.
*
*/
// TODO(issue/24571): remove '!'.
@ -117,16 +142,44 @@ export class NgForm extends ControlContainer implements Form,
new FormGroup({}, composeValidators(validators), composeAsyncValidators(asyncValidators));
}
/**
* @description
* Lifecycle method called after the view is initialized. For internal use only.
*/
ngAfterViewInit() { this._setUpdateStrategy(); }
/**
* @description
* The directive instance.
*/
get formDirective(): Form { return this; }
/**
* @description
* The internal `FormGroup` instance.
*/
get control(): FormGroup { return this.form; }
/**
* @description
* Returns an array representing the path to this group. Because this directive
* always lives at the top level of a form, it is always an empty array.
*/
get path(): string[] { return []; }
/**
* @description
* Returns a map of the controls in this group.
*/
get controls(): {[key: string]: AbstractControl} { return this.form.controls; }
/**
* @description
* Method that sets up the control directive in this group, re-calculates its value
* and validity, and adds the instance to the internal list of directives.
*
* @param dir The `NgModel` directive instance.
*/
addControl(dir: NgModel): void {
resolvedPromise.then(() => {
const container = this._findContainer(dir.path);
@ -138,8 +191,20 @@ export class NgForm extends ControlContainer implements Form,
});
}
/**
* @description
* Retrieves the `FormControl` instance from the provided `NgModel` directive.
*
* @param dir The `NgModel` directive instance.
*/
getControl(dir: NgModel): FormControl { return <FormControl>this.form.get(dir.path); }
/**
* @description
* Removes the `NgModel` instance from the internal list of directives
*
* @param dir The `NgModel` directive instance.
*/
removeControl(dir: NgModel): void {
resolvedPromise.then(() => {
const container = this._findContainer(dir.path);
@ -150,6 +215,12 @@ export class NgForm extends ControlContainer implements Form,
});
}
/**
* @description
* Adds a new `NgModelGroup` directive instance to the form.
*
* @param dir The `NgModelGroup` directive instance.
*/
addFormGroup(dir: NgModelGroup): void {
resolvedPromise.then(() => {
const container = this._findContainer(dir.path);
@ -160,6 +231,12 @@ export class NgForm extends ControlContainer implements Form,
});
}
/**
* @description
* Removes the `NgModelGroup` directive instance from the form.
*
* @param dir The `NgModelGroup` directive instance.
*/
removeFormGroup(dir: NgModelGroup): void {
resolvedPromise.then(() => {
const container = this._findContainer(dir.path);
@ -169,8 +246,20 @@ export class NgForm extends ControlContainer implements Form,
});
}
/**
* @description
* Retrieves the `FormGroup` for a provided `NgModelGroup` directive instance
*
* @param dir The `NgModelGroup` directive instance.
*/
getFormGroup(dir: NgModelGroup): FormGroup { return <FormGroup>this.form.get(dir.path); }
/**
* Sets the new value for the provided `NgControl` directive.
*
* @param dir The `NgControl` directive instance.
* @param value The new value for the directive's control.
*/
updateModel(dir: NgControl, value: any): void {
resolvedPromise.then(() => {
const ctrl = <FormControl>this.form.get(dir.path !);
@ -178,8 +267,21 @@ export class NgForm extends ControlContainer implements Form,
});
}
/**
* @description
* Sets the value for this `FormGroup`.
*
* @param value The new value
*/
setValue(value: {[key: string]: any}): void { this.control.setValue(value); }
/**
* @description
* Method called when the "submit" event is triggered on the form.
* Triggers the `ngSubmit` emitter to emit the "submit" event as its payload.
*
* @param $event The "submit" event object
*/
onSubmit($event: Event): boolean {
(this as{submitted: boolean}).submitted = true;
syncPendingControls(this.form, this._directives);
@ -187,8 +289,18 @@ export class NgForm extends ControlContainer implements Form,
return false;
}
/**
* @description
* Method called when the "reset" event is triggered on the form.
*/
onReset(): void { this.resetForm(); }
/**
* @description
* Resets the form to an initial value and resets its submitted status.
*
* @param value The new value for the form.
*/
resetForm(value: any = undefined): void {
this.form.reset(value);
(this as{submitted: boolean}).submitted = false;

View File

@ -10,7 +10,8 @@ import {Directive, Inject, InjectionToken, Optional} from '@angular/core';
import {TemplateDrivenErrors} from './template_driven_errors';
/**
* Token to provide to turn off the warning when using 'ngForm' deprecated selector.
* @description
* `InjectionToken` to provide to turn off the warning when using 'ngForm' deprecated selector.
*/
export const NG_FORM_SELECTOR_WARNING = new InjectionToken('NgFormSelectorWarning');

View File

@ -47,55 +47,83 @@ const resolvedPromise = Promise.resolve(null);
/**
* @description
*
* Creates a `FormControl` instance from a domain model and binds it
* to a form control element.
*
* The `FormControl` instance will track the value, user interaction, and
* validation status of the control and keep the view synced with the model. If used
* within a parent form, the directive will also register itself with the form as a child
* The `FormControl` instance tracks the value, user interaction, and
* validation status of the control and keeps the view synced with the model. If used
* within a parent form, the directive also registers itself with the form as a child
* control.
*
* This directive can be used by itself or as part of a larger form. All you need is the
* This directive is used by itself or as part of a larger form. Use the
* `ngModel` selector to activate it.
*
* It accepts a domain model as an optional `Input`. If you have a one-way binding
* to `ngModel` with `[]` syntax, changing the value of the domain model in the component
* class will set the value in the view. If you have a two-way binding with `[()]` syntax
* (also known as 'banana-box syntax'), the value in the UI will always be synced back to
* the domain model in your class as well.
* class sets the value in the view. If you have a two-way binding with `[()]` syntax
* (also known as 'banana-box syntax'), the value in the UI always syncs back to
* the domain model in your class.
*
* If you wish to inspect the properties of the associated `FormControl` (like
* validity state), you can also export the directive into a local template variable using
* `ngModel` as the key (ex: `#myVar="ngModel"`). You can then access the control using the
* directive's `control` property, but most properties you'll need (like `valid` and `dirty`)
* will fall through to the control anyway, so you can access them directly. You can see a
* full list of properties directly available in `AbstractControlDirective`.
* To inspect the properties of the associated `FormControl` (like validity state),
* export the directive into a local template variable using `ngModel` as the key (ex: `#myVar="ngModel"`).
* You then access the control using the directive's `control` property,
* but most properties used (like `valid` and `dirty`) fall through to the control anyway for direct access.
* See a full list of properties directly available in `AbstractControlDirective`.
*
* The following is an example of a simple standalone control using `ngModel`:
* @see `RadioControlValueAccessor`
* @see `SelectControlValueAccessor`
*
* @usageNotes
*
* ### Using ngModel on a standalone control
*
* The following examples show a simple standalone control using `ngModel`:
*
* {@example forms/ts/simpleNgModel/simple_ng_model_example.ts region='Component'}
*
* When using the `ngModel` within `<form>` tags, you'll also need to supply a `name` attribute
* so that the control can be registered with the parent form under that name.
*
* It's worth noting that in the context of a parent form, you often can skip one-way or
* two-way binding because the parent form will sync the value for you. You can access
* its properties by exporting it into a local template variable using `ngForm` (ex:
* `#f="ngForm"`). Then you can pass it where it needs to go on submit.
* In the context of a parent form, it's often unnecessary to include one-way or two-way binding,
* as the parent form syncs the value for you. You access its properties by exporting it into a
* local template variable using `ngForm` such as (`#f="ngForm"`). Use the variable where
* needed on form submission.
*
* If you do need to populate initial values into your form, using a one-way binding for
* `ngModel` tends to be sufficient as long as you use the exported form's value rather
* than the domain model's value on submit.
*
* ### Using ngModel within a form
*
* Take a look at an example of using `ngModel` within a form:
* The following example shows controls using `ngModel` within a form:
*
* {@example forms/ts/simpleForm/simple_form_example.ts region='Component'}
*
* ### Using a standalone ngModel within a group
*
* The following example shows you how to use a standalone ngModel control
* within a form. This controls the display of the form, but doesn't contain form data.
*
* To see `ngModel` examples with different form control types, see:
* ```html
* <form>
* <input name="login" ngModel placeholder="Login">
* <input type="checkbox" ngModel [ngModelOptions]="{standalone: true}"> Show more options?
* </form>
* <!-- form value: {login: ''} -->
* ```
*
* ### Setting the ngModel name attribute through options
*
* The following example shows you an alternate way to set the name attribute. The name attribute is used
* within a custom form component, and the name `@Input` property serves a different purpose.
*
* * Radio buttons: `RadioControlValueAccessor`
* * Selects: `SelectControlValueAccessor`
* ```html
* <form>
* <my-person-control name="Nancy" ngModel [ngModelOptions]="{name: 'user'}">
* </my-person-control>
* </form>
* <!-- form value: {user: ''} -->
* ```
*
* @ngModule FormsModule
* @publicApi
@ -110,55 +138,58 @@ export class NgModel extends NgControl implements OnChanges,
public readonly control: FormControl = new FormControl();
/** @internal */
_registered = false;
/**
* @description
* Internal reference to the view model value.
*/
viewModel: any;
/**
* @description
* Tracks the name bound to the directive. The parent form
* uses this name as a key to retrieve this control's value.
*/
// TODO(issue/24571): remove '!'.
@Input() name !: string;
/**
* @description
* Tracks whether the control is disabled.
*/
// TODO(issue/24571): remove '!'.
@Input('disabled') isDisabled !: boolean;
/**
* @description
* Tracks the value bound to this directive.
*/
@Input('ngModel') model: any;
/**
* Options object for this `ngModel` instance. You can configure the following properties:
* @description
* Tracks the configuration options for this `ngModel` instance.
*
* **name**: An alternative to setting the name attribute on the form control element.
* Sometimes, especially with custom form components, the name attribute might be used
* as an `@Input` property for a different purpose. In cases like these, you can configure
* the `ngModel` name through this option.
* **name**: An alternative to setting the name attribute on the form control element. See
* the [example](api/forms/NgModel#using-ngmodel-on-a-standalone-control) for using `NgModel`
* as a standalone control.
*
* ```html
* <form>
* <my-person-control name="Nancy" ngModel [ngModelOptions]="{name: 'user'}">
* </my-person-control>
* </form>
* <!-- form value: {user: ''} -->
* ```
* **standalone**: When set to true, the `ngModel` will not register itself with its parent form,
* and acts as if it's not in the form. Defaults to false.
*
* **standalone**: Defaults to false. If this is set to true, the `ngModel` will not
* register itself with its parent form, and will act as if it's not in the form. This
* can be handy if you have form meta-controls, a.k.a. form elements nested in
* the `<form>` tag that control the display of the form, but don't contain form data.
*
* ```html
* <form>
* <input name="login" ngModel placeholder="Login">
* <input type="checkbox" ngModel [ngModelOptions]="{standalone: true}"> Show more options?
* </form>
* <!-- form value: {login: ''} -->
* ```
*
* **updateOn**: Defaults to `'change'`. Defines the event upon which the form control
* value and validity will update. Also accepts `'blur'` and `'submit'`.
*
* ```html
* <input [(ngModel)]="firstName" [ngModelOptions]="{updateOn: 'blur'}">
* ```
* **updateOn**: Defines the event upon which the form control value and validity update.
* Defaults to 'change'. Possible values: `'change'` | `'blur'` | `'submit'`.
*
*/
// TODO(issue/24571): remove '!'.
@Input('ngModelOptions')
options !: {name?: string, standalone?: boolean, updateOn?: FormHooks};
/**
* @description
* Event emitter for producing the `ngModelChange` event after
* the view model updates.
*/
@Output('ngModelChange') update = new EventEmitter();
constructor(@Optional() @Host() parent: ControlContainer,
@ -173,6 +204,13 @@ export class NgModel extends NgControl implements OnChanges,
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
/**
* @description
* A lifecycle method called when the directive's inputs change. For internal use
* only.
*
* @param changes A object of key/value pairs for the set of changed inputs.
*/
ngOnChanges(changes: SimpleChanges) {
this._checkForErrors();
if (!this._registered) this._setUpControl();
@ -186,20 +224,50 @@ export class NgModel extends NgControl implements OnChanges,
}
}
/**
* @description
* Lifecycle method called before the directive's instance is destroyed. For internal
* use only.
*/
ngOnDestroy(): void { this.formDirective && this.formDirective.removeControl(this); }
/**
* @description
* Returns an array that represents the path from the top-level form to this control.
* Each index is the string name of the control on that level.
*/
get path(): string[] {
return this._parent ? controlPath(this.name, this._parent) : [this.name];
}
/**
* @description
* The top-level directive for this control if present, otherwise null.
*/
get formDirective(): any { return this._parent ? this._parent.formDirective : null; }
/**
* @description
* Synchronous validator function composed of all the synchronous validators
* registered with this directive.
*/
get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); }
/**
* @description
* Async validator function composed of all the async validators registered with this
* directive.
*/
get asyncValidator(): AsyncValidatorFn|null {
return composeAsyncValidators(this._rawAsyncValidators);
}
/**
* @description
* Sets the new value for the view model and emits an `ngModelChange` event.
*
* @param newValue The new value emitted by `ngModelChange`.
*/
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
this.update.emit(newValue);

View File

@ -22,21 +22,25 @@ export const modelGroupProvider: any = {
/**
* @description
*
* Creates and binds a `FormGroup` instance to a DOM element.
*
* This directive can only be used as a child of `NgForm` (or in other words,
* within `<form>` tags).
* This directive can only be used as a child of `NgForm` (within `<form>` tags).
*
* Use this directive if you'd like to create a sub-group within a form. This can
* come in handy if you want to validate a sub-group of your form separately from
* the rest of your form, or if some values in your domain model make more sense to
* consume together in a nested object.
* Use this directive to validate a sub-group of your form separately from the
* rest of your form, or if some values in your domain model make more sense
* to consume together in a nested object.
*
* Pass in the name you'd like this sub-group to have and it will become the key
* for the sub-group in the form's full value. You can also export the directive into
* Provide a name for the sub-group and it will become the key
* for the sub-group in the form's full value. If you need direct access, export the directive into
* a local template variable using `ngModelGroup` (ex: `#myGroup="ngModelGroup"`).
*
* @usageNotes
*
* ### Consuming controls in a grouping
*
* The following example shows you how to combine controls together in a sub-group
* of the form.
*
* {@example forms/ts/ngModelGroup/ng_model_group_example.ts region='Component'}
*
* @ngModule FormsModule
@ -44,6 +48,11 @@ export const modelGroupProvider: any = {
*/
@Directive({selector: '[ngModelGroup]', providers: [modelGroupProvider], exportAs: 'ngModelGroup'})
export class NgModelGroup extends AbstractFormGroupDirective implements OnInit, OnDestroy {
/**
* @description
* Tracks the name of the `NgModelGroup` bound to the directive. The name corresponds
* to a key in the parent `NgForm`.
*/
// TODO(issue/24571): remove '!'.
@Input('ngModelGroup') name !: string;

View File

@ -215,8 +215,6 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
/**
* @description
* Lifecycle method called before the directive's instance is destroyed. For internal use only.
*
* @param changes A object of key/value pairs for the set of changed inputs.
*/
ngOnDestroy(): void {
if (this.formDirective) {