Skip to content
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

Merged
merged 16 commits into from
Aug 28, 2018

Conversation

glouischandra
Copy link
Contributor

…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.
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Jul 27, 2018
Copy link
Member

@crisbeto crisbeto left a 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.

@glouischandra
Copy link
Contributor Author

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?

@crisbeto
Copy link
Member

You can look at the event.target to filter out only the events that you care about.

@glouischandra
Copy link
Contributor Author

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.

@jelbourn jelbourn added the G This is is related to a Google internal issue label Jul 27, 2018
Copy link
Member

@jelbourn jelbourn left a 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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omit spaces in braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -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;
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

* 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>();
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

* 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>();
Copy link
Member

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

Copy link
Contributor Author

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!

/**
* 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.
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.


/** Attaches value bar element animation end listener. */
ngAfterViewInit() {
if(this.mode === 'determinate' || this.mode === 'buffer') {
Copy link
Member

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -113,6 +131,21 @@ export class MatProgressBar extends _MatProgressBarMixinBase implements CanColor
return {transform: `scaleX(${scale})`};
}
}

/** Attaches value bar element animation end listener. */
Copy link
Member

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

Copy link
Contributor Author

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.

}
}

/** Removes value bar element animation end listener. */
Copy link
Member

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

Copy link
Contributor Author

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', () => {
Copy link
Member

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

Copy link
Contributor Author

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:

  1. Check that event is not bound?
  2. On animation === noop, always call output on value change?

Or would it be something else?

Copy link
Member

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.

Copy link
Contributor Author

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.


/** Attaches value bar element animation end listener. */
ngAfterViewInit() {
if(this.mode === 'determinate' || this.mode === 'buffer') {
Copy link
Member

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

Copy link
Contributor Author

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.


/** Attaches value bar element animation end listener. */
ngAfterViewInit() {
if(this.mode === 'determinate' || this.mode === 'buffer') {
Copy link
Member

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

Copy link
Contributor Author

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?

/** Attaches value bar element animation end listener. */
ngAfterViewInit() {
if(this.mode === 'determinate' || this.mode === 'buffer') {
this._valueAnimationSubscription = fromEvent(this.primaryValueBar.nativeElement, 'transitionend')
Copy link
Member

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:

  1. 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 whether event.target === this.primaryValueBar.nativeElement.
  2. The event should be bound outside the NgZone and it should re-enter the zone only when it emits.

Copy link
Contributor Author

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:

  1. 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?
  2. 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. That's true, but it's more of a future-proofing thing in case we decide to put something into the element.
  2. It's only really necessary if we end up doing point 1, otherwise it would be redundant.

Copy link
Contributor Author

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.

@Output() valueAnimationEnd = new EventEmitter<number>();

/** Reference to animation end subscription to be unsubscribed on destroy. */
private _valueAnimationSubscription: Subscription | null;
Copy link
Member

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.

Copy link
Contributor Author

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.

@jelbourn jelbourn added the target: major This PR is targeted for the next major release label Aug 1, 2018
Copy link
Member

@jelbourn jelbourn left a 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

/** 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});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omit spaces in braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/**
* 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.
Copy link
Member

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -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;
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

@jelbourn jelbourn left a 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

@@ -27,6 +35,11 @@ export class MatProgressBarBase {
constructor(public _elementRef: ElementRef) { }
}

/** Last animation end data. */
export interface AnimationEndData {
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@jelbourn jelbourn added the action: merge The PR is ready for merge by the caretaker label Aug 2, 2018
this._animationEndSubscription.unsubscribe();
}

/** Callback function on animation end, emit animationEnd event on determinate and buffer mode. */
Copy link
Member

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. */

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@ngbot
Copy link

ngbot bot commented Aug 22, 2018

Hi @glouischandra! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

@jelbourn jelbourn added the P2 The issue is important to a large percentage of users, with a workaround label Aug 24, 2018
@ngbot
Copy link

ngbot bot commented Aug 27, 2018

Hi @glouischandra! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

4 similar comments
@ngbot
Copy link

ngbot bot commented Aug 27, 2018

Hi @glouischandra! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

@ngbot
Copy link

ngbot bot commented Aug 27, 2018

Hi @glouischandra! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

@ngbot
Copy link

ngbot bot commented Aug 27, 2018

Hi @glouischandra! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

@ngbot
Copy link

ngbot bot commented Aug 27, 2018

Hi @glouischandra! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

@jelbourn jelbourn merged commit 6a1a707 into angular:master Aug 28, 2018
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement G This is is related to a Google internal issue P2 The issue is important to a large percentage of users, with a workaround target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants