diff --git a/packages/forms/src/directives/default_value_accessor.ts b/packages/forms/src/directives/default_value_accessor.ts index a9e0a8480e..ec93663b52 100644 --- a/packages/forms/src/directives/default_value_accessor.ts +++ b/packages/forms/src/directives/default_value_accessor.ts @@ -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('CompositionEventMode'); diff --git a/packages/forms/src/directives/ng_control_status.ts b/packages/forms/src/directives/ng_control_status.ts index 9138101731..735c1b5938 100644 --- a/packages/forms/src/directives/ng_control_status.ts +++ b/packages/forms/src/directives/ng_control_status.ts @@ -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({ diff --git a/packages/forms/src/directives/ng_form.ts b/packages/forms/src/directives/ng_form.ts index 4f3de9bb2d..e7a067bcd5 100644 --- a/packages/forms/src/directives/ng_form.ts +++ b/packages/forms/src/directives/ng_form.ts @@ -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 `
` 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 `` 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 `` + * To import the `FormsModule` but skip its usage in some forms, + * for example, to use native HTML5 validation, add the `ngNoForm` and the `` * tags won't create an `NgForm` directive. In reactive forms, using `ngNoForm` is * unnecessary because the `` 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); * * ``` * + * ### 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 + * + * + * + * ``` + * * @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 - *
- * - *
- * ``` + * **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 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 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 = 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; diff --git a/packages/forms/src/directives/ng_form_selector_warning.ts b/packages/forms/src/directives/ng_form_selector_warning.ts index 30d1767827..424f2e9de3 100644 --- a/packages/forms/src/directives/ng_form_selector_warning.ts +++ b/packages/forms/src/directives/ng_form_selector_warning.ts @@ -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'); diff --git a/packages/forms/src/directives/ng_model.ts b/packages/forms/src/directives/ng_model.ts index ae965f22cb..15ab847972 100644 --- a/packages/forms/src/directives/ng_model.ts +++ b/packages/forms/src/directives/ng_model.ts @@ -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 `
` 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 + * + * + * Show more options? + *
+ * + * ``` + * + * ### 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 + *
+ * + * + *
+ * + * ``` * * @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 - *
- * - * - *
- * - * ``` + * **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 `
` tag that control the display of the form, but don't contain form data. - * - * ```html - * - * - * Show more options? - *
- * - * ``` - * - * **updateOn**: Defaults to `'change'`. Defines the event upon which the form control - * value and validity will update. Also accepts `'blur'` and `'submit'`. - * - * ```html - * - * ``` + * **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); diff --git a/packages/forms/src/directives/ng_model_group.ts b/packages/forms/src/directives/ng_model_group.ts index b1e49b4b02..c7cabf035e 100644 --- a/packages/forms/src/directives/ng_model_group.ts +++ b/packages/forms/src/directives/ng_model_group.ts @@ -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 `
` tags). + * This directive can only be used as a child of `NgForm` (within `` 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; diff --git a/packages/forms/src/directives/reactive_directives/form_control_name.ts b/packages/forms/src/directives/reactive_directives/form_control_name.ts index bec7542dcf..b72c10a3a7 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_name.ts @@ -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) {