diff --git a/src/cdk/a11y/live-announcer/live-announcer.spec.ts b/src/cdk/a11y/live-announcer/live-announcer.spec.ts index 7a37b21d4340..d9b18ec0d927 100644 --- a/src/cdk/a11y/live-announcer/live-announcer.spec.ts +++ b/src/cdk/a11y/live-announcer/live-announcer.spec.ts @@ -108,6 +108,30 @@ describe('LiveAnnouncer', () => { .toBe(1, 'Expected only one live announcer element in the DOM.'); })); + it('should clear any previous timers when a new one is started', fakeAsync(() => { + expect(ariaLiveElement.textContent).toBeFalsy(); + + announcer.announce('One'); + tick(50); + + announcer.announce('Two'); + tick(75); + + expect(ariaLiveElement.textContent).toBeFalsy(); + + tick(25); + + expect(ariaLiveElement.textContent).toBe('Two'); + })); + + it('should clear pending timeouts on destroy', fakeAsync(() => { + announcer.announce('Hey Google'); + announcer.ngOnDestroy(); + + // Since we're testing whether the timeouts were flushed, we don't need any + // assertions here. `fakeAsync` will fail the test if a timer was left over. + })); + }); describe('with a custom element', () => { diff --git a/src/cdk/a11y/live-announcer/live-announcer.ts b/src/cdk/a11y/live-announcer/live-announcer.ts index 0d5a8d7dfa15..9dfd0aa9a1e8 100644 --- a/src/cdk/a11y/live-announcer/live-announcer.ts +++ b/src/cdk/a11y/live-announcer/live-announcer.ts @@ -29,8 +29,9 @@ export type AriaLivePoliteness = 'off' | 'polite' | 'assertive'; @Injectable({providedIn: 'root'}) export class LiveAnnouncer implements OnDestroy { - private readonly _liveElement: HTMLElement; + private _liveElement: HTMLElement; private _document: Document; + private _previousTimeout?: number; constructor( @Optional() @Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN) elementToken: any, @@ -63,7 +64,8 @@ export class LiveAnnouncer implements OnDestroy { // (using JAWS 17 at time of this writing). return this._ngZone.runOutsideAngular(() => { return new Promise(resolve => { - setTimeout(() => { + clearTimeout(this._previousTimeout); + this._previousTimeout = setTimeout(() => { this._liveElement.textContent = message; resolve(); }, 100); @@ -72,8 +74,11 @@ export class LiveAnnouncer implements OnDestroy { } ngOnDestroy() { + clearTimeout(this._previousTimeout); + if (this._liveElement && this._liveElement.parentNode) { this._liveElement.parentNode.removeChild(this._liveElement); + this._liveElement = null!; } }