diff --git a/packages/core/src/render3/instructions/listener.ts b/packages/core/src/render3/instructions/listener.ts index af43545555..3cb77b92c8 100644 --- a/packages/core/src/render3/instructions/listener.ts +++ b/packages/core/src/render3/instructions/listener.ts @@ -14,7 +14,7 @@ import {GlobalTargetResolver, RElement, Renderer3, isProceduralRenderer} from '. import {CLEANUP, FLAGS, LView, LViewFlags, RENDERER, TVIEW} from '../interfaces/view'; import {assertNodeOfPossibleTypes} from '../node_assert'; import {getLView, getPreviousOrParentTNode} from '../state'; -import {getComponentViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils'; +import {getComponentViewByIndex, getNativeByTNode, hasDirectives, unwrapRNode} from '../util/view_utils'; import {BindingDirection, generatePropertyAliases, getCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared'; /** @@ -132,12 +132,16 @@ function listenerInternal( // In order to have just one native event handler in presence of multiple handler functions, // we just register a first handler function as a native event listener and then chain // (coalesce) other handler functions on top of the first native handler function. - // + let existingListener = null; // Please note that the coalescing described here doesn't happen for events specifying an // alternative target (ex. (document:click)) - this is to keep backward compatibility with the // view engine. - const existingListener = - eventTargetResolver ? null : findExistingListener(lView, eventName, tNode.index); + // Also, we don't have to search for existing listeners is there are no directives + // matching on a given node as we can't register multiple event handlers for the same event in + // a template (this would mean having duplicate attributes). + if (!eventTargetResolver && hasDirectives(tNode)) { + existingListener = findExistingListener(lView, eventName, tNode.index); + } if (existingListener !== null) { // Attach a new listener at the head of the coalesced listeners list. (listenerFn).__ngNextListenerFn__ = (existingListener).__ngNextListenerFn__; diff --git a/packages/core/src/render3/util/view_utils.ts b/packages/core/src/render3/util/view_utils.ts index b79a179187..9b51f3f285 100644 --- a/packages/core/src/render3/util/view_utils.ts +++ b/packages/core/src/render3/util/view_utils.ts @@ -131,6 +131,13 @@ export function getNativeByTNode(tNode: TNode, hostView: LView): RNode { return unwrapRNode(hostView[tNode.index]); } +/** + * A helper function that returns `true` if a given `TNode` has any matching directives. + */ +export function hasDirectives(tNode: TNode): boolean { + return tNode.directiveEnd > tNode.directiveStart; +} + export function getTNode(index: number, view: LView): TNode { ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode'); ngDevMode && assertLessThan(index, view[TVIEW].data.length, 'wrong index for TNode'); diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 7f8e07c77b..2673be4132 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -848,6 +848,9 @@ { "name": "hasClassInput" }, + { + "name": "hasDirectives" + }, { "name": "hasParentInjector" },