Skip to content

Commit 2b78839

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 372436c commit 2b78839

File tree

3 files changed

+69
-2
lines changed

3 files changed

+69
-2
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
432432
this._setTriggerValue(event.source.value);
433433
this._onChange(event.source.value);
434434
this._element.nativeElement.focus();
435+
this.autocomplete._emitSelectEvent(event.source);
435436
}
436437

437438
this.closePanel();

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
MdAutocomplete,
2121
MdAutocompleteModule,
2222
MdAutocompleteTrigger,
23+
MdAutocompleteSelect,
2324
} from './index';
2425
import {MdInputModule} from '../input/index';
2526
import {Subscription} from 'rxjs/Subscription';
@@ -55,7 +56,8 @@ describe('MdAutocomplete', () => {
5556
AutocompleteWithOnPushDelay,
5657
AutocompleteWithNativeInput,
5758
AutocompleteWithoutPanel,
58-
AutocompleteWithFormsAndNonfloatingPlaceholder
59+
AutocompleteWithFormsAndNonfloatingPlaceholder,
60+
AutocompleteWithSelectEvent,
5961
],
6062
providers: [
6163
{provide: OverlayContainer, useFactory: () => {
@@ -1463,6 +1465,29 @@ describe('MdAutocomplete', () => {
14631465
expect(panel.classList).toContain(visibleClass, `Expected panel to be visible.`);
14641466
});
14651467
}));
1468+
1469+
it('should call emit an event when an option is selected', fakeAsync(() => {
1470+
let fixture = TestBed.createComponent(AutocompleteWithSelectEvent);
1471+
1472+
fixture.detectChanges();
1473+
fixture.componentInstance.trigger.openPanel();
1474+
tick();
1475+
fixture.detectChanges();
1476+
1477+
let options = overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
1478+
let spy = fixture.componentInstance.select;
1479+
1480+
options[1].click();
1481+
tick();
1482+
fixture.detectChanges();
1483+
1484+
expect(spy).toHaveBeenCalledTimes(1);
1485+
1486+
let event = spy.calls.mostRecent().args[0] as MdAutocompleteSelect;
1487+
1488+
expect(event.source).toBe(fixture.componentInstance.autocomplete);
1489+
expect(event.option.value).toBe('Washington');
1490+
}));
14661491
});
14671492

14681493
@Component({
@@ -1706,3 +1731,25 @@ class AutocompleteWithoutPanel {
17061731
class AutocompleteWithFormsAndNonfloatingPlaceholder {
17071732
formControl = new FormControl('California');
17081733
}
1734+
1735+
@Component({
1736+
template: `
1737+
<md-input-container>
1738+
<input mdInput placeholder="State" [mdAutocomplete]="auto" [(ngModel)]="selectedState">
1739+
</md-input-container>
1740+
1741+
<md-autocomplete #auto="mdAutocomplete" (select)="select($event)">
1742+
<md-option *ngFor="let state of states" [value]="state">
1743+
<span>{{ state }}</span>
1744+
</md-option>
1745+
</md-autocomplete>
1746+
`
1747+
})
1748+
class AutocompleteWithSelectEvent {
1749+
selectedState: string;
1750+
states = ['New York', 'Washington', 'Oregon'];
1751+
select = jasmine.createSpy('select callback');
1752+
1753+
@ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger;
1754+
@ViewChild(MdAutocomplete) autocomplete: MdAutocomplete;
1755+
}

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} 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',
@@ -60,6 +70,9 @@ export class MdAutocomplete implements AfterContentInit {
6070
/** Function that maps an option's control value to its display value in the trigger. */
6171
@Input() displayWith: ((value: any) => string) | null = null;
6272

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

@@ -85,13 +98,19 @@ export class MdAutocomplete implements AfterContentInit {
8598
}
8699

87100
/** Panel should hide itself when the option list is empty. */
88-
_setVisibility() {
101+
_setVisibility(): void {
89102
Promise.resolve().then(() => {
90103
this.showPanel = !!this.options.length;
91104
this._changeDetectorRef.markForCheck();
92105
});
93106
}
94107

108+
/** Emits the `select` event. */
109+
_emitSelectEvent(option: MdOption): void {
110+
const selectEvent = new MdAutocompleteSelect(this, option);
111+
this.select.emit(selectEvent);
112+
}
113+
95114
/** Sets a class on the panel based on whether it is visible. */
96115
_getClassList() {
97116
return {

0 commit comments

Comments
 (0)