-
Notifications
You must be signed in to change notification settings - Fork 6.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add CSS Transition end output on mat-progress-bar value animation #12409
Conversation
…in mat-form-field-flex. This approach is more easily overriden by partner team that customizes the look and feel of Material components.
…it caused the outline box to be squished on projects that uses the mixin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the use case for this output? You should be able to do the same by attaching a transitionend
event on the outside.
crisbeto - we need a notifier for when the animation completed to avoid it being cutoff if we're just relying on the value. This demo shows the problem: https://stackblitz.com/edit/matprogressanimation?file=app%2Fprogress-bar-determinate-example.html If we attach transitionend on mat-progress-bar tag would it be hard to distinguish on which element animation ended? I haven't tried this but would all transitionend on value or bufferValue bubble up together? The event added here is specific for value transition and it feels cleaner that way. Thoughts? |
You can look at the |
Thanks Cris, cmiiw: that works but I would need to know the specific details of elements inside mat-progress-bar which I think breaks encapsulation of this component. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@glouischandra @crisbeto Using just transitionend
is definitely preferable from the library maintainer perspective, but I'm fine adding the extra output.
import {Location} from '@angular/common'; | ||
import {MatProgressBarModule} from './index'; | ||
import { MatProgressBar } from './progress-bar'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Omit spaces in braces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
src/lib/progress-bar/progress-bar.ts
Outdated
@@ -82,6 +88,18 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements CanColor | |||
set bufferValue(v: number) { this._bufferValue = clamp(v || 0); } | |||
private _bufferValue: number = 0; | |||
|
|||
@ViewChild("primaryValueBar") primaryValueBar: ElementRef; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be single quotes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
src/lib/progress-bar/progress-bar.ts
Outdated
* Also note, if multiple event update is thrown in quick succession the last event completion is | ||
* the one firing the output. | ||
*/ | ||
@Output() valueAnimationEnd = new EventEmitter<number>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make the name here animationEnd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
src/lib/progress-bar/progress-bar.ts
Outdated
* Also note, if multiple event update is thrown in quick succession the last event completion is | ||
* the one firing the output. | ||
*/ | ||
@Output() valueAnimationEnd = new EventEmitter<number>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally avoid events that emit a primitive value because you can't ever add additional fields later. Instead an interface
is preferred
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created event interface, looks more flexible now, thanks!
src/lib/progress-bar/progress-bar.ts
Outdated
/** | ||
* Event that indicates animation end on value bar. Currently only support determinate and buffer. | ||
* Also note, if multiple event update is thrown in quick succession the last event completion is | ||
* the one firing the output. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rephrase the comment to say that the event will never be emitted for animations that don't "end" (i.e. 'query' and 'indeterminate'). Also that the event will never fire if animations are disabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
src/lib/progress-bar/progress-bar.ts
Outdated
|
||
/** Attaches value bar element animation end listener. */ | ||
ngAfterViewInit() { | ||
if(this.mode === 'determinate' || this.mode === 'buffer') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Space after if
(here and below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
src/lib/progress-bar/progress-bar.ts
Outdated
@@ -113,6 +131,21 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements CanColor | |||
return {transform: `scaleX(${scale})`}; | |||
} | |||
} | |||
|
|||
/** Attaches value bar element animation end listener. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove this comment since it doesn't always describe what ngAfterViewInit
does. Really the best thing would be to add another method like _addAnimationEndListener
and call it here, but I don't feel that strongly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the comment inside the callback hopefully that helps with documentation.
src/lib/progress-bar/progress-bar.ts
Outdated
} | ||
} | ||
|
||
/** Removes value bar element animation end listener. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment can be omitted; it's pretty clear/obvious what ngOnDestroy
is doing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
@@ -118,6 +120,53 @@ describe('MatProgressBar', () => { | |||
expect(progressElement.componentInstance.mode).toBe('buffer'); | |||
}); | |||
}); | |||
|
|||
describe('animation trigger on determinate setting', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to also have a unit test with NoopAnimationsModule
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not completely following the test where NoopAnimationsModule here, would it look like:
- Check that event is not bound?
- On animation === noop, always call output on value change?
Or would it be something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to ensure that we have a test that covers the code path where NoopAnimationsModule
is loaded and someone tries to register this event. The goal being to ensure that no error is thrown more so than that a particular outcome occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks that makes sense. I added some more test with NoopAnimations module turned on. Also I make the animationEnd fire when setting the value in NoopAnimations mode, my thoughts is that page author might be relying on this output to update their DOM in their test so we should fire the output synchronously when there's no animation. PTAL.
src/lib/progress-bar/progress-bar.ts
Outdated
|
||
/** Attaches value bar element animation end listener. */ | ||
ngAfterViewInit() { | ||
if(this.mode === 'determinate' || this.mode === 'buffer') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also check the _animationMode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok added, to the binding to only bind transition on _animationMode !== noop.
src/lib/progress-bar/progress-bar.ts
Outdated
|
||
/** Attaches value bar element animation end listener. */ | ||
ngAfterViewInit() { | ||
if(this.mode === 'determinate' || this.mode === 'buffer') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than only observing for animation events when it is determinate
or buffer
, we should always set up the subscription and only emit out valueAnimationEnd
event when appropriate. Otherwise, the mode
of the MatProgressBar
cannot be changed dynamically and still utilize this event
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that's nice tip! Definitely more flexible that way, I guess one possible downside is that the output is always executed on unsupported method, but that seems like small overhead?
src/lib/progress-bar/progress-bar.ts
Outdated
/** Attaches value bar element animation end listener. */ | ||
ngAfterViewInit() { | ||
if(this.mode === 'determinate' || this.mode === 'buffer') { | ||
this._valueAnimationSubscription = fromEvent(this.primaryValueBar.nativeElement, 'transitionend') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a couple of things:
- Since the
transitionend
event bubbles, it means that it could fire if a transition completes for an element inside the track. We should filter out based on whetherevent.target === this.primaryValueBar.nativeElement
. - The event should be bound outside the
NgZone
and it should re-enter the zone only when it emits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple questions on these before I make the change:
- The event is bound to specifically div mat-progress-bar-primary element inside the template, so adding the filter target would be redundant? What am I missing here?
- Is this more of best practice to run CSS event on NgZone? Would re-enter zone looks like this? https://github.com/angular/material2/blob/c2fc3f45b5ccdbff3ade56e0685b621688e9cb6d/src/cdk/text-field/autofill.ts#L72.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- That's true, but it's more of a future-proofing thing in case we decide to put something into the element.
- It's only really necessary if we end up doing point 1, otherwise it would be redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks for the explanation. I've updated the latest commit with these two points incorporated. PTAL.
src/lib/progress-bar/progress-bar.ts
Outdated
@Output() valueAnimationEnd = new EventEmitter<number>(); | ||
|
||
/** Reference to animation end subscription to be unsubscribed on destroy. */ | ||
private _valueAnimationSubscription: Subscription | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can initialize this to Subscription.EMPTY
. That way you won't have to null check it everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice tip! makes code more clean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks look, just a last few comments
src/lib/progress-bar/progress-bar.ts
Outdated
/** Callback function on animation end, emit animationEnd event on determinate and buffer mode. */ | ||
private emitAnimationEnd(): void { | ||
if (this.mode === 'determinate' || this.mode === 'buffer') { | ||
this.animationEnd.next({ value: this.value}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Omit spaces in braces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/lib/progress-bar/progress-bar.ts
Outdated
/** | ||
* Event that indicates animation end on value bar. This event will not be emitted for | ||
* animations that doesn't end, i.e. indeterminate and query. Also if animation is | ||
* disabled, this event will not emit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slight rewording:
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 animations
(indeterminate and query).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/lib/progress-bar/progress-bar.ts
Outdated
@@ -82,6 +94,18 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements CanColor | |||
set bufferValue(v: number) { this._bufferValue = clamp(v || 0); } | |||
private _bufferValue: number = 0; | |||
|
|||
@ViewChild('primaryValueBar') primaryValueBar: ElementRef; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This property should be prefixed with an underscore to indicate that it is not part of the public API surface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
…ations mode on ProgressBar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM aside from one last comment
src/lib/progress-bar/progress-bar.ts
Outdated
@@ -27,6 +35,11 @@ export class MatProgressBarBase { | |||
constructor(public _elementRef: ElementRef) { } | |||
} | |||
|
|||
/** Last animation end data. */ | |||
export interface AnimationEndData { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, one last thing I missed earlier- this should be ProgressAnimationEnd
; having Data
at the end is inconsistent with other events around the library
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/lib/progress-bar/progress-bar.ts
Outdated
this._animationEndSubscription.unsubscribe(); | ||
} | ||
|
||
/** Callback function on animation end, emit animationEnd event on determinate and buffer mode. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method comment should not describe when its used, only what it does. Should instead be:
/** Emit an animationEnd event if in determinate or buffer mode. */
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
Hi @glouischandra! This PR has merge conflicts due to recent upstream merges. |
Hi @glouischandra! This PR has merge conflicts due to recent upstream merges. |
4 similar comments
Hi @glouischandra! This PR has merge conflicts due to recent upstream merges. |
Hi @glouischandra! This PR has merge conflicts due to recent upstream merges. |
Hi @glouischandra! This PR has merge conflicts due to recent upstream merges. |
Hi @glouischandra! This PR has merge conflicts due to recent upstream merges. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Demo of the issue: https://stackblitz.com/edit/matprogressanimation?file=app%2Fprogress-bar-determinate-example.html