diff --git a/packages/core/src/render3/instructions/element.ts b/packages/core/src/render3/instructions/element.ts index 14b705dd17..fd093235f6 100644 --- a/packages/core/src/render3/instructions/element.ts +++ b/packages/core/src/render3/instructions/element.ts @@ -23,7 +23,7 @@ import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_n import {setUpAttributes} from '../util/attrs_utils'; import {getNativeByTNode, getTNode} from '../util/view_utils'; -import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared'; +import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared'; @@ -84,13 +84,14 @@ export function ɵɵelementStart( ngDevMode && ngDevMode.firstTemplatePass++; resolveDirectives(tView, lView, tNode, localRefs || null); - const inputData = initializeTNodeInputs(tView, tNode); - if (inputData && inputData.hasOwnProperty('class')) { - tNode.flags |= TNodeFlags.hasClassInput; - } - - if (inputData && inputData.hasOwnProperty('style')) { - tNode.flags |= TNodeFlags.hasStyleInput; + const inputData = tNode.inputs; + if (inputData != null) { + if (inputData.hasOwnProperty('class')) { + tNode.flags |= TNodeFlags.hasClassInput; + } + if (inputData.hasOwnProperty('style')) { + tNode.flags |= TNodeFlags.hasStyleInput; + } } if (tView.queries !== null) { diff --git a/packages/core/src/render3/instructions/listener.ts b/packages/core/src/render3/instructions/listener.ts index 9844a5808e..e002ec85c9 100644 --- a/packages/core/src/render3/instructions/listener.ts +++ b/packages/core/src/render3/instructions/listener.ts @@ -17,8 +17,7 @@ import {CLEANUP, FLAGS, LView, LViewFlags, RENDERER, TVIEW} from '../interfaces/ import {assertNodeOfPossibleTypes} from '../node_assert'; import {getLView, getPreviousOrParentTNode} from '../state'; import {getComponentViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils'; - -import {BindingDirection, generatePropertyAliases, getCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared'; +import {getCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared'; /** * Adds an event listener to the current node. @@ -186,36 +185,28 @@ function listenerInternal( } // subscribe to directive outputs - if (isTNodeDirectiveHost && processOutputs) { - let outputs = tNode.outputs; - if (outputs === undefined) { - // if we create TNode here, inputs must be undefined so we know they still need to be - // checked - outputs = tNode.outputs = generatePropertyAliases(tView, tNode, BindingDirection.Output); - } + const outputs = tNode.outputs; + let props: PropertyAliasValue|undefined; + if (processOutputs && outputs != null && (props = outputs[eventName])) { + const propsLength = props.length; + if (propsLength) { + const lCleanup = getCleanup(lView); + for (let i = 0; i < propsLength; i += 3) { + const index = props[i] as number; + ngDevMode && assertDataInRange(lView, index); + const minifiedName = props[i + 2]; + const directiveInstance = lView[index]; + const output = directiveInstance[minifiedName]; - let props: PropertyAliasValue|undefined; - if (outputs !== null && (props = outputs[eventName])) { - const propsLength = props.length; - if (propsLength) { - const lCleanup = getCleanup(lView); - for (let i = 0; i < propsLength; i += 3) { - const index = props[i] as number; - ngDevMode && assertDataInRange(lView, index); - const minifiedName = props[i + 2]; - const directiveInstance = lView[index]; - const output = directiveInstance[minifiedName]; - - if (ngDevMode && !isObservable(output)) { - throw new Error( - `@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`); - } - - const subscription = output.subscribe(listenerFn); - const idx = lCleanup.length; - lCleanup.push(listenerFn, subscription); - tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1)); + if (ngDevMode && !isObservable(output)) { + throw new Error( + `@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`); } + + const subscription = output.subscribe(listenerFn); + const idx = lCleanup.length; + lCleanup.push(listenerFn, subscription); + tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1)); } } } diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index bb2cde62d7..fd4aad9eab 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -21,7 +21,7 @@ import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} f import {throwMultipleComponentError} from '../errors'; import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPreOrderHooks} from '../hooks'; import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; -import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, FactoryFn, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; +import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector'; import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node'; import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer'; @@ -49,11 +49,6 @@ import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNod */ const _CLEAN_PROMISE = (() => Promise.resolve(null))(); -export const enum BindingDirection { - Input, - Output, -} - /** Sets the host bindings for the current view. */ export function setHostBindings(tView: TView, viewData: LView): void { const selectedIndex = getSelectedIndex(); @@ -803,41 +798,47 @@ export function createTNode( } -/** - * Consolidates all inputs or outputs of all directives on this logical node. - * - * @param tNode - * @param direction whether to consider inputs or outputs - * @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise - */ -export function generatePropertyAliases( - tView: TView, tNode: TNode, direction: BindingDirection): PropertyAliases|null { - let propStore: PropertyAliases|null = null; - const start = tNode.directiveStart; - const end = tNode.directiveEnd; +function generatePropertyAliases( + inputAliasMap: {[publicName: string]: string}, directiveDefIdx: number, + propStore: PropertyAliases | null): PropertyAliases|null { + for (let publicName in inputAliasMap) { + if (inputAliasMap.hasOwnProperty(publicName)) { + propStore = propStore === null ? {} : propStore; + const internalName = inputAliasMap[publicName]; - if (end > start) { - const isInput = direction === BindingDirection.Input; - const defs = tView.data; - - for (let i = start; i < end; i++) { - const directiveDef = defs[i] as DirectiveDef; - const propertyAliasMap: {[publicName: string]: string} = - isInput ? directiveDef.inputs : directiveDef.outputs; - for (let publicName in propertyAliasMap) { - if (propertyAliasMap.hasOwnProperty(publicName)) { - propStore = propStore || {}; - const internalName = propertyAliasMap[publicName]; - const hasProperty = propStore.hasOwnProperty(publicName); - hasProperty ? propStore[publicName].push(i, publicName, internalName) : - (propStore[publicName] = [i, publicName, internalName]); - } + if (propStore.hasOwnProperty(publicName)) { + propStore[publicName].push(directiveDefIdx, publicName, internalName); + } else { + (propStore[publicName] = [directiveDefIdx, publicName, internalName]); } } } return propStore; } +/** + * Initializes data structures required to work with directive outputs and outputs. + * Initialization is done for all directives matched on a given TNode. + */ +function initializeInputAndOutputAliases(tView: TView, tNode: TNode): void { + ngDevMode && assertFirstTemplatePass(tView); + + const start = tNode.directiveStart; + const end = tNode.directiveEnd; + const defs = tView.data; + + let inputsStore: PropertyAliases|null = null; + let outputsStore: PropertyAliases|null = null; + for (let i = start; i < end; i++) { + const directiveDef = defs[i] as DirectiveDef; + inputsStore = generatePropertyAliases(directiveDef.inputs, i, inputsStore); + outputsStore = generatePropertyAliases(directiveDef.outputs, i, outputsStore); + } + + tNode.inputs = inputsStore; + tNode.outputs = outputsStore; +} + /** * Mapping between attributes names that don't correspond to their element property names. * @@ -863,13 +864,11 @@ export function elementPropertyInternal( loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void { ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); const lView = getLView(); - const tView = lView[TVIEW]; const element = getNativeByIndex(index, lView) as RElement | RComment; const tNode = getTNode(index, lView); - let inputData: PropertyAliases|null|undefined; + let inputData = tNode.inputs; let dataValue: PropertyAliasValue|undefined; - if (!nativeOnly && (inputData = initializeTNodeInputs(tView, tNode)) && - (dataValue = inputData[propName])) { + if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) { setInputsForProperty(lView, dataValue, value); if (isComponentHost(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET); if (ngDevMode) { @@ -1055,6 +1054,8 @@ export function resolveDirectives( directiveDefIdx, def, tView, nodeIndex, initialPreOrderHooksLength, initialPreOrderCheckHooksLength); } + + initializeInputAndOutputAliases(tView, tNode); } if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap); } @@ -1773,16 +1774,6 @@ export function storePropertyBindingMetadata( export const CLEAN_PROMISE = _CLEAN_PROMISE; -export function initializeTNodeInputs(tView: TView, tNode: TNode): PropertyAliases|null { - // If tNode.inputs is undefined, a listener has created outputs, but inputs haven't - // yet been checked. - if (tNode.inputs === undefined) { - // mark inputs as checked - tNode.inputs = generatePropertyAliases(tView, tNode, BindingDirection.Input); - } - return tNode.inputs; -} - export function getCleanup(view: LView): any[] { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup() : []); diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index ffd429d6d5..f0bd05a1f1 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -423,7 +423,7 @@ "name": "initNodeFlags" }, { - "name": "initializeTNodeInputs" + "name": "initializeInputAndOutputAliases" }, { "name": "insertBloom" diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index ccfc080b08..48769eb646 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -936,7 +936,7 @@ "name": "initNodeFlags" }, { - "name": "initializeTNodeInputs" + "name": "initializeInputAndOutputAliases" }, { "name": "injectElementRef"