Skip to content

Commit 58d161a

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 595cffd commit 58d161a

File tree

3 files changed

+68
-2
lines changed

3 files changed

+68
-2
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
427427
this._setTriggerValue(event.source.value);
428428
this._onChange(event.source.value);
429429
this._element.nativeElement.focus();
430+
this.autocomplete._emitSelectEvent(event.source);
430431
}
431432

432433
this.closePanel();

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
MdAutocomplete,
2222
MdAutocompleteModule,
2323
MdAutocompleteTrigger,
24+
MdAutocompleteSelect,
2425
} from './index';
2526
import {MdInputModule} from '../input/index';
2627
import {Subscription} from 'rxjs/Subscription';
@@ -57,7 +58,8 @@ describe('MdAutocomplete', () => {
5758
AutocompleteWithNativeInput,
5859
AutocompleteWithoutPanel,
5960
AutocompleteWithFormsAndNonfloatingPlaceholder,
60-
AutocompleteWithGroups
61+
AutocompleteWithGroups,
62+
AutocompleteWithSelectEvent,
6163
],
6264
providers: [
6365
{provide: OverlayContainer, useFactory: () => {
@@ -1548,6 +1550,29 @@ describe('MdAutocomplete', () => {
15481550
expect(panel.classList).toContain(visibleClass, `Expected panel to be visible.`);
15491551
});
15501552
}));
1553+
1554+
it('should call emit an event when an option is selected', fakeAsync(() => {
1555+
let fixture = TestBed.createComponent(AutocompleteWithSelectEvent);
1556+
1557+
fixture.detectChanges();
1558+
fixture.componentInstance.trigger.openPanel();
1559+
tick();
1560+
fixture.detectChanges();
1561+
1562+
let options = overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
1563+
let spy = fixture.componentInstance.select;
1564+
1565+
options[1].click();
1566+
tick();
1567+
fixture.detectChanges();
1568+
1569+
expect(spy).toHaveBeenCalledTimes(1);
1570+
1571+
let event = spy.calls.mostRecent().args[0] as MdAutocompleteSelect;
1572+
1573+
expect(event.source).toBe(fixture.componentInstance.autocomplete);
1574+
expect(event.option.value).toBe('Washington');
1575+
}));
15511576
});
15521577

15531578
@Component({
@@ -1826,3 +1851,24 @@ class AutocompleteWithGroups {
18261851
}
18271852
];
18281853
}
1854+
1855+
@Component({
1856+
template: `
1857+
<md-input-container>
1858+
<input mdInput placeholder="State" [mdAutocomplete]="auto" [(ngModel)]="selectedState">
1859+
</md-input-container>
1860+
<md-autocomplete #auto="mdAutocomplete" (select)="select($event)">
1861+
<md-option *ngFor="let state of states" [value]="state">
1862+
<span>{{ state }}</span>
1863+
</md-option>
1864+
</md-autocomplete>
1865+
`
1866+
})
1867+
class AutocompleteWithSelectEvent {
1868+
selectedState: string;
1869+
states = ['New York', 'Washington', 'Oregon'];
1870+
select = jasmine.createSpy('select callback');
1871+
1872+
@ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger;
1873+
@ViewChild(MdAutocomplete) autocomplete: MdAutocomplete;
1874+
}

src/lib/autocomplete/autocomplete.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,26 @@ import {
1818
ViewEncapsulation,
1919
ChangeDetectorRef,
2020
ChangeDetectionStrategy,
21+
EventEmitter,
22+
Output,
2123
} from '@angular/core';
2224
import {MdOption, MdOptgroup} from '../core';
2325
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
2426

27+
2528
/**
2629
* Autocomplete IDs need to be unique across components, so this counter exists outside of
2730
* the component definition.
2831
*/
2932
let _uniqueAutocompleteIdCounter = 0;
3033

34+
/** Event object that is emitted when an autocomplete option is selected */
35+
export class MdAutocompleteSelect {
36+
constructor(public source: MdAutocomplete, public option: MdOption) { }
37+
}
38+
39+
export type AutocompletePositionY = 'above' | 'below';
40+
3141
@Component({
3242
moduleId: module.id,
3343
selector: 'md-autocomplete, mat-autocomplete',
@@ -63,6 +73,9 @@ export class MdAutocomplete implements AfterContentInit {
6373
/** Function that maps an option's control value to its display value in the trigger. */
6474
@Input() displayWith: ((value: any) => string) | null = null;
6575

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

@@ -88,13 +101,19 @@ export class MdAutocomplete implements AfterContentInit {
88101
}
89102

90103
/** Panel should hide itself when the option list is empty. */
91-
_setVisibility() {
104+
_setVisibility(): void {
92105
Promise.resolve().then(() => {
93106
this.showPanel = !!this.options.length;
94107
this._changeDetectorRef.markForCheck();
95108
});
96109
}
97110

111+
/** Emits the `select` event. */
112+
_emitSelectEvent(option: MdOption): void {
113+
const selectEvent = new MdAutocompleteSelect(this, option);
114+
this.select.emit(selectEvent);
115+
}
116+
98117
/** Sets a class on the panel based on whether it is visible. */
99118
_getClassList() {
100119
return {

0 commit comments

Comments
 (0)