diff --git a/src/material-experimental/mdc-progress-bar/BUILD.bazel b/src/material-experimental/mdc-progress-bar/BUILD.bazel
index 89d844e97c97..5a21a84daf44 100644
--- a/src/material-experimental/mdc-progress-bar/BUILD.bazel
+++ b/src/material-experimental/mdc-progress-bar/BUILD.bazel
@@ -21,11 +21,9 @@ ng_module(
),
assets = [":progress_bar_scss"] + glob(["**/*.html"]),
deps = [
- "//src/cdk/bidi",
"//src/material-experimental/mdc-core",
"//src/material/progress-bar",
"@npm//@angular/core",
- "@npm//@material/linear-progress",
],
)
diff --git a/src/material-experimental/mdc-progress-bar/progress-bar.html b/src/material-experimental/mdc-progress-bar/progress-bar.html
index 37dd53799026..c903f19305bc 100644
--- a/src/material-experimental/mdc-progress-bar/progress-bar.html
+++ b/src/material-experimental/mdc-progress-bar/progress-bar.html
@@ -3,10 +3,15 @@
More context in the issue: https://github.com/angular/components/issues/22165.
-->
-
+
diff --git a/src/material-experimental/mdc-progress-bar/progress-bar.spec.ts b/src/material-experimental/mdc-progress-bar/progress-bar.spec.ts
index 5d398004df60..41e4d7606c57 100644
--- a/src/material-experimental/mdc-progress-bar/progress-bar.spec.ts
+++ b/src/material-experimental/mdc-progress-bar/progress-bar.spec.ts
@@ -96,20 +96,24 @@ describe('MDC-based MatProgressBar', () => {
expect(getBufferValue()).toBe(100);
progressComponent.value = 40;
+ fixture.detectChanges();
expect(primaryStyles.transform).toBe('scaleX(0.4)');
expect(getBufferValue()).toBe(100);
progressComponent.value = 35;
progressComponent.bufferValue = 55;
+ fixture.detectChanges();
expect(primaryStyles.transform).toBe('scaleX(0.35)');
expect(getBufferValue()).toBe(100);
progressComponent.mode = 'buffer';
+ fixture.detectChanges();
expect(primaryStyles.transform).toBe('scaleX(0.35)');
expect(getBufferValue()).toEqual(55);
progressComponent.value = 60;
progressComponent.bufferValue = 60;
+ fixture.detectChanges();
expect(primaryStyles.transform).toBe('scaleX(0.6)');
expect(getBufferValue()).toEqual(60);
});
@@ -239,30 +243,31 @@ describe('MDC-based MatProgressBar', () => {
expect(animationEndSpy).not.toHaveBeenCalled();
// On animation end, output should be emitted.
- dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend');
+ dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true);
expect(animationEndSpy).toHaveBeenCalledWith({value: 40});
});
});
describe('animation trigger on buffer setting', () => {
let fixture: ComponentFixture
;
+ let progressElement: DebugElement;
let progressComponent: MatProgressBar;
let primaryValueBar: DebugElement;
beforeEach(() => {
fixture = createComponent(BufferProgressBar);
- const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!;
+ progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!;
progressComponent = progressElement.componentInstance;
primaryValueBar = progressElement.query(By.css('.mdc-linear-progress__primary-bar'))!;
});
- it('should bind on transitionend eventListener on primaryBarValue', () => {
- spyOn(primaryValueBar.nativeElement, 'addEventListener');
+ it('should bind on transitionend eventListener on the host node', () => {
+ spyOn(progressElement.nativeElement, 'addEventListener');
fixture.detectChanges();
- expect(primaryValueBar.nativeElement.addEventListener).toHaveBeenCalled();
- expect(primaryValueBar.nativeElement.addEventListener.calls.mostRecent().args[0]).toBe(
+ expect(progressElement.nativeElement.addEventListener).toHaveBeenCalled();
+ expect(progressElement.nativeElement.addEventListener.calls.mostRecent().args[0]).toBe(
'transitionend',
);
});
@@ -277,7 +282,7 @@ describe('MDC-based MatProgressBar', () => {
expect(animationEndSpy).not.toHaveBeenCalled();
// On animation end, output should be emitted.
- dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend');
+ dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true);
expect(animationEndSpy).toHaveBeenCalledWith({value: 40});
});
@@ -292,7 +297,7 @@ describe('MDC-based MatProgressBar', () => {
expect(animationEndSpy).not.toHaveBeenCalled();
// On animation end, output should be emitted.
- dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend');
+ dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true);
expect(animationEndSpy).toHaveBeenCalledWith({value: 40});
});
@@ -306,7 +311,7 @@ describe('MDC-based MatProgressBar', () => {
progressComponent.value = 30;
progressComponent.bufferValue = 60;
// On animation end, output should be emitted.
- dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend');
+ dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true);
expect(appRef.tick).not.toHaveBeenCalled();
@@ -315,7 +320,7 @@ describe('MDC-based MatProgressBar', () => {
progressComponent.value = 40;
progressComponent.bufferValue = 70;
// On animation end, output should be emitted.
- dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend');
+ dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true);
expect(appRef.tick).toHaveBeenCalled();
expect(animationEndSpy).toHaveBeenCalledWith({value: 40});
diff --git a/src/material-experimental/mdc-progress-bar/progress-bar.ts b/src/material-experimental/mdc-progress-bar/progress-bar.ts
index ae5668609ef5..09d80c96a3c5 100644
--- a/src/material-experimental/mdc-progress-bar/progress-bar.ts
+++ b/src/material-experimental/mdc-progress-bar/progress-bar.ts
@@ -19,6 +19,7 @@ import {
EventEmitter,
AfterViewInit,
OnDestroy,
+ ChangeDetectorRef,
} from '@angular/core';
import {CanColor, mixinColor} from '@angular/material-experimental/mdc-core';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
@@ -27,20 +28,12 @@ import {
MAT_PROGRESS_BAR_DEFAULT_OPTIONS,
ProgressAnimationEnd,
} from '@angular/material/progress-bar';
-import {
- MDCLinearProgressAdapter,
- MDCLinearProgressFoundation,
- WithMDCResizeObserver,
-} from '@material/linear-progress';
-import {Subscription, fromEvent, Observable} from 'rxjs';
-import {filter} from 'rxjs/operators';
-import {Directionality} from '@angular/cdk/bidi';
// Boilerplate for applying mixins to MatProgressBar.
/** @docs-private */
const _MatProgressBarBase = mixinColor(
class {
- constructor(public _elementRef: ElementRef) {}
+ constructor(public _elementRef: ElementRef) {}
},
'primary',
);
@@ -57,10 +50,12 @@ export type ProgressBarMode = 'determinate' | 'indeterminate' | 'buffer' | 'quer
// set tab index to -1 so screen readers will read the aria-label
// Note: there is a known issue with JAWS that does not read progressbar aria labels on FireFox
'tabindex': '-1',
- '[attr.aria-valuenow]': '(mode === "indeterminate" || mode === "query") ? null : value',
+ '[attr.aria-valuenow]': '_isIndeterminate() ? null : value',
'[attr.mode]': 'mode',
'class': 'mat-mdc-progress-bar mdc-linear-progress',
'[class._mat-animation-noopable]': '_isNoopAnimation',
+ '[class.mdc-linear-progress--animation-ready]': '!_isNoopAnimation',
+ '[class.mdc-linear-progress--indeterminate]': '_isIndeterminate()',
},
inputs: ['color'],
templateUrl: 'progress-bar.html',
@@ -75,7 +70,7 @@ export class MatProgressBar
constructor(
elementRef: ElementRef,
private _ngZone: NgZone,
- @Optional() dir?: Directionality,
+ private _changeDetectorRef: ChangeDetectorRef,
@Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string,
@Optional()
@Inject(MAT_PROGRESS_BAR_DEFAULT_OPTIONS)
@@ -83,12 +78,6 @@ export class MatProgressBar
) {
super(elementRef);
this._isNoopAnimation = _animationMode === 'NoopAnimations';
- if (dir) {
- this._dirChangeSubscription = dir.change.subscribe(() => {
- this._syncFoundation();
- this._foundation?.restartAnimation();
- });
- }
if (defaults) {
if (defaults.color) {
@@ -99,56 +88,6 @@ export class MatProgressBar
}
}
- /** Implements all of the logic of the MDC progress bar. */
- private _foundation: MDCLinearProgressFoundation | undefined;
-
- /** Adapter used by MDC to interact with the DOM. */
- private _adapter: MDCLinearProgressAdapter = {
- addClass: (className: string) => this._elementRef.nativeElement.classList.add(className),
- forceLayout: () => this._elementRef.nativeElement.offsetWidth,
- removeAttribute: (name: string) => this._elementRef.nativeElement.removeAttribute(name),
- setAttribute: (name: string, value: string) => {
- if (name !== 'aria-valuenow') {
- this._elementRef.nativeElement.setAttribute(name, value);
- }
- },
- hasClass: (className: string) => this._elementRef.nativeElement.classList.contains(className),
- removeClass: (className: string) => this._elementRef.nativeElement.classList.remove(className),
- setPrimaryBarStyle: (styleProperty: string, value: string) => {
- (this._primaryBar.style as any)[styleProperty] = value;
- },
- setBufferBarStyle: (styleProperty: string, value: string) => {
- (this._bufferBar.style as any)[styleProperty] = value;
- },
- setStyle: (styleProperty: string, value: string) => {
- (this._elementRef.nativeElement.style as any)[styleProperty] = value;
- },
- getWidth: () => this._elementRef.nativeElement.offsetWidth,
- attachResizeObserver: callback => {
- const resizeObserverConstructor =
- typeof window !== 'undefined' &&
- (window as unknown as WithMDCResizeObserver).ResizeObserver;
-
- if (resizeObserverConstructor) {
- return this._ngZone.runOutsideAngular(() => {
- const observer = new resizeObserverConstructor(callback);
-
- // Internal client users found production errors where `observe` was not a function
- // on the constructed `observer`. This should not happen, but adding this check for this
- // edge case.
- if (typeof observer.observe === 'function') {
- observer.observe(this._elementRef.nativeElement);
- return observer;
- }
-
- return null;
- });
- }
-
- return null;
- },
- };
-
/** Flag that indicates whether NoopAnimations mode is set to true. */
_isNoopAnimation = false;
@@ -159,7 +98,7 @@ export class MatProgressBar
}
set value(v: number) {
this._value = clamp(v || 0);
- this._syncFoundation();
+ this._changeDetectorRef.markForCheck();
}
private _value = 0;
@@ -170,13 +109,10 @@ export class MatProgressBar
}
set bufferValue(v: number) {
this._bufferValue = clamp(v || 0);
- this._syncFoundation();
+ this._changeDetectorRef.markForCheck();
}
private _bufferValue = 0;
- private _primaryBar: HTMLElement;
- private _bufferBar: HTMLElement;
-
/**
* Event emitted when animation of the primary progress bar completes. This event will not
* be emitted when animations are disabled, nor will it be emitted for modes with continuous
@@ -184,12 +120,6 @@ export class MatProgressBar
*/
@Output() readonly animationEnd = new EventEmitter();
- /** Reference to animation end subscription to be unsubscribed on destroy. */
- private _animationEndSubscription = Subscription.EMPTY;
-
- /** Subscription to when the layout direction changes. */
- private _dirChangeSubscription = Subscription.EMPTY;
-
/**
* Mode of the progress bar.
*
@@ -205,63 +135,51 @@ export class MatProgressBar
// Note that we don't technically need a getter and a setter here,
// but we use it to match the behavior of the existing mat-progress-bar.
this._mode = value;
- this._syncFoundation();
+ this._changeDetectorRef.markForCheck();
}
private _mode: ProgressBarMode = 'determinate';
ngAfterViewInit() {
- const element = this._elementRef.nativeElement;
-
- this._primaryBar = element.querySelector('.mdc-linear-progress__primary-bar') as HTMLElement;
- this._bufferBar = element.querySelector('.mdc-linear-progress__buffer-bar') as HTMLElement;
-
- this._foundation = new MDCLinearProgressFoundation(this._adapter);
- this._foundation.init();
- this._syncFoundation();
-
// Run outside angular so change detection didn't get triggered on every transition end
// instead only on the animation that we care about (primary value bar's transitionend)
this._ngZone.runOutsideAngular(() => {
- this._animationEndSubscription = (
- fromEvent(this._primaryBar, 'transitionend') as Observable
- )
- .pipe(filter((e: TransitionEvent) => e.target === this._primaryBar))
- .subscribe(() => {
- if (this.animationEnd.observers.length === 0) {
- return;
- }
-
- if (this.mode === 'determinate' || this.mode === 'buffer') {
- this._ngZone.run(() => this.animationEnd.next({value: this.value}));
- }
- });
+ this._elementRef.nativeElement.addEventListener('transitionend', this._transitionendHandler);
});
}
ngOnDestroy() {
- if (this._foundation) {
- this._foundation.destroy();
- }
- this._animationEndSubscription.unsubscribe();
- this._dirChangeSubscription.unsubscribe();
+ this._elementRef.nativeElement.removeEventListener('transitionend', this._transitionendHandler);
}
- /** Syncs the state of the progress bar with the MDC foundation. */
- private _syncFoundation() {
- const foundation = this._foundation;
+ /** Gets the transform style that should be applied to the primary bar. */
+ _getPrimaryBarTransform(): string {
+ return `scaleX(${this._isIndeterminate() ? 1 : this.value / 100})`;
+ }
- if (foundation) {
- const mode = this.mode;
- foundation.setDeterminate(mode !== 'indeterminate' && mode !== 'query');
+ /** Gets the `flex-basis` value that should be applied to the buffer bar. */
+ _getBufferBarFlexBasis(): string {
+ return `${this.mode === 'buffer' ? this.bufferValue : 100}%`;
+ }
- // Divide by 100 because MDC deals with values between 0 and 1.
- foundation.setProgress(this.value / 100);
+ /** Returns whether the progress bar is indeterminate. */
+ _isIndeterminate(): boolean {
+ return this.mode === 'indeterminate' || this.mode === 'query';
+ }
- if (mode === 'buffer') {
- foundation.setBuffer(this.bufferValue / 100);
- }
+ /** Event handler for `transitionend` events. */
+ private _transitionendHandler = (event: TransitionEvent) => {
+ if (
+ this.animationEnd.observers.length === 0 ||
+ !event.target ||
+ !(event.target as HTMLElement).classList.contains('mdc-linear-progress__primary-bar')
+ ) {
+ return;
}
- }
+
+ if (this.mode === 'determinate' || this.mode === 'buffer') {
+ this._ngZone.run(() => this.animationEnd.next({value: this.value}));
+ }
+ };
}
/** Clamps a value to be between two numbers, by default 0 and 100. */