feat(select): emit change event (#2458)

* 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.
This commit is contained in:
Kristiyan Kostadinov
2017-01-12 22:24:25 +01:00
committed by tinayuangao
parent 52aa71584f
commit e5bd15cc6b
4 changed files with 90 additions and 7 deletions
+10 -1
View File
@@ -16,7 +16,6 @@
</md-card>
</div>
<md-card>
<md-select placeholder="Drink" [(ngModel)]="currentDrink" [required]="isRequired" [disabled]="isDisabled"
#drinkControl="ngModel">
@@ -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>
+8 -1
View File
@@ -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();
}
}
+54 -1
View File
@@ -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;
@@ -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({
@@ -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
+18 -4
View File
@@ -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',
@@ -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,
@@ -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);
});
@@ -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(' ');