Skip to content

Commit 7b51728

Browse files
fix(compiler): fixes animation event host bindings not firing (#63217)
Host bindings for `(animate.enter)` and `(animate.leave)` were not firing properly. This fixes the compiler ingest to make sure they do fire. fixes: #63199 PR Close #63217
1 parent 5be80d3 commit 7b51728

File tree

8 files changed

+373
-15
lines changed

8 files changed

+373
-15
lines changed

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/animations/GOLDEN_PARTIAL.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,59 @@ export declare class MyComponent {
7979
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never, true, never>;
8080
}
8181

82+
/****************************************************************************************************
83+
* PARTIAL FILE: animate_enter_with_event_host_bindings.js
84+
****************************************************************************************************/
85+
import { Component } from '@angular/core';
86+
import * as i0 from "@angular/core";
87+
export class ChildComponent {
88+
fadeFn(event) {
89+
event.target.classList.add('fade');
90+
event.animationComplete();
91+
}
92+
}
93+
ChildComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ChildComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
94+
ChildComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: ChildComponent, isStandalone: true, selector: "child-component", host: { listeners: { "animate.enter": "fadeFn($event)" } }, ngImport: i0, template: `<p>Sliding Content</p>`, isInline: true });
95+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ChildComponent, decorators: [{
96+
type: Component,
97+
args: [{
98+
selector: 'child-component',
99+
host: { '(animate.enter)': 'fadeFn($event)' },
100+
template: `<p>Sliding Content</p>`,
101+
}]
102+
}] });
103+
export class MyComponent {
104+
}
105+
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
106+
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "my-component", ngImport: i0, template: `
107+
<child-component animate.enter="slide"></child-component>
108+
`, isInline: true, dependencies: [{ kind: "component", type: ChildComponent, selector: "child-component" }] });
109+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
110+
type: Component,
111+
args: [{
112+
selector: 'my-component',
113+
imports: [ChildComponent],
114+
template: `
115+
<child-component animate.enter="slide"></child-component>
116+
`,
117+
}]
118+
}] });
119+
120+
/****************************************************************************************************
121+
* PARTIAL FILE: animate_enter_with_event_host_bindings.d.ts
122+
****************************************************************************************************/
123+
import { AnimationCallbackEvent } from '@angular/core';
124+
import * as i0 from "@angular/core";
125+
export declare class ChildComponent {
126+
fadeFn(event: AnimationCallbackEvent): void;
127+
static ɵfac: i0.ɵɵFactoryDeclaration<ChildComponent, never>;
128+
static ɵcmp: i0.ɵɵComponentDeclaration<ChildComponent, "child-component", never, {}, {}, never, never, true, never>;
129+
}
130+
export declare class MyComponent {
131+
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
132+
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never, true, never>;
133+
}
134+
82135
/****************************************************************************************************
83136
* PARTIAL FILE: animate_enter_with_binding.js
84137
****************************************************************************************************/
@@ -314,3 +367,56 @@ export declare class MyComponent {
314367
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never, true, never>;
315368
}
316369

370+
/****************************************************************************************************
371+
* PARTIAL FILE: animate_leave_with_event_host_bindings.js
372+
****************************************************************************************************/
373+
import { Component } from '@angular/core';
374+
import * as i0 from "@angular/core";
375+
export class ChildComponent {
376+
fadeFn(event) {
377+
event.target.classList.add('fade');
378+
event.animationComplete();
379+
}
380+
}
381+
ChildComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ChildComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
382+
ChildComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: ChildComponent, isStandalone: true, selector: "child-component", host: { listeners: { "animate.leave": "fadeFn($event)" } }, ngImport: i0, template: `<p>Fading Content</p>`, isInline: true });
383+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ChildComponent, decorators: [{
384+
type: Component,
385+
args: [{
386+
selector: 'child-component',
387+
template: `<p>Fading Content</p>`,
388+
host: { '(animate.leave)': 'fadeFn($event)' },
389+
}]
390+
}] });
391+
export class MyComponent {
392+
}
393+
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
394+
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "my-component", ngImport: i0, template: `
395+
<child-component animate.leave="slide"></child-component>
396+
`, isInline: true, dependencies: [{ kind: "component", type: ChildComponent, selector: "child-component" }] });
397+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
398+
type: Component,
399+
args: [{
400+
selector: 'my-component',
401+
imports: [ChildComponent],
402+
template: `
403+
<child-component animate.leave="slide"></child-component>
404+
`,
405+
}]
406+
}] });
407+
408+
/****************************************************************************************************
409+
* PARTIAL FILE: animate_leave_with_event_host_bindings.d.ts
410+
****************************************************************************************************/
411+
import { AnimationCallbackEvent } from '@angular/core';
412+
import * as i0 from "@angular/core";
413+
export declare class ChildComponent {
414+
fadeFn(event: AnimationCallbackEvent): void;
415+
static ɵfac: i0.ɵɵFactoryDeclaration<ChildComponent, never>;
416+
static ɵcmp: i0.ɵɵComponentDeclaration<ChildComponent, "child-component", never, {}, {}, never, never, true, never>;
417+
}
418+
export declare class MyComponent {
419+
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
420+
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never, true, never>;
421+
}
422+

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/animations/TEST_CASES.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@
3535
}
3636
]
3737
},
38+
{
39+
"description": "should generate animate enter instructions with host binding and event",
40+
"inputFiles": [
41+
"animate_enter_with_event_host_bindings.ts"
42+
],
43+
"expectations": [
44+
{
45+
"files": [
46+
{
47+
"expected": "animate_enter_with_event_host_bindings_template.js",
48+
"generated": "animate_enter_with_event_host_bindings.js"
49+
}
50+
],
51+
"failureMessage": "Incorrect ɵɵanimateEnter() call"
52+
}
53+
]
54+
},
3855
{
3956
"description": "should generate animate enter instructions on element with a binding",
4057
"inputFiles": [
@@ -136,6 +153,23 @@
136153
"failureMessage": "Incorrect ɵɵanimateLeave() call"
137154
}
138155
]
156+
},
157+
{
158+
"description": "should generate animate leave instructions with host binding and event",
159+
"inputFiles": [
160+
"animate_leave_with_event_host_bindings.ts"
161+
],
162+
"expectations": [
163+
{
164+
"files": [
165+
{
166+
"expected": "animate_leave_with_event_host_bindings_template.js",
167+
"generated": "animate_leave_with_event_host_bindings.js"
168+
}
169+
],
170+
"failureMessage": "Incorrect ɵɵanimateLeave() call"
171+
}
172+
]
139173
}
140174
]
141175
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {AnimationCallbackEvent, Component} from '@angular/core';
2+
3+
@Component({
4+
selector: 'child-component',
5+
host: {'(animate.enter)': 'fadeFn($event)'},
6+
template: `<p>Sliding Content</p>`,
7+
})
8+
export class ChildComponent {
9+
fadeFn(event: AnimationCallbackEvent) {
10+
event.target.classList.add('fade');
11+
event.animationComplete();
12+
}
13+
}
14+
15+
@Component({
16+
selector: 'my-component',
17+
imports: [ChildComponent],
18+
template: `
19+
<child-component animate.enter="slide"></child-component>
20+
`,
21+
})
22+
export class MyComponent {
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
ChildComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ChildComponent, selectors: [["child-component"]], hostBindings: function ChildComponent_HostBindings(rf, ctx) { if (rf & 1) {
2+
i0.ɵɵanimateEnterListener(function ChildComponent_animateenter_HostBindingHandler($event) { return ctx.fadeFn($event); });
3+
} }, decls: 2, vars: 0, template: function ChildComponent_Template(rf, ctx) { if (rf & 1) {
4+
i0.ɵɵdomElementStart(0, "p");
5+
i0.ɵɵtext(1, "Sliding Content");
6+
i0.ɵɵdomElementEnd();
7+
} }, encapsulation: 2 });
8+
9+
MyComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 1, vars: 0, template: function MyComponent_Template(rf, ctx) { if (rf & 1) {
10+
i0.ɵɵelementStart(0, "child-component");
11+
i0.ɵɵanimateEnter("slide");
12+
i0.ɵɵelementEnd();
13+
} }, dependencies: [ChildComponent], encapsulation: 2 });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {AnimationCallbackEvent, Component} from '@angular/core';
2+
3+
@Component({
4+
selector: 'child-component',
5+
template: `<p>Fading Content</p>`,
6+
host: {'(animate.leave)': 'fadeFn($event)'},
7+
})
8+
export class ChildComponent {
9+
fadeFn(event: AnimationCallbackEvent) {
10+
event.target.classList.add('fade');
11+
event.animationComplete();
12+
}
13+
}
14+
15+
@Component({
16+
selector: 'my-component',
17+
imports: [ChildComponent],
18+
template: `
19+
<child-component animate.leave="slide"></child-component>
20+
`,
21+
})
22+
export class MyComponent {
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
ChildComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ChildComponent, selectors: [["child-component"]], hostBindings: function ChildComponent_HostBindings(rf, ctx) { if (rf & 1) {
2+
i0.ɵɵanimateLeaveListener(function ChildComponent_animateleave_HostBindingHandler($event) { return ctx.fadeFn($event); });
3+
} }, features: [i0.ɵɵAnimationsFeature()], decls: 2, vars: 0, template: function ChildComponent_Template(rf, ctx) { if (rf & 1) {
4+
i0.ɵɵdomElementStart(0, "p");
5+
i0.ɵɵtext(1, "Fading Content");
6+
i0.ɵɵdomElementEnd();
7+
} }, encapsulation: 2 });
8+
9+
MyComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], features: [i0.ɵɵAnimationsFeature()], decls: 1, vars: 0, template: function MyComponent_Template(rf, ctx) { if (rf & 1) {
10+
i0.ɵɵelementStart(0, "child-component");
11+
i0.ɵɵanimateLeave("slide");
12+
i0.ɵɵelementEnd();
13+
} }, dependencies: [ChildComponent], encapsulation: 2 });

packages/compiler/src/template/pipeline/src/ingest.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -200,21 +200,37 @@ export function ingestHostAttribute(
200200
}
201201

202202
export function ingestHostEvent(job: HostBindingCompilationJob, event: e.ParsedEvent) {
203-
const [phase, target] =
204-
event.type !== e.ParsedEventType.LegacyAnimation
205-
? [null, event.targetOrPhase]
206-
: [event.targetOrPhase, null];
207-
const eventBinding = ir.createListenerOp(
208-
job.root.xref,
209-
new ir.SlotHandle(),
210-
event.name,
211-
null,
212-
makeListenerHandlerOps(job.root, event.handler, event.handlerSpan),
213-
phase,
214-
target,
215-
true,
216-
event.sourceSpan,
217-
);
203+
let eventBinding: ir.CreateOp;
204+
if (event.type === e.ParsedEventType.Animation) {
205+
eventBinding = ir.createAnimationListenerOp(
206+
job.root.xref,
207+
new ir.SlotHandle(),
208+
event.name,
209+
null,
210+
makeListenerHandlerOps(job.root, event.handler, event.handlerSpan),
211+
event.name.endsWith('enter') ? ir.AnimationKind.ENTER : ir.AnimationKind.LEAVE,
212+
event.targetOrPhase,
213+
true,
214+
event.sourceSpan,
215+
);
216+
} else {
217+
const [phase, target] =
218+
event.type !== e.ParsedEventType.LegacyAnimation
219+
? [null, event.targetOrPhase]
220+
: [event.targetOrPhase, null];
221+
222+
eventBinding = ir.createListenerOp(
223+
job.root.xref,
224+
new ir.SlotHandle(),
225+
event.name,
226+
null,
227+
makeListenerHandlerOps(job.root, event.handler, event.handlerSpan),
228+
phase,
229+
target,
230+
true,
231+
event.sourceSpan,
232+
);
233+
}
218234
job.root.create.push(eventBinding);
219235
}
220236

0 commit comments

Comments
 (0)