Skip to content

Commit

Permalink
feat(select): emit change event (#2458)
Browse files Browse the repository at this point in the history
* feat(select): emit change event

Adds an event to `md-select` that is emitted when the selected option has changed.

Fixes #2248.

* Separate the change event example from the ones that use the form directives.

* Remove leftover event listener.

* More leftovers.
  • Loading branch information
crisbeto authored and tinayuangao committed Jan 12, 2017
1 parent 52aa715 commit e5bd15c
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 7 deletions.
11 changes: 10 additions & 1 deletion src/demo-app/select/select-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
</md-card>
</div>


<md-card>
<md-select placeholder="Drink" [(ngModel)]="currentDrink" [required]="isRequired" [disabled]="isDisabled"
#drinkControl="ngModel">
Expand All @@ -34,5 +33,15 @@
<button md-button (click)="drinkControl.reset()">RESET</button>
</md-card>

<div *ngIf="showSelect">
<md-card>
<md-select placeholder="Starter Pokemon" (change)="latestChangeEvent = $event">
<md-option *ngFor="let starter of pokemon" [value]="starter.value"> {{ starter.viewValue }} </md-option>
</md-select>

<p> Change event value: {{ latestChangeEvent?.value }} </p>
</md-card>
</div>

</div>
<div style="height: 500px">This div is for testing scrolled selects.</div>
9 changes: 8 additions & 1 deletion src/demo-app/select/select-demo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Component} from '@angular/core';
import {FormControl} from '@angular/forms';
import {MdSelectChange} from '@angular/material';

@Component({
moduleId: module.id,
Expand All @@ -12,6 +13,7 @@ export class SelectDemo {
isDisabled = false;
showSelect = false;
currentDrink: string;
latestChangeEvent: MdSelectChange;
foodControl = new FormControl('pizza-1');

foods = [
Expand All @@ -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();
}

}
55 changes: 54 additions & 1 deletion src/lib/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1155,6 +1155,38 @@ describe('MdSelect', () => {

});

describe('change event', () => {
let fixture: ComponentFixture<SelectWithChangeEvent>;
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({
Expand Down Expand Up @@ -1249,7 +1281,28 @@ class NgIfSelect {
@ViewChild(MdSelect) select: MdSelect;
}

@Component({
selector: 'select-with-change-event',
template: `
<md-select (change)="changeListener($event)">
<md-option *ngFor="let food of foods" [value]="food">{{ food }}</md-option>
</md-select>
`
})
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
Expand Down
22 changes: 18 additions & 4 deletions src/lib/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -217,10 +222,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<void> = new EventEmitter<void>();

/** Event emitted when the select has been closed. */
@Output() onClose = new EventEmitter();
@Output() onClose: EventEmitter<void> = new EventEmitter<void>();

/** Event emitted when the selected value has been changed by the user. */
@Output() change: EventEmitter<MdSelectChange> = new EventEmitter<MdSelectChange>();

constructor(private _element: ElementRef, private _renderer: Renderer,
private _viewportRuler: ViewportRuler, @Optional() private _dir: Dir,
Expand Down Expand Up @@ -434,8 +442,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);
});
Expand All @@ -449,6 +457,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(' ');
Expand Down

0 comments on commit e5bd15c

Please sign in to comment.