refactor(core): simplify signature of listenToDirectiveOutput (#60514) (#60547)

We can simplify signature of listenToDirectiveOutput by passing less
arguments (some of them can be derived from already passed arguments).

PR Close #60547
This commit is contained in:
Pawel Kozlowski 2025-03-21 14:18:09 +01:00 committed by Alex Rickabaugh
parent 88d7b7c965
commit 5a59af0ef8
6 changed files with 24 additions and 54 deletions

View file

@ -210,7 +210,7 @@ export function listenerInternal(
(<any>existingListener).__ngLastListenerFn__ = listenerFn;
processOutputs = false;
} else {
listenerFn = wrapListener(tNode, lView, context, listenerFn);
listenerFn = wrapListener(tNode, lView, listenerFn);
stashEventListener(target as RElement, eventName, listenerFn);
const cleanupFn = renderer.listen(target as RElement, eventName, listenerFn);
ngDevMode && ngDevMode.rendererAddEventListener++;
@ -221,7 +221,7 @@ export function listenerInternal(
} else {
// Even if there is no native listener to add, we still need to wrap the listener so that OnPush
// ancestors are marked dirty when an event occurs.
listenerFn = wrapListener(tNode, lView, context, listenerFn);
listenerFn = wrapListener(tNode, lView, listenerFn);
}
if (processOutputs) {
@ -232,33 +232,13 @@ export function listenerInternal(
for (let i = 0; i < hostDirectiveOutputConfig.length; i += 2) {
const index = hostDirectiveOutputConfig[i] as number;
const lookupName = hostDirectiveOutputConfig[i + 1] as string;
listenToOutput(
tNode,
tView,
lView,
index,
lookupName,
eventName,
listenerFn,
lCleanup,
tCleanup,
);
listenToOutput(tNode, lView, index, lookupName, eventName, listenerFn);
}
}
if (outputConfig && outputConfig.length) {
for (const index of outputConfig) {
listenToOutput(
tNode,
tView,
lView,
index,
eventName,
eventName,
listenerFn,
lCleanup,
tCleanup,
);
listenToOutput(tNode, lView, index, eventName, eventName, listenerFn);
}
}
}

View file

@ -9,7 +9,7 @@
import {assertIndexInRange} from '../../util/assert';
import {DirectiveDef} from '../interfaces/definition';
import {TNode} from '../interfaces/node';
import {CONTEXT, LView, TVIEW, TView} from '../interfaces/view';
import {LView, TVIEW} from '../interfaces/view';
import {getOrCreateLViewCleanup, getOrCreateTViewCleanup} from '../util/view_utils';
import {wrapListener} from './listeners';
@ -26,23 +26,19 @@ export function createOutputListener<T = unknown>(
eventName: string,
) {
// TODO(pk): decouple checks from the actual binding
const wrappedListener = wrapListener(tNode, lView, lView[CONTEXT], listenerFn);
const wrappedListener = wrapListener(tNode, lView, listenerFn);
// TODO(pk): simplify signature of listenToDirectiveOutput
listenToDirectiveOutput(tNode, lView[TVIEW], lView, targetDef, eventName, wrappedListener);
listenToDirectiveOutput(tNode, lView, targetDef, eventName, wrappedListener);
}
/** Listens to an output on a specific directive. */
function listenToDirectiveOutput(
tNode: TNode,
tView: TView,
lView: LView,
target: DirectiveDef<unknown>,
eventName: string,
listenerFn: (e?: any) => any,
): boolean {
const tCleanup = tView.firstCreatePass ? getOrCreateTViewCleanup(tView) : null;
const lCleanup = getOrCreateLViewCleanup(lView);
let hostIndex: number | null = null;
let hostDirectivesStart: number | null = null;
let hostDirectivesEnd: number | null = null;
@ -75,14 +71,11 @@ function listenToDirectiveOutput(
hasOutput = true;
listenToOutput(
tNode,
tView,
lView,
index,
hostDirectiveOutputs[i + 1] as string,
eventName,
listenerFn,
lCleanup,
tCleanup,
);
} else if (index > hostDirectivesEnd) {
break;
@ -93,17 +86,7 @@ function listenToDirectiveOutput(
if (target.outputs.hasOwnProperty(eventName)) {
ngDevMode && assertIndexInRange(lView, hostIndex);
hasOutput = true;
listenToOutput(
tNode,
tView,
lView,
hostIndex,
eventName,
eventName,
listenerFn,
lCleanup,
tCleanup,
);
listenToOutput(tNode, lView, hostIndex, eventName, eventName, listenerFn);
}
return hasOutput;
@ -111,18 +94,16 @@ function listenToDirectiveOutput(
export function listenToOutput(
tNode: TNode,
tView: TView,
lView: LView,
index: number,
directiveIndex: number,
lookupName: string,
eventName: 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>;
ngDevMode && assertIndexInRange(lView, directiveIndex);
const instance = lView[directiveIndex];
const tView = lView[TVIEW];
const def = tView.data[directiveIndex] as DirectiveDef<unknown>;
const propertyName = def.outputs[lookupName];
const output = instance[propertyName];
@ -130,6 +111,9 @@ export function listenToOutput(
throw new Error(`@Output ${propertyName} not initialized in '${instance.constructor.name}'.`);
}
// TODO(pk): introduce utility to store cleanup or find a different way of sharing code with listener
const tCleanup = tView.firstCreatePass ? getOrCreateTViewCleanup(tView) : null;
const lCleanup = getOrCreateLViewCleanup(lView);
const subscription = (output as SubscribableOutput<unknown>).subscribe(listenerFn);
const idx = lCleanup.length;
lCleanup.push(listenerFn, subscription);

View file

@ -11,7 +11,7 @@ import {setActiveConsumer} from '@angular/core/primitives/signals';
import {NotificationSource} from '../../change_detection/scheduling/zoneless_scheduling';
import {TNode} from '../interfaces/node';
import {isComponentHost} from '../interfaces/type_checks';
import {INJECTOR, LView} from '../interfaces/view';
import {CONTEXT, INJECTOR, LView} from '../interfaces/view';
import {getComponentLViewByIndex} from '../util/view_utils';
import {profiler} from '../profiler';
import {ProfilerEvent} from '../profiler_types';
@ -31,7 +31,6 @@ import {markViewDirty} from '../instructions/mark_view_dirty';
export function wrapListener(
tNode: TNode,
lView: LView<{} | null>,
context: {} | null,
listenerFn: (e?: any) => any,
): EventListener {
// Note: we are performing most of the work in the listener function itself
@ -48,6 +47,7 @@ export function wrapListener(
const startView = isComponentHost(tNode) ? getComponentLViewByIndex(tNode.index, lView) : lView;
markViewDirty(startView, NotificationSource.Listener);
const context = lView[CONTEXT];
let result = executeListenerWithErrorHandling(lView, context, listenerFn, e);
// A just-invoked listener function might have coalesced listeners so we need to check for
// their presence and invoke as needed.

View file

@ -388,8 +388,10 @@
"getNodeInjectable",
"getNullInjector",
"getOrCreateInjectable",
"getOrCreateLViewCleanup",
"getOrCreateNodeInjectorForNode",
"getOrCreateTNode",
"getOrCreateTViewCleanup",
"getOrCreateViewRefs",
"getOwnDefinition",
"getParentInjectorIndex",

View file

@ -375,8 +375,10 @@
"getNodeInjectable",
"getNullInjector",
"getOrCreateInjectable",
"getOrCreateLViewCleanup",
"getOrCreateNodeInjectorForNode",
"getOrCreateTNode",
"getOrCreateTViewCleanup",
"getOrCreateViewRefs",
"getOwnDefinition",
"getParentInjectorIndex",

View file

@ -319,8 +319,10 @@
"getNodeInjectable",
"getNullInjector",
"getOrCreateInjectable",
"getOrCreateLViewCleanup",
"getOrCreateNodeInjectorForNode",
"getOrCreateTNode",
"getOrCreateTViewCleanup",
"getOrCreateViewRefs",
"getOwnDefinition",
"getParentInjectorIndex",