refactor(compiler): generate switch block instructions (#51380)

Adds the logic to generate the instructions for `switch` instructions. For the following block:

```html
{#switch value()}
  {:case 0} case 0
  {:case 1} case 1
  {:case 2} case 2
  {:default} default
{/switch}
```

The compiler will produce the following output:

```ts
function App_Template(rf, ctx) {
  if (rf & 1) {
    ɵɵtemplate(0, App_Case_0_Template, 1, 0);
    ɵɵtemplate(1, App_Case_1_Template, 1, 0);
    ɵɵtemplate(2, App_Case_2_Template, 1, 0);
    ɵɵtemplate(3, App_Case_3_Template, 1, 0);
  }
  if (rf & 2) {
    let App_contFlowTmp;
    ɵɵconditional(0, (App_contFlowTmp = ctx.value()) === 0 ? 0 : App_contFlowTmp === 1 ? 1 : App_contFlowTmp === 2 ? 2 : 3);
  }
}
```

PR Close #51380
This commit is contained in:
Kristiyan Kostadinov 2023-08-15 14:27:30 +02:00 committed by Andrew Kushnir
parent 5bd9fbd2c3
commit 0c4c773fca
13 changed files with 653 additions and 2 deletions

View file

@ -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: `
<div>
{{message}}
{#switch value()}
{:case 0} case 0
{:case 1} case 1
{:case 2} case 2
{:default} default
{/switch}
</div>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
<div>
{{message}}
{#switch value()}
{:case 0} case 0
{:case 1} case 1
{:case 2} case 2
{:default} default
{/switch}
</div>
`,
}]
}] });
/****************************************************************************************************
* PARTIAL FILE: basic_switch.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyApp {
message: string;
value(): number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, false, never>;
}
/****************************************************************************************************
* 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: `
<div>
{{message}}
{#switch value()}
{:case 0} case 0
{:case 1} case 1
{:case 2} case 2
{/switch}
</div>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
<div>
{{message}}
{#switch value()}
{:case 0} case 0
{:case 1} case 1
{:case 2} case 2
{/switch}
</div>
`,
}]
}] });
/****************************************************************************************************
* 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<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, false, never>;
}
/****************************************************************************************************
* 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: `
<div>
{{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}
</div>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
args: [{
template: `
<div>
{{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}
</div>
`,
}]
}] });
/****************************************************************************************************
* 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<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, false, never>;
}
/****************************************************************************************************
* 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: `
<div>
{{message}}
{#switch value() | test}
{:case 0} case 0
{:case 1} case 1
{:default} default
{/switch}
</div>
`, 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: `
<div>
{{message}}
{#switch value() | test}
{:case 0} case 0
{:case 1} case 1
{:default} default
{/switch}
</div>
`,
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<TestPipe, never>;
static ɵpipe: i0.ɵɵPipeDeclaration<TestPipe, "test", true>;
}
export declare class MyApp {
message: string;
value: () => number;
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
}

View file

@ -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
}
]
}

View file

@ -0,0 +1,22 @@
import {Component} from '@angular/core';
@Component({
template: `
<div>
{{message}}
{#switch value()}
{:case 0} case 0
{:case 1} case 1
{:case 2} case 2
{:default} default
{/switch}
</div>
`,
})
export class MyApp {
message = 'hello';
value() {
return 1;
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,24 @@
import {Component} from '@angular/core';
@Component({
template: `
<div>
{{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}
</div>
`,
})
export class MyApp {
message = 'hello';
value = () => 1;
nestedValue = () => 2;
}

View file

@ -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);
}
}

View file

@ -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: `
<div>
{{message}}
{#switch value() | test}
{:case 0} case 0
{:case 1} case 1
{:default} default
{/switch}
</div>
`,
standalone: true,
imports: [TestPipe]
})
export class MyApp {
message = 'hello';
value = () => 1;
}

View file

@ -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);
}
}

View file

@ -0,0 +1,18 @@
import {Component} from '@angular/core';
@Component({
template: `
<div>
{{message}}
{#switch value()}
{:case 0} case 0
{:case 1} case 1
{:case 2} case 2
{/switch}
</div>
`,
})
export class MyApp {
message = 'hello';
value = () => 1;
}

View file

@ -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);
}
}

View file

@ -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};

View file

@ -178,6 +178,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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<void>, 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<void>, 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<void>, 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<void>, 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.
*

View file

@ -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,