From 14ff340e931e6c8ed3b20fb1baab2bce77f1e39d Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Fri, 19 Oct 2018 13:24:01 -0500 Subject: [PATCH] docs(forms): update reactive form directives API reference (#26823) PR Close #26823 --- .../form_control_directive.ts | 85 +++++++---- .../reactive_directives/form_control_name.ts | 98 ++++++++----- .../form_group_directive.ts | 134 +++++++++++++++--- .../reactive_directives/form_group_name.ts | 118 +++++++++------ 4 files changed, 310 insertions(+), 125 deletions(-) diff --git a/packages/forms/src/directives/reactive_directives/form_control_directive.ts b/packages/forms/src/directives/reactive_directives/form_control_directive.ts index 9ee732b060..46a869f902 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_directive.ts @@ -30,38 +30,17 @@ export const formControlBinding: any = { /** * @description - * - * Syncs a standalone `FormControl` instance to a form control element. - * - * This directive ensures that any values written to the `FormControl` - * instance programmatically will be written to the DOM element (model -> view). Conversely, - * any values written to the DOM element through user input will be reflected in the - * `FormControl` instance (view -> model). + * * Syncs a standalone `FormControl` instance to a form control element. + * + * @see [Reactive Forms Guide](guide/reactive-forms) + * @see `FormControl` + * @see `AbstractControl` * * @usageNotes - * Use this directive if you'd like to create and manage a `FormControl` instance directly. - * Simply create a `FormControl`, save it to your component class, and pass it into the - * `FormControlDirective`. * - * This directive is designed to be used as a standalone control. Unlike `FormControlName`, - * it does not require that your `FormControl` instance be part of any parent - * `FormGroup`, and it won't be registered to any `FormGroupDirective` that - * exists above it. - * - * **Get the value**: the `value` property is always synced and available on the - * `FormControl` instance. See a full list of available properties in - * `AbstractControl`. - * - * **Set the value**: You can pass in an initial value when instantiating the `FormControl`, - * or you can set it programmatically later using {@link AbstractControl#setValue setValue} or - * {@link AbstractControl#patchValue patchValue}. - * - * **Listen to value**: If you want to listen to changes in the value of the control, you can - * subscribe to the {@link AbstractControl#valueChanges valueChanges} event. You can also listen to - * {@link AbstractControl#statusChanges statusChanges} to be notified when the validation status is - * re-calculated. - * - * ### Example + * ### Registering a single form control + * + * The following examples shows how to register a standalone control and set its value. * * {@example forms/ts/simpleFormControl/simple_form_control_example.ts region='Component'} * @@ -138,11 +117,23 @@ export const formControlBinding: any = { @Directive({selector: '[formControl]', providers: [formControlBinding], exportAs: 'ngForm'}) export class FormControlDirective extends NgControl implements OnChanges { + /** + * @description + * Internal reference to the view model value. + */ viewModel: any; + /** + * @description + * Tracks the `FormControl` instance bound to the directive. + */ // TODO(issue/24571): remove '!'. @Input('formControl') form !: FormControl; + /** + * @description + * Triggers a warning that this input should not be used with reactive forms. + */ @Input('disabled') set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); } @@ -155,6 +146,7 @@ export class FormControlDirective extends NgControl implements OnChanges { @Output('ngModelChange') update = new EventEmitter(); /** + * @description * Static property used to track whether any ngModel warnings have been sent across * all instances of FormControlDirective. Used to support warning config of "once". * @@ -163,8 +155,9 @@ export class FormControlDirective extends NgControl implements OnChanges { static _ngModelWarningSentOnce = false; /** + * @description * Instance property used to track whether an ngModel warning has been sent out for this - * particular FormControlDirective instance. Used to support warning config of "always". + * particular `FormControlDirective` instance. Used to support warning config of "always". * * @internal */ @@ -181,6 +174,13 @@ export class FormControlDirective 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): void { if (this._isControlChanged(changes)) { setUpControl(this.form, this); @@ -197,16 +197,41 @@ export class FormControlDirective extends NgControl implements OnChanges { } } + /** + * @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 []; } + /** + * @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 + * The `FormControl` bound to this directive. + */ get control(): FormControl { return this.form; } + /** + * @description + * Sets the new value for the view model and emits an `ngModelChange` event. + * + * @param newValue The new value for the view model. + */ viewToModelUpdate(newValue: any): void { this.viewModel = newValue; this.update.emit(newValue); 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 737f2e81e0..bec7542dcf 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_name.ts @@ -29,42 +29,19 @@ export const controlNameBinding: any = { /** * @description - * * Syncs a `FormControl` in an existing `FormGroup` to a form control * element by name. - * - * This directive ensures that any values written to the `FormControl` - * instance programmatically will be written to the DOM element (model -> view). Conversely, - * any values written to the DOM element through user input will be reflected in the - * `FormControl` instance (view -> model). + * + * @see [Reactive Forms Guide](guide/reactive-forms) + * @see `FormControl` + * @see `AbstractControl` * * @usageNotes - * This directive is designed to be used with a parent `FormGroupDirective` (selector: - * `[formGroup]`). + * + * ### Register `FormControl` within a group * - * It accepts the string name of the `FormControl` instance you want to - * link, and will look for a `FormControl` registered with that name in the - * closest `FormGroup` or `FormArray` above it. - * - * **Access the control**: You can access the `FormControl` associated with - * this directive by using the {@link AbstractControl#get get} method. - * Ex: `this.form.get('first');` - * - * **Get value**: the `value` property is always synced and available on the `FormControl`. - * See a full list of available properties in `AbstractControl`. - * - * **Set value**: You can set an initial value for the control when instantiating the - * `FormControl`, or you can set it programmatically later using - * {@link AbstractControl#setValue setValue} or {@link AbstractControl#patchValue patchValue}. - * - * **Listen to value**: If you want to listen to changes in the value of the control, you can - * subscribe to the {@link AbstractControl#valueChanges valueChanges} event. You can also listen to - * {@link AbstractControl#statusChanges statusChanges} to be notified when the validation status is - * re-calculated. - * - * ### Example - * - * In this example, we create form controls for first name and last name. + * The following example shows how to register multiple form controls within a form group + * and set their value. * * {@example forms/ts/simpleFormGroup/simple_form_group_example.ts region='Component'} * @@ -150,14 +127,32 @@ export const controlNameBinding: any = { @Directive({selector: '[formControlName]', providers: [controlNameBinding]}) export class FormControlName extends NgControl implements OnChanges, OnDestroy { private _added = false; - /** @internal */ + /** + * @description + * Internal reference to the view model value. + * @internal + */ viewModel: any; + + /** + * @description + * Tracks the `FormControl` instance bound to the directive. + */ // TODO(issue/24571): remove '!'. readonly control !: FormControl; + /** + * @description + * Tracks the name of the `FormControl` bound to the directive. The name corresponds + * to a key in the parent `FormGroup` or `FormArray`. + */ // TODO(issue/24571): remove '!'. @Input('formControlName') name !: string; + /** + * @description + * Triggers a warning that this input should not be used with reactive forms. + */ @Input('disabled') set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); } @@ -170,6 +165,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { @Output('ngModelChange') update = new EventEmitter(); /** + * @description * Static property used to track whether any ngModel warnings have been sent across * all instances of FormControlName. Used to support warning config of "once". * @@ -178,6 +174,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { static _ngModelWarningSentOnce = false; /** + * @description * Instance property used to track whether an ngModel warning has been sent out for this * particular FormControlName instance. Used to support warning config of "always". * @@ -200,6 +197,12 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { 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) { if (!this._added) this._setUpControl(); if (isPropertyUpdated(changes, this.viewModel)) { @@ -209,23 +212,54 @@ 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) { this.formDirective.removeControl(this); } } + /** + * @description + * Sets the new value for the view model and emits an `ngModelChange` event. + * + * @param newValue The new value for the view model. + */ viewToModelUpdate(newValue: any): void { this.viewModel = newValue; this.update.emit(newValue); } + /** + * @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 controlPath(this.name, this._parent !); } + /** + * @description + * The top-level directive for this group 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 { return composeAsyncValidators(this._rawAsyncValidators) !; } diff --git a/packages/forms/src/directives/reactive_directives/form_group_directive.ts b/packages/forms/src/directives/reactive_directives/form_group_directive.ts index 9db4ac995c..2d3e617ade 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_directive.ts @@ -31,25 +31,14 @@ export const formDirectiveProvider: any = { * `FormGroup` instance to match any child `FormControl`, `FormGroup`, * and `FormArray` instances to child `FormControlName`, `FormGroupName`, * and `FormArrayName` directives. + * + * @see [Reactive Forms Guide](guide/reactive-forms) + * @see `AbstractControl` * - * @usageNotes - * **Set value**: You can set the form's initial value when instantiating the - * `FormGroup`, or you can set it programmatically later using the `FormGroup`'s - * {@link AbstractControl#setValue setValue} or {@link AbstractControl#patchValue patchValue} - * methods. + * ### Register Form Group * - * **Listen to value**: If you want to listen to changes in the value of the form, you can subscribe - * to the `FormGroup`'s {@link AbstractControl#valueChanges valueChanges} event. You can also - * listen to its {@link AbstractControl#statusChanges statusChanges} event to be notified when the - * validation status is re-calculated. - * - * Furthermore, 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 - * submission event. - * - * ### Example - * - * In this example, we create form controls for first name and last name. + * The following example registers a `FormGroup` with first name and last name controls, + * and listens for the *ngSubmit* event when the button is clicked. * * {@example forms/ts/simpleFormGroup/simple_form_group_example.ts region='Component'} * @@ -64,13 +53,31 @@ export const formDirectiveProvider: any = { }) export class FormGroupDirective extends ControlContainer implements Form, OnChanges { + /** + * @description + * Reports whether the form submission has been triggered. + */ public readonly submitted: boolean = false; // TODO(issue/24571): remove '!'. private _oldForm !: FormGroup; + + /** + * @description + * Tracks the list of added `FormControlName` instances + */ directives: FormControlName[] = []; + /** + * @description + * Tracks the `FormGroup` bound to this directive. + */ @Input('formGroup') form: FormGroup = null !; + + /** + * @description + * Emits an event when the form submission has been triggered. + */ @Output() ngSubmit = new EventEmitter(); constructor( @@ -79,6 +86,12 @@ export class FormGroupDirective extends ControlContainer implements Form, super(); } + /** + * @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): void { this._checkFormPresent(); if (changes.hasOwnProperty('form')) { @@ -88,12 +101,32 @@ export class FormGroupDirective extends ControlContainer implements Form, } } + /** + * @description + * Returns this directive's instance. + */ get formDirective(): Form { return this; } + /** + * @description + * Returns the `FormGroup` bound to this directive. + */ 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 always an empty array. + */ get path(): string[] { return []; } + /** + * @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 `FormControlName` directive instance. + */ addControl(dir: FormControlName): FormControl { const ctrl: any = this.form.get(dir.path); setUpControl(ctrl, dir); @@ -102,35 +135,92 @@ export class FormGroupDirective extends ControlContainer implements Form, return ctrl; } + /** + * @description + * Retrieves the `FormControl` instance from the provided `FormControlName` directive + * + * @param dir The `FormControlName` directive instance. + */ getControl(dir: FormControlName): FormControl { return this.form.get(dir.path); } + /** + * @description + * Removes the `FormControlName` instance from the internal list of directives + * + * @param dir The `FormControlName` directive instance. + */ removeControl(dir: FormControlName): void { removeDir(this.directives, dir); } + /** + * Adds a new `FormGroupName` directive instance to the form. + * + * @param dir The `FormGroupName` directive instance. + */ addFormGroup(dir: FormGroupName): void { const ctrl: any = this.form.get(dir.path); setUpFormContainer(ctrl, dir); ctrl.updateValueAndValidity({emitEvent: false}); } + /** + * No-op method to remove the form group. + * + * @param dir The `FormGroupName` directive instance. + */ removeFormGroup(dir: FormGroupName): void {} + /** + * @description + * Retrieves the `FormGroup` for a provided `FormGroupName` directive instance + * + * @param dir The `FormGroupName` directive instance. + */ getFormGroup(dir: FormGroupName): FormGroup { return this.form.get(dir.path); } + /** + * Adds a new `FormArrayName` directive instance to the form. + * + * @param dir The `FormArrayName` directive instance. + */ addFormArray(dir: FormArrayName): void { const ctrl: any = this.form.get(dir.path); setUpFormContainer(ctrl, dir); ctrl.updateValueAndValidity({emitEvent: false}); } + /** + * No-op method to remove the form array. + * + * @param dir The `FormArrayName` directive instance. + */ removeFormArray(dir: FormArrayName): void {} + /** + * @description + * Retrieves the `FormArray` for a provided `FormArrayName` directive instance. + * + * @param dir The `FormArrayName` directive instance. + */ getFormArray(dir: FormArrayName): FormArray { return this.form.get(dir.path); } + /** + * Sets the new value for the provided `FormControlName` directive. + * + * @param dir The `FormControlName` directive instance. + * @param value The new value for the directive's control. + */ updateModel(dir: FormControlName, value: any): void { const ctrl  = this.form.get(dir.path); ctrl.setValue(value); } + /** + * @description + * Method called with 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); @@ -138,8 +228,18 @@ export class FormGroupDirective 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/reactive_directives/form_group_name.ts b/packages/forms/src/directives/reactive_directives/form_group_name.ts index c079dd901a..e7c5a69f45 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_name.ts @@ -28,37 +28,42 @@ export const formGroupNameProvider: any = { * * Syncs a nested `FormGroup` to a DOM element. * - * This directive can only be used with a parent `FormGroupDirective` (selector: - * `[formGroup]`). + * This directive can only be used with a parent `FormGroupDirective`. * - * It accepts the string name of the nested `FormGroup` you want to link, and - * will look for a `FormGroup` registered with that name in the parent + * It accepts the string name of the nested `FormGroup` to link, and + * looks for a `FormGroup` registered with that name in the parent * `FormGroup` instance you passed into `FormGroupDirective`. * - * Nested form groups can come in handy when you want to validate a sub-group of a - * form separately from the rest or when you'd like to group the values of certain + * Use nested form groups to validate a sub-group of a + * form separately from the rest or to group the values of certain * controls into their own nested object. + * + * @see [Reactive Forms Guide](guide/reactive-forms) * * @usageNotes - * **Access the group**: You can access the associated `FormGroup` using the - * {@link AbstractControl#get get} method. Ex: `this.form.get('name')`. + * + * ### Access the group by name + * + * The following example uses the {@link AbstractControl#get get} method to access the + * associated `FormGroup` * - * You can also access individual controls within the group using dot syntax. - * Ex: `this.form.get('name.first')` + * ```ts + * this.form.get('name'); + * ``` + * + * ### Access individual controls in the group + * + * The following example uses the {@link AbstractControl#get get} method to access + * individual controls within the group using dot syntax. * - * **Get the value**: the `value` property is always synced and available on the - * `FormGroup`. See a full list of available properties in `AbstractControl`. + * ```ts + * this.form.get('name.first'); + * ``` * - * **Set the value**: You can set an initial value for each child control when instantiating - * the `FormGroup`, or you can set it programmatically later using - * {@link AbstractControl#setValue setValue} or {@link AbstractControl#patchValue patchValue}. - * - * **Listen to value**: If you want to listen to changes in the value of the group, you can - * subscribe to the {@link AbstractControl#valueChanges valueChanges} event. You can also listen to - * {@link AbstractControl#statusChanges statusChanges} to be notified when the validation status is - * re-calculated. - * - * ### Example + * ### Register a nested `FormGroup`. + * + * The following example registers a nested *name* `FormGroup` within an existing `FormGroup`, + * and provides methods to retrieve the nested `FormGroup` and individual controls. * * {@example forms/ts/nestedFormGroup/nested_form_group_example.ts region='Component'} * @@ -67,6 +72,11 @@ export const formGroupNameProvider: any = { */ @Directive({selector: '[formGroupName]', providers: [formGroupNameProvider]}) export class FormGroupName extends AbstractFormGroupDirective implements OnInit, OnDestroy { + /** + * @description + * Tracks the name of the `FormGroup` bound to the directive. The name corresponds + * to a key in the parent `FormGroup` or `FormArray`. + */ // TODO(issue/24571): remove '!'. @Input('formGroupName') name !: string; @@ -104,32 +114,11 @@ export const formArrayNameProvider: any = { * It accepts the string name of the nested `FormArray` you want to link, and * will look for a `FormArray` registered with that name in the parent * `FormGroup` instance you passed into `FormGroupDirective`. - * - * Nested form arrays can come in handy when you have a group of form controls but - * you're not sure how many there will be. Form arrays allow you to create new - * form controls dynamically. + * + * @see [Reactive Forms Guide](guide/reactive-forms) + * @see `AbstractControl` * * @usageNotes - * **Access the array**: You can access the associated `FormArray` using the - * {@link AbstractControl#get get} method on the parent `FormGroup`. - * Ex: `this.form.get('cities')`. - * - * **Get the value**: the `value` property is always synced and available on the - * `FormArray`. See a full list of available properties in `AbstractControl`. - * - * **Set the value**: You can set an initial value for each child control when instantiating - * the `FormArray`, or you can set the value programmatically later using the - * `FormArray`'s {@link AbstractControl#setValue setValue} or - * {@link AbstractControl#patchValue patchValue} methods. - * - * **Listen to value**: If you want to listen to changes in the value of the array, you can - * subscribe to the `FormArray`'s {@link AbstractControl#valueChanges valueChanges} event. - * You can also listen to its {@link AbstractControl#statusChanges statusChanges} event to be - * notified when the validation status is re-calculated. - * - * **Add new controls**: You can add new controls to the `FormArray` dynamically by calling - * its {@link FormArray#push push} method. - * Ex: `this.form.get('cities').push(new FormControl());` * * ### Example * @@ -149,6 +138,11 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy /** @internal */ _asyncValidators: any[]; + /** + * @description + * Tracks the name of the `FormArray` bound to the directive. The name corresponds + * to a key in the parent `FormGroup` or `FormArray`. + */ // TODO(issue/24571): remove '!'. @Input('formArrayName') name !: string; @@ -162,27 +156,59 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy this._asyncValidators = asyncValidators; } + /** + * @description + * A lifecycle method called when the directive's inputs are initialized. For internal use only. + * + * @throws If the directive does not have a valid parent. + */ ngOnInit(): void { this._checkParentType(); this.formDirective !.addFormArray(this); } + /** + * @description + * A lifecycle method called before the directive's instance is destroyed. For internal use only. + */ ngOnDestroy(): void { if (this.formDirective) { this.formDirective.removeFormArray(this); } } + /** + * @description + * The `FormArray` bound to this directive. + */ get control(): FormArray { return this.formDirective !.getFormArray(this); } + /** + * @description + * The top-level directive for this group if present, otherwise null. + */ get formDirective(): FormGroupDirective|null { return this._parent ? this._parent.formDirective : null; } + /** + * @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 controlPath(this.name, this._parent); } + /** + * @description + * Synchronous validator function composed of all the synchronous validators registered with this + * directive. + */ get validator(): ValidatorFn|null { return composeValidators(this._validators); } + /** + * @description + * Async validator function composed of all the async validators registered with this directive. + */ get asyncValidator(): AsyncValidatorFn|null { return composeAsyncValidators(this._asyncValidators); }