From 21e4ae204cba31bda500e6a057a9fcdbe7a06ce7 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 20 Apr 2017 23:09:16 +0200 Subject: [PATCH] feat(autocomplete): emit event when an option is selected Emits the `select` event when an option in the autocomplete is selected. **Note:** I went with passing the selected option from the trigger to the panel, instead of listening to the `onSelectionChange` inside the panel, because it involves keeping track of less subscriptions and not having to re-construct them when the list of options changes. Fixes #4094. Fixes #3645. --- src/lib/autocomplete/autocomplete-trigger.ts | 1 + src/lib/autocomplete/autocomplete.spec.ts | 51 +++++++++++++++++++- src/lib/autocomplete/autocomplete.ts | 21 +++++++- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 39876a48534a..8b1a06b2b875 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -332,6 +332,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { this._clearPreviousSelectedOption(event.source); this._setTriggerValue(event.source.value); this._onChange(event.source.value); + this.autocomplete._emitSelectEvent(event.source); } this.closePanel(); diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 203a29f53394..2d052ab7c2d9 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -20,7 +20,7 @@ import {ENTER, DOWN_ARROW, SPACE, UP_ARROW, HOME, END} from '../core/keyboard/ke import {MdOption} from '../core/option/option'; import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler'; -import {MdAutocomplete} from './autocomplete'; +import {MdAutocomplete, MdAutocompleteSelect} from './autocomplete'; import {MdInputContainer} from '../input/input-container'; import {Observable} from 'rxjs/Observable'; import {dispatchFakeEvent} from '../core/testing/dispatch-events'; @@ -47,7 +47,8 @@ describe('MdAutocomplete', () => { AutocompleteWithoutForms, NgIfAutocomplete, AutocompleteWithNgModel, - AutocompleteWithOnPushDelay + AutocompleteWithOnPushDelay, + AutocompleteWithSelectEvent ], providers: [ {provide: OverlayContainer, useFactory: () => { @@ -1146,6 +1147,29 @@ describe('MdAutocomplete', () => { expect(panel.classList).toContain(visibleClass, `Expected panel to be visible.`); }); })); + + it('should call emit an event when an option is selected', fakeAsync(() => { + let fixture = TestBed.createComponent(AutocompleteWithSelectEvent); + + fixture.detectChanges(); + fixture.componentInstance.trigger.openPanel(); + tick(); + fixture.detectChanges(); + + let options = overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + let spy = fixture.componentInstance.select; + + options[1].click(); + tick(); + fixture.detectChanges(); + + expect(spy).toHaveBeenCalledTimes(1); + + let event = spy.calls.mostRecent().args[0] as MdAutocompleteSelect; + + expect(event.source).toBe(fixture.componentInstance.autocomplete); + expect(event.option.value).toBe('Washington'); + })); }); @Component({ @@ -1319,6 +1343,29 @@ class AutocompleteWithOnPushDelay implements OnInit { } +@Component({ + template: ` + + + + + + + {{ state }} + + + ` +}) +class AutocompleteWithSelectEvent { + selectedState: string; + states = ['New York', 'Washington', 'Oregon']; + select = jasmine.createSpy('select callback'); + + @ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger; + @ViewChild(MdAutocomplete) autocomplete: MdAutocomplete; +} + + /** This is a mock keyboard event to test keyboard events in the autocomplete. */ class MockKeyboardEvent { constructor(public keyCode: number) {} diff --git a/src/lib/autocomplete/autocomplete.ts b/src/lib/autocomplete/autocomplete.ts index 520a0aa33329..9627beff4f77 100644 --- a/src/lib/autocomplete/autocomplete.ts +++ b/src/lib/autocomplete/autocomplete.ts @@ -9,16 +9,24 @@ import { ViewChild, ViewEncapsulation, ChangeDetectorRef, + Output, + EventEmitter, } from '@angular/core'; -import {MdOption} from '../core'; +import {MdOption} from '../core/option/option'; import {ActiveDescendantKeyManager} from '../core/a11y/activedescendant-key-manager'; + /** * Autocomplete IDs need to be unique across components, so this counter exists outside of * the component definition. */ let _uniqueAutocompleteIdCounter = 0; +/** Event object that is emitted when an autocomplete option is selected */ +export class MdAutocompleteSelect { + constructor(public source: MdAutocomplete, public option: MdOption) { } +} + export type AutocompletePositionY = 'above' | 'below'; @Component({ @@ -50,6 +58,9 @@ export class MdAutocomplete implements AfterContentInit { /** Function that maps an option's control value to its display value in the trigger. */ @Input() displayWith: (value: any) => string; + /** Event that is emitted whenever an option from the list is selected. */ + @Output() select: EventEmitter = new EventEmitter(); + /** Unique ID to be used by autocomplete trigger's "aria-owns" property. */ id: string = `md-autocomplete-${_uniqueAutocompleteIdCounter++}`; @@ -70,13 +81,19 @@ export class MdAutocomplete implements AfterContentInit { } /** Panel should hide itself when the option list is empty. */ - _setVisibility() { + _setVisibility(): void { Promise.resolve().then(() => { this.showPanel = !!this.options.length; this._changeDetectorRef.markForCheck(); }); } + /** Emits the `select` event. */ + _emitSelectEvent(option: MdOption): void { + const selectEvent = new MdAutocompleteSelect(this, option); + this.select.emit(selectEvent); + } + /** Sets a class on the panel based on its position (used to set y-offset). */ _getClassList() { return {