mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(slide-toggle): prevent error when disabling while focused (#12325)
Fixes Angular throwing an `ExpressionChangedAfterItHasBeenCheckedError` when disabling the slide-toggle while the component has been focused. Fixes #12323
This commit is contained in:
committed by
Jeremy Elbourn
parent
c4cacceba9
commit
e273a7a415
@@ -1,7 +1,14 @@
|
||||
import {MutationObserverFactory} from '@angular/cdk/observers';
|
||||
import {dispatchFakeEvent} from '@angular/cdk/testing';
|
||||
import {Component} from '@angular/core';
|
||||
import {ComponentFixture, fakeAsync, flushMicrotasks, TestBed, tick} from '@angular/core/testing';
|
||||
import {
|
||||
ComponentFixture,
|
||||
fakeAsync,
|
||||
flush,
|
||||
flushMicrotasks,
|
||||
TestBed,
|
||||
tick,
|
||||
} from '@angular/core/testing';
|
||||
import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms';
|
||||
import {defaultRippleAnimationConfig} from '@angular/material/core';
|
||||
import {By, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
|
||||
@@ -773,7 +780,7 @@ describe('MatSlideToggle with forms', () => {
|
||||
expect(slideToggleElement.classList).toContain('mat-checked');
|
||||
}));
|
||||
|
||||
it('should have the correct control state initially and after interaction', () => {
|
||||
it('should have the correct control state initially and after interaction', fakeAsync(() => {
|
||||
// The control should start off valid, pristine, and untouched.
|
||||
expect(slideToggleModel.valid).toBe(true);
|
||||
expect(slideToggleModel.pristine).toBe(true);
|
||||
@@ -795,13 +802,31 @@ describe('MatSlideToggle with forms', () => {
|
||||
// also turn touched.
|
||||
dispatchFakeEvent(inputElement, 'blur');
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(slideToggleModel.valid).toBe(true);
|
||||
expect(slideToggleModel.pristine).toBe(false);
|
||||
expect(slideToggleModel.touched).toBe(true);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not throw an error when disabling while focused', fakeAsync(() => {
|
||||
expect(() => {
|
||||
// Focus the input element because after disabling, the `blur` event should automatically
|
||||
// fire and not result in a changed after checked exception. Related: #12323
|
||||
inputElement.focus();
|
||||
|
||||
// Flush the two nested timeouts from the FocusMonitor that are being created on `focus`.
|
||||
flush();
|
||||
|
||||
slideToggle.disabled = true;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
}).not.toThrow();
|
||||
}));
|
||||
|
||||
it('should not set the control to touched when changing the state programmatically',
|
||||
fakeAsync(() => {
|
||||
|
||||
it('should not set the control to touched when changing the state programmatically', () => {
|
||||
// The control should start off with being untouched.
|
||||
expect(slideToggleModel.touched).toBe(false);
|
||||
|
||||
@@ -815,10 +840,11 @@ describe('MatSlideToggle with forms', () => {
|
||||
// also turn touched.
|
||||
dispatchFakeEvent(inputElement, 'blur');
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(slideToggleModel.touched).toBe(true);
|
||||
expect(slideToggleElement.classList).toContain('mat-checked');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not set the control to touched when changing the model', fakeAsync(() => {
|
||||
// The control should start off with being untouched.
|
||||
|
||||
@@ -289,7 +289,12 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
// For keyboard focus show a persistent ripple as focus indicator.
|
||||
this._focusRipple = this._ripple.launch(0, 0, {persistent: true});
|
||||
} else if (!focusOrigin) {
|
||||
this.onTouched();
|
||||
// When a focused element becomes disabled, the browser *immediately* fires a blur event.
|
||||
// Angular does not expect events to be raised during change detection, so any state change
|
||||
// (such as a form control's 'ng-touched') will cause a changed-after-checked error.
|
||||
// See https://github.com/angular/angular/issues/17793. To work around this, we defer telling
|
||||
// the form control it has been touched until the next tick.
|
||||
Promise.resolve().then(() => this.onTouched());
|
||||
|
||||
// Fade out and clear the focus ripple if one is currently present.
|
||||
if (this._focusRipple) {
|
||||
|
||||
Reference in New Issue
Block a user