diff --git a/src/demo-app/select/select-demo.html b/src/demo-app/select/select-demo.html
index aae5985aae20..f1e903f4824b 100644
--- a/src/demo-app/select/select-demo.html
+++ b/src/demo-app/select/select-demo.html
@@ -16,7 +16,6 @@
-
@@ -34,5 +33,15 @@
+
+
+
+ {{ starter.viewValue }}
+
+
+ Change event value: {{ latestChangeEvent?.value }}
+
+
+
This div is for testing scrolled selects.
diff --git a/src/demo-app/select/select-demo.ts b/src/demo-app/select/select-demo.ts
index ee2c542537c2..4196edbc633d 100644
--- a/src/demo-app/select/select-demo.ts
+++ b/src/demo-app/select/select-demo.ts
@@ -1,5 +1,6 @@
import {Component} from '@angular/core';
import {FormControl} from '@angular/forms';
+import {MdSelectChange} from '@angular/material';
@Component({
moduleId: module.id,
@@ -12,6 +13,7 @@ export class SelectDemo {
isDisabled = false;
showSelect = false;
currentDrink: string;
+ latestChangeEvent: MdSelectChange;
foodControl = new FormControl('pizza-1');
foods = [
@@ -32,8 +34,13 @@ export class SelectDemo {
{value: 'milk-8', viewValue: 'Milk'},
];
+ pokemon = [
+ {value: 'bulbasaur-0', viewValue: 'Bulbasaur'},
+ {value: 'charizard-1', viewValue: 'Charizard'},
+ {value: 'squirtle-2', viewValue: 'Squirtle'}
+ ];
+
toggleDisabled() {
this.foodControl.enabled ? this.foodControl.disable() : this.foodControl.enable();
}
-
}
diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts
index f5e3f6b9f40b..5a075462ac1b 100644
--- a/src/lib/select/select.spec.ts
+++ b/src/lib/select/select.spec.ts
@@ -16,7 +16,7 @@ describe('MdSelect', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdSelectModule.forRoot(), ReactiveFormsModule, FormsModule],
- declarations: [BasicSelect, NgModelSelect, ManySelects, NgIfSelect],
+ declarations: [BasicSelect, NgModelSelect, ManySelects, NgIfSelect, SelectWithChangeEvent],
providers: [
{provide: OverlayContainer, useFactory: () => {
overlayContainerElement = document.createElement('div') as HTMLElement;
@@ -1140,6 +1140,38 @@ describe('MdSelect', () => {
});
+ describe('change event', () => {
+ let fixture: ComponentFixture;
+ let trigger: HTMLElement;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SelectWithChangeEvent);
+ fixture.detectChanges();
+
+ trigger = fixture.debugElement.query(By.css('.md-select-trigger')).nativeElement;
+ });
+
+ it('should emit an event when the selected option has changed', () => {
+ trigger.click();
+ fixture.detectChanges();
+
+ (overlayContainerElement.querySelector('md-option') as HTMLElement).click();
+
+ expect(fixture.componentInstance.changeListener).toHaveBeenCalled();
+ });
+
+ it('should not emit multiple change events for the same option', () => {
+ trigger.click();
+ fixture.detectChanges();
+
+ let option = overlayContainerElement.querySelector('md-option') as HTMLElement;
+
+ option.click();
+ option.click();
+
+ expect(fixture.componentInstance.changeListener).toHaveBeenCalledTimes(1);
+ });
+ });
});
@Component({
@@ -1234,7 +1266,28 @@ class NgIfSelect {
@ViewChild(MdSelect) select: MdSelect;
}
+@Component({
+ selector: 'select-with-change-event',
+ template: `
+
+ {{ food }}
+
+ `
+})
+class SelectWithChangeEvent {
+ foods: string[] = [
+ 'steak-0',
+ 'pizza-1',
+ 'tacos-2',
+ 'sandwich-3',
+ 'chips-4',
+ 'eggs-5',
+ 'pasta-6',
+ 'sushi-7'
+ ];
+ changeListener = jasmine.createSpy('MdSelect change listener');
+}
/**
* TODO: Move this to core testing utility until Angular has event faking
diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts
index e2de6c548829..dcbc1ad92374 100644
--- a/src/lib/select/select.ts
+++ b/src/lib/select/select.ts
@@ -64,6 +64,11 @@ export const SELECT_PANEL_PADDING_Y = 16;
*/
export const SELECT_PANEL_VIEWPORT_PADDING = 8;
+/** Change event object that is emitted when the select value has changed. */
+export class MdSelectChange {
+ constructor(public source: MdSelect, public value: any) { }
+}
+
@Component({
moduleId: module.id,
selector: 'md-select, mat-select',
@@ -214,10 +219,13 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
set required(value: any) { this._required = coerceBooleanProperty(value); }
/** Event emitted when the select has been opened. */
- @Output() onOpen = new EventEmitter();
+ @Output() onOpen: EventEmitter = new EventEmitter();
/** Event emitted when the select has been closed. */
- @Output() onClose = new EventEmitter();
+ @Output() onClose: EventEmitter = new EventEmitter();
+
+ /** Event emitted when the selected value has been changed by the user. */
+ @Output() change: EventEmitter = new EventEmitter();
constructor(private _element: ElementRef, private _renderer: Renderer,
private _viewportRuler: ViewportRuler, @Optional() private _dir: Dir,
@@ -429,8 +437,8 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
private _listenToOptions(): void {
this.options.forEach((option: MdOption) => {
const sub = option.onSelect.subscribe((isUserInput: boolean) => {
- if (isUserInput) {
- this._onChange(option.value);
+ if (isUserInput && this._selected !== option) {
+ this._emitChangeEvent(option);
}
this._onSelect(option);
});
@@ -444,6 +452,12 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
this._subscriptions = [];
}
+ /** Emits an event when the user selects an option. */
+ private _emitChangeEvent(option: MdOption): void {
+ this._onChange(option.value);
+ this.change.emit(new MdSelectChange(this, option.value));
+ }
+
/** Records option IDs to pass to the aria-owns property. */
private _setOptionIds() {
this._optionIds = this.options.map(option => option.id).join(' ');