Skip to content

Commit

Permalink
fix(live-announcer): duplicate live element when coming in from the s…
Browse files Browse the repository at this point in the history
…erver (#12378)

Fixes the case where the user might get multiple live announcer elements, if they're coming in from a server-side-rendered page. Along the same lines as #11940.
  • Loading branch information
crisbeto authored and jelbourn committed Aug 1, 2018
1 parent e644350 commit a10bfa4
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 3 deletions.
27 changes: 27 additions & 0 deletions src/cdk/a11y/live-announcer/live-announcer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,33 @@ describe('LiveAnnouncer', () => {
tick(100);
expect(spy).toHaveBeenCalled();
}));

it('should ensure that there is only one live element at a time', fakeAsync(() => {
announcer.ngOnDestroy();
fixture.destroy();

TestBed.resetTestingModule().configureTestingModule({
imports: [A11yModule],
declarations: [TestApp],
});

const extraElement = document.createElement('div');
extraElement.classList.add('cdk-live-announcer-element');
document.body.appendChild(extraElement);

inject([LiveAnnouncer], (la: LiveAnnouncer) => {
announcer = la;
ariaLiveElement = getLiveElement();
fixture = TestBed.createComponent(TestApp);
})();

announcer.announce('Hey Google');
tick(100);

expect(document.body.querySelectorAll('.cdk-live-announcer-element').length)
.toBe(1, 'Expected only one live announcer element in the DOM.');
}));

});

describe('with a custom element', () => {
Expand Down
15 changes: 12 additions & 3 deletions src/cdk/a11y/live-announcer/live-announcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ export type AriaLivePoliteness = 'off' | 'polite' | 'assertive';
@Injectable({providedIn: 'root'})
export class LiveAnnouncer implements OnDestroy {
private readonly _liveElement: HTMLElement;
private _document: Document;

constructor(
@Optional() @Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN) elementToken: any,
@Inject(DOCUMENT) private _document: any) {
@Inject(DOCUMENT) _document: any) {

// We inject the live element and document as `any` because the constructor signature cannot
// reference browser globals (HTMLElement, Document) on non-browser environments, since having
// a class decorator causes TypeScript to preserve the constructor signature types.
this._document = _document;
this._liveElement = elementToken || this._createLiveElement();
}

Expand Down Expand Up @@ -73,9 +75,16 @@ export class LiveAnnouncer implements OnDestroy {
}

private _createLiveElement(): HTMLElement {
let liveEl = this._document.createElement('div');
const elementClass = 'cdk-live-announcer-element';
const previousElements = this._document.getElementsByClassName(elementClass);

liveEl.classList.add('cdk-live-announcer-element');
// Remove any old containers. This can happen when coming in from a server-side-rendered page.
for (let i = 0; i < previousElements.length; i++) {
previousElements[i].parentNode!.removeChild(previousElements[i]);
}

const liveEl = this._document.createElement('div');
liveEl.classList.add(elementClass);
liveEl.classList.add('cdk-visually-hidden');

liveEl.setAttribute('aria-atomic', 'true');
Expand Down

0 comments on commit a10bfa4

Please sign in to comment.