Skip to content

Commit ed825e6

Browse files
authored
fix(material/button-toggle): toggle name falling out of sync if name changes (#24713)
Currently we assign the group name to each individual toggle on init or when the group name changes, but that doesn't account for when the toggle's name changes independently. These changes switch to using a getter which will update automatically if anything changes. Fixes #24666.
1 parent 6dd4b3c commit ed825e6

File tree

4 files changed

+48
-30
lines changed

4 files changed

+48
-30
lines changed

src/material/button-toggle/button-toggle.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[attr.tabindex]="disabled ? -1 : tabIndex"
55
[attr.aria-pressed]="checked"
66
[disabled]="disabled || null"
7-
[attr.name]="name || null"
7+
[attr.name]="_getButtonName()"
88
[attr.aria-label]="ariaLabel"
99
[attr.aria-labelledby]="ariaLabelledby"
1010
(click)="_onButtonClick()">

src/material/button-toggle/button-toggle.spec.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,15 @@ describe('MatButtonToggle with forms', () => {
113113

114114
it('should set individual radio names based on the group name', () => {
115115
expect(groupInstance.name).toBeTruthy();
116-
for (let buttonToggle of buttonToggleInstances) {
117-
expect(buttonToggle.name).toBe(groupInstance.name);
116+
for (let buttonToggle of innerButtons) {
117+
expect(buttonToggle.getAttribute('name')).toBe(groupInstance.name);
118118
}
119119

120120
groupInstance.name = 'new name';
121-
for (let buttonToggle of buttonToggleInstances) {
122-
expect(buttonToggle.name).toBe(groupInstance.name);
121+
fixture.detectChanges();
122+
123+
for (let buttonToggle of innerButtons) {
124+
expect(buttonToggle.getAttribute('name')).toBe(groupInstance.name);
123125
}
124126
});
125127

@@ -137,6 +139,15 @@ describe('MatButtonToggle with forms', () => {
137139
.toBe(true);
138140
});
139141

142+
it('should set the name of the buttons to the group name if the name of the button changes after init', () => {
143+
const firstButton = innerButtons[0];
144+
expect(firstButton.getAttribute('name')).toBe(fixture.componentInstance.groupName);
145+
146+
fixture.componentInstance.options[0].name = 'changed-name';
147+
fixture.detectChanges();
148+
expect(firstButton.getAttribute('name')).toBe(fixture.componentInstance.groupName);
149+
});
150+
140151
it('should check the corresponding button toggle on a group value change', () => {
141152
expect(groupInstance.value).toBeFalsy();
142153
for (let buttonToggle of buttonToggleInstances) {
@@ -313,8 +324,8 @@ describe('MatButtonToggle without forms', () => {
313324

314325
it('should set individual button toggle names based on the group name', () => {
315326
expect(groupInstance.name).toBeTruthy();
316-
for (let buttonToggle of buttonToggleInstances) {
317-
expect(buttonToggle.name).toBe(groupInstance.name);
327+
for (let buttonToggle of buttonToggleLabelElements) {
328+
expect(buttonToggle.getAttribute('name')).toBe(groupInstance.name);
318329
}
319330
});
320331

@@ -930,8 +941,10 @@ class ButtonTogglesInsideButtonToggleGroup {
930941
[name]="groupName"
931942
[(ngModel)]="modelValue"
932943
(change)="lastEvent = $event">
933-
<mat-button-toggle *ngFor="let option of options" [value]="option.value"
934-
[disableRipple]="disableRipple">
944+
<mat-button-toggle *ngFor="let option of options"
945+
[value]="option.value"
946+
[disableRipple]="disableRipple"
947+
[name]="option.name">
935948
{{option.label}}
936949
</mat-button-toggle>
937950
</mat-button-toggle-group>
@@ -941,9 +954,9 @@ class ButtonToggleGroupWithNgModel {
941954
groupName = 'group-name';
942955
modelValue: string;
943956
options = [
944-
{label: 'Red', value: 'red'},
945-
{label: 'Green', value: 'green'},
946-
{label: 'Blue', value: 'blue'},
957+
{label: 'Red', value: 'red', name: ''},
958+
{label: 'Green', value: 'green', name: ''},
959+
{label: 'Blue', value: 'blue', name: ''},
947960
];
948961
lastEvent: MatButtonToggleChange;
949962
disableRipple = false;

src/material/button-toggle/button-toggle.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,7 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
155155
}
156156
set name(value: string) {
157157
this._name = value;
158-
159-
if (this._buttonToggles) {
160-
this._buttonToggles.forEach(toggle => {
161-
toggle.name = this._name;
162-
toggle._markForCheck();
163-
});
164-
}
158+
this._markButtonsForCheck();
165159
}
166160
private _name = `mat-button-toggle-group-${uniqueIdCounter++}`;
167161

@@ -210,6 +204,7 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
210204
}
211205
set multiple(value: BooleanInput) {
212206
this._multiple = coerceBooleanProperty(value);
207+
this._markButtonsForCheck();
213208
}
214209

215210
/** Whether multiple button toggle group is disabled. */
@@ -219,10 +214,7 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
219214
}
220215
set disabled(value: BooleanInput) {
221216
this._disabled = coerceBooleanProperty(value);
222-
223-
if (this._buttonToggles) {
224-
this._buttonToggles.forEach(toggle => toggle._markForCheck());
225-
}
217+
this._markButtonsForCheck();
226218
}
227219

228220
/** Event emitted when the group's value changes. */
@@ -387,6 +379,11 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
387379
// it is used by Angular to sync up the two-way data binding.
388380
this.valueChange.emit(this.value);
389381
}
382+
383+
/** Marks all of the child button toggles to be checked. */
384+
private _markButtonsForCheck() {
385+
this._buttonToggles?.forEach(toggle => toggle._markForCheck());
386+
}
390387
}
391388

392389
// Boilerplate for applying mixins to the MatButtonToggle class.
@@ -420,7 +417,6 @@ export class MatButtonToggle
420417
extends _MatButtonToggleBase
421418
implements OnInit, AfterViewInit, CanDisableRipple, OnDestroy
422419
{
423-
private _isSingleSelector = false;
424420
private _checked = false;
425421

426422
/**
@@ -521,13 +517,8 @@ export class MatButtonToggle
521517

522518
ngOnInit() {
523519
const group = this.buttonToggleGroup;
524-
this._isSingleSelector = group && !group.multiple;
525520
this.id = this.id || `mat-button-toggle-${uniqueIdCounter++}`;
526521

527-
if (this._isSingleSelector) {
528-
this.name = group.name;
529-
}
530-
531522
if (group) {
532523
if (group._isPrechecked(this)) {
533524
this.checked = true;
@@ -564,7 +555,7 @@ export class MatButtonToggle
564555

565556
/** Checks the button toggle due to an interaction with the underlying native button. */
566557
_onButtonClick() {
567-
const newChecked = this._isSingleSelector ? true : !this._checked;
558+
const newChecked = this._isSingleSelector() ? true : !this._checked;
568559

569560
if (newChecked !== this._checked) {
570561
this._checked = newChecked;
@@ -587,4 +578,17 @@ export class MatButtonToggle
587578
// Use `markForCheck` to explicit update button toggle's status.
588579
this._changeDetectorRef.markForCheck();
589580
}
581+
582+
/** Gets the name that should be assigned to the inner DOM node. */
583+
_getButtonName(): string | null {
584+
if (this._isSingleSelector()) {
585+
return this.buttonToggleGroup.name;
586+
}
587+
return this.name || null;
588+
}
589+
590+
/** Whether the toggle is in single selection mode. */
591+
private _isSingleSelector(): boolean {
592+
return this.buttonToggleGroup && !this.buttonToggleGroup.multiple;
593+
}
590594
}

tools/public_api_guard/material/button-toggle.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export class MatButtonToggle extends _MatButtonToggleBase implements OnInit, Aft
4747
get disabled(): boolean;
4848
set disabled(value: BooleanInput);
4949
focus(options?: FocusOptions): void;
50+
_getButtonName(): string | null;
5051
id: string;
5152
_markForCheck(): void;
5253
name: string;

0 commit comments

Comments
 (0)