From 00e60136613b51552d85fbb2ecb3e18c6b6e1f55 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 20 Sep 2023 09:40:49 +0200 Subject: [PATCH] refactor(compiler): implement final instruction generation for interaction triggers (#51830) Updates the logic that generates the instructions for the `on interaction` and `prefetch on interaction` triggers to their final shape. Now the instructions take two arguments: 1. `triggerIndex` - index at which to find the trigger in the view where it will be rendered. 2. `walkUpTimes` - tells the runtime how many views up it needs to go to find the trigger element. If the argument is omitted, it means that the trigger is in the same view as the deferred block. A positive number means that the runtime needs to go up X amount of times to find the trigger. A negative number means that the trigger is inside the root view of the placeholder block. Negative numbers are capped at -1 since the placeholder is always in the same position at runtime. PR Close #51830 --- .../GOLDEN_PARTIAL.js | 153 ++++++++++++++++++ .../r3_view_compiler_deferred/TEST_CASES.json | 63 ++++++++ ...eferred_interaction_parent_view_trigger.ts | 19 +++ ...nteraction_parent_view_trigger_template.js | 22 +++ ...eferred_interaction_placeholder_trigger.ts | 19 +++ ...nteraction_placeholder_trigger_template.js | 20 +++ .../deferred_interaction_same_view_trigger.ts | 19 +++ ..._interaction_same_view_trigger_template.js | 15 ++ ...eferred_with_prefetch_triggers_template.js | 2 +- .../deferred_with_triggers_template.js | 2 +- packages/compiler/src/jit_compiler_facade.ts | 5 +- .../compiler/src/render3/view/compiler.ts | 3 +- .../compiler/src/render3/view/template.ts | 40 ++++- .../core/src/render3/instructions/defer.ts | 16 +- 14 files changed, 380 insertions(+), 18 deletions(-) create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_parent_view_trigger.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_parent_view_trigger_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_placeholder_trigger.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_placeholder_trigger_template.js create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_same_view_trigger.ts create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_same_view_trigger_template.js diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js index 9a1b1a78bb0..fa80d826fde 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js @@ -511,3 +511,156 @@ export declare class MyApp { static ɵcmp: i0.ɵɵComponentDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: deferred_interaction_same_view_trigger.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyApp { + constructor() { + this.message = 'hello'; + } +} +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}} + {#defer on interaction(button); prefetch on interaction(button)}{/defer} + +
+
+
+ +
+
+
+ `, isInline: true }); +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{ + type: Component, + args: [{ + template: ` + {{message}} + {#defer on interaction(button); prefetch on interaction(button)}{/defer} + +
+
+
+ +
+
+
+ `, + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: deferred_interaction_same_view_trigger.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyApp { + message: string; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * PARTIAL FILE: deferred_interaction_parent_view_trigger.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyApp { + constructor() { + this.message = 'hello'; + } +} +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}} + + + + + {#defer on interaction(button); prefetch on interaction(button)}{/defer} + + + + `, isInline: true }); +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{ + type: Component, + args: [{ + template: ` + + {{message}} + + + + + {#defer on interaction(button); prefetch on interaction(button)}{/defer} + + + + `, + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: deferred_interaction_parent_view_trigger.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyApp { + message: string; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * PARTIAL FILE: deferred_interaction_placeholder_trigger.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyApp { + constructor() { + this.message = 'hello'; + } +} +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}} + {#defer on interaction(button); prefetch on interaction(button)} + Main + {:placeholder} +
+
+ +
+
+ {/defer} + `, isInline: true }); +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{ + type: Component, + args: [{ + template: ` + {{message}} + {#defer on interaction(button); prefetch on interaction(button)} + Main + {:placeholder} +
+
+ +
+
+ {/defer} + `, + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: deferred_interaction_placeholder_trigger.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyApp { + message: string; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json index 8a27aa4c142..9203456a534 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json @@ -192,6 +192,69 @@ } ], "skipForTemplatePipeline": true + }, + { + "description": "should generate a deferred block with an interaction trigger in the same view", + "angularCompilerOptions": { + "_enabledBlockTypes": ["defer"] + }, + "inputFiles": [ + "deferred_interaction_same_view_trigger.ts" + ], + "expectations": [ + { + "files": [ + { + "expected": "deferred_interaction_same_view_trigger_template.js", + "generated": "deferred_interaction_same_view_trigger.js" + } + ], + "failureMessage": "Incorrect template" + } + ], + "skipForTemplatePipeline": true + }, + { + "description": "should generate a deferred block with an interaction trigger in a parent view", + "angularCompilerOptions": { + "_enabledBlockTypes": ["defer"] + }, + "inputFiles": [ + "deferred_interaction_parent_view_trigger.ts" + ], + "expectations": [ + { + "files": [ + { + "expected": "deferred_interaction_parent_view_trigger_template.js", + "generated": "deferred_interaction_parent_view_trigger.js" + } + ], + "failureMessage": "Incorrect template" + } + ], + "skipForTemplatePipeline": true + }, + { + "description": "should generate a deferred block with an interaction trigger inside the placeholder", + "angularCompilerOptions": { + "_enabledBlockTypes": ["defer"] + }, + "inputFiles": [ + "deferred_interaction_placeholder_trigger.ts" + ], + "expectations": [ + { + "files": [ + { + "expected": "deferred_interaction_placeholder_trigger_template.js", + "generated": "deferred_interaction_placeholder_trigger.js" + } + ], + "failureMessage": "Incorrect template" + } + ], + "skipForTemplatePipeline": true } ] } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_parent_view_trigger.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_parent_view_trigger.ts new file mode 100644 index 00000000000..600b2b7aa21 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_parent_view_trigger.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + + {{message}} + + + + + {#defer on interaction(button); prefetch on interaction(button)}{/defer} + + + + `, +}) +export class MyApp { + message = 'hello'; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_parent_view_trigger_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_parent_view_trigger_template.js new file mode 100644 index 00000000000..d95666c9e70 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_parent_view_trigger_template.js @@ -0,0 +1,22 @@ +function MyApp_ng_template_0_ng_template_4_ng_template_0_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵtemplate(0, MyApp_ng_template_0_ng_template_4_ng_template_0_Defer_0_Template, 0, 0); + $r3$.ɵɵdefer(1, 0); + $r3$.ɵɵdeferOnInteraction(1, 2); + $r3$.ɵɵdeferPrefetchOnInteraction(1, 2); + } +} +… +function MyApp_ng_template_0_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵtext(0); + $r3$.ɵɵelementStart(1, "button", null, 0); + $r3$.ɵɵtext(3, "Click me"); + $r3$.ɵɵelementEnd(); + $r3$.ɵɵtemplate(4, MyApp_ng_template_0_ng_template_4_Template, 1, 0, "ng-template"); + } + if (rf & 2) { + const ctx_r0 = $r3$.ɵɵnextContext(); + $r3$.ɵɵtextInterpolate1(" ", ctx_r0.message, " "); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_placeholder_trigger.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_placeholder_trigger.ts new file mode 100644 index 00000000000..aeb8db6183f --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_placeholder_trigger.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + {{message}} + {#defer on interaction(button); prefetch on interaction(button)} + Main + {:placeholder} +
+
+ +
+
+ {/defer} + `, +}) +export class MyApp { + message = 'hello'; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_placeholder_trigger_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_placeholder_trigger_template.js new file mode 100644 index 00000000000..e99639a5527 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_placeholder_trigger_template.js @@ -0,0 +1,20 @@ +function MyApp_DeferPlaceholder_2_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelementStart(0, "div")(1, "div")(2, "button", null, 0); + $r3$.ɵɵtext(4, "Click me"); + $r3$.ɵɵelementEnd()()(); + } +} +… +function MyApp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵtext(0); + $r3$.ɵɵtemplate(1, MyApp_Defer_1_Template, 1, 0)(2, MyApp_DeferPlaceholder_2_Template, 5, 0); + $r3$.ɵɵdefer(3, 1, null, null, 2); + $r3$.ɵɵdeferOnInteraction(2, -1); + $r3$.ɵɵdeferPrefetchOnInteraction(2, -1); + } + if (rf & 2) { + $r3$.ɵɵtextInterpolate1(" ", ctx.message, " "); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_same_view_trigger.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_same_view_trigger.ts new file mode 100644 index 00000000000..3919ad6494c --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_same_view_trigger.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + {{message}} + {#defer on interaction(button); prefetch on interaction(button)}{/defer} + +
+
+
+ +
+
+
+ `, +}) +export class MyApp { + message = 'hello'; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_same_view_trigger_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_same_view_trigger_template.js new file mode 100644 index 00000000000..2b64eefb296 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_interaction_same_view_trigger_template.js @@ -0,0 +1,15 @@ +function MyApp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵtext(0); + $r3$.ɵɵtemplate(1, MyApp_Defer_1_Template, 0, 0); + $r3$.ɵɵdefer(2, 1); + $r3$.ɵɵdeferOnInteraction(7); + $r3$.ɵɵdeferPrefetchOnInteraction(7); + $r3$.ɵɵelementStart(4, "div")(5, "div")(6, "div")(7, "button", null, 0); + $r3$.ɵɵtext(9, "Click me"); + $r3$.ɵɵelementEnd()()()(); + } + if (rf & 2) { + $r3$.ɵɵtextInterpolate1(" ", ctx.message, " "); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_prefetch_triggers_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_prefetch_triggers_template.js index a0e3dbfc49b..54c842b2f6f 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_prefetch_triggers_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_prefetch_triggers_template.js @@ -8,7 +8,7 @@ function MyApp_Template(rf, ctx) { $r3$.ɵɵdeferPrefetchOnImmediate(); $r3$.ɵɵdeferPrefetchOnTimer(1337); $r3$.ɵɵdeferPrefetchOnHover(); - $r3$.ɵɵdeferPrefetchOnInteraction("button"); + $r3$.ɵɵdeferPrefetchOnInteraction(0, -1); $r3$.ɵɵdeferPrefetchOnViewport("button"); } if (rf & 2) { diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_triggers_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_triggers_template.js index 3d76031fed4..1a7c0186f57 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_triggers_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_triggers_template.js @@ -7,7 +7,7 @@ function MyApp_Template(rf, ctx) { $r3$.ɵɵdeferOnImmediate(); $r3$.ɵɵdeferOnTimer(1337); $r3$.ɵɵdeferOnHover(); - $r3$.ɵɵdeferOnInteraction("button"); + $r3$.ɵɵdeferOnInteraction(0, -1); $r3$.ɵɵdeferOnViewport("button"); } if (rf & 2) { diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index ec95c5f7eb2..fedc0701d87 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - -import {BoundTarget, R3TargetBinder, SelectorMatcher} from './compiler'; import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, InputMap, InputTransformFunction, OpaqueValue, R3ComponentMetadataFacade, R3DeclareComponentFacade, R3DeclareDependencyMetadataFacade, R3DeclareDirectiveDependencyFacade, R3DeclareDirectiveFacade, R3DeclareFactoryFacade, R3DeclareInjectableFacade, R3DeclareInjectorFacade, R3DeclareNgModuleFacade, R3DeclarePipeDependencyFacade, R3DeclarePipeFacade, R3DeclareQueryMetadataFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, R3TemplateDependencyFacade} from './compiler_facade_interface'; import {ConstantPool} from './constant_pool'; import {ChangeDetectionStrategy, HostBinding, HostListener, Input, Output, ViewEncapsulation} from './core'; @@ -25,9 +23,12 @@ import {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compile import {createMayBeForwardRefExpression, ForwardRefHandling, getSafePropertyAccessString, MaybeForwardRefExpression, wrapReference} from './render3/util'; import {DeclarationListEmitMode, R3ComponentMetadata, R3DeferBlockMetadata, R3DirectiveDependencyMetadata, R3DirectiveMetadata, R3HostDirectiveMetadata, R3HostMetadata, R3PipeDependencyMetadata, R3QueryMetadata, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata} from './render3/view/api'; import {compileComponentFromMetadata, compileDirectiveFromMetadata, ParsedHostBindings, parseHostBindings, verifyHostBindings} from './render3/view/compiler'; +import type {BoundTarget} from './render3/view/t2_api'; +import {R3TargetBinder} from './render3/view/t2_binder'; import {makeBindingParser, parseTemplate} from './render3/view/template'; import {ResourceLoader} from './resource_loader'; import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry'; +import {SelectorMatcher} from './selector'; let enabledBlockTypes: Set|undefined; diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 4c015ea3561..c3b62211901 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -195,7 +195,8 @@ export function compileComponentFromMetadata( const template = meta.template; const templateBuilder = new TemplateDefinitionBuilder( constantPool, BindingScope.createRootScope(), 0, templateTypeName, null, null, templateName, - R3.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds, meta.deferBlocks); + R3.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds, meta.deferBlocks, + new Map()); const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []); diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 21c90f7a096..f663235558c 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -244,6 +244,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver private _namespace: o.ExternalReference, relativeContextFilePath: string, private i18nUseExternalIds: boolean, private deferBlocks: Map, + private elementLocations: Map, private _constants: ComponentDefConsts = createComponentDefConsts()) { this._bindingScope = parentBindingScope.nestedScope(level); @@ -665,6 +666,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver visitElement(element: t.Element) { const elementIndex = this.allocateDataSlot(); const stylingBuilder = new StylingBuilder(null); + this.elementLocations.set(element, {index: elementIndex, level: this.level}); let isNonBindableMode: boolean = false; const isI18nRootElement: boolean = @@ -943,7 +945,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const visitor = new TemplateDefinitionBuilder( this.constantPool, this._bindingScope, this.level + 1, contextName, this.i18n, index, name, this._namespace, this.fileBasedI18nSuffix, this.i18nUseExternalIds, this.deferBlocks, - this._constants); + this.elementLocations, this._constants); // Nested templates must not be visited until after their parent templates have completed // processing, so they are queued here until after the initial pass. Otherwise, we wouldn't @@ -1407,13 +1409,37 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver hover.sourceSpan, prefetch ? R3.deferPrefetchOnHover : R3.deferOnHover); } - // TODO(crisbeto): currently the reference is passed as a string. - // Update this once we figure out how we should refer to the target. - // `deferOnInteraction(target)` + // TODO: `deferOnInteraction(index, walkUpTimes)` if (interaction) { - this.creationInstruction( - interaction.sourceSpan, prefetch ? R3.deferPrefetchOnInteraction : R3.deferOnInteraction, - [o.literal(interaction.reference)]); + const instructionRef = prefetch ? R3.deferPrefetchOnInteraction : R3.deferOnInteraction; + const triggerEl = metadata.triggerElements.get(interaction); + + // Don't generate anything if a trigger cannot be resolved. + // We'll have template diagnostics to surface these to users. + if (triggerEl) { + this.creationInstruction(interaction.sourceSpan, instructionRef, () => { + const location = this.elementLocations.get(triggerEl); + + if (!location) { + throw new Error( + `Could not determine location of reference passed into ` + + `'interaction' trigger. Template may not have been fully analyzed.`); + } + + // A negative depth means that the trigger is inside the placeholder. + // Cap it at -1 since we only care whether or not it's negative. + const depth = Math.max(this.level - location.level, -1); + const params = [o.literal(location.index)]; + + // The most common case should be a trigger within the view so we can omit a depth of + // zero. For triggers in parent views and in the placeholder we need to pass it in. + if (depth !== 0) { + params.push(o.literal(depth)); + } + + return params; + }); + } } // TODO(crisbeto): currently the reference is passed as a string. diff --git a/packages/core/src/render3/instructions/defer.ts b/packages/core/src/render3/instructions/defer.ts index e216116fce2..e79a776d426 100644 --- a/packages/core/src/render3/instructions/defer.ts +++ b/packages/core/src/render3/instructions/defer.ts @@ -21,7 +21,7 @@ import {isDestroyed, isLContainer, isLView} from '../interfaces/type_checks'; import {HEADER_OFFSET, INJECTOR, LView, PARENT, TVIEW, TView} from '../interfaces/view'; import {getCurrentTNode, getLView, getSelectedTNode, getTView, nextBindingIndex} from '../state'; import {isPlatformBrowser} from '../util/misc_utils'; -import {getConstant, getTNode, removeLViewOnDestroy, storeLViewOnDestroy} from '../util/view_utils'; +import {getConstant, getTNode, removeLViewOnDestroy, storeLViewOnDestroy, walkUpViews} from '../util/view_utils'; import {addLViewToLContainer, createAndRenderEmbeddedLView, removeLViewFromLContainer, shouldAddViewToDom} from '../view_manipulation'; import {ɵɵtemplate} from './template'; @@ -33,7 +33,7 @@ import {ɵɵtemplate} from './template'; * only placeholder content is rendered (if provided). */ function shouldTriggerDeferBlock(injector: Injector): boolean { - const config = injector.get(DEFER_BLOCK_CONFIG, {optional: true}); + const config = injector.get(DEFER_BLOCK_CONFIG, null, {optional: true}); if (config?.behavior === DeferBlockBehavior.Manual) { return false; } @@ -240,17 +240,21 @@ export function ɵɵdeferPrefetchOnHover() {} // TODO: implement runtime logic. /** * Creates runtime data structures for the `on interaction` deferred trigger. - * @param target Optional element on which to listen for hover events. + * @param triggerIndex Index at which to find the trigger element. + * @param walkUpTimes Number of times to walk up/down the tree hierarchy to find the trigger. * @codeGenApi */ -export function ɵɵdeferOnInteraction(target?: unknown) {} // TODO: implement runtime logic. +export function ɵɵdeferOnInteraction(triggerIndex: number, walkUpTimes?: number) { +} // TODO: implement runtime logic. /** * Creates runtime data structures for the `prefetch on interaction` deferred trigger. - * @param target Optional element on which to listen for hover events. + * @param triggerIndex Index at which to find the trigger element. + * @param walkUpTimes Number of times to walk up/down the tree hierarchy to find the trigger. * @codeGenApi */ -export function ɵɵdeferPrefetchOnInteraction(target?: unknown) {} // TODO: implement runtime logic. +export function ɵɵdeferPrefetchOnInteraction(triggerIndex: number, walkUpTimes?: number) { +} // TODO: implement runtime logic. /** * Creates runtime data structures for the `on viewport` deferred trigger.