diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index e918e5cebe58..c928d2ac2701 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -181,6 +181,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { if (this._panelOpen) { this.autocomplete._isOpen = this._panelOpen = false; + this.autocomplete.closed.emit(); if (this._overlayRef && this._overlayRef.hasAttached()) { this._overlayRef.detach(); @@ -499,8 +500,16 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { this._closingActionsSubscription = this._subscribeToClosingActions(); } + const wasOpen = this.panelOpen; + this.autocomplete._setVisibility(); this.autocomplete._isOpen = this._panelOpen = true; + + // We need to do an extra `panelOpen` check in here, because the + // autocomplete won't be shown if there are no options. + if (this.panelOpen && wasOpen !== this.panelOpen) { + this.autocomplete.opened.emit(); + } } private _getOverlayConfig(): OverlayConfig { diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 3bf1f45483b4..a9d8e974833c 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -410,6 +410,38 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.panel.isOpen).toBeTruthy( `Expected the panel to be opened on focus.`); })); + + it('should emit an event when the panel is opened', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + expect(fixture.componentInstance.openedSpy).toHaveBeenCalled(); + }); + + it('should not emit the opened event multiple times while typing', fakeAsync(() => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + expect(fixture.componentInstance.openedSpy).toHaveBeenCalledTimes(1); + + typeInElement('Alabam', input); + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + + expect(fixture.componentInstance.openedSpy).toHaveBeenCalledTimes(1); + })); + + it('should emit an event when the panel is closed', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + fixture.componentInstance.trigger.closePanel(); + fixture.detectChanges(); + + expect(fixture.componentInstance.closedSpy).toHaveBeenCalled(); + }); + }); it('should have the correct text direction in RTL', () => { @@ -423,7 +455,6 @@ describe('MatAutocomplete', () => { const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane')!; expect(overlayPane.getAttribute('dir')).toEqual('rtl'); - }); describe('forms integration', () => { @@ -1814,7 +1845,7 @@ describe('MatAutocomplete', () => { + [disableRipple]="disableRipple" (opened)="openedSpy()" (closed)="closedSpy()"> {{ state.code }}: {{ state.name }} @@ -1828,6 +1859,8 @@ class SimpleAutocomplete implements OnDestroy { floatLabel = 'auto'; width: number; disableRipple = false; + openedSpy = jasmine.createSpy('autocomplete opened spy'); + closedSpy = jasmine.createSpy('autocomplete closed spy'); @ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger; @ViewChild(MatAutocomplete) panel: MatAutocomplete; diff --git a/src/lib/autocomplete/autocomplete.ts b/src/lib/autocomplete/autocomplete.ts index 580641024373..de85cd5cec68 100644 --- a/src/lib/autocomplete/autocomplete.ts +++ b/src/lib/autocomplete/autocomplete.ts @@ -126,6 +126,12 @@ export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterC @Output() readonly optionSelected: EventEmitter = new EventEmitter(); + /** Event that is emitted when the autocomplete panel is opened. */ + @Output() readonly opened: EventEmitter = new EventEmitter(); + + /** Event that is emitted when the autocomplete panel is closed. */ + @Output() readonly closed: EventEmitter = new EventEmitter(); + /** * Takes classes set on the host mat-autocomplete element and applies them to the panel * inside the overlay container to allow for easy styling. @@ -159,7 +165,7 @@ export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterC ngAfterContentInit() { this._keyManager = new ActiveDescendantKeyManager(this.options).withWrap(); - // Set the initial visibiity state. + // Set the initial visibility state. this._setVisibility(); } @@ -184,7 +190,7 @@ export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterC this._classList['mat-autocomplete-visible'] = this.showPanel; this._classList['mat-autocomplete-hidden'] = !this.showPanel; this._changeDetectorRef.markForCheck(); -} + } /** Emits the `select` event. */ _emitSelectEvent(option: MatOption): void {