Skip to content

Commit 518ee59

Browse files
committed
fix(autocomplete): marking element as touched too early when clicking on options
Currently we mark the autocomplete's CVA as touched on each `blur` event, which can happen a little too early if the user holds their pointer while clicking on an item, causing the form validation to show up too early. These changes defer marking the element as touched until the panel has closed. Fixes #13732.
1 parent 45a4300 commit 518ee59

File tree

3 files changed

+48
-4
lines changed

3 files changed

+48
-4
lines changed

src/material/autocomplete/autocomplete-trigger.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {
4747
} from '@angular/material/core';
4848
import {MatFormField} from '@angular/material/form-field';
4949
import {defer, fromEvent, merge, Observable, of as observableOf, Subject, Subscription} from 'rxjs';
50-
import {delay, filter, map, switchMap, take, tap} from 'rxjs/operators';
50+
import {delay, filter, map, switchMap, take, tap, takeUntil} from 'rxjs/operators';
5151

5252
import {MatAutocomplete} from './autocomplete';
5353
import {MatAutocompleteOrigin} from './autocomplete-origin';
@@ -116,7 +116,7 @@ export function getMatAutocompleteMissingPanelError(): Error {
116116
// Note: we use `focusin`, as opposed to `focus`, in order to open the panel
117117
// a little earlier. This avoids issues where IE delays the focusing of the input.
118118
'(focusin)': '_handleFocus()',
119-
'(blur)': '_onTouched()',
119+
'(blur)': '_handleBlur()',
120120
'(input)': '_handleInput($event)',
121121
'(keydown)': '_handleKeydown($event)',
122122
},
@@ -460,6 +460,16 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewIn
460460
}
461461
}
462462

463+
_handleBlur(): void {
464+
if (this.panelOpen) {
465+
this.autocomplete.closed
466+
.pipe(take(1), takeUntil(this.autocomplete.opened.asObservable()))
467+
.subscribe(() => this._onTouched());
468+
} else {
469+
this._onTouched();
470+
}
471+
}
472+
463473
/**
464474
* In "auto" mode, the label will animate down as soon as focus is lost.
465475
* This causes the value to jump when selecting an option with the mouse.

src/material/autocomplete/autocomplete.spec.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -814,15 +814,48 @@ describe('MatAutocomplete', () => {
814814
fixture.componentInstance.trigger.openPanel();
815815
fixture.detectChanges();
816816
expect(fixture.componentInstance.stateCtrl.touched)
817-
.toBe(false, `Expected control to start out untouched.`);
817+
.toBe(false, 'Expected control to start out untouched.');
818818

819819
dispatchFakeEvent(input, 'blur');
820820
fixture.detectChanges();
821+
fixture.componentInstance.trigger.closePanel();
822+
fixture.detectChanges();
821823

822824
expect(fixture.componentInstance.stateCtrl.touched)
823-
.toBe(true, `Expected control to become touched on blur.`);
825+
.toBe(true, 'Expected control to become touched on blur.');
824826
});
825827

828+
it('should mark the autocomplete control as touched on blur if the panel is closed', () => {
829+
expect(fixture.componentInstance.stateCtrl.touched)
830+
.toBe(false, 'Expected control to start out untouched.');
831+
832+
dispatchFakeEvent(input, 'blur');
833+
fixture.detectChanges();
834+
835+
expect(fixture.componentInstance.stateCtrl.touched)
836+
.toBe(true, 'Expected control to become touched on blur.');
837+
});
838+
839+
it('should not mark the autocomplete control as touched if the input was blurred while ' +
840+
'the panel is open', () => {
841+
fixture.componentInstance.trigger.openPanel();
842+
fixture.detectChanges();
843+
expect(fixture.componentInstance.stateCtrl.touched)
844+
.toBe(false, 'Expected control to start out untouched.');
845+
846+
dispatchFakeEvent(input, 'blur');
847+
fixture.detectChanges();
848+
849+
expect(fixture.componentInstance.stateCtrl.touched)
850+
.toBe(false, 'Expected control to remain untouched.');
851+
852+
fixture.componentInstance.trigger.closePanel();
853+
fixture.detectChanges();
854+
855+
expect(fixture.componentInstance.stateCtrl.touched)
856+
.toBe(true, 'Expected control to be touched once the panel is closed.');
857+
});
858+
826859
it('should disable the input when used with a value accessor and without `matInput`', () => {
827860
overlayContainer.ngOnDestroy();
828861
fixture.destroy();

tools/public_api_guard/material/autocomplete.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export declare class MatAutocompleteTrigger implements ControlValueAccessor, Aft
8282
readonly panelOpen: boolean;
8383
position: 'auto' | 'above' | 'below';
8484
constructor(_element: ElementRef<HTMLInputElement>, _overlay: Overlay, _viewContainerRef: ViewContainerRef, _zone: NgZone, _changeDetectorRef: ChangeDetectorRef, scrollStrategy: any, _dir: Directionality, _formField: MatFormField, _document: any, _viewportRuler?: ViewportRuler | undefined);
85+
_handleBlur(): void;
8586
_handleFocus(): void;
8687
_handleInput(event: KeyboardEvent): void;
8788
_handleKeydown(event: KeyboardEvent): void;

0 commit comments

Comments
 (0)