mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(core): share code between ComponentRef and targeted instructions (#60440)
This refactor reorganizes code such that the dynamic bindings logic and targeted listener instruction can share most of the code. PR Close #60440
This commit is contained in:
@@ -11,12 +11,11 @@ import {RuntimeError, RuntimeErrorCode} from '../errors';
|
||||
import {Type, Writable} from '../interface/type';
|
||||
import {assertNotDefined} from '../util/assert';
|
||||
import {bindingUpdated} from './bindings';
|
||||
import {listenToDirectiveOutput, wrapListener} from './instructions/listener';
|
||||
import {setDirectiveInput, storePropertyBindingMetadata} from './instructions/shared';
|
||||
import {DirectiveDef} from './interfaces/definition';
|
||||
import {CONTEXT} from './interfaces/view';
|
||||
import {getCurrentTNode, getLView, getSelectedTNode, getTView, nextBindingIndex} from './state';
|
||||
import {stringifyForError} from './util/stringify_utils';
|
||||
import {createOutputListener} from './view/directive_outputs';
|
||||
|
||||
/** Symbol used to store and retrieve metadata about a binding. */
|
||||
export const BINDING = /* @__PURE__ */ Symbol('BINDING');
|
||||
@@ -152,25 +151,9 @@ export function outputBinding<T>(eventName: string, listener: (event: T) => unkn
|
||||
}
|
||||
|
||||
const lView = getLView<{} | null>();
|
||||
const tView = getTView();
|
||||
const tNode = getCurrentTNode()!;
|
||||
const context = lView[CONTEXT];
|
||||
const wrappedListener = wrapListener(tNode, lView, context, listener);
|
||||
const hasBound = listenToDirectiveOutput(
|
||||
tNode,
|
||||
tView,
|
||||
lView,
|
||||
target,
|
||||
eventName,
|
||||
wrappedListener,
|
||||
);
|
||||
|
||||
if (!hasBound && ngDevMode) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_BINDING_TARGET,
|
||||
`${stringifyForError(target.type)} does not have an output with a public name of "${eventName}".`,
|
||||
);
|
||||
}
|
||||
createOutputListener(tNode, lView, listener, target, eventName);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -6,30 +6,23 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {setActiveConsumer} from '@angular/core/primitives/signals';
|
||||
|
||||
import {NotificationSource} from '../../change_detection/scheduling/zoneless_scheduling';
|
||||
import {assertIndexInRange} from '../../util/assert';
|
||||
import {TNode, TNodeType} from '../interfaces/node';
|
||||
import {GlobalTargetResolver, Renderer} from '../interfaces/renderer';
|
||||
import {RElement, RNode} from '../interfaces/renderer_dom';
|
||||
import {isComponentHost, isDirectiveHost} from '../interfaces/type_checks';
|
||||
import {isDirectiveHost} from '../interfaces/type_checks';
|
||||
import {CLEANUP, CONTEXT, LView, RENDERER, TView} from '../interfaces/view';
|
||||
import {assertTNodeType} from '../node_assert';
|
||||
import {profiler} from '../profiler';
|
||||
import {ProfilerEvent} from '../profiler_types';
|
||||
import {getCurrentDirectiveDef, getCurrentTNode, getLView, getTView} from '../state';
|
||||
import {
|
||||
getComponentLViewByIndex,
|
||||
getNativeByTNode,
|
||||
getOrCreateLViewCleanup,
|
||||
getOrCreateTViewCleanup,
|
||||
unwrapRNode,
|
||||
} from '../util/view_utils';
|
||||
|
||||
import {markViewDirty} from './mark_view_dirty';
|
||||
import {handleError, loadComponentRenderer} from './shared';
|
||||
import {DirectiveDef} from '../interfaces/definition';
|
||||
import {listenToOutput} from '../view/directive_outputs';
|
||||
import {wrapListener} from '../view/listeners';
|
||||
import {loadComponentRenderer} from './shared';
|
||||
|
||||
/**
|
||||
* Contains a reference to a function that disables event replay feature
|
||||
@@ -266,189 +259,3 @@ export function listenerInternal(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function listenToOutput(
|
||||
tNode: TNode,
|
||||
tView: TView,
|
||||
lView: LView,
|
||||
index: 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>;
|
||||
const propertyName = def.outputs[lookupName];
|
||||
const output = instance[propertyName];
|
||||
|
||||
if (ngDevMode && !isOutputSubscribable(output)) {
|
||||
throw new Error(`@Output ${propertyName} not initialized in '${instance.constructor.name}'.`);
|
||||
}
|
||||
|
||||
const subscription = (output as SubscribableOutput<unknown>).subscribe(listenerFn);
|
||||
const idx = lCleanup.length;
|
||||
lCleanup.push(listenerFn, subscription);
|
||||
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
|
||||
}
|
||||
|
||||
function executeListenerWithErrorHandling(
|
||||
lView: LView,
|
||||
context: {} | null,
|
||||
listenerFn: (e?: any) => any,
|
||||
e: any,
|
||||
): boolean {
|
||||
const prevConsumer = setActiveConsumer(null);
|
||||
try {
|
||||
profiler(ProfilerEvent.OutputStart, context, listenerFn);
|
||||
// Only explicitly returning false from a listener should preventDefault
|
||||
return listenerFn(e) !== false;
|
||||
} catch (error) {
|
||||
// TODO(atscott): This should report to the application error handler, not the ErrorHandler on LView injector
|
||||
handleError(lView, error);
|
||||
return false;
|
||||
} finally {
|
||||
profiler(ProfilerEvent.OutputEnd, context, listenerFn);
|
||||
setActiveConsumer(prevConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an event listener with a function that marks ancestors dirty and prevents default behavior,
|
||||
* if applicable.
|
||||
*
|
||||
* @param tNode The TNode associated with this listener
|
||||
* @param lView The LView that contains this listener
|
||||
* @param listenerFn The listener function to call
|
||||
* @param wrapWithPreventDefault Whether or not to prevent default behavior
|
||||
* (the procedural renderer does this already, so in those cases, we should skip)
|
||||
*/
|
||||
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
|
||||
// to optimize listener registration.
|
||||
return function wrapListenerIn_markDirtyAndPreventDefault(e: any) {
|
||||
// Ivy uses `Function` as a special token that allows us to unwrap the function
|
||||
// so that it can be invoked programmatically by `DebugNode.triggerEventHandler`.
|
||||
if (e === Function) {
|
||||
return listenerFn;
|
||||
}
|
||||
|
||||
// In order to be backwards compatible with View Engine, events on component host nodes
|
||||
// must also mark the component view itself dirty (i.e. the view that it owns).
|
||||
const startView = isComponentHost(tNode) ? getComponentLViewByIndex(tNode.index, lView) : lView;
|
||||
markViewDirty(startView, NotificationSource.Listener);
|
||||
|
||||
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.
|
||||
let nextListenerFn = (<any>wrapListenerIn_markDirtyAndPreventDefault).__ngNextListenerFn__;
|
||||
while (nextListenerFn) {
|
||||
// We should prevent default if any of the listeners explicitly return false
|
||||
result = executeListenerWithErrorHandling(lView, context, nextListenerFn, e) && result;
|
||||
nextListenerFn = (<any>nextListenerFn).__ngNextListenerFn__;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/** Describes a subscribable output field value. */
|
||||
interface SubscribableOutput<T> {
|
||||
subscribe(listener: (v: T) => void): {unsubscribe: () => void};
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given value represents a subscribable output.
|
||||
*
|
||||
* For example, an `EventEmitter, a `Subject`, an `Observable` or an
|
||||
* `OutputEmitter`.
|
||||
*/
|
||||
function isOutputSubscribable(value: unknown): value is SubscribableOutput<unknown> {
|
||||
return (
|
||||
value != null && typeof (value as Partial<SubscribableOutput<unknown>>).subscribe === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
/** Listens to an output on a specific directive. */
|
||||
export 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;
|
||||
let hasOutput = false;
|
||||
|
||||
if (ngDevMode && !tNode.directiveToIndex?.has(target.type)) {
|
||||
throw new Error(`Node does not have a directive with type ${target.type.name}`);
|
||||
}
|
||||
|
||||
const data = tNode.directiveToIndex!.get(target.type)!;
|
||||
|
||||
if (typeof data === 'number') {
|
||||
hostIndex = data;
|
||||
} else {
|
||||
[hostIndex, hostDirectivesStart, hostDirectivesEnd] = data;
|
||||
}
|
||||
|
||||
if (
|
||||
hostDirectivesStart !== null &&
|
||||
hostDirectivesEnd !== null &&
|
||||
tNode.hostDirectiveOutputs?.hasOwnProperty(eventName)
|
||||
) {
|
||||
const hostDirectiveOutputs = tNode.hostDirectiveOutputs[eventName];
|
||||
|
||||
for (let i = 0; i < hostDirectiveOutputs.length; i += 2) {
|
||||
const index = hostDirectiveOutputs[i] as number;
|
||||
|
||||
if (index >= hostDirectivesStart && index <= hostDirectivesEnd) {
|
||||
ngDevMode && assertIndexInRange(lView, index);
|
||||
hasOutput = true;
|
||||
listenToOutput(
|
||||
tNode,
|
||||
tView,
|
||||
lView,
|
||||
index,
|
||||
hostDirectiveOutputs[i + 1] as string,
|
||||
eventName,
|
||||
listenerFn,
|
||||
lCleanup,
|
||||
tCleanup,
|
||||
);
|
||||
} else if (index > hostDirectivesEnd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hostIndex !== null && target.outputs.hasOwnProperty(eventName)) {
|
||||
ngDevMode && assertIndexInRange(lView, hostIndex);
|
||||
hasOutput = true;
|
||||
listenToOutput(
|
||||
tNode,
|
||||
tView,
|
||||
lView,
|
||||
hostIndex,
|
||||
eventName,
|
||||
eventName,
|
||||
listenerFn,
|
||||
lCleanup,
|
||||
tCleanup,
|
||||
);
|
||||
}
|
||||
|
||||
return hasOutput;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../../di/injector';
|
||||
import {ErrorHandler, INTERNAL_APPLICATION_ERROR_HANDLER} from '../../error_handler';
|
||||
import {INTERNAL_APPLICATION_ERROR_HANDLER} from '../../error_handler';
|
||||
import {hasSkipHydrationAttrOnRElement} from '../../hydration/skip_hydration';
|
||||
import {PRESERVE_HOST_CONTENT, PRESERVE_HOST_CONTENT_DEFAULT} from '../../hydration/tokens';
|
||||
import {processTextNodeMarkersBeforeHydration} from '../../hydration/utils';
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
InitialInputData,
|
||||
InitialInputs,
|
||||
LocalRefExtractor,
|
||||
NodeInputBindings,
|
||||
TContainerNode,
|
||||
TDirectiveHostNode,
|
||||
TElementContainerNode,
|
||||
@@ -58,7 +57,6 @@ import {isNodeMatchingSelectorList} from '../node_selector_matcher';
|
||||
import {profiler} from '../profiler';
|
||||
import {ProfilerEvent} from '../profiler_types';
|
||||
import {
|
||||
getBindingsEnabled,
|
||||
getCurrentDirectiveIndex,
|
||||
getSelectedIndex,
|
||||
isInCheckNoChangesMode,
|
||||
@@ -613,18 +611,6 @@ export function handleUncaughtError(lView: LView, error: any): void {
|
||||
errorHandler?.(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error thrown in an LView.
|
||||
* @deprecated Use handleUncaughtError to report to application error handler
|
||||
*/
|
||||
export function handleError(lView: LView, error: any): void {
|
||||
const injector = lView[INJECTOR];
|
||||
if (!injector) {
|
||||
return;
|
||||
}
|
||||
injector.get(ErrorHandler, null)?.handleError(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all directive inputs with the specific public name on the node.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {RuntimeError, RuntimeErrorCode} from '../../errors';
|
||||
import {assertIndexInRange} from '../../util/assert';
|
||||
import {DirectiveDef} from '../interfaces/definition';
|
||||
import {TNode} from '../interfaces/node';
|
||||
import {CONTEXT, LView, TVIEW, TView} from '../interfaces/view';
|
||||
import {stringifyForError} from '../util/stringify_utils';
|
||||
import {getOrCreateLViewCleanup, getOrCreateTViewCleanup} from '../util/view_utils';
|
||||
import {wrapListener} from './listeners';
|
||||
|
||||
/** Describes a subscribable output field value. */
|
||||
interface SubscribableOutput<T> {
|
||||
subscribe(listener: (v: T) => void): {unsubscribe: () => void};
|
||||
}
|
||||
|
||||
export function createOutputListener<T = unknown>(
|
||||
tNode: TNode,
|
||||
lView: LView<{} | null>,
|
||||
listenerFn: (e?: any) => any,
|
||||
targetDef: DirectiveDef<unknown>,
|
||||
eventName: string,
|
||||
) {
|
||||
// TODO(pk): decouple checks from the actual binding
|
||||
const wrappedListener = wrapListener(tNode, lView, lView[CONTEXT], listenerFn);
|
||||
|
||||
// TODO(pk): simplify signature of listenToDirectiveOutput
|
||||
const hasBound = listenToDirectiveOutput(
|
||||
tNode,
|
||||
lView[TVIEW],
|
||||
lView,
|
||||
targetDef,
|
||||
eventName,
|
||||
wrappedListener,
|
||||
);
|
||||
|
||||
if (!hasBound && ngDevMode) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_BINDING_TARGET,
|
||||
`${stringifyForError(targetDef.type)} does not have an output with a public name of "${eventName}".`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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;
|
||||
let hasOutput = false;
|
||||
|
||||
if (ngDevMode && !tNode.directiveToIndex?.has(target.type)) {
|
||||
throw new Error(`Node does not have a directive with type ${target.type.name}`);
|
||||
}
|
||||
|
||||
const data = tNode.directiveToIndex!.get(target.type)!;
|
||||
|
||||
if (typeof data === 'number') {
|
||||
hostIndex = data;
|
||||
} else {
|
||||
[hostIndex, hostDirectivesStart, hostDirectivesEnd] = data;
|
||||
}
|
||||
|
||||
if (
|
||||
hostDirectivesStart !== null &&
|
||||
hostDirectivesEnd !== null &&
|
||||
tNode.hostDirectiveOutputs?.hasOwnProperty(eventName)
|
||||
) {
|
||||
const hostDirectiveOutputs = tNode.hostDirectiveOutputs[eventName];
|
||||
|
||||
for (let i = 0; i < hostDirectiveOutputs.length; i += 2) {
|
||||
const index = hostDirectiveOutputs[i] as number;
|
||||
|
||||
if (index >= hostDirectivesStart && index <= hostDirectivesEnd) {
|
||||
ngDevMode && assertIndexInRange(lView, index);
|
||||
hasOutput = true;
|
||||
listenToOutput(
|
||||
tNode,
|
||||
tView,
|
||||
lView,
|
||||
index,
|
||||
hostDirectiveOutputs[i + 1] as string,
|
||||
eventName,
|
||||
listenerFn,
|
||||
lCleanup,
|
||||
tCleanup,
|
||||
);
|
||||
} else if (index > hostDirectivesEnd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target.outputs.hasOwnProperty(eventName)) {
|
||||
ngDevMode && assertIndexInRange(lView, hostIndex);
|
||||
hasOutput = true;
|
||||
listenToOutput(
|
||||
tNode,
|
||||
tView,
|
||||
lView,
|
||||
hostIndex,
|
||||
eventName,
|
||||
eventName,
|
||||
listenerFn,
|
||||
lCleanup,
|
||||
tCleanup,
|
||||
);
|
||||
}
|
||||
|
||||
return hasOutput;
|
||||
}
|
||||
|
||||
export function listenToOutput(
|
||||
tNode: TNode,
|
||||
tView: TView,
|
||||
lView: LView,
|
||||
index: 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>;
|
||||
const propertyName = def.outputs[lookupName];
|
||||
const output = instance[propertyName];
|
||||
|
||||
if (ngDevMode && !isOutputSubscribable(output)) {
|
||||
throw new Error(`@Output ${propertyName} not initialized in '${instance.constructor.name}'.`);
|
||||
}
|
||||
|
||||
const subscription = (output as SubscribableOutput<unknown>).subscribe(listenerFn);
|
||||
const idx = lCleanup.length;
|
||||
lCleanup.push(listenerFn, subscription);
|
||||
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given value represents a subscribable output.
|
||||
*
|
||||
* For example, an `EventEmitter, a `Subject`, an `Observable` or an
|
||||
* `OutputEmitter`.
|
||||
*/
|
||||
function isOutputSubscribable(value: unknown): value is SubscribableOutput<unknown> {
|
||||
return (
|
||||
value != null && typeof (value as Partial<SubscribableOutput<unknown>>).subscribe === 'function'
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
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 {getComponentLViewByIndex} from '../util/view_utils';
|
||||
import {profiler} from '../profiler';
|
||||
import {ProfilerEvent} from '../profiler_types';
|
||||
import {ErrorHandler} from '../../error_handler';
|
||||
import {markViewDirty} from '../instructions/mark_view_dirty';
|
||||
|
||||
/**
|
||||
* Wraps an event listener with a function that marks ancestors dirty and prevents default behavior,
|
||||
* if applicable.
|
||||
*
|
||||
* @param tNode The TNode associated with this listener
|
||||
* @param lView The LView that contains this listener
|
||||
* @param listenerFn The listener function to call
|
||||
* @param wrapWithPreventDefault Whether or not to prevent default behavior
|
||||
* (the procedural renderer does this already, so in those cases, we should skip)
|
||||
*/
|
||||
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
|
||||
// to optimize listener registration.
|
||||
return function wrapListenerIn_markDirtyAndPreventDefault(e: any) {
|
||||
// Ivy uses `Function` as a special token that allows us to unwrap the function
|
||||
// so that it can be invoked programmatically by `DebugNode.triggerEventHandler`.
|
||||
if (e === Function) {
|
||||
return listenerFn;
|
||||
}
|
||||
|
||||
// In order to be backwards compatible with View Engine, events on component host nodes
|
||||
// must also mark the component view itself dirty (i.e. the view that it owns).
|
||||
const startView = isComponentHost(tNode) ? getComponentLViewByIndex(tNode.index, lView) : lView;
|
||||
markViewDirty(startView, NotificationSource.Listener);
|
||||
|
||||
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.
|
||||
let nextListenerFn = (<any>wrapListenerIn_markDirtyAndPreventDefault).__ngNextListenerFn__;
|
||||
while (nextListenerFn) {
|
||||
// We should prevent default if any of the listeners explicitly return false
|
||||
result = executeListenerWithErrorHandling(lView, context, nextListenerFn, e) && result;
|
||||
nextListenerFn = (<any>nextListenerFn).__ngNextListenerFn__;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function executeListenerWithErrorHandling(
|
||||
lView: LView,
|
||||
context: {} | null,
|
||||
listenerFn: (e?: any) => any,
|
||||
e: any,
|
||||
): boolean {
|
||||
const prevConsumer = setActiveConsumer(null);
|
||||
try {
|
||||
profiler(ProfilerEvent.OutputStart, context, listenerFn);
|
||||
// Only explicitly returning false from a listener should preventDefault
|
||||
return listenerFn(e) !== false;
|
||||
} catch (error) {
|
||||
// TODO(atscott): This should report to the application error handler, not the ErrorHandler on LView injector
|
||||
handleError(lView, error);
|
||||
return false;
|
||||
} finally {
|
||||
profiler(ProfilerEvent.OutputEnd, context, listenerFn);
|
||||
setActiveConsumer(prevConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error thrown in an LView.
|
||||
* @deprecated Use handleUncaughtError to report to application error handler
|
||||
*/
|
||||
function handleError(lView: LView, error: any): void {
|
||||
const injector = lView[INJECTOR];
|
||||
if (!injector) {
|
||||
return;
|
||||
}
|
||||
injector.get(ErrorHandler, null)?.handleError(error);
|
||||
}
|
||||
@@ -466,6 +466,7 @@
|
||||
"init_di_attr",
|
||||
"init_di_setup",
|
||||
"init_directive",
|
||||
"init_directive_outputs",
|
||||
"init_directives",
|
||||
"init_directives2",
|
||||
"init_discovery",
|
||||
@@ -581,6 +582,7 @@
|
||||
"init_linker",
|
||||
"init_list_reconciliation",
|
||||
"init_listener",
|
||||
"init_listeners",
|
||||
"init_local_compilation",
|
||||
"init_locale_data_api",
|
||||
"init_locale_en",
|
||||
|
||||
Reference in New Issue
Block a user