diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js
new file mode 100644
index 00000000000..5df9fa36496
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js
@@ -0,0 +1,230 @@
+/****************************************************************************************************
+ * PARTIAL FILE: basic_switch.js
+ ****************************************************************************************************/
+import { Component } from '@angular/core';
+import * as i0 from "@angular/core";
+export class MyApp {
+ constructor() {
+ this.message = 'hello';
+ }
+ value() {
+ return 1;
+ }
+}
+MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
+MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
+
+ {{message}}
+ {#switch value()}
+ {:case 0} case 0
+ {:case 1} case 1
+ {:case 2} case 2
+ {:default} default
+ {/switch}
+
+ `, isInline: true });
+i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
+ type: Component,
+ args: [{
+ template: `
+
+ {{message}}
+ {#switch value()}
+ {:case 0} case 0
+ {:case 1} case 1
+ {:case 2} case 2
+ {:default} default
+ {/switch}
+
+ `,
+ }]
+ }] });
+
+/****************************************************************************************************
+ * PARTIAL FILE: basic_switch.d.ts
+ ****************************************************************************************************/
+import * as i0 from "@angular/core";
+export declare class MyApp {
+ message: string;
+ value(): number;
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+ static ɵcmp: i0.ɵɵComponentDeclaration;
+}
+
+/****************************************************************************************************
+ * PARTIAL FILE: switch_without_default.js
+ ****************************************************************************************************/
+import { Component } from '@angular/core';
+import * as i0 from "@angular/core";
+export class MyApp {
+ constructor() {
+ this.message = 'hello';
+ this.value = () => 1;
+ }
+}
+MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
+MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
+
+ {{message}}
+ {#switch value()}
+ {:case 0} case 0
+ {:case 1} case 1
+ {:case 2} case 2
+ {/switch}
+
+ `, isInline: true });
+i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
+ type: Component,
+ args: [{
+ template: `
+
+ {{message}}
+ {#switch value()}
+ {:case 0} case 0
+ {:case 1} case 1
+ {:case 2} case 2
+ {/switch}
+
+ `,
+ }]
+ }] });
+
+/****************************************************************************************************
+ * PARTIAL FILE: switch_without_default.d.ts
+ ****************************************************************************************************/
+import * as i0 from "@angular/core";
+export declare class MyApp {
+ message: string;
+ value: () => number;
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+ static ɵcmp: i0.ɵɵComponentDeclaration;
+}
+
+/****************************************************************************************************
+ * PARTIAL FILE: nested_switch.js
+ ****************************************************************************************************/
+import { Component } from '@angular/core';
+import * as i0 from "@angular/core";
+export class MyApp {
+ constructor() {
+ this.message = 'hello';
+ this.value = () => 1;
+ this.nestedValue = () => 2;
+ }
+}
+MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
+MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
+
+ {{message}}
+ {#switch value()}
+ {:case 0} case 0
+ {:case 1}
+ {#switch nestedValue()}
+ {:case 0} nested case 0
+ {:case 1} nested case 1
+ {:case 2} nested case 2
+ {/switch}
+ {:case 2} case 2
+ {/switch}
+
+ `, isInline: true });
+i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
+ type: Component,
+ args: [{
+ template: `
+
+ {{message}}
+ {#switch value()}
+ {:case 0} case 0
+ {:case 1}
+ {#switch nestedValue()}
+ {:case 0} nested case 0
+ {:case 1} nested case 1
+ {:case 2} nested case 2
+ {/switch}
+ {:case 2} case 2
+ {/switch}
+
+ `,
+ }]
+ }] });
+
+/****************************************************************************************************
+ * PARTIAL FILE: nested_switch.d.ts
+ ****************************************************************************************************/
+import * as i0 from "@angular/core";
+export declare class MyApp {
+ message: string;
+ value: () => number;
+ nestedValue: () => number;
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+ static ɵcmp: i0.ɵɵComponentDeclaration;
+}
+
+/****************************************************************************************************
+ * PARTIAL FILE: switch_with_pipe.js
+ ****************************************************************************************************/
+import { Component, Pipe } from '@angular/core';
+import * as i0 from "@angular/core";
+export class TestPipe {
+ tranform(value) {
+ return value;
+ }
+}
+TestPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
+TestPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestPipe, isStandalone: true, name: "test" });
+i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestPipe, decorators: [{
+ type: Pipe,
+ args: [{ standalone: true, name: 'test' }]
+ }] });
+export class MyApp {
+ constructor() {
+ this.message = 'hello';
+ this.value = () => 1;
+ }
+}
+MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
+MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
+
+ {{message}}
+ {#switch value() | test}
+ {:case 0} case 0
+ {:case 1} case 1
+ {:default} default
+ {/switch}
+
+ `, isInline: true, dependencies: [{ kind: "pipe", type: TestPipe, name: "test" }] });
+i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
+ type: Component,
+ args: [{
+ template: `
+
+ {{message}}
+ {#switch value() | test}
+ {:case 0} case 0
+ {:case 1} case 1
+ {:default} default
+ {/switch}
+
+ `,
+ standalone: true,
+ imports: [TestPipe]
+ }]
+ }] });
+
+/****************************************************************************************************
+ * PARTIAL FILE: switch_with_pipe.d.ts
+ ****************************************************************************************************/
+import * as i0 from "@angular/core";
+export declare class TestPipe {
+ tranform(value: unknown): unknown;
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+ static ɵpipe: i0.ɵɵPipeDeclaration;
+}
+export declare class MyApp {
+ message: string;
+ value: () => number;
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+ static ɵcmp: i0.ɵɵComponentDeclaration;
+}
+
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/TEST_CASES.json
new file mode 100644
index 00000000000..3649a3f04b2
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/TEST_CASES.json
@@ -0,0 +1,89 @@
+{
+ "$schema": "../test_case_schema.json",
+ "cases": [
+ {
+ "description": "should generate a basic switch block",
+ "angularCompilerOptions": {
+ "_enabledBlockTypes": ["switch"]
+ },
+ "inputFiles": [
+ "basic_switch.ts"
+ ],
+ "expectations": [
+ {
+ "files": [
+ {
+ "expected": "basic_switch_template.js",
+ "generated": "basic_switch.js"
+ }
+ ],
+ "failureMessage": "Incorrect template"
+ }
+ ],
+ "skipForTemplatePipeline": true
+ },
+ {
+ "description": "should generate a switch block without a default block",
+ "angularCompilerOptions": {
+ "_enabledBlockTypes": ["switch"]
+ },
+ "inputFiles": [
+ "switch_without_default.ts"
+ ],
+ "expectations": [
+ {
+ "files": [
+ {
+ "expected": "switch_without_default_template.js",
+ "generated": "switch_without_default.js"
+ }
+ ],
+ "failureMessage": "Incorrect template"
+ }
+ ],
+ "skipForTemplatePipeline": true
+ },
+ {
+ "description": "should generate nested switch blocks",
+ "angularCompilerOptions": {
+ "_enabledBlockTypes": ["switch"]
+ },
+ "inputFiles": [
+ "nested_switch.ts"
+ ],
+ "expectations": [
+ {
+ "files": [
+ {
+ "expected": "nested_switch_template.js",
+ "generated": "nested_switch.js"
+ }
+ ],
+ "failureMessage": "Incorrect template"
+ }
+ ],
+ "skipForTemplatePipeline": true
+ },
+ {
+ "description": "should generate switch block with a pipe in its expression",
+ "angularCompilerOptions": {
+ "_enabledBlockTypes": ["switch"]
+ },
+ "inputFiles": [
+ "switch_with_pipe.ts"
+ ],
+ "expectations": [
+ {
+ "files": [
+ {
+ "expected": "switch_with_pipe_template.js",
+ "generated": "switch_with_pipe.js"
+ }
+ ],
+ "failureMessage": "Incorrect template"
+ }
+ ],
+ "skipForTemplatePipeline": true
+ }
+ ]
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_switch.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_switch.ts
new file mode 100644
index 00000000000..55b22b05f88
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_switch.ts
@@ -0,0 +1,22 @@
+import {Component} from '@angular/core';
+
+@Component({
+ template: `
+
+ {{message}}
+ {#switch value()}
+ {:case 0} case 0
+ {:case 1} case 1
+ {:case 2} case 2
+ {:default} default
+ {/switch}
+
+ `,
+})
+export class MyApp {
+ message = 'hello';
+
+ value() {
+ return 1;
+ }
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_switch_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_switch_template.js
new file mode 100644
index 00000000000..307e4956efe
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_switch_template.js
@@ -0,0 +1,42 @@
+function MyApp_Case_2_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " case 0 ");
+ }
+}
+
+function MyApp_Case_3_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " case 1 ");
+ }
+}
+
+function MyApp_Case_4_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " case 2 ");
+ }
+}
+
+function MyApp_Case_5_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " default ");
+ }
+}
+…
+function MyApp_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵelementStart(0, "div");
+ $r3$.ɵɵtext(1);
+ $r3$.ɵɵtemplate(2, MyApp_Case_2_Template, 1, 0);
+ $r3$.ɵɵtemplate(3, MyApp_Case_3_Template, 1, 0);
+ $r3$.ɵɵtemplate(4, MyApp_Case_4_Template, 1, 0);
+ $r3$.ɵɵtemplate(5, MyApp_Case_5_Template, 1, 0);
+ $r3$.ɵɵelementEnd();
+ }
+ if (rf & 2) {
+ let MyApp_contFlowTmp;
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵconditional(2, (MyApp_contFlowTmp = ctx.value()) === 0 ? 2 : MyApp_contFlowTmp === 1 ? 3 : MyApp_contFlowTmp === 2 ? 4 : 5);
+ }
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_switch.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_switch.ts
new file mode 100644
index 00000000000..92951b90ea0
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_switch.ts
@@ -0,0 +1,24 @@
+import {Component} from '@angular/core';
+
+@Component({
+ template: `
+
+ {{message}}
+ {#switch value()}
+ {:case 0} case 0
+ {:case 1}
+ {#switch nestedValue()}
+ {:case 0} nested case 0
+ {:case 1} nested case 1
+ {:case 2} nested case 2
+ {/switch}
+ {:case 2} case 2
+ {/switch}
+
+ `,
+})
+export class MyApp {
+ message = 'hello';
+ value = () => 1;
+ nestedValue = () => 2;
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_switch_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_switch_template.js
new file mode 100644
index 00000000000..f9038d29339
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_switch_template.js
@@ -0,0 +1,60 @@
+function MyApp_Case_2_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " case 0 ");
+ }
+}
+
+function MyApp_Case_3_Case_0_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " nested case 0 ");
+ }
+}
+
+function MyApp_Case_3_Case_1_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " nested case 1 ");
+ }
+}
+
+function MyApp_Case_3_Case_2_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " nested case 2 ");
+ }
+}
+
+function MyApp_Case_3_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtemplate(0, MyApp_Case_3_Case_0_Template, 1, 0);
+ $r3$.ɵɵtemplate(1, MyApp_Case_3_Case_1_Template, 1, 0);
+ $r3$.ɵɵtemplate(2, MyApp_Case_3_Case_2_Template, 1, 0);
+ }
+ if (rf & 2) {
+ const ctx_r1 = $r3$.ɵɵnextContext();
+ let MyApp_Case_3_contFlowTmp;
+ $r3$.ɵɵconditional(0, (MyApp_Case_3_contFlowTmp = ctx_r1.nestedValue()) === 0 ? 0 : MyApp_Case_3_contFlowTmp === 1 ? 1 : MyApp_Case_3_contFlowTmp === 2 ? 2 : -1);
+ }
+}
+
+function MyApp_Case_4_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " case 2 ");
+ }
+}
+…
+function MyApp_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵelementStart(0, "div");
+ $r3$.ɵɵtext(1);
+ $r3$.ɵɵtemplate(2, MyApp_Case_2_Template, 1, 0);
+ $r3$.ɵɵtemplate(3, MyApp_Case_3_Template, 3, 4);
+ $r3$.ɵɵtemplate(4, MyApp_Case_4_Template, 1, 0);
+ $r3$.ɵɵelementEnd();
+ }
+ if (rf & 2) {
+ let MyApp_contFlowTmp;
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵconditional(2, (MyApp_contFlowTmp = ctx.value()) === 0 ? 2 : MyApp_contFlowTmp === 1 ? 3 : MyApp_contFlowTmp === 2 ? 4 : -1);
+ }
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_with_pipe.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_with_pipe.ts
new file mode 100644
index 00000000000..6397ddc1a4e
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_with_pipe.ts
@@ -0,0 +1,27 @@
+import {Component, Pipe} from '@angular/core';
+
+@Pipe({standalone: true, name: 'test'})
+export class TestPipe {
+ tranform(value: unknown) {
+ return value;
+ }
+}
+
+@Component({
+ template: `
+
+ {{message}}
+ {#switch value() | test}
+ {:case 0} case 0
+ {:case 1} case 1
+ {:default} default
+ {/switch}
+
+ `,
+ standalone: true,
+ imports: [TestPipe]
+})
+export class MyApp {
+ message = 'hello';
+ value = () => 1;
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_with_pipe_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_with_pipe_template.js
new file mode 100644
index 00000000000..6cb85e28ae5
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_with_pipe_template.js
@@ -0,0 +1,18 @@
+function MyApp_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵelementStart(0, "div");
+ $r3$.ɵɵtext(1);
+ $r3$.ɵɵpipe(2, "test");
+ $r3$.ɵɵtemplate(3, MyApp_Case_3_Template, 1, 0);
+ $r3$.ɵɵtemplate(4, MyApp_Case_4_Template, 1, 0);
+ $r3$.ɵɵtemplate(5, MyApp_Case_5_Template, 1, 0);
+ $r3$.ɵɵelementEnd();
+ }
+ if (rf & 2) {
+ let MyApp_contFlowTmp;
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
+ $r3$.ɵɵadvance(2);
+ $r3$.ɵɵconditional(3, (MyApp_contFlowTmp = $r3$.ɵɵpipeBind1(2, 4, ctx.value())) === 0 ? 3 : MyApp_contFlowTmp === 1 ? 4 : 5);
+ }
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_without_default.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_without_default.ts
new file mode 100644
index 00000000000..658528f9dbc
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_without_default.ts
@@ -0,0 +1,18 @@
+import {Component} from '@angular/core';
+
+@Component({
+ template: `
+
+ {{message}}
+ {#switch value()}
+ {:case 0} case 0
+ {:case 1} case 1
+ {:case 2} case 2
+ {/switch}
+
+ `,
+})
+export class MyApp {
+ message = 'hello';
+ value = () => 1;
+}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_without_default_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_without_default_template.js
new file mode 100644
index 00000000000..cb0d268165d
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/switch_without_default_template.js
@@ -0,0 +1,35 @@
+function MyApp_Case_2_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " case 0 ");
+ }
+}
+
+function MyApp_Case_3_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " case 1 ");
+ }
+}
+
+function MyApp_Case_4_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵtext(0, " case 2 ");
+ }
+}
+…
+function MyApp_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵelementStart(0, "div");
+ $r3$.ɵɵtext(1);
+ $r3$.ɵɵtemplate(2, MyApp_Case_2_Template, 1, 0);
+ $r3$.ɵɵtemplate(3, MyApp_Case_3_Template, 1, 0);
+ $r3$.ɵɵtemplate(4, MyApp_Case_4_Template, 1, 0);
+ $r3$.ɵɵelementEnd();
+ }
+ if (rf & 2) {
+ let MyApp_contFlowTmp;
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵconditional(2, (MyApp_contFlowTmp = ctx.value()) === 0 ? 2 : MyApp_contFlowTmp === 1 ? 3 : MyApp_contFlowTmp === 2 ? 4 : -1);
+ }
+}
diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts
index c4a5b081d08..e3e885b886e 100644
--- a/packages/compiler/src/render3/r3_identifiers.ts
+++ b/packages/compiler/src/render3/r3_identifiers.ts
@@ -161,6 +161,8 @@ export class Identifiers {
static deferPrefetchOnViewport:
o.ExternalReference = {name: 'ɵɵdeferPrefetchOnViewport', moduleName: CORE};
+ static conditional: o.ExternalReference = {name: 'ɵɵconditional', moduleName: CORE};
+
static text: o.ExternalReference = {name: 'ɵɵtext', moduleName: CORE};
static enableBindings: o.ExternalReference = {name: 'ɵɵenableBindings', moduleName: CORE};
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index e2c2f90106c..214ed10e303 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -178,6 +178,13 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
/** Temporary variable declarations generated from visiting pipes, literals, etc. */
private _tempVariables: o.Statement[] = [];
+
+ /**
+ * Temporary variable used to store state between control flow instructions.
+ * Should be accessed via the `allocateControlFlowTempVariable` method.
+ */
+ private _controlFlowTempVariable: o.ReadVarExpr|null = null;
+
/**
* List of callbacks to build nested templates. Nested templates must not be visited until
* after the parent template has finished visiting all of its nodes. This ensures that all
@@ -1001,6 +1008,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
readonly visitDeferredBlockError = invalid;
readonly visitDeferredBlockLoading = invalid;
readonly visitDeferredBlockPlaceholder = invalid;
+ readonly visitSwitchBlockCase = invalid;
visitBoundText(text: t.BoundText) {
if (this.i18n) {
@@ -1088,6 +1096,66 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
return null;
}
+ visitSwitchBlock(block: t.SwitchBlock): void {
+ // Allocate slots for the primary block expression.
+ const blockExpression = block.expression.visit(this._valueConverter);
+ this.allocateBindingSlots(blockExpression);
+
+ // We have to process the block in two steps: once here and again in the update instruction
+ // callback in order to generate the correct expressions when pipes or pure functions are used.
+ const caseData = block.cases.map(currentCase => {
+ const index = this.createEmbeddedTemplateFn(
+ null, currentCase.children, '_Case', currentCase.sourceSpan);
+ let expression: AST|null = null;
+
+ if (currentCase.expression !== null) {
+ expression = currentCase.expression.visit(this._valueConverter);
+ this.allocateBindingSlots(expression);
+ }
+
+ return {index, expression};
+ });
+
+ // Use the index of the first block as the index for the entire container.
+ const containerIndex = caseData[0].index;
+
+ this.updateInstructionWithAdvance(containerIndex, block.sourceSpan, R3.conditional, () => {
+ const generateCases = (caseIndex: number): o.Expression => {
+ // If we've gone beyond the last branch, return the special -1
+ // value which means that no view will be rendered.
+ if (caseIndex > caseData.length - 1) {
+ return o.literal(-1);
+ }
+
+ const {index, expression} = caseData[caseIndex];
+
+ // If the case has no expression, it means that it's the `default` case.
+ // Return its index and stop the recursion. Assumes that there's only one
+ // `default` condition and that it's defined last.
+ if (expression === null) {
+ return o.literal(index);
+ }
+
+ // If this is the very first comparison, we need to assign the value of the primary
+ // expression as a part of the comparison so the remaining cases can reuse it. In practice
+ // this looks as follows:
+ // ```
+ // let temp;
+ // conditional(1, (temp = ctx.foo) === 1 ? 1 : temp === 2 ? 2 : temp === 3 ? 3 : 4);
+ // ```
+ const comparisonTarget = caseIndex === 0 ?
+ this.allocateControlFlowTempVariable().set(
+ this.convertPropertyBinding(blockExpression)) :
+ this.allocateControlFlowTempVariable();
+
+ return comparisonTarget.identical(this.convertPropertyBinding(expression))
+ .conditional(o.literal(index), generateCases(caseIndex + 1));
+ };
+
+ return [o.literal(containerIndex), generateCases(0)];
+ });
+ }
+
visitDeferredBlock(deferred: t.DeferredBlock): void {
const {loading, placeholder, error, triggers, prefetchTriggers} = deferred;
const primaryTemplateIndex =
@@ -1233,8 +1301,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
}
// TODO: implement control flow instructions
- visitSwitchBlock(block: t.SwitchBlock): void {}
- visitSwitchBlockCase(block: t.SwitchBlockCase): void {}
visitForLoopBlock(block: t.ForLoopBlock): void {}
visitForLoopBlockEmpty(block: t.ForLoopBlockEmpty): void {}
visitIfBlock(block: t.IfBlock): void {}
@@ -1406,6 +1472,23 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
return args;
}
+ /**
+ * Creates and returns a variable that can be used to
+ * store the state between control flow instructions.
+ */
+ private allocateControlFlowTempVariable(): o.ReadVarExpr {
+ // Note: the assumption here is that we'll only need one temporary variable for all control
+ // flow instructions. It's expected that any instructions will overwrite it before passing it
+ // into the parameters.
+ if (this._controlFlowTempVariable === null) {
+ const name = `${this.contextName}_contFlowTmp`;
+ this._tempVariables.push(new o.DeclareVarStmt(name));
+ this._controlFlowTempVariable = o.variable(name);
+ }
+
+ return this._controlFlowTempVariable;
+ }
+
/**
* Prepares all attribute expression values for the `TAttributes` array.
*
diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts
index 5f5251f2618..4ad0d739874 100644
--- a/packages/core/src/render3/jit/environment.ts
+++ b/packages/core/src/render3/jit/environment.ts
@@ -138,6 +138,7 @@ export const angularCoreEnv: {[name: string]: Function} =
'ɵɵclassProp': r3.ɵɵclassProp,
'ɵɵadvance': r3.ɵɵadvance,
'ɵɵtemplate': r3.ɵɵtemplate,
+ 'ɵɵconditional': r3.ɵɵconditional,
'ɵɵdefer': r3.ɵɵdefer,
'ɵɵdeferWhen': r3.ɵɵdeferWhen,
'ɵɵdeferOnIdle': r3.ɵɵdeferOnIdle,