Skip to content

fix(material/badge): correctly apply badge description #23562

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 27, 2021

Conversation

jelbourn
Copy link
Member

Previously, MatBadge was applying the provided description as an aria-label to the badge content element, which is a child element of the badge host. Then it was also applying an aria-describedby to that same content element. The end result is that the badge was being treated mainly as text content for a control, which isn't really the best way to convey its complementary nature. This behavior was causing problems in NVDA, where the badge content was overriding the host button's label.

This change makes the badge content itself aria-hidden and applies the badge description to the badge's host element via aria-describedby and completely removes any setting of aria-label. It also makes a handful of minor cleanup refactorings.

cc @zarend @amysorto

@jelbourn jelbourn added P2 The issue is important to a large percentage of users, with a workaround Accessibility This issue is related to accessibility (a11y) area: material/badge labels Sep 10, 2021
@jelbourn jelbourn requested a review from crisbeto September 10, 2021 02:53
@google-cla google-cla bot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Sep 10, 2021
@jelbourn jelbourn requested a review from a team as a code owner September 10, 2021 03:01
Copy link
Member

@crisbeto crisbeto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few nits.

}

this._content = newContentNormalized;
this._hasContent = !!newContentNormalized;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this a little redundant considering that we can check this._content?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, now that it always normalizes the content this is unnecessary. Removed.

@@ -84,22 +85,20 @@ export class MatBadge extends _MatBadgeBase implements OnDestroy, OnChanges, Can
@Input('matBadgePosition') position: MatBadgePosition = 'above after';

/** The content for the badge */
@Input('matBadge') content: string | number | undefined | null;
@Input('matBadge')
get content(): string | number | undefined | null {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could pull the string | number | undefined | null into a type so we don't have to repeat it in 3 places.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, I'm going to leave as-is for now.

@jelbourn jelbourn added the target: patch This PR is targeted for the next patch release label Sep 10, 2021
@jelbourn jelbourn added the action: merge The PR is ready for merge by the caretaker label Sep 10, 2021
@josephperrott josephperrott removed the request for review from a team September 10, 2021 22:36
@jelbourn
Copy link
Member Author

I've made a small change to this PR so that the badge element isn't added to the DOM until OnInit. A handful of Google apps have brittle tests that check the text content of the badge's host element, so moving the badge content before the e.g. button's content makes the tests start failing.

Copy link
Contributor

@zarend zarend left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will work 👍 .

It could be less confusing for a reader of this code to understand when we need to create the badge element. I have suggestions in the above comments, but that is not a big enough problem to be a blocker for me.

// ViewEngine only: when creating a badge through the Renderer, Angular remembers its index.
// We have to destroy it ourselves, otherwise it'll be retained in memory.
if (this._renderer.destroyNode) {
this._renderer.destroyNode!(this._badgeElement);
Copy link
Contributor

@zarend zarend Sep 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this non-null assert shouldn't be necessary since we truthy checked it in the if-statement's condition.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// on which the badge is attached won't necessarily be in the DOM until this point.
this._clearExistingBadges();

if (this.content && !this._badgeElement) {
this._badgeElement = this._createBadgeElement();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO: it's confusing that _updateContent sometimes handles creating the badge element, and sometimes the caller handles it. I spent a few minutes head scratching and thinking through the event lifecycle to figure this out.

From the method's description: /** Update the text content of the badge. */. In this situation it would make more sense for _updateContent to do that one thing, and not do anything else. It's up to the caller to make sure the badge element is created.

I suggest making the caller handle creating the badge element, or making _updateContent create the badge element in all situations, or making a _createBadgeElementIfNotExists (final name depends on bike shedding) method to handle creating the badge element in a single place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that the logic could be organized more cleanly here, but I want to avoid refactoring any more than I've already done here. As a middle ground, I've renamed _updateContent to _updateRenderedContent`, which I think does a better job of capturing that it may create a DOM element if it doesn't already exist.

private _updateHostAriaDescription(newDescription: string, oldDescription: string): void {
// ensure content available before setting label
const content = this._updateTextContent();
/** Update the text content of the badge. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and also create the badge element for when OnChanges happens

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to

/** Update the text content of the badge element in the DOM, creating the element if necessary. */

Previously, `MatBadge` was applying the provided description as an `aria-label` to the badge content element, which is a child element of the badge host. Then it was _also_ applying an `aria-describedby` to that same content element. The end result is that the badge was being treated mainly as text content for a control, which isn't really the best way to convey its complmentary nature. This behavior was causing problems in NVDA, where the badge content was overriding the host button's label.

This change makes the badge content itself `aria-hidden` and applies the badge description to the badge's _host_ element via `aria-describedby` and completely removes any setting of `aria-label`. It also makes a handful of minor cleanup refactorings.
@amysorto amysorto merged commit f5883db into angular:master Sep 27, 2021
amysorto pushed a commit that referenced this pull request Sep 27, 2021
Previously, `MatBadge` was applying the provided description as an `aria-label` to the badge content element, which is a child element of the badge host. Then it was _also_ applying an `aria-describedby` to that same content element. The end result is that the badge was being treated mainly as text content for a control, which isn't really the best way to convey its complmentary nature. This behavior was causing problems in NVDA, where the badge content was overriding the host button's label.

This change makes the badge content itself `aria-hidden` and applies the badge description to the badge's _host_ element via `aria-describedby` and completely removes any setting of `aria-label`. It also makes a handful of minor cleanup refactorings.

(cherry picked from commit f5883db)
jelbourn added a commit to jelbourn/components that referenced this pull request Oct 14, 2021
…ar#23562)"

This reverts commit 6b71169.

Reverts this badge fix because it relies on a querySelector API that is not supported in IE11.
jelbourn added a commit to jelbourn/components that referenced this pull request Oct 14, 2021
…ar#23562)"

This reverts commit 6b71169.

Reverts this badge fix because it relies on a querySelector API that is not supported in IE11.

Related to angular#23736
zarend pushed a commit that referenced this pull request Oct 14, 2021
…" (#23754)

This reverts commit 6b71169.

Reverts this badge fix because it relies on a querySelector API that is not supported in IE11.

Related to #23736
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Oct 28, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Accessibility This issue is related to accessibility (a11y) action: merge The PR is ready for merge by the caretaker area: material/badge cla: yes PR author has agreed to Google's Contributor License Agreement P2 The issue is important to a large percentage of users, with a workaround target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants