From 628ab4033bd3d165f235806632484895bb1f056f Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 20 Feb 2025 17:38:09 +0100 Subject: [PATCH] refactor(core): do not store input/output public names (#60036) Reworks the `TNode.inputs` and `TNode.outputs` to not store the public names of bindings. The only reason they were stored was for host directive re-aliasing which is handled through a different data structure now. PR Close #60036 --- .../core/src/render3/instructions/listener.ts | 46 +++++++++++++------ .../core/src/render3/instructions/shared.ts | 16 ++----- packages/core/src/render3/interfaces/node.ts | 26 ++++------- packages/core/src/render3/view/directives.ts | 13 ++---- 4 files changed, 50 insertions(+), 51 deletions(-) diff --git a/packages/core/src/render3/instructions/listener.ts b/packages/core/src/render3/instructions/listener.ts index e8898062923..7ad1869b548 100644 --- a/packages/core/src/render3/instructions/listener.ts +++ b/packages/core/src/render3/instructions/listener.ts @@ -10,7 +10,7 @@ import {setActiveConsumer} from '@angular/core/primitives/signals'; import {NotificationSource} from '../../change_detection/scheduling/zoneless_scheduling'; import {assertIndexInRange} from '../../util/assert'; -import {NodeOutputBindings, TNode, TNodeType} from '../interfaces/node'; +import {TNode, TNodeType} from '../interfaces/node'; import {GlobalTargetResolver, Renderer} from '../interfaces/renderer'; import {RElement} from '../interfaces/renderer_dom'; import {isComponentHost, isDirectiveHost} from '../interfaces/type_checks'; @@ -238,22 +238,34 @@ export function listenerInternal( if (hostDirectiveOutputConfig && hostDirectiveOutputConfig.length) { for (let i = 0; i < hostDirectiveOutputConfig.length; i += 2) { const index = hostDirectiveOutputConfig[i] as number; - ngDevMode && assertIndexInRange(lView, index); - const instance = lView[index]; const lookupName = hostDirectiveOutputConfig[i + 1] as string; - const def = tView.data[index] as DirectiveDef; - const minifiedName = def.outputs[lookupName]; - listenToOutput(tNode, instance, eventName, minifiedName, listenerFn, lCleanup, tCleanup); + listenToOutput( + tNode, + tView, + lView, + index, + lookupName, + eventName, + listenerFn, + lCleanup, + tCleanup, + ); } } if (outputConfig && outputConfig.length) { - for (let i = 0; i < outputConfig.length; i += 2) { - const index = outputConfig[i] as number; - ngDevMode && assertIndexInRange(lView, index); - const instance = lView[index]; - const minifiedName = outputConfig[i + 1] as string; - listenToOutput(tNode, instance, eventName, minifiedName, listenerFn, lCleanup, tCleanup); + for (const index of outputConfig) { + listenToOutput( + tNode, + tView, + lView, + index, + eventName, + eventName, + listenerFn, + lCleanup, + tCleanup, + ); } } } @@ -261,13 +273,19 @@ export function listenerInternal( function listenToOutput( tNode: TNode, - instance: any, + tView: TView, + lView: LView, + index: number, + lookupName: string, eventName: string, - propertyName: string, listenerFn: (e?: any) => any, lCleanup: any[], tCleanup: any[] | null, ) { + ngDevMode && assertIndexInRange(lView, index); + const instance = lView[index]; + const def = tView.data[index] as DirectiveDef; + const propertyName = def.outputs[lookupName]; const output = instance[propertyName]; if (ngDevMode && !isOutputSubscribable(output)) { diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 4a0efa4efee..2d810c937b7 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -324,8 +324,6 @@ function setNgReflectProperties( return; } - // TODO: this is identical to the block below, but will diverge in a future refactor. - // Figure out if we still can't consolidate them somehow. const inputConfig = tNode.inputs?.[publicName]; const hostInputConfig = tNode.hostDirectiveInputs?.[publicName]; @@ -340,11 +338,9 @@ function setNgReflectProperties( // Note: we set the private name of the input as the reflected property, not the public one. if (inputConfig) { - for (let i = 0; i < inputConfig.length; i += 2) { - const index = inputConfig[i] as number; - const lookupName = inputConfig[i + 1] as string; + for (const index of inputConfig) { const def = tView.data[index] as DirectiveDef; - setNgReflectProperty(lView, tNode, def.inputs[lookupName][0], value); + setNgReflectProperty(lView, tNode, def.inputs[publicName][0], value); } } } @@ -636,8 +632,6 @@ export function setInputsForProperty( const hostDirectiveInputs = tNode.hostDirectiveInputs?.[publicName]; let hasMatch = false; - // TODO: this is identical to the block below, but will diverge in a future refactor. - // Figure out if we still can't consolidate them somehow. if (hostDirectiveInputs) { for (let i = 0; i < hostDirectiveInputs.length; i += 2) { const index = hostDirectiveInputs[i] as number; @@ -650,13 +644,11 @@ export function setInputsForProperty( } if (inputs) { - for (let i = 0; i < inputs.length; i += 2) { - const index = inputs[i] as number; + for (const index of inputs) { ngDevMode && assertIndexInRange(lView, index); - const privateName = inputs[i + 1] as string; const instance = lView[index]; const def = tView.data[index] as DirectiveDef; - writeToDirectiveInput(def, instance, privateName, value); + writeToDirectiveInput(def, instance, publicName, value); hasMatch = true; } } diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index ff931c60270..0bac9123d7e 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -782,36 +782,28 @@ export interface TLetDeclarationNode extends TNode { export type TDirectiveHostNode = TElementNode | TContainerNode | TElementContainerNode; /** - * Store the runtime output names for all the directives. + * Maps the public names of outputs available on a specific node to the index + * of the directive instance that defines the output, for example: * - * i+0: directive instance index - * i+1: privateName - * - * e.g. * ``` * { - * "publicName": [0, 'change-minified'] + * "publicName": [0, 5] * } + * ``` */ -export type NodeOutputBindings = Record; +export type NodeOutputBindings = Record; /** - * Store the runtime input for all directives applied to a node. + * Maps the public names of inputs applied to a specific node to the index of the + * directive instance to which the input value should be written, for example: * - * This allows efficiently setting the same input on a directive that - * might apply to multiple directives. - * - * i+0: directive instance index - * i+1: privateName - * - * e.g. * ``` * { - * "publicName": [0, 'change-minified'] + * "publicName": [0, 5] * } * ``` */ -export type NodeInputBindings = Record; +export type NodeInputBindings = Record; /** * This array contains information about input properties that diff --git a/packages/core/src/render3/view/directives.ts b/packages/core/src/render3/view/directives.ts index 332ca15bfb8..3635c69debb 100644 --- a/packages/core/src/render3/view/directives.ts +++ b/packages/core/src/render3/view/directives.ts @@ -289,18 +289,14 @@ function setupSelectorMatchedInputsOrOutputs( for (const publicName in aliasMap) { if (aliasMap.hasOwnProperty(publicName)) { - const value = aliasMap[publicName]; let bindings: NodeInputBindings | NodeOutputBindings; - let privateName: string; if (mode === BindingType.Inputs) { bindings = tNode.inputs ??= {}; - privateName = publicName; } else { bindings = tNode.outputs ??= {}; - privateName = value as string; } bindings[publicName] ??= []; - bindings[publicName].push(directiveIndex, privateName); + bindings[publicName].push(directiveIndex); setShadowStylingInputFlags(tNode, publicName); } } @@ -397,10 +393,11 @@ function setupInitialInputs(tNode: TNode, directiveIndex: number, isHostDirectiv // through the directive def, but we want to do it using the inputs store so that it can // account for host directive aliases. const inputConfig = inputs![attrName as string]; - for (let j = 0; j < inputConfig.length; j += 2) { - if (inputConfig[j] === directiveIndex) { + + for (const index of inputConfig) { + if (index === directiveIndex) { inputsToStore ??= []; - inputsToStore.push(inputConfig[j + 1] as string, attrs[i + 1] as string); + inputsToStore.push(attrName as string, attrs[i + 1] as string); // A directive can't have multiple inputs with the same name so we can break here. break; }