Skip to content

Commit

Permalink
feat(autocomplete): emit event when an option is selected
Browse files Browse the repository at this point in the history
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 angular#4094.
Fixes angular#3645.
  • Loading branch information
crisbeto committed Aug 20, 2017
1 parent 372436c commit 2b78839
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,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();
Expand Down
49 changes: 48 additions & 1 deletion src/lib/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
MdAutocomplete,
MdAutocompleteModule,
MdAutocompleteTrigger,
MdAutocompleteSelect,
} from './index';
import {MdInputModule} from '../input/index';
import {Subscription} from 'rxjs/Subscription';
Expand Down Expand Up @@ -55,7 +56,8 @@ describe('MdAutocomplete', () => {
AutocompleteWithOnPushDelay,
AutocompleteWithNativeInput,
AutocompleteWithoutPanel,
AutocompleteWithFormsAndNonfloatingPlaceholder
AutocompleteWithFormsAndNonfloatingPlaceholder,
AutocompleteWithSelectEvent,
],
providers: [
{provide: OverlayContainer, useFactory: () => {
Expand Down Expand Up @@ -1463,6 +1465,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<HTMLElement>;
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({
Expand Down Expand Up @@ -1706,3 +1731,25 @@ class AutocompleteWithoutPanel {
class AutocompleteWithFormsAndNonfloatingPlaceholder {
formControl = new FormControl('California');
}

@Component({
template: `
<md-input-container>
<input mdInput placeholder="State" [mdAutocomplete]="auto" [(ngModel)]="selectedState">
</md-input-container>
<md-autocomplete #auto="mdAutocomplete" (select)="select($event)">
<md-option *ngFor="let state of states" [value]="state">
<span>{{ state }}</span>
</md-option>
</md-autocomplete>
`
})
class AutocompleteWithSelectEvent {
selectedState: string;
states = ['New York', 'Washington', 'Oregon'];
select = jasmine.createSpy('select callback');

@ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger;
@ViewChild(MdAutocomplete) autocomplete: MdAutocomplete;
}
21 changes: 20 additions & 1 deletion src/lib/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,26 @@ import {
ViewEncapsulation,
ChangeDetectorRef,
ChangeDetectionStrategy,
EventEmitter,
Output,
} from '@angular/core';
import {MdOption} 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',
Expand Down Expand Up @@ -60,6 +70,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<MdAutocompleteSelect> = new EventEmitter<MdAutocompleteSelect>();

/** Unique ID to be used by autocomplete trigger's "aria-owns" property. */
id: string = `md-autocomplete-${_uniqueAutocompleteIdCounter++}`;

Expand All @@ -85,13 +98,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 {
Expand Down

0 comments on commit 2b78839

Please sign in to comment.