Skip to content

Commit 61007dc

Browse files
JeanMecheatscott
authored andcommitted
fix(forms): Add event for forms submitted & reset (angular#55667)
This commit adds 2 new events to the unified control event observable. PR Close angular#55667
1 parent 28a24b6 commit 61007dc

File tree

6 files changed

+89
-2
lines changed

6 files changed

+89
-2
lines changed

goldens/public-api/forms/index.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,13 @@ export interface FormRecord<TControl> {
565565
}): void;
566566
}
567567

568+
// @public
569+
export class FormResetEvent extends ControlEvent {
570+
constructor(source: AbstractControl);
571+
// (undocumented)
572+
readonly source: AbstractControl;
573+
}
574+
568575
// @public
569576
export class FormsModule {
570577
static withConfig(opts: {
@@ -578,6 +585,13 @@ export class FormsModule {
578585
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]>;
579586
}
580587

588+
// @public
589+
export class FormSubmittedEvent extends ControlEvent {
590+
constructor(source: AbstractControl);
591+
// (undocumented)
592+
readonly source: AbstractControl;
593+
}
594+
581595
// @public
582596
export const isFormArray: (control: unknown) => control is FormArray<any>;
583597

packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@
248248
{
249249
"name": "FormRecord"
250250
},
251+
{
252+
"name": "FormResetEvent"
253+
},
254+
{
255+
"name": "FormSubmittedEvent"
256+
},
251257
{
252258
"name": "FormsExampleModule"
253259
},

packages/forms/src/directives/reactive_directives/form_group_directive.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../valid
4545

4646
import {FormControlName} from './form_control_name';
4747
import {FormArrayName, FormGroupName} from './form_group_name';
48+
import {FormResetEvent, FormSubmittedEvent} from '../../model/abstract_model';
4849

4950
const formDirectiveProvider: Provider = {
5051
provide: ControlContainer,
@@ -302,6 +303,8 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan
302303
(this as Writable<this>).submitted = true;
303304
syncPendingControls(this.form, this.directives);
304305
this.ngSubmit.emit($event);
306+
this.form._events.next(new FormSubmittedEvent(this.control));
307+
305308
// Forms with `method="dialog"` have some special behavior that won't reload the page and that
306309
// shouldn't be prevented. Note that we need to null check the `event` and the `target`, because
307310
// some internal apps call this method directly with the wrong arguments.
@@ -325,6 +328,7 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan
325328
resetForm(value: any = undefined): void {
326329
this.form.reset(value);
327330
(this as Writable<this>).submitted = false;
331+
this.form._events.next(new FormResetEvent(this.form));
328332
}
329333

330334
/** @internal */

packages/forms/src/forms.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export {
7474
AbstractControlOptions,
7575
ControlEvent,
7676
FormControlStatus,
77+
FormResetEvent,
78+
FormSubmittedEvent,
7779
PristineChangeEvent as PristineEvent,
7880
StatusChangeEvent as StatusEvent,
7981
TouchedChangeEvent as TouchedEvent,

packages/forms/src/model/abstract_model.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,27 @@ export class StatusChangeEvent extends ControlEvent {
144144
}
145145
}
146146

147+
/**
148+
* Event fired when a form is submitted
149+
*
150+
* @publicApi
151+
*/
152+
export class FormSubmittedEvent extends ControlEvent {
153+
constructor(public readonly source: AbstractControl) {
154+
super();
155+
}
156+
}
157+
/**
158+
* Event fired when a form is reset.
159+
*
160+
* @publicApi
161+
*/
162+
export class FormResetEvent extends ControlEvent {
163+
constructor(public readonly source: AbstractControl) {
164+
super();
165+
}
166+
}
167+
147168
/**
148169
* Gets validators from either an options object or given validators.
149170
*/
@@ -683,7 +704,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
683704
*
684705
* @internal
685706
*/
686-
private readonly _events = new Subject<ControlEvent<TValue>>();
707+
readonly _events = new Subject<ControlEvent<TValue>>();
687708

688709
/**
689710
* A multicasting observable that emits an event every time the state of the control changes.

packages/forms/test/reactive_integration_spec.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import {
2929
FormControl,
3030
FormControlDirective,
3131
FormControlName,
32-
FormControlState,
3332
FormGroup,
3433
FormGroupDirective,
3534
FormsModule,
@@ -51,6 +50,8 @@ import {map, tap} from 'rxjs/operators';
5150
import {
5251
ControlEvent,
5352
FormControlStatus,
53+
FormResetEvent,
54+
FormSubmittedEvent,
5455
PristineChangeEvent,
5556
StatusChangeEvent,
5657
TouchedChangeEvent,
@@ -1391,6 +1392,45 @@ describe('reactive forms integration tests', () => {
13911392
fc.markAsDirty({emitEvent: false});
13921393
expect(fcEvents.length).toBe(0);
13931394
});
1395+
1396+
it('formControl should emit an event when resetting a form', () => {
1397+
const fixture = initTest(FormGroupComp);
1398+
const form = new FormGroup({'login': new FormControl('', Validators.required)});
1399+
fixture.componentInstance.form = form;
1400+
fixture.detectChanges();
1401+
1402+
const formGroupDir = fixture.debugElement.children[0].injector.get(FormGroupDirective);
1403+
1404+
const events: ControlEvent[] = [];
1405+
fixture.componentInstance.form.events.subscribe((event) => events.push(event));
1406+
formGroupDir.resetForm();
1407+
1408+
expect(events.length).toBe(4);
1409+
expect(events[0]).toBeInstanceOf(TouchedChangeEvent);
1410+
expect(events[1]).toBeInstanceOf(ValueChangeEvent);
1411+
expect(events[2]).toBeInstanceOf(StatusChangeEvent);
1412+
1413+
// The event that matters
1414+
expect(events[3]).toBeInstanceOf(FormResetEvent);
1415+
expect(events[3].source).toBe(form);
1416+
});
1417+
1418+
it('formControl should emit an event when submitting a form', () => {
1419+
const fixture = initTest(FormGroupComp);
1420+
const form = new FormGroup({'login': new FormControl('', Validators.required)});
1421+
fixture.componentInstance.form = form;
1422+
fixture.detectChanges();
1423+
1424+
const formGroupDir = fixture.debugElement.children[0].injector.get(FormGroupDirective);
1425+
1426+
const events: ControlEvent[] = [];
1427+
fixture.componentInstance.form.events.subscribe((event) => events.push(event));
1428+
formGroupDir.onSubmit({} as any);
1429+
1430+
expect(events.length).toBe(1);
1431+
expect(events[0]).toBeInstanceOf(FormSubmittedEvent);
1432+
expect(events[0].source).toBe(form);
1433+
});
13941434
});
13951435

13961436
describe('setting status classes', () => {

0 commit comments

Comments
 (0)