From 2dd5c7c3f1238c882e64af90b8a6371401a918ac Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 23 Aug 2017 22:25:17 +0200 Subject: [PATCH] feat(autocomplete): emit event when an option is selected (#4187) Fixes #4094. Fixes #3645. --- src/lib/autocomplete/autocomplete-trigger.ts | 1 + src/lib/autocomplete/autocomplete.spec.ts | 75 +++++++++++++++++++- src/lib/autocomplete/autocomplete.ts | 21 +++++- 3 files changed, 95 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..01e940027f05 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -21,6 +21,7 @@ import { MdAutocomplete, MdAutocompleteModule, MdAutocompleteTrigger, + MdAutocompleteSelectedEvent, } 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,55 @@ describe('MdAutocomplete', () => { expect(panel.classList).toContain(visibleClass, `Expected panel to be visible.`); }); })); + + it('should 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.optionSelected; + + options[1].click(); + tick(); + fixture.detectChanges(); + + expect(spy).toHaveBeenCalledTimes(1); + + let event = spy.calls.mostRecent().args[0] as MdAutocompleteSelectedEvent; + + expect(event.source).toBe(fixture.componentInstance.autocomplete); + expect(event.option.value).toBe('Washington'); + })); + + it('should emit an event when a newly-added option is selected', fakeAsync(() => { + let fixture = TestBed.createComponent(AutocompleteWithSelectEvent); + + fixture.detectChanges(); + fixture.componentInstance.trigger.openPanel(); + tick(); + fixture.detectChanges(); + + fixture.componentInstance.states.push('Puerto Rico'); + fixture.detectChanges(); + + let options = overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + let spy = fixture.componentInstance.optionSelected; + + options[3].click(); + tick(); + fixture.detectChanges(); + + expect(spy).toHaveBeenCalledTimes(1); + + let event = spy.calls.mostRecent().args[0] as MdAutocompleteSelectedEvent; + + expect(event.source).toBe(fixture.componentInstance.autocomplete); + expect(event.option.value).toBe('Puerto Rico'); + })); }); @Component({ @@ -1826,3 +1877,25 @@ class AutocompleteWithGroups { } ]; } + +@Component({ + template: ` + + + + + + + {{ state }} + + + ` +}) +class AutocompleteWithSelectEvent { + selectedState: string; + states = ['New York', 'Washington', 'Oregon']; + optionSelected = jasmine.createSpy('optionSelected 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..3126485ea6e5 100644 --- a/src/lib/autocomplete/autocomplete.ts +++ b/src/lib/autocomplete/autocomplete.ts @@ -18,16 +18,25 @@ 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 MdAutocompleteSelectedEvent { + constructor(public source: MdAutocomplete, public option: MdOption) { } +} + + @Component({ moduleId: module.id, selector: 'md-autocomplete, mat-autocomplete', @@ -63,6 +72,10 @@ 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() optionSelected: 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 event = new MdAutocompleteSelectedEvent(this, option); + this.optionSelected.emit(event); + } + /** Sets a class on the panel based on whether it is visible. */ _getClassList() { return {