Skip to content

Commit

Permalink
fix(forms): Add event for forms submitted & reset (#55667)
Browse files Browse the repository at this point in the history
This commit adds 2 new events to the unified control event observable.

PR Close #55667
  • Loading branch information
JeanMeche authored and atscott committed May 9, 2024
1 parent 28a24b6 commit 61007dc
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 2 deletions.
14 changes: 14 additions & 0 deletions goldens/public-api/forms/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,13 @@ export interface FormRecord<TControl> {
}): void;
}

// @public
export class FormResetEvent extends ControlEvent {
constructor(source: AbstractControl);
// (undocumented)
readonly source: AbstractControl;
}

// @public
export class FormsModule {
static withConfig(opts: {
Expand All @@ -578,6 +585,13 @@ export class FormsModule {
static ɵmod: i0.ɵɵNgModuleDeclaration<FormsModule, [typeof i1_2.NgModel, typeof i2_2.NgModelGroup, typeof i3_2.NgForm], never, [typeof i4_2InternalFormsSharedModule, typeof i1_2.NgModel, typeof i2_2.NgModelGroup, typeof i3_2.NgForm]>;
}

// @public
export class FormSubmittedEvent extends ControlEvent {
constructor(source: AbstractControl);
// (undocumented)
readonly source: AbstractControl;
}

// @public
export const isFormArray: (control: unknown) => control is FormArray<any>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@
{
"name": "FormRecord"
},
{
"name": "FormResetEvent"
},
{
"name": "FormSubmittedEvent"
},
{
"name": "FormsExampleModule"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../valid

import {FormControlName} from './form_control_name';
import {FormArrayName, FormGroupName} from './form_group_name';
import {FormResetEvent, FormSubmittedEvent} from '../../model/abstract_model';

const formDirectiveProvider: Provider = {
provide: ControlContainer,
Expand Down Expand Up @@ -302,6 +303,8 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan
(this as Writable<this>).submitted = true;
syncPendingControls(this.form, this.directives);
this.ngSubmit.emit($event);
this.form._events.next(new FormSubmittedEvent(this.control));

// Forms with `method="dialog"` have some special behavior that won't reload the page and that
// shouldn't be prevented. Note that we need to null check the `event` and the `target`, because
// some internal apps call this method directly with the wrong arguments.
Expand All @@ -325,6 +328,7 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan
resetForm(value: any = undefined): void {
this.form.reset(value);
(this as Writable<this>).submitted = false;
this.form._events.next(new FormResetEvent(this.form));
}

/** @internal */
Expand Down
2 changes: 2 additions & 0 deletions packages/forms/src/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export {
AbstractControlOptions,
ControlEvent,
FormControlStatus,
FormResetEvent,
FormSubmittedEvent,
PristineChangeEvent as PristineEvent,
StatusChangeEvent as StatusEvent,
TouchedChangeEvent as TouchedEvent,
Expand Down
23 changes: 22 additions & 1 deletion packages/forms/src/model/abstract_model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,27 @@ export class StatusChangeEvent extends ControlEvent {
}
}

/**
* Event fired when a form is submitted
*
* @publicApi
*/
export class FormSubmittedEvent extends ControlEvent {
constructor(public readonly source: AbstractControl) {
super();
}
}
/**
* Event fired when a form is reset.
*
* @publicApi
*/
export class FormResetEvent extends ControlEvent {
constructor(public readonly source: AbstractControl) {
super();
}
}

/**
* Gets validators from either an options object or given validators.
*/
Expand Down Expand Up @@ -683,7 +704,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
*
* @internal
*/
private readonly _events = new Subject<ControlEvent<TValue>>();
readonly _events = new Subject<ControlEvent<TValue>>();

/**
* A multicasting observable that emits an event every time the state of the control changes.
Expand Down
42 changes: 41 additions & 1 deletion packages/forms/test/reactive_integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
FormControl,
FormControlDirective,
FormControlName,
FormControlState,
FormGroup,
FormGroupDirective,
FormsModule,
Expand All @@ -51,6 +50,8 @@ import {map, tap} from 'rxjs/operators';
import {
ControlEvent,
FormControlStatus,
FormResetEvent,
FormSubmittedEvent,
PristineChangeEvent,
StatusChangeEvent,
TouchedChangeEvent,
Expand Down Expand Up @@ -1391,6 +1392,45 @@ describe('reactive forms integration tests', () => {
fc.markAsDirty({emitEvent: false});
expect(fcEvents.length).toBe(0);
});

it('formControl should emit an event when resetting a form', () => {
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('', Validators.required)});
fixture.componentInstance.form = form;
fixture.detectChanges();

const formGroupDir = fixture.debugElement.children[0].injector.get(FormGroupDirective);

const events: ControlEvent[] = [];
fixture.componentInstance.form.events.subscribe((event) => events.push(event));
formGroupDir.resetForm();

expect(events.length).toBe(4);
expect(events[0]).toBeInstanceOf(TouchedChangeEvent);
expect(events[1]).toBeInstanceOf(ValueChangeEvent);
expect(events[2]).toBeInstanceOf(StatusChangeEvent);

// The event that matters
expect(events[3]).toBeInstanceOf(FormResetEvent);
expect(events[3].source).toBe(form);
});

it('formControl should emit an event when submitting a form', () => {
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('', Validators.required)});
fixture.componentInstance.form = form;
fixture.detectChanges();

const formGroupDir = fixture.debugElement.children[0].injector.get(FormGroupDirective);

const events: ControlEvent[] = [];
fixture.componentInstance.form.events.subscribe((event) => events.push(event));
formGroupDir.onSubmit({} as any);

expect(events.length).toBe(1);
expect(events[0]).toBeInstanceOf(FormSubmittedEvent);
expect(events[0].source).toBe(form);
});
});

describe('setting status classes', () => {
Expand Down

0 comments on commit 61007dc

Please sign in to comment.