Skip to content

Commit a150494

Browse files
crisbetovivian-hu-zz
authored andcommittedNov 8, 2018
fix(a11y): aria-live directive announcing the same text multiple times (#13467)
The `CdkAriaLive` directive uses a `MutationObserver` to monitor for DOM content changes and to announce them through the `LiveAnnouncer`. Since the `MutationObserver` also fires for things like attribute changes, the same text can be announced more than once. This is visible on the calendar where the same text is announced twice when changing views.
1 parent 1ef16ac commit a150494

File tree

2 files changed

+25
-2
lines changed

2 files changed

+25
-2
lines changed
 

‎src/cdk/a11y/live-announcer/live-announcer.spec.ts

+16
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,22 @@ describe('CdkAriaLive', () => {
231231

232232
expect(announcer.announce).toHaveBeenCalledWith('Newest content', 'assertive');
233233
}));
234+
235+
it('should not announce the same text multiple times', fakeAsync(() => {
236+
fixture.componentInstance.content = 'Content';
237+
fixture.detectChanges();
238+
invokeMutationCallbacks();
239+
flush();
240+
241+
expect(announcer.announce).toHaveBeenCalledTimes(1);
242+
243+
fixture.detectChanges();
244+
invokeMutationCallbacks();
245+
flush();
246+
247+
expect(announcer.announce).toHaveBeenCalledTimes(1);
248+
}));
249+
234250
});
235251

236252

‎src/cdk/a11y/live-announcer/live-announcer.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,21 @@ export class CdkAriaLive implements OnDestroy {
131131
.observe(this._elementRef)
132132
.subscribe(() => {
133133
// Note that we use textContent here, rather than innerText, in order to avoid a reflow.
134-
const element = this._elementRef.nativeElement;
135-
this._liveAnnouncer.announce(element.textContent, this._politeness);
134+
const elementText = this._elementRef.nativeElement.textContent;
135+
136+
// The `MutationObserver` fires also for attribute
137+
// changes which we don't want to announce.
138+
if (elementText !== this._previousAnnouncedText) {
139+
this._liveAnnouncer.announce(elementText, this._politeness);
140+
this._previousAnnouncedText = elementText;
141+
}
136142
});
137143
});
138144
}
139145
}
140146
private _politeness: AriaLivePoliteness = 'off';
141147

148+
private _previousAnnouncedText?: string;
142149
private _subscription: Subscription | null;
143150

144151
constructor(private _elementRef: ElementRef, private _liveAnnouncer: LiveAnnouncer,

0 commit comments

Comments
 (0)
Please sign in to comment.