From 58d161a1140744d4181241c8a264afa0dbd82a9e 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 | 48 +++++++++++++++++++- src/lib/autocomplete/autocomplete.ts | 21 ++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index c0d0281f1a48..ade59b533b37 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -427,6 +427,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { this._setTriggerValue(event.source.value); this._onChange(event.source.value); this._element.nativeElement.focus(); + this.autocomplete._emitSelectEvent(event.source); } this.closePanel(); diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 3678b583caa5..6d5d47278798 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -21,6 +21,7 @@ import { MdAutocomplete, MdAutocompleteModule, MdAutocompleteTrigger, + MdAutocompleteSelect, } from './index'; import {MdInputModule} from '../input/index'; import {Subscription} from 'rxjs/Subscription'; @@ -57,7 +58,8 @@ describe('MdAutocomplete', () => { AutocompleteWithNativeInput, AutocompleteWithoutPanel, AutocompleteWithFormsAndNonfloatingPlaceholder, - AutocompleteWithGroups + AutocompleteWithGroups, + AutocompleteWithSelectEvent, ], providers: [ {provide: OverlayContainer, useFactory: () => { @@ -1548,6 +1550,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({ @@ -1826,3 +1851,24 @@ class AutocompleteWithGroups { } ]; } + +@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; +} diff --git a/src/lib/autocomplete/autocomplete.ts b/src/lib/autocomplete/autocomplete.ts index a7253fde8b13..60da405aebe4 100644 --- a/src/lib/autocomplete/autocomplete.ts +++ b/src/lib/autocomplete/autocomplete.ts @@ -18,16 +18,26 @@ import { ViewEncapsulation, ChangeDetectorRef, ChangeDetectionStrategy, + EventEmitter, + Output, } from '@angular/core'; import {MdOption, MdOptgroup} from '../core'; import {ActiveDescendantKeyManager} from '@angular/cdk/a11y'; + /** * 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({ moduleId: module.id, selector: 'md-autocomplete, mat-autocomplete', @@ -63,6 +73,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) | null = null; + /** 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++}`; @@ -88,13 +101,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 whether it is visible. */ _getClassList() { return {