Skip to content

Commit 67cc2c2

Browse files
crisbetojelbourn
authored andcommitted
fix(autocomplete): closed event emitting when panel wasn't shown due to lack of options (#10176)
Adds an extra check to ensure that the `MatAutocomplete.closed` event won't emit when the user tried to open the panel, but it wasn't shown because there were no rendered options. Fixes #10154.
1 parent 44637f3 commit 67cc2c2

File tree

2 files changed

+52
-21
lines changed

2 files changed

+52
-21
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,10 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
166166
}
167167

168168
/** Whether or not the autocomplete panel is open. */
169-
get panelOpen(): boolean { return this._panelOpen && this.autocomplete.showPanel; }
170-
private _panelOpen: boolean = false;
169+
get panelOpen(): boolean {
170+
return this._overlayAttached && this.autocomplete.showPanel;
171+
}
172+
private _overlayAttached: boolean = false;
171173

172174
/** Opens the autocomplete suggestion panel. */
173175
openPanel(): void {
@@ -179,24 +181,30 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
179181
closePanel(): void {
180182
this._resetLabel();
181183

182-
if (this._panelOpen) {
183-
this.autocomplete._isOpen = this._panelOpen = false;
184+
if (!this._overlayAttached) {
185+
return;
186+
}
187+
188+
if (this.panelOpen) {
189+
// Only emit if the panel was visible.
184190
this.autocomplete.closed.emit();
191+
}
185192

186-
if (this._overlayRef && this._overlayRef.hasAttached()) {
187-
this._overlayRef.detach();
188-
this._closingActionsSubscription.unsubscribe();
189-
}
193+
this.autocomplete._isOpen = this._overlayAttached = false;
190194

191-
// Note that in some cases this can end up being called after the component is destroyed.
192-
// Add a check to ensure that we don't try to run change detection on a destroyed view.
193-
if (!this._componentDestroyed) {
194-
// We need to trigger change detection manually, because
195-
// `fromEvent` doesn't seem to do it at the proper time.
196-
// This ensures that the label is reset when the
197-
// user clicks outside.
198-
this._changeDetectorRef.detectChanges();
199-
}
195+
if (this._overlayRef && this._overlayRef.hasAttached()) {
196+
this._overlayRef.detach();
197+
this._closingActionsSubscription.unsubscribe();
198+
}
199+
200+
// Note that in some cases this can end up being called after the component is destroyed.
201+
// Add a check to ensure that we don't try to run change detection on a destroyed view.
202+
if (!this._componentDestroyed) {
203+
// We need to trigger change detection manually, because
204+
// `fromEvent` doesn't seem to do it at the proper time.
205+
// This ensures that the label is reset when the
206+
// user clicks outside.
207+
this._changeDetectorRef.detectChanges();
200208
}
201209
}
202210

@@ -207,11 +215,11 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
207215
get panelClosingActions(): Observable<MatOptionSelectionChange> {
208216
return merge(
209217
this.optionSelections,
210-
this.autocomplete._keyManager.tabOut.pipe(filter(() => this._panelOpen)),
218+
this.autocomplete._keyManager.tabOut.pipe(filter(() => this._overlayAttached)),
211219
this._closeKeyEventStream,
212220
this._outsideClickStream,
213221
this._overlayRef ?
214-
this._overlayRef.detachments().pipe(filter(() => this._panelOpen)) :
222+
this._overlayRef.detachments().pipe(filter(() => this._overlayAttached)) :
215223
observableOf()
216224
);
217225
}
@@ -253,7 +261,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
253261
const formField = this._formField ?
254262
this._formField._elementRef.nativeElement : null;
255263

256-
return this._panelOpen &&
264+
return this._overlayAttached &&
257265
clickTarget !== this._element.nativeElement &&
258266
(!formField || !formField.contains(clickTarget)) &&
259267
(!!this._overlayRef && !this._overlayRef.overlayElement.contains(clickTarget));
@@ -503,7 +511,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
503511
const wasOpen = this.panelOpen;
504512

505513
this.autocomplete._setVisibility();
506-
this.autocomplete._isOpen = this._panelOpen = true;
514+
this.autocomplete._isOpen = this._overlayAttached = true;
507515

508516
// We need to do an extra `panelOpen` check in here, because the
509517
// autocomplete won't be shown if there are no options.

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,16 @@ describe('MatAutocomplete', () => {
418418
expect(fixture.componentInstance.openedSpy).toHaveBeenCalled();
419419
});
420420

421+
it('should not emit the `opened` event when no options are being shown', () => {
422+
fixture.componentInstance.filteredStates = fixture.componentInstance.states = [];
423+
fixture.detectChanges();
424+
425+
fixture.componentInstance.trigger.openPanel();
426+
fixture.detectChanges();
427+
428+
expect(fixture.componentInstance.openedSpy).not.toHaveBeenCalled();
429+
});
430+
421431
it('should not emit the opened event multiple times while typing', fakeAsync(() => {
422432
fixture.componentInstance.trigger.openPanel();
423433
fixture.detectChanges();
@@ -442,6 +452,19 @@ describe('MatAutocomplete', () => {
442452
expect(fixture.componentInstance.closedSpy).toHaveBeenCalled();
443453
});
444454

455+
it('should not emit the `closed` event when no options were shown', () => {
456+
fixture.componentInstance.filteredStates = fixture.componentInstance.states = [];
457+
fixture.detectChanges();
458+
459+
fixture.componentInstance.trigger.openPanel();
460+
fixture.detectChanges();
461+
462+
fixture.componentInstance.trigger.closePanel();
463+
fixture.detectChanges();
464+
465+
expect(fixture.componentInstance.closedSpy).not.toHaveBeenCalled();
466+
});
467+
445468
});
446469

447470
it('should have the correct text direction in RTL', () => {

0 commit comments

Comments
 (0)