Skip to content

Commit a2e5f48

Browse files
AndrewKushnirdylhunn
authored andcommitted
fix(compiler): generate proper code for nullish coalescing in styling host bindings (#53305)
This commit fixes an issue where having an expression with nullish coalescing in styling host bindings leads to JS errors due to the fact that a declaration for a temporary variable was not included into the generated code. Resolves #53295. PR Close #53305
1 parent 13ade13 commit a2e5f48

File tree

8 files changed

+165
-11
lines changed

8 files changed

+165
-11
lines changed
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
hostBindings: function MyApp_HostBindings(rf, ctx) {
22
if (rf & 1) {
33
i0.ɵɵlistener("click", function MyApp_click_HostBindingHandler() {
4-
let $tmp$;
5-
return ctx.logLastName(($tmp$ = ($tmp$ = ctx.lastName) !== null && $tmp$ !== undefined ? $tmp$ : ctx.lastNameFallback) !== null && $tmp$ !== undefined ? $tmp$ : "unknown");
4+
let $tmp_0$;
5+
return ctx.logLastName(($tmp_0$ = ($tmp_0$ = ctx.lastName) !== null && $tmp_0$ !== undefined ? $tmp_0$ : ctx.lastNameFallback) !== null && $tmp_0$ !== undefined ? $tmp_0$ : "unknown");
66
});
77
}
88
if (rf & 2) {
9-
let $tmp$;
10-
i0.ɵɵattribute("first-name", "Hello, " + (($tmp$ = ctx.firstName) !== null && $tmp$ !== undefined ? $tmp$ : "Frodo") + "!");
9+
let $tmp_1$;
10+
i0.ɵɵattribute("first-name", "Hello, " + (($tmp_1$ = ctx.firstName) !== null && $tmp_1$ !== undefined ? $tmp_1$ : "Frodo") + "!");
1111
}
1212
}

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/GOLDEN_PARTIAL.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,74 @@ export declare class MyModule {
8282
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
8383
}
8484

85+
/****************************************************************************************************
86+
* PARTIAL FILE: host_class_bindings_with_temporaries.js
87+
****************************************************************************************************/
88+
import { Directive } from '@angular/core';
89+
import * as i0 from "@angular/core";
90+
export class HostBindingDir {
91+
constructor() {
92+
this.value = null;
93+
}
94+
}
95+
HostBindingDir.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: HostBindingDir, deps: [], target: i0.ɵɵFactoryTarget.Directive });
96+
HostBindingDir.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: HostBindingDir, isStandalone: true, selector: "[hostBindingDir]", host: { properties: { "class.a": "value ?? \"class-a\"", "class.b": "value ?? \"class-b\"" } }, ngImport: i0 });
97+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: HostBindingDir, decorators: [{
98+
type: Directive,
99+
args: [{
100+
standalone: true,
101+
selector: '[hostBindingDir]',
102+
host: {
103+
'[class.a]': 'value ?? "class-a"',
104+
'[class.b]': 'value ?? "class-b"',
105+
},
106+
}]
107+
}] });
108+
109+
/****************************************************************************************************
110+
* PARTIAL FILE: host_class_bindings_with_temporaries.d.ts
111+
****************************************************************************************************/
112+
import * as i0 from "@angular/core";
113+
export declare class HostBindingDir {
114+
value: number | null;
115+
static ɵfac: i0.ɵɵFactoryDeclaration<HostBindingDir, never>;
116+
static ɵdir: i0.ɵɵDirectiveDeclaration<HostBindingDir, "[hostBindingDir]", never, {}, {}, never, never, true, never>;
117+
}
118+
119+
/****************************************************************************************************
120+
* PARTIAL FILE: host_style_bindings_with_temporaries.js
121+
****************************************************************************************************/
122+
import { Directive } from '@angular/core';
123+
import * as i0 from "@angular/core";
124+
export class HostBindingDir {
125+
constructor() {
126+
this.value = null;
127+
}
128+
}
129+
HostBindingDir.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: HostBindingDir, deps: [], target: i0.ɵɵFactoryTarget.Directive });
130+
HostBindingDir.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: HostBindingDir, isStandalone: true, selector: "[hostBindingDir]", host: { properties: { "style.fontSize": "value ?? \"15px\"", "style.fontWeight": "value ?? \"bold\"" } }, ngImport: i0 });
131+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: HostBindingDir, decorators: [{
132+
type: Directive,
133+
args: [{
134+
standalone: true,
135+
selector: '[hostBindingDir]',
136+
host: {
137+
'[style.fontSize]': 'value ?? "15px"',
138+
'[style.fontWeight]': 'value ?? "bold"',
139+
},
140+
}]
141+
}] });
142+
143+
/****************************************************************************************************
144+
* PARTIAL FILE: host_style_bindings_with_temporaries.d.ts
145+
****************************************************************************************************/
146+
import * as i0 from "@angular/core";
147+
export declare class HostBindingDir {
148+
value: number | null;
149+
static ɵfac: i0.ɵɵFactoryDeclaration<HostBindingDir, never>;
150+
static ɵdir: i0.ɵɵDirectiveDeclaration<HostBindingDir, "[hostBindingDir]", never, {}, {}, never, never, true, never>;
151+
}
152+
85153
/****************************************************************************************************
86154
* PARTIAL FILE: host_bindings_with_pure_functions.js
87155
****************************************************************************************************/

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/TEST_CASES.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,34 @@
2929
}
3030
]
3131
},
32+
{
33+
"description": "should support host class bindings with temporary expressions",
34+
"inputFiles": [
35+
"host_class_bindings_with_temporaries.ts"
36+
],
37+
"expectations": [
38+
{
39+
"failureMessage": "Invalid host binding code",
40+
"files": [
41+
"host_class_bindings_with_temporaries.js"
42+
]
43+
}
44+
]
45+
},
46+
{
47+
"description": "should support host style bindings with temporary expressions",
48+
"inputFiles": [
49+
"host_style_bindings_with_temporaries.ts"
50+
],
51+
"expectations": [
52+
{
53+
"failureMessage": "Invalid host binding code",
54+
"files": [
55+
"host_style_bindings_with_temporaries.js"
56+
]
57+
}
58+
]
59+
},
3260
{
3361
"description": "should support host bindings with pure functions",
3462
"inputFiles": [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
hostBindings: function HostBindingDir_HostBindings(rf, ctx) {
2+
if (rf & 2) {
3+
let $tmp_0$;
4+
let $tmp_1$;
5+
$r3$.ɵɵclassProp("a", ($tmp_0$ = ctx.value) !== null && $tmp_0$ !== undefined ? $tmp_0$ : "class-a")
6+
("b", ($tmp_1$ = ctx.value) !== null && $tmp_1$ !== undefined ? $tmp_1$ : "class-b");
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {Directive} from '@angular/core';
2+
3+
@Directive({
4+
standalone: true,
5+
selector: '[hostBindingDir]',
6+
host: {
7+
'[class.a]': 'value ?? "class-a"',
8+
'[class.b]': 'value ?? "class-b"',
9+
},
10+
})
11+
export class HostBindingDir {
12+
value: number|null = null;
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
hostBindings: function HostBindingDir_HostBindings(rf, ctx) {
2+
if (rf & 2) {
3+
let $tmp_0$;
4+
let $tmp_1$;
5+
$r3$.ɵɵstyleProp("font-size", ($tmp_0$ = ctx.value) !== null && $tmp_0$ !== undefined ? $tmp_0$ : "15px")
6+
("font-weight", ($tmp_1$ = ctx.value) !== null && $tmp_1$ !== undefined ? $tmp_1$ : "bold");
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {Directive} from '@angular/core';
2+
3+
@Directive({
4+
standalone: true,
5+
selector: '[hostBindingDir]',
6+
host: {
7+
'[style.fontSize]': 'value ?? "15px"',
8+
'[style.fontWeight]': 'value ?? "bold"',
9+
},
10+
})
11+
export class HostBindingDir {
12+
value: number|null = null;
13+
}

packages/compiler/src/render3/view/compiler.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,10 @@ function createHostBindingsFunction(
618618

619619
return emitHostBindingFunction(hostJob);
620620
}
621+
622+
let bindingId = 0;
623+
const getNextBindingId = () => `${bindingId++}`;
624+
621625
const bindingContext = o.variable(CONTEXT_NAME);
622626
const styleBuilder = new StylingBuilder(bindingContext);
623627

@@ -681,7 +685,7 @@ function createHostBindingsFunction(
681685
for (const binding of allOtherBindings) {
682686
// resolve literal arrays and literal objects
683687
const value = binding.expression.visit(getValueConverter());
684-
const bindingExpr = bindingFn(bindingContext, value);
688+
const bindingExpr = bindingFn(bindingContext, value, getNextBindingId);
685689

686690
const {bindingName, instruction, isAttribute} = getBindingNameAndInstruction(binding);
687691

@@ -768,10 +772,13 @@ function createHostBindingsFunction(
768772
totalHostVarsCount +=
769773
Math.max(call.allocateBindingSlots - MIN_STYLING_BINDING_SLOTS_REQUIRED, 0);
770774

775+
const {params, stmts} =
776+
convertStylingCall(call, bindingContext, bindingFn, getNextBindingId);
777+
updateVariables.push(...stmts);
771778
updateInstructions.push({
772779
reference: instruction.reference,
773-
paramsOrFn: convertStylingCall(call, bindingContext, bindingFn),
774-
span: null
780+
paramsOrFn: params,
781+
span: null,
775782
});
776783
}
777784
});
@@ -801,13 +808,22 @@ function createHostBindingsFunction(
801808
return null;
802809
}
803810

804-
function bindingFn(implicit: any, value: AST) {
805-
return convertPropertyBinding(null, implicit, value, 'b');
811+
function bindingFn(implicit: any, value: AST, getNextBindingIdFn: () => string) {
812+
return convertPropertyBinding(null, implicit, value, getNextBindingIdFn());
806813
}
807814

808815
function convertStylingCall(
809-
call: StylingInstructionCall, bindingContext: any, bindingFn: Function) {
810-
return call.params(value => bindingFn(bindingContext, value).currValExpr);
816+
call: StylingInstructionCall, bindingContext: any, bindingFn: Function,
817+
getNextBindingIdFn: () => string) {
818+
const stmts: o.Statement[] = [];
819+
const params = call.params(value => {
820+
const result = bindingFn(bindingContext, value, getNextBindingIdFn);
821+
if (Array.isArray(result.stmts) && result.stmts.length > 0) {
822+
stmts.push(...result.stmts);
823+
}
824+
return result.currValExpr;
825+
});
826+
return {params, stmts};
811827
}
812828

813829
function getBindingNameAndInstruction(binding: ParsedProperty):

0 commit comments

Comments
 (0)