Skip to content

fix(compiler): compile animate.enter and animate.leave listeners to the animation instructions - #597

Open
GabeSilvaDev wants to merge 1 commit into
voidzero-dev:mainfrom
GabeSilvaDev:fix/animate-event-listeners
Open

GabeSilvaDev wants to merge 1 commit into
voidzero-dev:mainfrom
GabeSilvaDev:fix/animate-event-listeners

Conversation

@GabeSilvaDev

Copy link
Copy Markdown
Contributor

Summary

(animate.enter) and (animate.leave) listeners compile to the wrong instruction, so the handler never runs. For (animate.leave) that also means the element is removed right away instead of when the handler calls animationComplete().

@Component({
  selector: 'app-card',
  template: '<div (animate.leave)="onLeave($event)"></div>',
  host: { '(animate.enter)': 'onEnter($event)', 'animate.leave': 'fade-out' },
})
export class Card {}

OXC on main:

// template
i0.ɵɵlistener("@animate.leave.start", function Card_Template_div_animation_animate_leave_start_0_listener($event) { ... });
// hostBindings
i0.ɵɵlistener("animate.leave", ...); // for '(animate.enter)': a DOM event named "animate.enter"
// host 'animate.leave': 'fade-out' is dropped

ngc, and OXC with this change:

// template
i0.ɵɵanimateLeaveListener(function Card_Template_div_animateleave_0_listener($event) { ... });
// hostBindings
i0.ɵɵanimateEnterListener(function Card_animateenter_HostBindingHandler($event) { ... });
i0.ɵɵanimateLeave("fade-out");

The gaps were in these places:

Piece Change
pipeline/ingest.rs AnimationListenerOp existed in the IR, and naming and reify handled it, but nothing created it. A comment in ingest_listener_owned noted that ParsedEventType::Animation "should be dispatched to AnimationListenerOp by a separate template-path fix". Template and host events of that type now create it, as in ingestElementEvents / ingestHostEvent. The last handler expression becomes a return statement (makeListenerHandlerOps).
directive/compiler.rs, component/transform.rs Host listener names starting with animate. are now ParsedEventType::Animation, as in the binding parser's _parseRegularEvent. They were Regular before. As upstream does, the check runs on the name before any window: / document: / body: target is split off.
reify/statements/misc.rs create_animation_listener_stmt emitted ɵɵsyntheticHostListener("@name.start"/"@name.done", fn). It now emits ɵɵanimateEnterListener(fn) / ɵɵanimateLeaveListener(fn), like animationListener in instruction.ts.
reify/mod.rs Host reify ignored AnimationString ops, so a static animate.enter / animate.leave host attribute was converted and then dropped. It now emits ɵɵanimateEnter / ɵɵanimateLeave, as the template reify already does.

The instructions exist since Angular 20.2.0, the release that added animate.enter / animate.leave. Like the existing ɵɵanimateEnter / ɵɵanimateLeave emit, they are not gated on the Angular version.

Tests

  • Three integration tests:
    • an (animate.enter) listener;
    • an (animate.leave) listener with several statements next to a static animate.leave;
    • a host (animate.leave) listener with a static host animate.enter.
  • Four e2e compare fixtures in animations/animate-listener.fixture.ts:
    • an (animate.enter) listener;
    • an (animate.leave) listener next to a static animate.leave, inside @if;
    • a host (animate.leave) listener with a static host animate.enter;
    • @HostListener('animate.enter', ['$event']) on a directive.
  • All four fixtures fail on main and match ngc 22.2.2 with this change.
  • cargo fmt --all -- --check, cargo test --all-features (3329 passed), and cargo run -p oxc_angular_conformance (1264/1264, no snapshot diff) all pass.

One difference is left, and it was already there before this change: when an element has both a static animate.enter and a bound [animate.leave], ngc emits ɵɵanimateLeave before ɵɵanimateEnter and OXC emits them the other way round. Both register on the same element in the creation pass, so behavior is the same. I left it out of this PR.

Copilot AI balanced review requested due to automatic review settings October 21, 2026 11:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-21T11:17:24.584186Z 98db00a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

…he animation instructions

Template listeners compiled to ɵɵsyntheticHostListener("@animate.enter.start", ...), a legacy animation listener, and host listeners to ɵɵlistener("animate.leave", ...), a DOM event that never fires. Create AnimationListenerOp for these events, as Angular's ingest does, and reify it to the enter/leave listener instructions.

A static animate.enter / animate.leave host attribute was dropped because host reify ignored AnimationString ops; emit ɵɵanimateEnter / ɵɵanimateLeave for it.
@GabeSilvaDev
GabeSilvaDev force-pushed the fix/animate-event-listeners branch from 98db00a to b323642 Compare October 21, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants