diff --git a/packages/forms/src/directives/ng_form.ts b/packages/forms/src/directives/ng_form.ts index 431fa84d3908f..ae66484e01815 100644 --- a/packages/forms/src/directives/ng_form.ts +++ b/packages/forms/src/directives/ng_form.ts @@ -298,7 +298,9 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit { (this as {submitted: boolean}).submitted = true; syncPendingControls(this.form, this._directives); this.ngSubmit.emit($event); - return false; + // Forms with `method="dialog"` have some special behavior + // that won't reload the page and that shouldn't be prevented. + return ($event?.target as HTMLFormElement | null)?.method === 'dialog'; } /** 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 3f19560ecfeaa..21ee9f6081c14 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_directive.ts @@ -94,8 +94,8 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan @Output() ngSubmit = new EventEmitter(); constructor( - @Optional() @Self() @Inject(NG_VALIDATORS) private validators: (Validator|ValidatorFn)[], - @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private asyncValidators: + @Optional() @Self() @Inject(NG_VALIDATORS) validators: (Validator|ValidatorFn)[], + @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: (AsyncValidator|AsyncValidatorFn)[]) { super(); this._setValidators(validators); @@ -271,7 +271,9 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan (this as {submitted: boolean}).submitted = true; syncPendingControls(this.form, this.directives); this.ngSubmit.emit($event); - return false; + // Forms with `method="dialog"` have some special behavior + // that won't reload the page and that shouldn't be prevented. + return ($event?.target as HTMLFormElement | null)?.method === 'dialog'; } /** diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts index b795ce5b9b153..7a0bc61fee504 100644 --- a/packages/forms/test/reactive_integration_spec.ts +++ b/packages/forms/test/reactive_integration_spec.ts @@ -7,7 +7,7 @@ */ import {ɵgetDOM as getDOM} from '@angular/common'; -import {Component, Directive, forwardRef, Input, NgModule, OnDestroy, Type} from '@angular/core'; +import {Component, Directive, ElementRef, forwardRef, Input, NgModule, OnDestroy, Type, ViewChild} from '@angular/core'; import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; import {AbstractControl, AsyncValidator, AsyncValidatorFn, COMPOSITION_BUFFER_MODE, ControlValueAccessor, DefaultValueAccessor, FormArray, FormBuilder, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormsModule, MaxValidator, MinLengthValidator, MinValidator, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NG_VALUE_ACCESSOR, ReactiveFormsModule, Validator, Validators} from '@angular/forms'; import {By} from '@angular/platform-browser/src/dom/debug/by'; @@ -2114,6 +2114,20 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); expect(passwordControl.value).toEqual('Carson', 'Expected value to change on submit.'); expect(passwordControl.valid).toBe(true, 'Expected validation to run on submit.'); }); + + it('should not prevent the default action on forms with method="dialog"', fakeAsync(() => { + if (typeof HTMLDialogElement === 'undefined') { + return; + } + + const fixture = initTest(NativeDialogForm); + fixture.detectChanges(); + tick(); + const event = dispatchEvent(fixture.componentInstance.form.nativeElement, 'submit'); + fixture.detectChanges(); + + expect(event.defaultPrevented).toBe(false); + })); }); }); @@ -5437,3 +5451,17 @@ class MinMaxFormControlComp { min: number|string = 1; max: number|string = 10; } + +@Component({ + template: ` + +
+ +
+
+ ` +}) +class NativeDialogForm { + @ViewChild('form') form!: ElementRef; + formGroup = new FormGroup({}); +} diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts index 5385d6e939776..96f797d129bf9 100644 --- a/packages/forms/test/template_integration_spec.ts +++ b/packages/forms/test/template_integration_spec.ts @@ -7,7 +7,7 @@ */ import {CommonModule, ɵgetDOM as getDOM} from '@angular/common'; -import {Component, Directive, forwardRef, Input, Type, ViewChild} from '@angular/core'; +import {Component, Directive, ElementRef, forwardRef, Input, Type, ViewChild} from '@angular/core'; import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing'; import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, ControlValueAccessor, FormControl, FormsModule, MaxLengthValidator, MaxValidator, MinLengthValidator, MinValidator, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgForm, NgModel, Validator} from '@angular/forms'; import {By} from '@angular/platform-browser/src/dom/debug/by'; @@ -1062,6 +1062,21 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat ['fired', 'fired'], 'Expected ngModelChanges to fire again on submit if value changed.'); })); + + + it('should not prevent the default action on forms with method="dialog"', fakeAsync(() => { + if (typeof HTMLDialogElement === 'undefined') { + return; + } + + const fixture = initTest(NativeDialogForm); + fixture.detectChanges(); + tick(); + const event = dispatchEvent(fixture.componentInstance.form.nativeElement, 'submit'); + fixture.detectChanges(); + + expect(event.defaultPrevented).toBe(false); + })); }); describe('ngFormOptions', () => { @@ -2932,3 +2947,17 @@ class NgModelNoMinMaxValidator { max!: number; @ViewChild('myDir') myDir: any; } + +@Component({ + selector: 'ng-model-nested', + template: ` + +
+ +
+
+ ` +}) +class NativeDialogForm { + @ViewChild('form') form!: ElementRef; +} diff --git a/packages/platform-browser/testing/src/browser_util.ts b/packages/platform-browser/testing/src/browser_util.ts index aa28e9563e14d..8acf2ac7c895d 100644 --- a/packages/platform-browser/testing/src/browser_util.ts +++ b/packages/platform-browser/testing/src/browser_util.ts @@ -97,10 +97,11 @@ export class BrowserDetection { export const browserDetection: BrowserDetection = BrowserDetection.setup(); -export function dispatchEvent(element: any, eventType: any): void { +export function dispatchEvent(element: any, eventType: any): Event { const evt: Event = getDOM().getDefaultDocument().createEvent('Event'); evt.initEvent(eventType, true, true); getDOM().dispatchEvent(element, evt); + return evt; } export function createMouseEvent(eventType: string): MouseEvent {