Skip to content

Commit 289eecd

Browse files
authored
fix(material-experimental/mdc-progress-bar): changed after checked error on animation end event with noop animations (#18508)
Along the same lines as #18441. If the `animationEnd` of a progress bar is used to update something in the view of an app that has disabled animations, a "changed after checked" error will be thrown because of the timing at which we dispatch the event. These changes work around the issue by not having separate logic for when the animations are enabled or disabled.
1 parent 5615979 commit 289eecd

File tree

3 files changed

+21
-51
lines changed

3 files changed

+21
-51
lines changed

src/material-experimental/mdc-progress-bar/progress-bar.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
// Explicitly set to `block` since the browser defaults custom elements to `inline`.
88
display: block;
99

10+
&._mat-animation-noopable {
11+
.mdc-linear-progress__primary-bar {
12+
// There's a `transitionend` event that depends on this element. Add a very short
13+
// transition when animations are disabled so that the event can still fire.
14+
transition: transform 1ms;
15+
}
16+
}
17+
1018
&:not(._mat-animation-noopable) {
1119
@include mdc-linear-progress-core-styles($query: animation);
1220
}

src/material-experimental/mdc-progress-bar/progress-bar.spec.ts

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import {TestBed, async, ComponentFixture} from '@angular/core/testing';
1+
import {TestBed, ComponentFixture} from '@angular/core/testing';
22
import {Component, DebugElement, Type} from '@angular/core';
33
import {By} from '@angular/platform-browser';
44
import {dispatchFakeEvent} from '@angular/cdk/testing/private';
5-
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
65
import {MatProgressBarModule} from './index';
76
import {MatProgressBar} from './progress-bar';
87

@@ -213,33 +212,6 @@ describe('MDC-based MatProgressBar', () => {
213212
});
214213
});
215214

216-
describe('With NoopAnimations', () => {
217-
let progressComponent: MatProgressBar;
218-
let primaryValueBar: DebugElement;
219-
let fixture: ComponentFixture<BasicProgressBar>;
220-
221-
beforeEach(async(() => {
222-
fixture = createComponent(BasicProgressBar, [MatProgressBarModule, NoopAnimationsModule]);
223-
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!;
224-
progressComponent = progressElement.componentInstance;
225-
primaryValueBar = progressElement.query(By.css('.mdc-linear-progress__primary-bar'))!;
226-
}));
227-
228-
it('should not bind transition end listener', () => {
229-
spyOn(primaryValueBar.nativeElement, 'addEventListener');
230-
fixture.detectChanges();
231-
232-
expect(primaryValueBar.nativeElement.addEventListener).not.toHaveBeenCalled();
233-
});
234-
235-
it('should trigger the animationEnd output on value set', () => {
236-
fixture.detectChanges();
237-
spyOn(progressComponent.animationEnd, 'next');
238-
239-
progressComponent.value = 40;
240-
expect(progressComponent.animationEnd.next).toHaveBeenCalledWith({ value: 40 });
241-
});
242-
});
243215
});
244216

245217
@Component({template: '<mat-progress-bar></mat-progress-bar>'})

src/material-experimental/mdc-progress-bar/progress-bar.ts

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,6 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements AfterVie
100100
set value(v: number) {
101101
this._value = clamp(v || 0);
102102
this._syncFoundation();
103-
104-
// When noop animation is set to true, trigger animationEnd directly.
105-
if (this._isNoopAnimation) {
106-
this._emitAnimationEnd();
107-
}
108103
}
109104
private _value = 0;
110105

@@ -162,16 +157,18 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements AfterVie
162157
this._foundation.init();
163158
this._syncFoundation();
164159

165-
if (!this._isNoopAnimation) {
166-
// Run outside angular so change detection didn't get triggered on every transition end
167-
// instead only on the animation that we care about (primary value bar's transitionend)
168-
this._ngZone.runOutsideAngular((() => {
169-
this._animationEndSubscription =
170-
(fromEvent(this._primaryBar, 'transitionend') as Observable<TransitionEvent>)
171-
.pipe(filter(((e: TransitionEvent) => e.target === this._primaryBar)))
172-
.subscribe(() => this._ngZone.run(() => this._emitAnimationEnd()));
173-
}));
174-
}
160+
// Run outside angular so change detection didn't get triggered on every transition end
161+
// instead only on the animation that we care about (primary value bar's transitionend)
162+
this._ngZone.runOutsideAngular((() => {
163+
this._animationEndSubscription =
164+
(fromEvent(this._primaryBar, 'transitionend') as Observable<TransitionEvent>)
165+
.pipe(filter(((e: TransitionEvent) => e.target === this._primaryBar)))
166+
.subscribe(() => {
167+
if (this.mode === 'determinate' || this.mode === 'buffer') {
168+
this._ngZone.run(() => this.animationEnd.next({value: this.value}));
169+
}
170+
});
171+
}));
175172
}
176173

177174
ngOnDestroy() {
@@ -182,13 +179,6 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements AfterVie
182179
this._dirChangeSubscription.unsubscribe();
183180
}
184181

185-
/** Emit an animationEnd event if in determinate or buffer mode. */
186-
private _emitAnimationEnd(): void {
187-
if (this.mode === 'determinate' || this.mode === 'buffer') {
188-
this.animationEnd.next({value: this.value});
189-
}
190-
}
191-
192182
/** Syncs the state of the progress bar with the MDC foundation. */
193183
private _syncFoundation() {
194184
const foundation = this._foundation;

0 commit comments

Comments
 (0)