Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(core): add enter and leave animation instructions#62682

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
thePunderWoman wants to merge1 commit intoangular:main
base:main
Choose a base branch
Loading
fromthePunderWoman:animate-instructions

Conversation

thePunderWoman
Copy link
Contributor

This adds the instructions to support enter and leave animations on nodes.

Does this PR introduce a breaking change?

  • Yes
  • No

AndrewKushnir and IgorSedov reacted with thumbs up emojieneajaho, nifiro, JeanMeche, michael-small, HyperLife1119, AndrewKushnir, IgorSedov, ThomOrlo, and artaommahe reacted with hooray emoji
@thePunderWomanthePunderWoman added area: coreIssues related to the framework runtime target: minorThis PR is targeted for the next minor release labelsJul 17, 2025
@ngbotngbotbot added this to theBacklog milestoneJul 17, 2025
@angular-robotangular-robotbot added the detected: featurePR contains a feature commit labelJul 17, 2025
@thePunderWomanthePunderWomanforce-pushed theanimate-instructions branch 2 times, most recently from843db01 to46066a1CompareJuly 17, 2025 11:04
@thePunderWomanthePunderWomanforce-pushed theanimate-instructions branch 2 times, most recently from3c38d4a to056dcb3CompareJuly 17, 2025 11:53
@github-actionsGitHub Actions
Copy link

github-actionsbot commentedJul 17, 2025
edited
Loading

Deployed adev-preview for85f73c2 to:https://ng-dev-previews-fw--pr-angular-angular-62682-adev-prev-yvy73xs1.web.app

Note: As new commits are pushed to this pull request, this link is updated after the preview is rebuilt.

animate(el:Element,removeFn:Function):void{
if(!this.outElements.has(el))return;
constdetails=this.outElements.get(el)!;
consttimeout=setTimeout(()=>removeFn(),4000);
Copy link
Member

@JeanMecheJeanMecheJul 17, 2025
edited
Loading

Choose a reason for hiding this comment

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

4000 feels like a magic number here. Can we use a named a named constant and/or have a comment on why this value. (Would animations longer than 4s just get removed after ther 4s ?)

michael-small reacted with eyes emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Certainly. I've added a const and associated comment about why 4 seconds.

Copy link
ContributorAuthor

@thePunderWomanthePunderWomanJul 17, 2025
edited
Loading

Choose a reason for hiding this comment

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

Also to quickly answer your question here, if you're using an animation function (rather than classes) foranimate.leave, you are required to call the complete function to tell the framework when to remove the element. The feedback on the RFC was to have a timeout for safety, and so I went with the same timeout duration as cross document view transitions. So after a 4 second timeout, the element gets removed. If your animation is done before that, no issues.

Copy link
Contributor

Choose a reason for hiding this comment

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

What if we can have an injection token that configures it? And the default would still be 4s.

Copy link
Contributor

Choose a reason for hiding this comment

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

We can keep it like this for now, but we should probably output a dev-mode warning for cases when timeout is triggered (for ex. for cases when the "done" callback is never invoked). That can help to surface this situations and we can see if there are cases when the timeout needs to be adjusted.

A couple additional thoughts:

  • This can be done in a followup PR
  • We should consider cases when those warnings are produced for elements inside of@for loops and avoid spamming console with those warnings

@thePunderWomanthePunderWomanforce-pushed theanimate-instructions branch 2 times, most recently from72ef660 to4d2fc63CompareJuly 17, 2025 14:34
@thePunderWomanthePunderWoman added the action: reviewThe PR is still awaiting reviews from at least one requested reviewer labelJul 17, 2025
@@ -667,6 +679,23 @@ export class ElementRef<T = any> {
nativeElement:T;
}

// @public
exportclassElementRegistry {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this symbol in the public API, was it intentionally exposed?

@@ -96,6 +97,7 @@
"EffectRefImpl",
"EffectScheduler",
"ElementRef",
"ElementRegistry",
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks unexpected, should we try making it tree-shakable (maybe via using lightweight injection token in the non-tree-shakable part and providing an actual implementation once the feature is brought it via special instructions)?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I'm not sure how we would be able to do this since there's no provider function for Animations.

Copy link
Member

@JeanMecheJeanMecheJul 18, 2025
edited
Loading

Choose a reason for hiding this comment

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

We could use thefeatures prop, like we did for standalone in the past (#58288 removed it)

AndrewKushnir reacted with thumbs up emoji
Copy link
Contributor

@AndrewKushnirAndrewKushnirJul 18, 2025
edited
Loading

Choose a reason for hiding this comment

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

We could use the features prop, like we did for standalone in the past (#58288 removed it)

+1, left a comment here:#62682 (comment)

*
*@codeGenApi
*/
exportfunctionɵɵanimateEnter(value:string|Function):typeofɵɵanimateEnter{
Copy link
Contributor

Choose a reason for hiding this comment

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

This function is pretty long, so I was thinking that it might benefit from either having more docs, explaining the overall logic and/or refactor the logic and extract it into multiple functions (I suspect that some of the functions can also be reused across instructions within this file).

Copy link
ContributorAuthor

@thePunderWomanthePunderWomanJul 18, 2025
edited
Loading

Choose a reason for hiding this comment

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

Yeah there was a lot of currying happening here because of needed variable values and wanting to be able to clean up the event listeners. I've switched to listeners that only fire once now, which allowed this to be broken up. So, this should hopefully work well. Otherwise I'll have to figure out a good cleanup solution.

Update: We really only need to clean up the listeners foranimate.enter and only two of them. I've found a nice clean way to deal with that. This should be decently organized now.

):LongestAnimation|undefined{
if(!(event.targetinstanceofElement))return;
constnativeElement=event.target;
if(typeofnativeElement.getAnimations==='function'){
Copy link
Contributor

Choose a reason for hiding this comment

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

We have this check in a few places and I was wondering if we are checking for thegetAnimations function presence on a given element or it's a test whether it's generally supported in the current environment? If the latter - we can just do it once somewhere and reuse the result.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

This is primarily becausegetAnimations does not exist outside of a browser environment. There's two cases where we check it, one is here and the other is when we set up a cancellation on enter animations. I don't know that it makes sense to use it only once in that case. The cancellation function only is used inanimateEnter cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it, thanks. My thinking was to have this check somewhere at the module level, e.g. check ifdocument.body.getAnimations is present and just use this info everywhere. The problem that in this case well be bypassingrenderer completely, which may not be great (but likely in practice it'd not be a big problem). Maybe there are some other ways to do it? WDYT?

Another idea is that we can move this check from this utility function into an instruction, so that we just exit early (similar to SSR checks).

@@ -273,6 +281,7 @@ class DefaultDomRenderer2 implements Renderer2 {
privatereadonlyngZone:NgZone,
privatereadonlyplatformIsServer:boolean,
privatereadonlytracingService:TracingService<TracingSnapshot>|null,
privatereadonlyregistry:ElementRegistry|null,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is likely where theElementRegistry symbol is "leaking" (becomes non-tree-shakable).

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Yeah. The DOM renderer is the key place this registry is needed to delay removal of elements.

@thePunderWomanthePunderWomanforce-pushed theanimate-instructions branch 2 times, most recently from277e2bf toe6569a6CompareJuly 18, 2025 12:25
@thePunderWoman
Copy link
ContributorAuthor

/gemini review

Copy link

@gemini-code-assistgemini-code-assistbot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support foranimate.enter andanimate.leave instructions for animations on nodes. The changes include new public APIs likeAnimationCallbackEvent,AnimationFunction, andANIMATIONS_DISABLED, a newElementRegistry service to manage animations for leaving elements, and the core implementation in the rendering instructions. The changes are well-supported by a comprehensive suite of acceptance tests. I've identified a couple of potential issues inpackages/core/src/animation.ts andpackages/core/src/render3/instructions/animation.ts. Please see my detailed comments.

This adds the instructions to support enter and leave animations on nodes.
@@ -92,7 +92,7 @@ export const enum RuntimeErrorCode {
ASSERTION_NOT_INSIDE_REACTIVE_CONTEXT=-602,

// Styling Errors
Copy link
Contributor

Choose a reason for hiding this comment

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

We'd probably need to update a section title above (that says "// Styling Errors").

*@codeGenApi
*/
exportfunctionɵɵanimateEnter(value:string|Function):typeofɵɵanimateEnter{
performanceMarkFeature('NgAnimateEnter');
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we just have 1 metric that we track (e.g. something likeperformanceMarkFeature('NgLightweightAnimations')) vs having individual metrics for enter and leave?

returnɵɵanimateEnter;
}

assertAnimationTypes(value,'animate.enter');
Copy link
Contributor

Choose a reason for hiding this comment

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

The dev mode check should be present at this level, otherwise the function itself would be retained (the contents of the function would be tree-shaken, but not the function def itself).

consttNode=getCurrentTNode()!;
constnativeElement=getNativeByTNode(tNode,lView)asHTMLElement;

if((nativeElementasNode).nodeType!==Node.ELEMENT_NODE){
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious what'd be an example of such usage (I believe host bindings might be in that category)? If we come up with an example, we can see what'd be the best way to handle it and whether the code should be at compile-time (preferable, if possible) or at runtime.

animate(el:Element,removeFn:Function):void{
if(!this.outElements.has(el))return;
constdetails=this.outElements.get(el)!;
consttimeout=setTimeout(()=>removeFn(),4000);
Copy link
Contributor

Choose a reason for hiding this comment

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

We can keep it like this for now, but we should probably output a dev-mode warning for cases when timeout is triggered (for ex. for cases when the "done" callback is never invoked). That can help to surface this situations and we can see if there are cases when the timeout needs to be adjusted.

A couple additional thoughts:

  • This can be done in a followup PR
  • We should consider cases when those warnings are produced for elements inside of@for loops and avoid spamming console with those warnings

Comment on lines +242 to +243
constelementRegistry=lView[INJECTOR]!.get(ElementRegistry);
constanimationsDisabled=lView[INJECTOR]!.get(ANIMATIONS_DISABLED,DEFAULT_ANIMATIONS_DISABLED);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
constelementRegistry=lView[INJECTOR]!.get(ElementRegistry);
constanimationsDisabled=lView[INJECTOR]!.get(ANIMATIONS_DISABLED,DEFAULT_ANIMATIONS_DISABLED);
constinjector=lView[INJECTOR]!;
constelementRegistry=injector.get(ElementRegistry);
constanimationsDisabled=injector.get(ANIMATIONS_DISABLED,DEFAULT_ANIMATIONS_DISABLED);

):LongestAnimation|undefined{
if(!(event.targetinstanceofElement))return;
constnativeElement=event.target;
if(typeofnativeElement.getAnimations==='function'){
Copy link
Contributor

Choose a reason for hiding this comment

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

Got it, thanks. My thinking was to have this check somewhere at the module level, e.g. check ifdocument.body.getAnimations is present and just use this info everywhere. The problem that in this case well be bypassingrenderer completely, which may not be great (but likely in practice it'd not be a big problem). Maybe there are some other ways to do it? WDYT?

Another idea is that we can move this check from this utility function into an instruction, so that we just exit early (similar to SSR checks).

*/
@Injectable({providedIn:'root'})
exportclassElementRegistry{
privateoutElements=newMap<Element,AnimationDetails>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it be aWeakMap instead?

Comment on lines +329 to 338
if(this.registry&&this.registry.has(oldChildasElement)){
this.registry.animate(oldChild,(timeout:ReturnType<typeofsetTimeout>)=>{
clearTimeout(timeout);
this.registry?.remove(oldChild);
oldChild.remove();
});
return;
}
// child was removed
oldChild.remove();
Copy link
Contributor

Choose a reason for hiding this comment

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

We should try to minimize the amount of non-tree-shakable code here (in favor of having more code inside of the registry itself):

Suggested change
if(this.registry&&this.registry.has(oldChildasElement)){
this.registry.animate(oldChild,(timeout:ReturnType<typeofsetTimeout>)=>{
clearTimeout(timeout);
this.registry?.remove(oldChild);
oldChild.remove();
});
return;
}
// child was removed
oldChild.remove();
if(!this.registry?.animateRemoval(oldChild,()=>oldChild.remove())){
// Either animation registry is not present (i.e. a component doesn't use animations)
// or that element was not in the registry. If that's the case - remove the element here.
// Otherwise, it'd be removed by the animation logic at the end of the animation.
oldChild.remove();
}

Comment on lines +153 to +154
@Inject(ElementRegistry)
privatereadonlyregistry:ElementRegistry|null,
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on the current implementation, theElementRegistry would always be present (since it's provided inroot).

I like the idea that@JeanMeche proposed: we can use "features" subsystem to bring inElementRegistry in a tree-shakable way. Another benefit would be that theElementRegistry would not need to be exposed via public API, since renderers would not need to have it as an argument. We'll be able to retrieve it from the component type (seetype: RendererType2 in thecreateRenderer method below). The "feature" would basically be a function that given an injector, returns an instance of theElementRegistry (e.g. callsinjector.get(ElementRegistry, ...)).

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@JeanMecheJeanMecheJeanMeche left review comments

@eneajahoeneajahoeneajaho left review comments

@michael-smallmichael-smallmichael-small left review comments

@AndrewKushnirAndrewKushnirAndrewKushnir left review comments

@gemini-code-assistgemini-code-assist[bot]gemini-code-assist[bot] left review comments

@crisbetocrisbetoAwaiting requested review from crisbeto

@kirjskirjsAwaiting requested review from kirjs

@mmalerbammalerbaAwaiting requested review from mmalerba

At least 1 approving review is required to merge this pull request.

Assignees
No one assigned
Labels
action: reviewThe PR is still awaiting reviews from at least one requested revieweradev: previewarea: coreIssues related to the framework runtimedetected: featurePR contains a feature committarget: minorThis PR is targeted for the next minor release
Projects
None yet
Milestone
Backlog
Development

Successfully merging this pull request may close these issues.

5 participants
@thePunderWoman@JeanMeche@eneajaho@michael-small@AndrewKushnir

[8]ページ先頭

©2009-2025 Movatter.jp