Skip to content

Commit 21e4ae2

Browse files
committed
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.
1 parent 8d0cd04 commit 21e4ae2

File tree

3 files changed

+69
-4
lines changed

3 files changed

+69
-4
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
332332
this._clearPreviousSelectedOption(event.source);
333333
this._setTriggerValue(event.source.value);
334334
this._onChange(event.source.value);
335+
this.autocomplete._emitSelectEvent(event.source);
335336
}
336337

337338
this.closePanel();

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {ENTER, DOWN_ARROW, SPACE, UP_ARROW, HOME, END} from '../core/keyboard/ke
2020
import {MdOption} from '../core/option/option';
2121
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
2222
import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
23-
import {MdAutocomplete} from './autocomplete';
23+
import {MdAutocomplete, MdAutocompleteSelect} from './autocomplete';
2424
import {MdInputContainer} from '../input/input-container';
2525
import {Observable} from 'rxjs/Observable';
2626
import {dispatchFakeEvent} from '../core/testing/dispatch-events';
@@ -47,7 +47,8 @@ describe('MdAutocomplete', () => {
4747
AutocompleteWithoutForms,
4848
NgIfAutocomplete,
4949
AutocompleteWithNgModel,
50-
AutocompleteWithOnPushDelay
50+
AutocompleteWithOnPushDelay,
51+
AutocompleteWithSelectEvent
5152
],
5253
providers: [
5354
{provide: OverlayContainer, useFactory: () => {
@@ -1146,6 +1147,29 @@ describe('MdAutocomplete', () => {
11461147
expect(panel.classList).toContain(visibleClass, `Expected panel to be visible.`);
11471148
});
11481149
}));
1150+
1151+
it('should call emit an event when an option is selected', fakeAsync(() => {
1152+
let fixture = TestBed.createComponent(AutocompleteWithSelectEvent);
1153+
1154+
fixture.detectChanges();
1155+
fixture.componentInstance.trigger.openPanel();
1156+
tick();
1157+
fixture.detectChanges();
1158+
1159+
let options = overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
1160+
let spy = fixture.componentInstance.select;
1161+
1162+
options[1].click();
1163+
tick();
1164+
fixture.detectChanges();
1165+
1166+
expect(spy).toHaveBeenCalledTimes(1);
1167+
1168+
let event = spy.calls.mostRecent().args[0] as MdAutocompleteSelect;
1169+
1170+
expect(event.source).toBe(fixture.componentInstance.autocomplete);
1171+
expect(event.option.value).toBe('Washington');
1172+
}));
11491173
});
11501174

11511175
@Component({
@@ -1319,6 +1343,29 @@ class AutocompleteWithOnPushDelay implements OnInit {
13191343
}
13201344

13211345

1346+
@Component({
1347+
template: `
1348+
<md-input-container>
1349+
<input mdInput placeholder="State" [mdAutocomplete]="auto" [(ngModel)]="selectedState">
1350+
</md-input-container>
1351+
1352+
<md-autocomplete #auto="mdAutocomplete" (select)="select($event)">
1353+
<md-option *ngFor="let state of states" [value]="state">
1354+
<span>{{ state }}</span>
1355+
</md-option>
1356+
</md-autocomplete>
1357+
`
1358+
})
1359+
class AutocompleteWithSelectEvent {
1360+
selectedState: string;
1361+
states = ['New York', 'Washington', 'Oregon'];
1362+
select = jasmine.createSpy('select callback');
1363+
1364+
@ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger;
1365+
@ViewChild(MdAutocomplete) autocomplete: MdAutocomplete;
1366+
}
1367+
1368+
13221369
/** This is a mock keyboard event to test keyboard events in the autocomplete. */
13231370
class MockKeyboardEvent {
13241371
constructor(public keyCode: number) {}

src/lib/autocomplete/autocomplete.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@ import {
99
ViewChild,
1010
ViewEncapsulation,
1111
ChangeDetectorRef,
12+
Output,
13+
EventEmitter,
1214
} from '@angular/core';
13-
import {MdOption} from '../core';
15+
import {MdOption} from '../core/option/option';
1416
import {ActiveDescendantKeyManager} from '../core/a11y/activedescendant-key-manager';
1517

18+
1619
/**
1720
* Autocomplete IDs need to be unique across components, so this counter exists outside of
1821
* the component definition.
1922
*/
2023
let _uniqueAutocompleteIdCounter = 0;
2124

25+
/** Event object that is emitted when an autocomplete option is selected */
26+
export class MdAutocompleteSelect {
27+
constructor(public source: MdAutocomplete, public option: MdOption) { }
28+
}
29+
2230
export type AutocompletePositionY = 'above' | 'below';
2331

2432
@Component({
@@ -50,6 +58,9 @@ export class MdAutocomplete implements AfterContentInit {
5058
/** Function that maps an option's control value to its display value in the trigger. */
5159
@Input() displayWith: (value: any) => string;
5260

61+
/** Event that is emitted whenever an option from the list is selected. */
62+
@Output() select: EventEmitter<MdAutocompleteSelect> = new EventEmitter<MdAutocompleteSelect>();
63+
5364
/** Unique ID to be used by autocomplete trigger's "aria-owns" property. */
5465
id: string = `md-autocomplete-${_uniqueAutocompleteIdCounter++}`;
5566

@@ -70,13 +81,19 @@ export class MdAutocomplete implements AfterContentInit {
7081
}
7182

7283
/** Panel should hide itself when the option list is empty. */
73-
_setVisibility() {
84+
_setVisibility(): void {
7485
Promise.resolve().then(() => {
7586
this.showPanel = !!this.options.length;
7687
this._changeDetectorRef.markForCheck();
7788
});
7889
}
7990

91+
/** Emits the `select` event. */
92+
_emitSelectEvent(option: MdOption): void {
93+
const selectEvent = new MdAutocompleteSelect(this, option);
94+
this.select.emit(selectEvent);
95+
}
96+
8097
/** Sets a class on the panel based on its position (used to set y-offset). */
8198
_getClassList() {
8299
return {

0 commit comments

Comments
 (0)