diff --git a/src/lib/button-toggle/button-toggle.ts b/src/lib/button-toggle/button-toggle.ts index 4475dec407dc..5c4ae67fedc3 100644 --- a/src/lib/button-toggle/button-toggle.ts +++ b/src/lib/button-toggle/button-toggle.ts @@ -16,6 +16,7 @@ import { HostBinding, Input, OnInit, + OnDestroy, Optional, Output, QueryList, @@ -271,7 +272,7 @@ export class MdButtonToggleGroupMultiple extends _MdButtonToggleGroupMixinBase 'class': 'mat-button-toggle' } }) -export class MdButtonToggle implements OnInit { +export class MdButtonToggle implements OnInit, OnDestroy { /** Whether or not this button toggle is checked. */ private _checked: boolean = false; @@ -287,6 +288,9 @@ export class MdButtonToggle implements OnInit { /** Whether or not the button toggle is a single selection. */ private _isSingleSelector: boolean = null; + /** Unregister function for _buttonToggleDispatcher **/ + private _removeUniqueSelectionListener: () => void = () => {}; + @ViewChild('input') _inputElement: ElementRef; /** The parent button toggle group (exclusive selection). Optional. */ @@ -372,11 +376,12 @@ export class MdButtonToggle implements OnInit { this.buttonToggleGroupMultiple = toggleGroupMultiple; if (this.buttonToggleGroup) { - _buttonToggleDispatcher.listen((id: string, name: string) => { - if (id != this.id && name == this.name) { - this.checked = false; - } - }); + this._removeUniqueSelectionListener = + _buttonToggleDispatcher.listen((id: string, name: string) => { + if (id != this.id && name == this.name) { + this.checked = false; + } + }); this._type = 'radio'; this.name = this.buttonToggleGroup.name; @@ -446,4 +451,9 @@ export class MdButtonToggle implements OnInit { event.value = this._value; this.change.emit(event); } + + // Unregister buttonToggleDispatcherListener on destroy + ngOnDestroy(): void { + this._removeUniqueSelectionListener(); + } } diff --git a/src/lib/core/coordination/unique-selection-dispatcher.spec.ts b/src/lib/core/coordination/unique-selection-dispatcher.spec.ts new file mode 100644 index 000000000000..32b8e81fa930 --- /dev/null +++ b/src/lib/core/coordination/unique-selection-dispatcher.spec.ts @@ -0,0 +1,32 @@ +import {UniqueSelectionDispatcher} from './unique-selection-dispatcher'; + + +describe('Unique selection dispatcher', () => { + + describe('register', () => { + it('once unregistered the listener must not be called on notify', (done) => { + let dispatcher: UniqueSelectionDispatcher = new UniqueSelectionDispatcher(); + let called = false; + + // Register first listener + dispatcher.listen(() => { + called = true; + }); + + // Register a listener + let deregisterFn = dispatcher.listen(() => { + done.fail('Should not be called'); + }); + + // Unregister + deregisterFn(); + + // Call registered listeners + dispatcher.notify('testId', 'testName'); + + expect(called).toBeTruthy('Registered listener must be called.'); + + done(); + }); + }); +}); diff --git a/src/lib/core/coordination/unique-selection-dispatcher.ts b/src/lib/core/coordination/unique-selection-dispatcher.ts index 46eb5d0b8189..6f96f5713f74 100644 --- a/src/lib/core/coordination/unique-selection-dispatcher.ts +++ b/src/lib/core/coordination/unique-selection-dispatcher.ts @@ -36,9 +36,17 @@ export class UniqueSelectionDispatcher { } } - /** Listen for future changes to item selection. */ - listen(listener: UniqueSelectionDispatcherListener) { + /** + * Listen for future changes to item selection. + * @return Function used to unregister listener + **/ + listen(listener: UniqueSelectionDispatcherListener): () => void { this._listeners.push(listener); + return () => { + this._listeners = this._listeners.filter((registered: UniqueSelectionDispatcherListener) => { + return listener !== registered; + }); + }; } } diff --git a/src/lib/expansion/accordion-item.ts b/src/lib/expansion/accordion-item.ts index 08cbfb4467da..52ec10681bc8 100644 --- a/src/lib/expansion/accordion-item.ts +++ b/src/lib/expansion/accordion-item.ts @@ -48,19 +48,24 @@ export class AccordionItem implements OnDestroy { } private _expanded: boolean; + /** Unregister function for _expansionDispatcher **/ + private _removeUniqueSelectionListener: () => void = () => {}; + constructor(@Optional() public accordion: CdkAccordion, protected _expansionDispatcher: UniqueSelectionDispatcher) { - _expansionDispatcher.listen((id: string, accordionId: string) => { - if (this.accordion && !this.accordion.multi && - this.accordion.id === accordionId && this.id !== id) { - this.expanded = false; - } - }); + this._removeUniqueSelectionListener = + _expansionDispatcher.listen((id: string, accordionId: string) => { + if (this.accordion && !this.accordion.multi && + this.accordion.id === accordionId && this.id !== id) { + this.expanded = false; + } + }); } /** Emits an event for the accordion item being destroyed. */ ngOnDestroy() { this.destroyed.emit(); + this._removeUniqueSelectionListener(); } /** Toggles the expanded state of the accordion item. */ diff --git a/src/lib/radio/radio.ts b/src/lib/radio/radio.ts index 2d141f3fd7c4..1cea973ed216 100644 --- a/src/lib/radio/radio.ts +++ b/src/lib/radio/radio.ts @@ -458,6 +458,9 @@ export class MdRadioButton extends _MdRadioButtonMixinBase /** Reference to the current focus ripple. */ private _focusRipple: RippleRef; + /** Unregister function for _radioDispatcher **/ + private _removeUniqueSelectionListener: () => void = () => {}; + /** The native `` element */ @ViewChild('input') _inputElement: ElementRef; @@ -473,11 +476,12 @@ export class MdRadioButton extends _MdRadioButtonMixinBase // TODO(jelbourn): Assert that there's no name binding AND a parent radio group. this.radioGroup = radioGroup; - _radioDispatcher.listen((id: string, name: string) => { - if (id != this.id && name == this.name) { - this.checked = false; - } - }); + this._removeUniqueSelectionListener = + _radioDispatcher.listen((id: string, name: string) => { + if (id != this.id && name == this.name) { + this.checked = false; + } + }); } /** Focuses the radio button. */ @@ -513,6 +517,7 @@ export class MdRadioButton extends _MdRadioButtonMixinBase ngOnDestroy() { this._focusOriginMonitor.stopMonitoring(this._inputElement.nativeElement); + this._removeUniqueSelectionListener(); } /** Dispatch change event with current value. */