Skip to content

Commit 0cd5f45

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 5a65a63 commit 0cd5f45

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-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();

0 commit comments

Comments
 (0)