refactor(core): allow AnimationClassBindingFn to return undefined or null

The AnimationClassBindingFn type was too restrictive, only allowing `string | string[]`. However, the runtime (`getClassListFromValue`) safely handles `undefined` and `null` values by treating them as no animation.

This change updates the type to allow `undefined` and `null`, which is consistent with other class/style bindings in Angular and avoids requiring workarounds (like empty strings) in host bindings.

Added a compliance test case to verify that `[animate.enter]` with a potentially `undefined` value compiles correctly.

(cherry picked from commit a7bde662c3)
This commit is contained in:
Andrew Scott
2026-06-25 20:41:34 +00:00
committed by Alex Rickabaugh
parent 6c83124b46
commit c3be83cfe1
5 changed files with 84 additions and 1 deletions
@@ -787,3 +787,34 @@ export declare abstract class AbstractComp {
static ɵcmp: i0.ɵɵComponentDeclaration<AbstractComp, "test-comp", never, {}, {}, never, never, true, never>;
}
/****************************************************************************************************
* PARTIAL FILE: host_animate_enter.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class TestCmp {
disabled = false;
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: TestCmp, isStandalone: true, selector: "test-cmp", host: { properties: { "animate.enter": "disabled ? undefined : 'enter-class'" } }, ngImport: i0, template: '', isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, decorators: [{
type: Component,
args: [{
selector: 'test-cmp',
template: '',
host: {
'[animate.enter]': "disabled ? undefined : 'enter-class'",
}
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: host_animate_enter.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class TestCmp {
disabled: boolean;
static ɵfac: i0.ɵɵFactoryDeclaration<TestCmp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<TestCmp, "test-cmp", never, {}, {}, never, never, true, never>;
}
@@ -179,6 +179,22 @@
}
],
"compilationModeFilter": ["full compile", "instruction compile"]
},
{
"description": "should support host binding with animate.enter",
"inputFiles": ["host_animate_enter.ts"],
"expectations": [
{
"failureMessage": "Incorrect generated output",
"files": [
{
"expected": "host_animate_enter.js",
"generated": "host_animate_enter.js"
}
]
}
],
"compilationModeFilter": ["full compile", "instruction compile"]
}
]
}
@@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
…
export class TestCmp {
disabled = false;
…
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({
type: TestCmp,
selectors: [["test-cmp"]],
hostBindings: function TestCmp_HostBindings(rf, ctx) {
if (rf & 1) {
i0.ɵɵanimateEnter(function TestCmp_HostBindings_animateenter_cb() {
return ctx.disabled ? undefined : "enter-class";
});
}
},
decls: 0,
vars: 0,
template: function TestCmp_Template(rf, ctx) { },
encapsulation: 2
});
}
…
@@ -0,0 +1,12 @@
import {Component} from '@angular/core';
@Component({
selector: 'test-cmp',
template: '',
host: {
'[animate.enter]': "disabled ? undefined : 'enter-class'",
}
})
export class TestCmp {
disabled = false;
}
+1 -1
View File
@@ -94,4 +94,4 @@ export interface AnimationLViewData {
/**
* Function that returns the class or class list binded to the animate instruction
*/
export type AnimationClassBindingFn = () => string | string[];
export type AnimationClassBindingFn = () => string | string[] | undefined | null;