From 0007908387d258f12538812cc0e315106e0763aa Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 29 Mar 2018 10:24:53 +0200 Subject: [PATCH] fix(button-toggle): changed after checked error for repeated toggles with a preselected value Fixes a regression that causes a "changed after checked" error to be thrown when there is a button toggle group with an initial value and a set of repeated toggles. Fixes #10607. --- src/lib/button-toggle/button-toggle.spec.ts | 29 ++++++++++++++++++- src/lib/button-toggle/button-toggle.ts | 31 ++++++++++++++------- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/lib/button-toggle/button-toggle.spec.ts b/src/lib/button-toggle/button-toggle.spec.ts index ba34ae6fd28a..51d9390831c0 100644 --- a/src/lib/button-toggle/button-toggle.spec.ts +++ b/src/lib/button-toggle/button-toggle.spec.ts @@ -1,7 +1,7 @@ import {fakeAsync, tick, ComponentFixture, TestBed} from '@angular/core/testing'; import {dispatchMouseEvent} from '@angular/cdk/testing'; import {NgModel, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms'; -import {Component, DebugElement} from '@angular/core'; +import {Component, DebugElement, ViewChild, ViewChildren, QueryList} from '@angular/core'; import {By} from '@angular/platform-browser'; import { MatButtonToggleGroup, @@ -209,6 +209,7 @@ describe('MatButtonToggle without forms', () => { StandaloneButtonToggle, ButtonToggleWithAriaLabel, ButtonToggleWithAriaLabelledby, + RepeatedButtonTogglesWithPreselectedValue, ], }); @@ -662,6 +663,14 @@ describe('MatButtonToggle without forms', () => { expect(inputElement.getAttribute('aria-labelledby')).toBe(null); }); }); + + it('should not throw on init when toggles are repeated and there is an initial value', () => { + const fixture = TestBed.createComponent(RepeatedButtonTogglesWithPreselectedValue); + + expect(() => fixture.detectChanges()).not.toThrow(); + expect(fixture.componentInstance.toggleGroup.value).toBe('Two'); + expect(fixture.componentInstance.toggles.toArray()[1].checked).toBe(true); + }); }); @Component({ @@ -759,3 +768,21 @@ class ButtonToggleWithAriaLabel { } template: `` }) class ButtonToggleWithAriaLabelledby {} + + +@Component({ + template: ` + + + {{toggle}} + + + ` +}) +class RepeatedButtonTogglesWithPreselectedValue { + @ViewChild(MatButtonToggleGroup) toggleGroup: MatButtonToggleGroup; + @ViewChildren(MatButtonToggle) toggles: QueryList; + + possibleValues = ['One', 'Two', 'Three']; + value = 'Two'; +} diff --git a/src/lib/button-toggle/button-toggle.ts b/src/lib/button-toggle/button-toggle.ts index 5f83e66a72a6..bc4f600e9845 100644 --- a/src/lib/button-toggle/button-toggle.ts +++ b/src/lib/button-toggle/button-toggle.ts @@ -180,14 +180,8 @@ export class MatButtonToggleGroup extends _MatButtonToggleGroupMixinBase impleme } ngAfterContentInit() { - // If there was an attempt to assign a value before init, use it to set the - // initial selection, otherwise check the `checked` state of the toggles. - if (typeof this._tempValue !== 'undefined') { - this._setSelectionByValue(this._tempValue); - this._tempValue = undefined; - } else { - this._selectionModel.select(...this._buttonToggles.filter(toggle => toggle.checked)); - } + this._selectionModel.select(...this._buttonToggles.filter(toggle => toggle.checked)); + this._tempValue = undefined; } /** @@ -261,6 +255,19 @@ export class MatButtonToggleGroup extends _MatButtonToggleGroupMixinBase impleme return this._selectionModel.isSelected(toggle); } + /** Determines whether a button toggle should be checked on init. */ + _isPrechecked(toggle: MatButtonToggle) { + if (typeof this._tempValue === 'undefined') { + return false; + } + + if (this.multiple && Array.isArray(this._tempValue)) { + return !!this._tempValue.find(value => toggle.value != null && value === toggle.value); + } + + return toggle.value === this._tempValue; + } + /** Updates the selection state of the toggles in the group based on a value. */ private _setSelectionByValue(value: any|any[]) { // If the toggles haven't been initialized yet, save the value for later. @@ -409,6 +416,10 @@ export class MatButtonToggle extends _MatButtonToggleMixinBase implements OnInit this.name = this.buttonToggleGroup.name; } + if (this.buttonToggleGroup && this.buttonToggleGroup._isPrechecked(this)) { + this.checked = true; + } + this._focusMonitor.monitor(this._elementRef.nativeElement, true); } @@ -449,8 +460,8 @@ export class MatButtonToggle extends _MatButtonToggleMixinBase implements OnInit * update bound properties of the radio button. */ _markForCheck() { - // When group value changes, the button will not be notified. Use `markForCheck` to explicit - // update button toggle's status + // When the group value changes, the button will not be notified. + // Use `markForCheck` to explicit update button toggle's status. this._changeDetectorRef.markForCheck(); } }