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
This commit is contained in:
Kristiyan Kostadinov
2025-02-20 17:38:09 +01:00
committed by kirjs
parent 9f44b41053
commit 628ab4033b
4 changed files with 50 additions and 51 deletions
@@ -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<unknown>;
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<unknown>;
const propertyName = def.outputs[lookupName];
const output = instance[propertyName];
if (ngDevMode && !isOutputSubscribable(output)) {
@@ -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<unknown>;
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<any>;
writeToDirectiveInput(def, instance, privateName, value);
writeToDirectiveInput(def, instance, publicName, value);
hasMatch = true;
}
}
+9 -17
View File
@@ -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<string, (number | string)[]>;
export type NodeOutputBindings = Record<string, number[]>;
/**
* 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<string, (number | string)[]>;
export type NodeInputBindings = Record<string, number[]>;
/**
* This array contains information about input properties that
+5 -8
View File
@@ -289,18 +289,14 @@ function setupSelectorMatchedInputsOrOutputs<T>(
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;
}