diff --git a/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts b/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts index 621e2d69855e..92f2572e5506 100644 --- a/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts +++ b/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts @@ -2825,6 +2825,25 @@ describe('MDC-based MatAutocomplete', () => { expect(closedSpy).toHaveBeenCalledTimes(1); })); + it('should emit a closed event if no option is displayed', fakeAsync(() => { + const {openedSpy, closedSpy} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + typeInElement(input, 'Alabama'); // Valid option + fixture.detectChanges(); + tick(); + + expect(openedSpy).toHaveBeenCalledTimes(1); + expect(closedSpy).toHaveBeenCalledTimes(0); + + typeInElement(input, '_x'); // Invalidate option to 'Alabama_x' + fixture.detectChanges(); + tick(); + + expect(openedSpy).toHaveBeenCalledTimes(1); + expect(closedSpy).toHaveBeenCalledTimes(1); + })); + it( 'should clear the input if the user presses escape while there was a pending ' + 'auto selection and there is no previous value', diff --git a/src/material/autocomplete/autocomplete-trigger.ts b/src/material/autocomplete/autocomplete-trigger.ts index 50a205af693f..1ea94334949e 100644 --- a/src/material/autocomplete/autocomplete-trigger.ts +++ b/src/material/autocomplete/autocomplete-trigger.ts @@ -537,13 +537,20 @@ export abstract class _MatAutocompleteTriggerBase if (this.panelOpen) { this._overlayRef!.updatePosition(); + } - // If the `panelOpen` state changed, we need to make sure to emit the `opened` - // event, because we may not have emitted it when the panel was attached. This - // can happen if the users opens the panel and there are no options, but the - // options come in slightly later or as a result of the value changing. - if (wasOpen !== this.panelOpen) { + if (wasOpen !== this.panelOpen) { + // If the `panelOpen` state changed, we need to make sure to emit the `opened` or + // `closed` event, because we may not have emitted it. This can happen + // - if the users opens the panel and there are no options, but the + // options come in slightly later or as a result of the value changing, + // - if the panel is closed after the user entered a string that did not match any + // of the available options, + // - if a valid string is entered after an invalid one. + if (this.panelOpen) { this.autocomplete.opened.emit(); + } else { + this.autocomplete.closed.emit(); } } }); diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts index ed41c3acbceb..a01b8ee0a8b0 100644 --- a/src/material/autocomplete/autocomplete.spec.ts +++ b/src/material/autocomplete/autocomplete.spec.ts @@ -2827,6 +2827,25 @@ describe('MatAutocomplete', () => { expect(closedSpy).toHaveBeenCalledTimes(1); })); + it('should emit a closed event if no option is displayed', fakeAsync(() => { + const {openedSpy, closedSpy} = fixture.componentInstance; + const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); + + typeInElement(input, 'Alabama'); // Valid option + fixture.detectChanges(); + tick(); + + expect(openedSpy).toHaveBeenCalledTimes(1); + expect(closedSpy).toHaveBeenCalledTimes(0); + + typeInElement(input, '_x'); // Invalidate option to 'Alabama_x' + fixture.detectChanges(); + tick(); + + expect(openedSpy).toHaveBeenCalledTimes(1); + expect(closedSpy).toHaveBeenCalledTimes(1); + })); + it( 'should clear the input if the user presses escape while there was a pending ' + 'auto selection and there is no previous value',