Skip to content

Commit 004eeb8

Browse files
authored
fix(progress-bar): changed after checked error on animation end event with noop animations (#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 a761359 commit 004eeb8

File tree

3 files changed

+19
-54
lines changed

3 files changed

+19
-54
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,10 @@ $mat-progress-bar-piece-animation-duration: 250ms !default;
147147
.mat-progress-bar-secondary.mat-progress-bar-fill::after,
148148
.mat-progress-bar-background {
149149
animation: none;
150-
transition: none;
150+
151+
// Use a 1ms transition, because we have an event that
152+
// is dispatched based on a `transitionend` being fired.
153+
transition-duration: 1ms;
151154
}
152155
}
153156
}

src/material/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, MAT_PROGRESS_BAR_LOCATION} from './index';
76
import {MatProgressBar} from './progress-bar';
87

@@ -261,33 +260,6 @@ describe('MatProgressBar', () => {
261260
});
262261
});
263262

264-
describe('With NoopAnimations', () => {
265-
let progressComponent: MatProgressBar;
266-
let primaryValueBar: DebugElement;
267-
let fixture: ComponentFixture<BasicProgressBar>;
268-
269-
beforeEach(async(() => {
270-
fixture = createComponent(BasicProgressBar, [MatProgressBarModule, NoopAnimationsModule]);
271-
const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!;
272-
progressComponent = progressElement.componentInstance;
273-
primaryValueBar = progressElement.query(By.css('.mat-progress-bar-primary'))!;
274-
}));
275-
276-
it('should not bind transition end listener', () => {
277-
spyOn(primaryValueBar.nativeElement, 'addEventListener');
278-
fixture.detectChanges();
279-
280-
expect(primaryValueBar.nativeElement.addEventListener).not.toHaveBeenCalled();
281-
});
282-
283-
it('should trigger the animationEnd output on value set', () => {
284-
fixture.detectChanges();
285-
spyOn(progressComponent.animationEnd, 'next');
286-
287-
progressComponent.value = 40;
288-
expect(progressComponent.animationEnd.next).toHaveBeenCalledWith({ value: 40 });
289-
});
290-
});
291263
});
292264

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

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

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,6 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements CanColor
134134
get value(): number { return this._value; }
135135
set value(v: number) {
136136
this._value = clamp(coerceNumberProperty(v) || 0);
137-
138-
// When noop animation is set to true, trigger animationEnd directly.
139-
if (this._isNoopAnimation) {
140-
this._emitAnimationEnd();
141-
}
142137
}
143138
private _value: number = 0;
144139

@@ -194,31 +189,26 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements CanColor
194189
}
195190

196191
ngAfterViewInit() {
197-
if (!this._isNoopAnimation) {
198-
// Run outside angular so change detection didn't get triggered on every transition end
199-
// instead only on the animation that we care about (primary value bar's transitionend)
200-
this._ngZone.runOutsideAngular((() => {
201-
const element = this._primaryValueBar.nativeElement;
202-
203-
this._animationEndSubscription =
204-
(fromEvent(element, 'transitionend') as Observable<TransitionEvent>)
205-
.pipe(filter(((e: TransitionEvent) => e.target === element)))
206-
.subscribe(() => this._ngZone.run(() => this._emitAnimationEnd()));
207-
}));
208-
}
192+
// Run outside angular so change detection didn't get triggered on every transition end
193+
// instead only on the animation that we care about (primary value bar's transitionend)
194+
this._ngZone.runOutsideAngular((() => {
195+
const element = this._primaryValueBar.nativeElement;
196+
197+
this._animationEndSubscription =
198+
(fromEvent(element, 'transitionend') as Observable<TransitionEvent>)
199+
.pipe(filter(((e: TransitionEvent) => e.target === element)))
200+
.subscribe(() => {
201+
if (this.mode === 'determinate' || this.mode === 'buffer') {
202+
this._ngZone.run(() => this.animationEnd.next({value: this.value}));
203+
}
204+
});
205+
}));
209206
}
210207

211208
ngOnDestroy() {
212209
this._animationEndSubscription.unsubscribe();
213210
}
214211

215-
/** Emit an animationEnd event if in determinate or buffer mode. */
216-
private _emitAnimationEnd(): void {
217-
if (this.mode === 'determinate' || this.mode === 'buffer') {
218-
this.animationEnd.next({value: this.value});
219-
}
220-
}
221-
222212
static ngAcceptInputType_value: NumberInput;
223213
}
224214

0 commit comments

Comments
 (0)