Skip to content

Commit 73d0687

Browse files
crisbetowagnermaciel
authored andcommitted
fix(material/checkbox): native input not in sync if checked state is changed inside event (#22316)
Fixes that the state of the native checkbox might be out of sync if the `checked` property is changed inside the `changed` output. Fixes #22149. (cherry picked from commit c679416)
1 parent 7f36cba commit 73d0687

File tree

4 files changed

+39
-0
lines changed

4 files changed

+39
-0
lines changed

src/material-experimental/mdc-checkbox/checkbox.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,19 @@ describe('MDC-based MatCheckbox', () => {
355355
expect(testComponent.onCheckboxChange).not.toHaveBeenCalled();
356356
}));
357357

358+
it('should keep the view in sync if the `checked` value changes inside the `change` listener',
359+
fakeAsync(() => {
360+
spyOn(testComponent, 'onCheckboxChange').and.callFake(() => {
361+
checkboxInstance.checked = false;
362+
});
363+
364+
labelElement.click();
365+
fixture.detectChanges();
366+
flush();
367+
368+
expect(inputElement.checked).toBe(false);
369+
}));
370+
358371
it('should forward the required attribute', fakeAsync(() => {
359372
testComponent.isRequired = true;
360373
fixture.detectChanges();

src/material-experimental/mdc-checkbox/checkbox.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,12 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements AfterViewInit,
370370
newEvent.checked = this.checked;
371371
this._cvaOnChange(this.checked);
372372
this.change.next(newEvent);
373+
374+
// Assigning the value again here is redundant, but we have to do it in case it was
375+
// changed inside the `change` listener which will cause the input to be out of sync.
376+
if (this._nativeCheckbox) {
377+
this._nativeCheckbox.nativeElement.checked = this.checked;
378+
}
373379
}
374380

375381
/** Gets the value for the `aria-checked` attribute of the native input. */

src/material/checkbox/checkbox.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,20 @@ describe('MatCheckbox', () => {
355355
expect(testComponent.onCheckboxChange).not.toHaveBeenCalled();
356356
}));
357357

358+
it('should keep the view in sync if the `checked` value changes inside the `change` listener',
359+
fakeAsync(() => {
360+
spyOn(testComponent, 'onCheckboxChange').and.callFake(() => {
361+
checkboxInstance.checked = false;
362+
});
363+
364+
labelElement.click();
365+
fixture.detectChanges();
366+
flush();
367+
368+
expect(inputElement.checked).toBe(false);
369+
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-checked');
370+
}));
371+
358372
it('should forward the required attribute', () => {
359373
testComponent.isRequired = true;
360374
fixture.detectChanges();

src/material/checkbox/checkbox.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
371371

372372
this._controlValueAccessorChangeFn(this.checked);
373373
this.change.emit(event);
374+
375+
// Assigning the value again here is redundant, but we have to do it in case it was
376+
// changed inside the `change` listener which will cause the input to be out of sync.
377+
if (this._inputElement) {
378+
this._inputElement.nativeElement.checked = this.checked;
379+
}
374380
}
375381

376382
/** Toggles the `checked` state of the checkbox. */

0 commit comments

Comments
 (0)