diff --git a/src/material/button-toggle/BUILD.bazel b/src/material/button-toggle/BUILD.bazel index f562fdd8fd17..88df9d933427 100644 --- a/src/material/button-toggle/BUILD.bazel +++ b/src/material/button-toggle/BUILD.bazel @@ -54,6 +54,7 @@ ng_test_library( deps = [ ":button-toggle", "//src/cdk/testing", + "@npm//@angular/common", "@npm//@angular/forms", "@npm//@angular/platform-browser", ], diff --git a/src/material/button-toggle/button-toggle.spec.ts b/src/material/button-toggle/button-toggle.spec.ts index 400825e6dd38..37d43d328def 100644 --- a/src/material/button-toggle/button-toggle.spec.ts +++ b/src/material/button-toggle/button-toggle.spec.ts @@ -1,5 +1,6 @@ import {dispatchMouseEvent} from '@angular/cdk/testing'; import {Component, DebugElement, QueryList, ViewChild, ViewChildren} from '@angular/core'; +import {CommonModule} from '@angular/common'; import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; @@ -15,10 +16,11 @@ describe('MatButtonToggle with forms', () => { beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ - imports: [MatButtonToggleModule, FormsModule, ReactiveFormsModule], + imports: [MatButtonToggleModule, FormsModule, ReactiveFormsModule, CommonModule], declarations: [ ButtonToggleGroupWithNgModel, ButtonToggleGroupWithFormControl, + ButtonToggleGroupWithIndirectDescendantToggles, ], }); @@ -232,6 +234,24 @@ describe('MatButtonToggle with forms', () => { })); }); + + it('should be able to pick up toggles that are not direct descendants', fakeAsync(() => { + const fixture = TestBed.createComponent(ButtonToggleGroupWithIndirectDescendantToggles); + fixture.detectChanges(); + + const button = fixture.nativeElement.querySelector('.mat-button-toggle button'); + const groupDebugElement = fixture.debugElement.query(By.directive(MatButtonToggleGroup))!; + const groupInstance = + groupDebugElement.injector.get(MatButtonToggleGroup); + + button.click(); + fixture.detectChanges(); + tick(); + + expect(groupInstance.value).toBe('red'); + expect(fixture.componentInstance.control.value).toBe('red'); + expect(groupInstance._buttonToggles.length).toBe(3); + })); }); describe('MatButtonToggle without forms', () => { @@ -966,6 +986,22 @@ class ButtonToggleGroupWithFormControl { control = new FormControl(); } +@Component({ + // We need the `ngSwitch` so that there's a directive between the group and the toggles. + template: ` + + + Value Red + Value Green + Value Blue + + + ` +}) +class ButtonToggleGroupWithIndirectDescendantToggles { + control = new FormControl(); +} + /** Simple test component with an aria-label set. */ @Component({ template: `` diff --git a/src/material/button-toggle/button-toggle.ts b/src/material/button-toggle/button-toggle.ts index 4cef7a7e6f5c..d0a341fc9e45 100644 --- a/src/material/button-toggle/button-toggle.ts +++ b/src/material/button-toggle/button-toggle.ts @@ -131,7 +131,11 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After _onTouched: () => any = () => {}; /** Child button toggle buttons. */ - @ContentChildren(forwardRef(() => MatButtonToggle)) _buttonToggles: QueryList; + @ContentChildren(forwardRef(() => MatButtonToggle), { + // Note that this would technically pick up toggles + // from nested groups, but that's not a case that we support. + descendants: true + }) _buttonToggles: QueryList; /** The appearance for all the buttons in the group. */ @Input() appearance: MatButtonToggleAppearance;