From 7ffcb504df2e14610cfb4fd219dbb5cfb3c30f15 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 4 Mar 2025 14:49:51 +0100 Subject: [PATCH] refactor(compiler): account for host element in binder (#60267) Updates the `R3TargetBinder` to ingest host element nodes. PR Close #60267 --- packages/compiler/src/render3/view/t2_api.ts | 5 ++- .../compiler/src/render3/view/t2_binder.ts | 35 ++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/compiler/src/render3/view/t2_api.ts b/packages/compiler/src/render3/view/t2_api.ts index 59ceaa5b627..62254d8ad32 100644 --- a/packages/compiler/src/render3/view/t2_api.ts +++ b/packages/compiler/src/render3/view/t2_api.ts @@ -19,6 +19,7 @@ import { Element, ForLoopBlock, ForLoopBlockEmpty, + HostElement, IfBlockBranch, LetDeclaration, Node, @@ -40,7 +41,8 @@ export type ScopedNode = | DeferredBlockError | DeferredBlockLoading | DeferredBlockPlaceholder - | Content; + | Content + | HostElement; /** Possible values that a reference can be resolved to. */ export type ReferenceTarget = @@ -68,6 +70,7 @@ export type TemplateEntity = Reference | Variable | LetDeclaration; */ export interface Target { template?: Node[]; + host?: HostElement; } /** diff --git a/packages/compiler/src/render3/view/t2_binder.ts b/packages/compiler/src/render3/view/t2_binder.ts index 88a0b3b4f47..47c90aaf4ca 100644 --- a/packages/compiler/src/render3/view/t2_binder.ts +++ b/packages/compiler/src/render3/view/t2_binder.ts @@ -31,6 +31,7 @@ import { Element, ForLoopBlock, ForLoopBlockEmpty, + HostElement, HoverDeferredTrigger, Icu, IfBlock, @@ -169,7 +170,7 @@ export class R3TargetBinder implements TargetB * metadata about the types referenced in the template. */ bind(target: Target): BoundTarget { - if (!target.template) { + if (!target.template && !target.host) { throw new Error('Empty bound targets are not supported'); } @@ -221,6 +222,21 @@ export class R3TargetBinder implements TargetB ); } + // Bind the host element in a separate scope. Note that it only uses the + // `TemplateBinder` since directives don't apply inside a host context. + if (target.host) { + TemplateBinder.applyWithScope( + target.host, + Scope.apply(target.host), + expressions, + symbols, + nestingLevel, + usedPipes, + eagerPipes, + deferBlocks, + ); + } + return new R3BoundTarget( target, directives, @@ -280,7 +296,7 @@ class Scope implements Visitor { * Process a template (either as a `Template` sub-template with variables, or a plain array of * template `Node`s) and construct its `Scope`. */ - static apply(template: Node[]): Scope { + static apply(template: ScopedNode | Node[]): Scope { const scope = Scope.newRootScope(); scope.ingest(template); return scope; @@ -315,7 +331,7 @@ class Scope implements Visitor { nodeOrNodes instanceof Content ) { nodeOrNodes.children.forEach((node) => node.visit(this)); - } else { + } else if (!(nodeOrNodes instanceof HostElement)) { // No overarching `Template` instance, so process the nodes directly. nodeOrNodes.forEach((node) => node.visit(this)); } @@ -702,7 +718,7 @@ class TemplateBinder extends RecursiveAstVisitor implements Visitor { /** * Process a template and extract metadata about expressions and symbols within. * - * @param nodes the nodes of the template to process + * @param nodeOrNodes the nodes of the template to process * @param scope the `Scope` of the template being processed. * @returns three maps which contain metadata about the template: `expressions` which interprets * special `AST` nodes in expressions as pointing to references or variables declared within the @@ -712,7 +728,7 @@ class TemplateBinder extends RecursiveAstVisitor implements Visitor { * at 1. */ static applyWithScope( - nodes: Node[], + nodeOrNodes: ScopedNode | Node[], scope: Scope, expressions: Map, symbols: Map, @@ -721,7 +737,7 @@ class TemplateBinder extends RecursiveAstVisitor implements Visitor { eagerPipes: Set, deferBlocks: DeferBlockScopes, ): void { - const template = nodes instanceof Template ? nodes : null; + const template = nodeOrNodes instanceof Template ? nodeOrNodes : null; // The top-level template has nesting level 0. const binder = new TemplateBinder( expressions, @@ -734,7 +750,7 @@ class TemplateBinder extends RecursiveAstVisitor implements Visitor { template, 0, ); - binder.ingest(nodes); + binder.ingest(nodeOrNodes); } private ingest(nodeOrNodes: ScopedNode | Node[]): void { @@ -777,6 +793,9 @@ class TemplateBinder extends RecursiveAstVisitor implements Visitor { ) { nodeOrNodes.children.forEach((node) => node.visit(this)); this.nestingLevel.set(nodeOrNodes, this.level); + } else if (nodeOrNodes instanceof HostElement) { + // Host elements are always at the top level. + this.nestingLevel.set(nodeOrNodes, 0); } else { // Visit each node from the top-level template. nodeOrNodes.forEach(this.visitNode); @@ -967,7 +986,7 @@ class TemplateBinder extends RecursiveAstVisitor implements Visitor { * * See `BoundTarget` for documentation on the individual methods. */ -export class R3BoundTarget implements BoundTarget { +class R3BoundTarget implements BoundTarget { /** Deferred blocks, ordered as they appear in the template. */ private deferredBlocks: DeferredBlock[];