From a088b8c2036aef487685938e3e8747deb264b3c3 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Tue, 27 Nov 2018 12:05:26 -0800 Subject: [PATCH] feat(ivy): introduce "allocHostVars" instruction as a replacement for "hostVars" field (FW-692) (#27299) PR Close #27299 --- .../r3_view_compiler_binding_spec.ts | 16 +- .../r3_view_compiler_styling_spec.ts | 4 + .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 1 + .../compiler/src/render3/r3_identifiers.ts | 2 + .../compiler/src/render3/view/compiler.ts | 34 ++-- .../core/src/core_render3_private_export.ts | 1 + packages/core/src/render3/component.ts | 14 +- packages/core/src/render3/definition.ts | 17 -- .../features/inherit_definition_feature.ts | 1 - packages/core/src/render3/index.ts | 1 + packages/core/src/render3/instructions.ts | 82 +++++--- .../core/src/render3/interfaces/definition.ts | 8 - packages/core/src/render3/interfaces/view.ts | 2 +- packages/core/src/render3/jit/environment.ts | 1 + packages/core/src/render3/state.ts | 12 ++ .../bundle.golden_symbols.json | 15 +- .../hello_world/bundle.golden_symbols.json | 12 +- .../hello_world_r2/bundle.golden_symbols.json | 12 +- .../bundling/todo/bundle.golden_symbols.json | 15 +- .../todo_r2/bundle.golden_symbols.json | 15 +- .../Inherit_definition_feature_spec.ts | 10 +- packages/core/test/render3/di_spec.ts | 6 +- .../core/test/render3/host_binding_spec.ts | 176 ++++++++++++++++-- .../core/test/render3/integration_spec.ts | 10 +- .../test/render3/view_container_ref_spec.ts | 6 +- 25 files changed, 329 insertions(+), 144 deletions(-) diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts index f40fbbd7e1..450a95e8c1 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts @@ -146,11 +146,13 @@ describe('compiler compliance: bindings', () => { selectors: [["", "hostBindingDir", ""]], factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); }, hostBindings: function HostBindingDir_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵallocHostVars(1); + } if (rf & 2) { $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind(ctx.dirId)); } - }, - hostVars: 1 + } }); `; @@ -191,11 +193,13 @@ describe('compiler compliance: bindings', () => { selectors: [["host-binding-comp"]], factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); }, hostBindings: function HostBindingComp_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵallocHostVars(3); + } if (rf & 2) { $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, ctx.id))); } }, - hostVars: 3, consts: 0, vars: 0, template: function HostBindingComp_Template(rf, ctx) {}, @@ -237,11 +241,13 @@ describe('compiler compliance: bindings', () => { selectors: [["", "hostAttributeDir", ""]], factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); }, hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵallocHostVars(1); + } if (rf & 2) { $r3$.ɵelementAttribute(elIndex, "required", $r3$.ɵbind(ctx.required)); } - }, - hostVars: 1 + } }); `; diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts index d6b0342c4a..d6c11c46bb 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts @@ -773,6 +773,7 @@ describe('compiler compliance: styling', () => { … hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { if (rf & 1) { + $r3$.ɵallocHostVars(4); $r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx); } if (rf & 2) { @@ -831,6 +832,7 @@ describe('compiler compliance: styling', () => { … hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { if (rf & 1) { + $r3$.ɵallocHostVars(6); $r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx); } if (rf & 2) { @@ -896,6 +898,7 @@ describe('compiler compliance: styling', () => { … function WidthDirective_HostBindings(rf, ctx, elIndex) { if (rf & 1) { + $r3$.ɵallocHostVars(2); $r3$.ɵelementStyling(_c0, _c1, null, ctx); } if (rf & 2) { @@ -907,6 +910,7 @@ describe('compiler compliance: styling', () => { … function HeightDirective_HostBindings(rf, ctx, elIndex) { if (rf & 1) { + $r3$.ɵallocHostVars(2); $r3$.ɵelementStyling(_c2, _c3, null, ctx); } if (rf & 2) { diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index f6b9ebe491..20491ba23f 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -512,6 +512,7 @@ describe('ngtsc behavioral tests', () => { const hostBindingsFn = ` hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) { if (rf & 1) { + i0.ɵallocHostVars(3); i0.ɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); }); i0.ɵlistener("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); }); i0.ɵelementStyling(_c0, null, null, ctx); diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 7283d9e01b..79a29effdf 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -66,6 +66,8 @@ export class Identifiers { static disableBindings: o.ExternalReference = {name: 'ɵdisableBindings', moduleName: CORE}; + static allocHostVars: o.ExternalReference = {name: 'ɵallocHostVars', moduleName: CORE}; + static getCurrentView: o.ExternalReference = {name: 'ɵgetCurrentView', moduleName: CORE}; static restoreView: o.ExternalReference = {name: 'ɵrestoreView', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 60f17b111f..011049245c 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -62,8 +62,8 @@ function baseDirectiveFields( definitionMap.set('contentQueriesRefresh', createContentQueriesRefreshFunction(meta)); - // Initialize hostVars to number of bound host properties (interpolations illegal) - let hostVars = Object.keys(meta.host.properties).length; + // Initialize hostVarsCount to number of bound host properties (interpolations illegal) + const hostVarsCount = Object.keys(meta.host.properties).length; const elVarExp = o.variable('elIndex'); const contextVarExp = o.variable(CONTEXT_NAME); @@ -94,18 +94,9 @@ function baseDirectiveFields( // e.g. `hostBindings: (rf, ctx, elIndex) => { ... } definitionMap.set( - 'hostBindings', createHostBindingsFunction( - meta, elVarExp, contextVarExp, styleBuilder, bindingParser, constantPool, - (slots: number) => { - const originalSlots = hostVars; - hostVars += slots; - return originalSlots; - })); - - if (hostVars) { - // e.g. `hostVars: 2 - definitionMap.set('hostVars', o.literal(hostVars)); - } + 'hostBindings', + createHostBindingsFunction( + meta, elVarExp, contextVarExp, styleBuilder, bindingParser, constantPool, hostVarsCount)); // e.g 'inputs: {a: 'a'}` definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs)); @@ -645,12 +636,12 @@ function createViewQueriesFunction( function createHostBindingsFunction( meta: R3DirectiveMetadata, elVarExp: o.ReadVarExpr, bindingContext: o.ReadVarExpr, styleBuilder: StylingBuilder, bindingParser: BindingParser, constantPool: ConstantPool, - allocatePureFunctionSlots: (slots: number) => number): o.Expression|null { + hostVarsCount: number): o.Expression|null { const createStatements: o.Statement[] = []; const updateStatements: o.Statement[] = []; + let totalHostVarsCount = hostVarsCount; const hostBindingSourceSpan = meta.typeSourceSpan; - const directiveSummary = metadataAsSummary(meta); // Calculate host event bindings @@ -670,9 +661,13 @@ function createHostBindingsFunction( }; if (bindings) { + const hostVarsCountFn = (numSlots: number): number => { + totalHostVarsCount += numSlots; + return hostVarsCount; + }; const valueConverter = new ValueConverter( constantPool, - /* new nodes are illegal here */ () => error('Unexpected node'), allocatePureFunctionSlots, + /* new nodes are illegal here */ () => error('Unexpected node'), hostVarsCountFn, /* pipes are illegal here */ () => error('Unexpected pipe')); for (const binding of bindings) { @@ -716,6 +711,11 @@ function createHostBindingsFunction( } } + if (totalHostVarsCount) { + createStatements.unshift( + o.importExpr(R3.allocHostVars).callFn([o.literal(totalHostVarsCount)]).toStmt()); + } + if (createStatements.length > 0 || updateStatements.length > 0) { const hostBindingsFnName = meta.name ? `${meta.name}_HostBindings` : null; const statements: o.Statement[] = []; diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index cec987d2e8..283a613159 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -85,6 +85,7 @@ export { reference as ɵreference, enableBindings as ɵenableBindings, disableBindings as ɵdisableBindings, + allocHostVars as ɵallocHostVars, elementAttribute as ɵelementAttribute, elementContainerStart as ɵelementContainerStart, elementContainerEnd as ɵelementContainerEnd, diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index e16217757b..a36af73f38 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -17,13 +17,13 @@ import {getComponentDef} from './definition'; import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di'; import {publishDefaultGlobalUtils} from './global_utils'; import {queueInitHooks, queueLifecycleHooks} from './hooks'; -import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, prefillHostVars, queueComponentIndexForCheck, refreshDescendantViews} from './instructions'; -import {ComponentDef, ComponentType} from './interfaces/definition'; +import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions'; +import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition'; import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node'; import {PlayerHandler} from './interfaces/player'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view'; -import {enterView, leaveView, resetComponentState} from './state'; +import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state'; import {defaultScheduler, getRootView, readPatchedLView, stringify} from './util'; @@ -195,7 +195,13 @@ export function createRootComponent( hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef)); - if (tView.firstTemplatePass) prefillHostVars(tView, rootView, componentDef.hostVars); + if (tView.firstTemplatePass && componentDef.hostBindings) { + const rootTNode = getPreviousOrParentTNode(); + setCurrentDirectiveDef(componentDef); + componentDef.hostBindings(RenderFlags.Create, component, rootTNode.index); + setCurrentDirectiveDef(null); + } + return component; } diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index fb2f9c26b7..a98a8c7f82 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -73,14 +73,6 @@ export function defineComponent(componentDefinition: { */ vars: number; - /** - * The number of host bindings (including pure fn bindings) in this component. - * - * Used to calculate the length of the LView array for the *parent* component - * of this component. - */ - hostVars?: number; - /** * Static attributes to set on host element. * @@ -262,7 +254,6 @@ export function defineComponent(componentDefinition: { providersResolver: null, consts: componentDefinition.consts, vars: componentDefinition.vars, - hostVars: componentDefinition.hostVars || 0, factory: componentDefinition.factory, template: componentDefinition.template || null !, hostBindings: componentDefinition.hostBindings || null, @@ -586,14 +577,6 @@ export const defineDirective = defineComponent as any as(directiveDefinition: */ features?: DirectiveDefFeature[]; - /** - * The number of host bindings (including pure fn bindings) in this directive. - * - * Used to calculate the length of the LView array for the *parent* component - * of this directive. - */ - hostVars?: number; - /** * Function executed by the parent template to allow child directive to apply host bindings. */ diff --git a/packages/core/src/render3/features/inherit_definition_feature.ts b/packages/core/src/render3/features/inherit_definition_feature.ts index 1b53cb641e..33e76644b2 100644 --- a/packages/core/src/render3/features/inherit_definition_feature.ts +++ b/packages/core/src/render3/features/inherit_definition_feature.ts @@ -76,7 +76,6 @@ export function InheritDefinitionFeature(definition: DirectiveDef| Componen superHostBindings(rf, ctx, elementIndex); prevHostBindings(rf, ctx, elementIndex); }; - (definition as any).hostVars += superDef.hostVars; } else { definition.hostBindings = superHostBindings; } diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 6d46469d97..daa8221a91 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -21,6 +21,7 @@ export {CssSelectorList} from './interfaces/projection'; // clang-format off export { + allocHostVars, bind, interpolation1, interpolation2, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 50c3c090e8..0d85beaf4d 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -16,7 +16,6 @@ import {Sanitizer} from '../sanitization/security'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; import {Type} from '../type'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect'; -import {noop} from '../util/noop'; import {assertDataInRange, assertDefined, assertEqual, assertHasParent, assertLessThan, assertNotEqual, assertPreviousIsParent} from './assert'; import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from './bindings'; @@ -38,7 +37,7 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLA import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; -import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCreationMode, getElementDepthCount, getFirstTemplatePass, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode} from './state'; +import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCreationMode, getCurrentDirectiveDef, getElementDepthCount, getFirstTemplatePass, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode} from './state'; import {createStylingContextTemplate, renderStyleAndClassBindings, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings'; import {BoundPlayerFactory} from './styling/player_factory'; import {getStylingContext} from './styling/util'; @@ -46,7 +45,6 @@ import {NO_CHANGE} from './tokens'; import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, loadInternal, readElementValue, readPatchedLView, stringify} from './util'; - /** * A permanent marker promise which signifies that the current CD tree is * clean. @@ -125,10 +123,12 @@ export function setHostBindings(tView: TView, viewData: LView): void { setBindingRoot(bindingRootIndex); } else { // If it's not a number, it's a host binding function that needs to be executed. - viewData[BINDING_INDEX] = bindingRootIndex; - instruction( - RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]), - currentElementIndex); + if (instruction !== null) { + viewData[BINDING_INDEX] = bindingRootIndex; + instruction( + RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]), + currentElementIndex); + } currentDirectiveIndex++; } } @@ -613,6 +613,7 @@ function createDirectivesAndLocals( previousOrParentTNode, localRefs || null); } instantiateAllDirectives(tView, viewData, previousOrParentTNode); + invokeDirectivesHostBindings(tView, viewData, previousOrParentTNode); saveResolvedLocalsInData(viewData, previousOrParentTNode, localRefExtractor); } @@ -1415,7 +1416,6 @@ function resolveDirectives( // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in tsickle. ngDevMode && assertEqual(getFirstTemplatePass(), true, 'should run on first template pass only'); const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null; - let totalHostVars = 0; if (directives) { initNodeFlags(tNode, tView.data.length, directives.length); // When the same token is provided by several directives on the same node, some rules apply in @@ -1435,7 +1435,6 @@ function resolveDirectives( const directiveDefIdx = tView.data.length; baseResolveDirective(tView, viewData, def, def.factory); - totalHostVars += def.hostVars; saveNameToExportMap(tView.data !.length - 1, def, exportsMap); // Init hooks are queued now so ngOnInit is called in host components before @@ -1444,7 +1443,6 @@ function resolveDirectives( } } if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap); - prefillHostVars(tView, viewData, totalHostVars); } /** @@ -1468,6 +1466,31 @@ function instantiateAllDirectives(tView: TView, viewData: LView, previousOrParen } } +function invokeDirectivesHostBindings(tView: TView, viewData: LView, previousOrParentTNode: TNode) { + const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift; + const end = start + (previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask); + const expando = tView.expandoInstructions !; + const firstTemplatePass = getFirstTemplatePass(); + for (let i = start; i < end; i++) { + const def = tView.data[i] as DirectiveDef; + const directive = viewData[i]; + if (def.hostBindings) { + const previousExpandoLength = expando.length; + setCurrentDirectiveDef(def); + def.hostBindings !(RenderFlags.Create, directive, previousOrParentTNode.index); + setCurrentDirectiveDef(null); + // `hostBindings` function may or may not contain `allocHostVars` call + // (e.g. it may not if it only contains host listeners), so we need to check whether + // `expandoInstructions` has changed and if not - we push `null` to keep indices in sync + if (previousExpandoLength === expando.length && firstTemplatePass) { + expando.push(null); + } + } else if (firstTemplatePass) { + expando.push(null); + } + } +} + /** * Generates a new block in TView.expandoInstructions for this node. * @@ -1492,7 +1515,9 @@ export function generateExpandoInstructionBlock( * after directives are matched (so all directives are saved, then bindings). * Because we are updating the blueprint, we only need to do this once. */ -export function prefillHostVars(tView: TView, lView: LView, totalHostVars: number): void { +function prefillHostVars(tView: TView, lView: LView, totalHostVars: number): void { + ngDevMode && + assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.'); for (let i = 0; i < totalHostVars; i++) { lView.push(NO_CHANGE); tView.blueprint.push(NO_CHANGE); @@ -1534,10 +1559,6 @@ function postProcessBaseDirective( 'directives should be created before any bindings'); ngDevMode && assertPreviousIsParent(getIsParent()); - if (def.hostBindings) { - def.hostBindings(RenderFlags.Create, directive, previousOrParentTNode.index); - } - attachPatchData(directive, lView); if (native) { attachPatchData(native, lView); @@ -1594,13 +1615,21 @@ export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void (tView.components || (tView.components = [])).push(previousOrParentTNode.index); } -/** Stores index of directive and host element so it will be queued for binding refresh during CD. +/** + * Stores host binding fn and number of host vars so it will be queued for binding refresh during + * CD. */ -function queueHostBindingForCheck(tView: TView, def: DirectiveDef| ComponentDef): void { +function queueHostBindingForCheck( + tView: TView, def: DirectiveDef| ComponentDef, hostVars: number): void { ngDevMode && assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.'); - tView.expandoInstructions !.push(def.hostBindings || noop); - if (def.hostVars) tView.expandoInstructions !.push(def.hostVars); + const expando = tView.expandoInstructions !; + // check whether a given `hostBindings` function already exists in expandoInstructions, + // which can happen in case directive definition was extended from base definition (as a part of + // the `InheritDefinitionFeature` logic) + if (expando.length < 2 || expando[expando.length - 2] !== def.hostBindings) { + expando.push(def.hostBindings !, hostVars); + } } /** Caches local names and their matching directive indices for query and template lookups. */ @@ -1661,8 +1690,6 @@ function baseResolveDirective( const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null); tView.blueprint.push(nodeInjectorFactory); viewData.push(nodeInjectorFactory); - - queueHostBindingForCheck(tView, def); } function addComponentLogic( @@ -2495,6 +2522,19 @@ export function bind(value: T): T|NO_CHANGE { return bindingUpdated(lView, lView[BINDING_INDEX]++, value) ? value : NO_CHANGE; } +/** + * Allocates the necessary amount of slots for host vars. + * + * @param count Amount of vars to be allocated + */ +export function allocHostVars(count: number): void { + if (!getFirstTemplatePass()) return; + const lView = getLView(); + const tView = lView[TVIEW]; + queueHostBindingForCheck(tView, getCurrentDirectiveDef() !, count); + prefillHostVars(tView, lView, count); +} + /** * Create interpolation bindings with a variable number of expressions. * diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 0bb01335cd..356ad5d5bb 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -136,14 +136,6 @@ export interface DirectiveDef extends BaseDef { /** Refreshes content queries associated with directives in a given view */ contentQueriesRefresh: ((directiveIndex: number, queryIndex: number) => void)|null; - /** - * The number of host bindings (including pure fn bindings) in this directive/component. - * - * Used to calculate the length of the LView array for the *parent* component - * of this directive/component. - */ - readonly hostVars: number; - /** Refreshes host bindings on the associated directive. */ hostBindings: HostBindingsFunction|null; diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 73cda62238..915f474c63 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -346,7 +346,7 @@ export interface TView { * * See VIEW_DATA.md for more information. */ - expandoInstructions: (number|HostBindingsFunction)[]|null; + expandoInstructions: (number|HostBindingsFunction|null)[]|null; /** * Full registry of directives and components that may be found in this view. diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 700e51362a..a40d6fa0fc 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -46,6 +46,7 @@ export const angularCoreEnv: {[name: string]: Function} = { 'ɵnamespaceSVG': r3.namespaceSVG, 'ɵenableBindings': r3.enableBindings, 'ɵdisableBindings': r3.disableBindings, + 'ɵallocHostVars': r3.allocHostVars, 'ɵelementStart': r3.elementStart, 'ɵelementEnd': r3.elementEnd, 'ɵelement': r3.element, diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 5d18c1fbe9..76b5ab90e3 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -8,6 +8,7 @@ import {assertDefined} from './assert'; import {executeHooks} from './hooks'; +import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {TElementNode, TNode, TNodeFlags, TViewNode} from './interfaces/node'; import {LQueries} from './interfaces/query'; import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, HOST_NODE, LView, LViewFlags, OpaqueViewState, QUERIES, TVIEW} from './interfaces/view'; @@ -34,6 +35,17 @@ export function decreaseElementDepthCount() { elementDepthCount--; } +let currentDirectiveDef: DirectiveDef|ComponentDef|null = null; + +export function getCurrentDirectiveDef(): DirectiveDef|ComponentDef|null { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return currentDirectiveDef; +} + +export function setCurrentDirectiveDef(def: DirectiveDef| ComponentDef| null): void { + currentDirectiveDef = def; +} + /** * Stores whether directives should be matched to elements. * diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index 244c15b100..736978ae73 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -842,6 +842,9 @@ { "name": "invertObject" }, + { + "name": "invokeDirectivesHostBindings" + }, { "name": "isClassBased" }, @@ -968,9 +971,6 @@ { "name": "noSideEffects" }, - { - "name": "noop" - }, { "name": "onChangesWrapper" }, @@ -983,9 +983,6 @@ { "name": "postProcessDirective" }, - { - "name": "prefillHostVars" - }, { "name": "prepareInitialFlag" }, @@ -1001,9 +998,6 @@ { "name": "queueDestroyHooks" }, - { - "name": "queueHostBindingForCheck" - }, { "name": "queueInitHooks" }, @@ -1091,6 +1085,9 @@ { "name": "setContextPlayersDirty" }, + { + "name": "setCurrentDirectiveDef" + }, { "name": "setDirty" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 18636da26c..0488fe037e 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -377,24 +377,15 @@ { "name": "noSideEffects" }, - { - "name": "noop" - }, { "name": "onChangesWrapper" }, { "name": "postProcessBaseDirective" }, - { - "name": "prefillHostVars" - }, { "name": "queueComponentIndexForCheck" }, - { - "name": "queueHostBindingForCheck" - }, { "name": "readElementValue" }, @@ -431,6 +422,9 @@ { "name": "setBindingRoot" }, + { + "name": "setCurrentDirectiveDef" + }, { "name": "setFirstTemplatePass" }, diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index d1ab00e9e8..36d2b1d197 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -1142,9 +1142,6 @@ { "name": "noSideEffects" }, - { - "name": "noop" - }, { "name": "noop$1" }, @@ -1184,9 +1181,6 @@ { "name": "postProcessBaseDirective" }, - { - "name": "prefillHostVars" - }, { "name": "projectionNodeStack" }, @@ -1208,9 +1202,6 @@ { "name": "queueDestroyHooks" }, - { - "name": "queueHostBindingForCheck" - }, { "name": "queueInitHooks" }, @@ -1292,6 +1283,9 @@ { "name": "setCheckNoChangesMode" }, + { + "name": "setCurrentDirectiveDef" + }, { "name": "setCurrentInjector" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 47e10c533a..cb17bbffcb 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -872,6 +872,9 @@ { "name": "invertObject" }, + { + "name": "invokeDirectivesHostBindings" + }, { "name": "isComponent" }, @@ -986,9 +989,6 @@ { "name": "noSideEffects" }, - { - "name": "noop" - }, { "name": "onChangesWrapper" }, @@ -1001,9 +1001,6 @@ { "name": "postProcessDirective" }, - { - "name": "prefillHostVars" - }, { "name": "prepareInitialFlag" }, @@ -1019,9 +1016,6 @@ { "name": "queueDestroyHooks" }, - { - "name": "queueHostBindingForCheck" - }, { "name": "queueInitHooks" }, @@ -1112,6 +1106,9 @@ { "name": "setContextPlayersDirty" }, + { + "name": "setCurrentDirectiveDef" + }, { "name": "setDirty" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 5caee32e17..98d0676673 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -2105,6 +2105,9 @@ { "name": "invertObject" }, + { + "name": "invokeDirectivesHostBindings" + }, { "name": "isArray" }, @@ -2333,9 +2336,6 @@ { "name": "noSideEffects" }, - { - "name": "noop" - }, { "name": "noop$1" }, @@ -2399,9 +2399,6 @@ { "name": "postProcessDirective" }, - { - "name": "prefillHostVars" - }, { "name": "prepareInitialFlag" }, @@ -2426,9 +2423,6 @@ { "name": "queueDestroyHooks" }, - { - "name": "queueHostBindingForCheck" - }, { "name": "queueInitHooks" }, @@ -2546,6 +2540,9 @@ { "name": "setContextPlayersDirty" }, + { + "name": "setCurrentDirectiveDef" + }, { "name": "setCurrentInjector" }, diff --git a/packages/core/test/render3/Inherit_definition_feature_spec.ts b/packages/core/test/render3/Inherit_definition_feature_spec.ts index fc89cf7884..bdc2a8d881 100644 --- a/packages/core/test/render3/Inherit_definition_feature_spec.ts +++ b/packages/core/test/render3/Inherit_definition_feature_spec.ts @@ -7,7 +7,7 @@ */ import {Inject, InjectionToken} from '../../src/core'; -import {ComponentDef, DirectiveDef, InheritDefinitionFeature, NgOnChangesFeature, ProvidersFeature, RenderFlags, bind, defineBase, defineComponent, defineDirective, directiveInject, element, elementProperty, load} from '../../src/render3/index'; +import {ComponentDef, DirectiveDef, InheritDefinitionFeature, NgOnChangesFeature, ProvidersFeature, RenderFlags, allocHostVars, bind, defineBase, defineComponent, defineDirective, directiveInject, element, elementProperty, load} from '../../src/render3/index'; import {ComponentFixture, createComponent} from './render_util'; @@ -309,11 +309,13 @@ describe('InheritDefinitionFeature', () => { type: SuperDirective, selectors: [['', 'superDir', '']], hostBindings: (rf: RenderFlags, ctx: SuperDirective, elementIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elementIndex, 'id', bind(ctx.id)); } }, - hostVars: 1, factory: () => new SuperDirective(), }); } @@ -325,11 +327,13 @@ describe('InheritDefinitionFeature', () => { type: SubDirective, selectors: [['', 'subDir', '']], hostBindings: (rf: RenderFlags, ctx: SubDirective, elementIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elementIndex, 'title', bind(ctx.title)); } }, - hostVars: 1, factory: () => subDir = new SubDirective(), features: [InheritDefinitionFeature] }); diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 51129f7015..8d7c83d80b 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -13,7 +13,7 @@ import {defineComponent} from '../../src/render3/definition'; import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di'; import {ProvidersFeature, defineDirective, elementProperty, load, templateRefExtractor} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLView, createTView, directiveInject, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, injectAttribute, interpolation2, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions'; +import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLView, createTView, directiveInject, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, injectAttribute, interpolation2, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions'; import {isProceduralRenderer, RElement} from '../../src/render3/interfaces/renderer'; import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; import {getNativeByIndex} from '../../src/render3/util'; @@ -654,8 +654,10 @@ describe('di', () => { type: HostBindingDir, selectors: [['', 'hostBindingDir', '']], factory: () => hostBindingDir = new HostBindingDir(), - hostVars: 1, hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elementIndex, 'id', bind(ctx.id)); } diff --git a/packages/core/test/render3/host_binding_spec.ts b/packages/core/test/render3/host_binding_spec.ts index 92ef1f1079..845b3ffa6d 100644 --- a/packages/core/test/render3/host_binding_spec.ts +++ b/packages/core/test/render3/host_binding_spec.ts @@ -9,7 +9,7 @@ import {ElementRef, EventEmitter} from '@angular/core'; import {AttributeMarker, defineComponent, template, defineDirective, ProvidersFeature, NgOnChangesFeature, QueryList} from '../../src/render3/index'; -import {bind, directiveInject, element, elementEnd, elementProperty, elementStart, load, text, textBinding, loadQueryList, registerContentQuery} from '../../src/render3/instructions'; +import {allocHostVars, bind, directiveInject, element, elementEnd, elementProperty, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery} from '../../src/render3/instructions'; import {query, queryRefresh} from '../../src/render3/query'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {pureFunction1, pureFunction2} from '../../src/render3/pure_function'; @@ -49,8 +49,10 @@ describe('host bindings', () => { type: HostBindingDir, selectors: [['', 'hostBindingDir', '']], factory: () => hostBindingDir = new HostBindingDir(), - hostVars: 1, hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elementIndex, 'id', bind(ctx.id)); } @@ -68,8 +70,10 @@ describe('host bindings', () => { factory: () => new HostBindingComp(), consts: 0, vars: 0, - hostVars: 1, hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'id', bind(ctx.id)); } @@ -89,8 +93,10 @@ describe('host bindings', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => directiveInstance = new Directive, - hostVars: 1, hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elementIndex, 'className', bind(ctx.klass)); } @@ -139,8 +145,10 @@ describe('host bindings', () => { () => new CompWithProviders(directiveInject(ServiceOne), directiveInject(ServiceTwo)), consts: 0, vars: 0, - hostVars: 1, hostBindings: (rf: RenderFlags, ctx: CompWithProviders, elIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'id', bind(ctx.id)); } @@ -173,8 +181,10 @@ describe('host bindings', () => { factory: () => new HostTitleComp(), consts: 0, vars: 0, - hostVars: 1, hostBindings: (rf: RenderFlags, ctx: HostTitleComp, elIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'title', bind(ctx.title)); } @@ -207,6 +217,66 @@ describe('host bindings', () => { expect(hostBindingDiv.id).toEqual('bar'); }); + it('should support consecutive components with host bindings', () => { + let comps: HostBindingComp[] = []; + + class HostBindingComp { + // @HostBinding() + id = 'blue'; + + static ngComponentDef = defineComponent({ + type: HostBindingComp, + selectors: [['host-binding-comp']], + factory: () => { + const comp = new HostBindingComp(); + comps.push(comp); + return comp; + }, + consts: 0, + vars: 0, + hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'id', bind(ctx.id)); + } + }, + template: (rf: RenderFlags, ctx: HostBindingComp) => {} + }); + } + + /** + * + * + * */ + const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + element(0, 'host-binding-comp'); + element(1, 'host-binding-comp'); + } + }, 2, 0, [HostBindingComp]); + + const fixture = new ComponentFixture(App); + const hostBindingEls = + fixture.hostElement.querySelectorAll('host-binding-comp') as NodeListOf; + + expect(hostBindingEls.length).toBe(2); + + comps[0].id = 'red'; + fixture.update(); + expect(hostBindingEls[0].id).toBe('red'); + + // second element should not be affected + expect(hostBindingEls[1].id).toBe('blue'); + + comps[1].id = 'red'; + fixture.update(); + + // now second element should take updated value + expect(hostBindingEls[1].id).toBe('red'); + }); + it('should support dirs with host bindings on the same node as dirs without host bindings', () => { const SomeDir = createDirective('someDir'); @@ -253,9 +323,11 @@ describe('host bindings', () => { template: (rf: RenderFlags, ctx: InitHookComp) => {}, consts: 0, vars: 0, - hostVars: 1, features: [NgOnChangesFeature], hostBindings: (rf: RenderFlags, ctx: InitHookComp, elIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'title', bind(ctx.value)); } @@ -420,9 +492,11 @@ describe('host bindings', () => { factory: () => hostBindingComp = new HostBindingComp(), consts: 0, vars: 0, - hostVars: 8, hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { // LView: [..., id, dir, title, ctx.id, pf1, ctx.title, ctx.otherTitle, pf2] + if (rf & RenderFlags.Create) { + allocHostVars(8); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'id', bind(pureFunction1(3, ff, ctx.id))); elementProperty(elIndex, 'dir', bind(ctx.dir)); @@ -496,9 +570,11 @@ describe('host bindings', () => { factory: () => hostBindingComp = new HostBindingComp(), consts: 0, vars: 0, - hostVars: 3, hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { // LView: [..., id, ctx.id, pf1] + if (rf & RenderFlags.Create) { + allocHostVars(3); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'id', bind(pureFunction1(1, ff, ctx.id))); } @@ -525,9 +601,11 @@ describe('host bindings', () => { type: HostBindingDir, selectors: [['', 'hostDir', '']], factory: () => hostBindingDir = new HostBindingDir(), - hostVars: 3, hostBindings: (rf: RenderFlags, ctx: HostBindingDir, elIndex: number) => { - // LView [..., title, ctx.title, pf1] + // LView: [..., title, ctx.title, pf1] + if (rf & RenderFlags.Create) { + allocHostVars(3); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'title', bind(pureFunction1(1, ff1, ctx.title))); } @@ -559,6 +637,69 @@ describe('host bindings', () => { expect(hostElement.id).toBe('red,green'); }); + it('should support directives with and without allocHostVars on the same component', () => { + let events: string[] = []; + + const ff1 = (v: any) => [v, 'other title']; + + /** + * @Directive({ + * ... + * host: { + * '[title]': '[title, 'other title']' + * } + * }) + * + */ + class HostBindingDir { + title = 'my title'; + + static ngDirectiveDef = defineDirective({ + type: HostBindingDir, + selectors: [['', 'hostDir', '']], + factory: () => new HostBindingDir(), + hostBindings: (rf: RenderFlags, ctx: HostBindingDir, elIndex: number) => { + // LViewData [..., title, ctx.title, pf1] + if (rf & RenderFlags.Create) { + allocHostVars(3); + } + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'title', bind(pureFunction1(1, ff1, ctx.title))); + } + } + }); + } + + class HostListenerDir { + /* @HostListener('click') */ + onClick() { events.push('click!'); } + + static ngDirectiveDef = defineDirective({ + type: HostListenerDir, + selectors: [['', 'hostListenerDir', '']], + factory: function HostListenerDir_Factory() { return new HostListenerDir(); }, + hostBindings: function HostListenerDir_HostBindings( + rf: RenderFlags, ctx: any, elIndex: number) { + if (rf & RenderFlags.Create) { + listener('click', function() { return ctx.onClick(); }); + } + } + }); + } + + // + const fixture = new TemplateFixture(() => { + elementStart(0, 'button', ['hostListenerDir', '', 'hostDir', '']); + text(1, 'Click'); + elementEnd(); + }, () => {}, 2, 0, [HostListenerDir, HostBindingDir]); + + const button = fixture.hostElement.querySelector('button') !; + button.click(); + expect(events).toEqual(['click!']); + expect(button.title).toEqual('my title,other title'); + }); + it('should support ternary expressions in host bindings', () => { let hostBindingComp !: HostBindingComp; @@ -587,9 +728,11 @@ describe('host bindings', () => { factory: () => hostBindingComp = new HostBindingComp(), consts: 0, vars: 0, - hostVars: 6, hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { // LView: [..., id, title, ctx.id, pf1, ctx.title, pf1] + if (rf & RenderFlags.Create) { + allocHostVars(6); + } if (rf & RenderFlags.Update) { elementProperty( elIndex, 'id', bind(ctx.condition ? pureFunction1(2, ff, ctx.id) : 'green')); @@ -677,8 +820,10 @@ describe('host bindings', () => { factory: () => new HostBindingWithContentChildren(), consts: 0, vars: 0, - hostVars: 1, hostBindings: (rf: RenderFlags, ctx: HostBindingWithContentChildren, elIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'id', bind(ctx.foos.length)); } @@ -734,8 +879,10 @@ describe('host bindings', () => { factory: () => new HostBindingWithContentHooks(), consts: 0, vars: 0, - hostVars: 1, hostBindings: (rf: RenderFlags, ctx: HostBindingWithContentHooks, elIndex: number) => { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'id', bind(ctx.myValue)); } @@ -755,5 +902,4 @@ describe('host bindings', () => { const hostBindingEl = fixture.hostElement.querySelector('host-binding-comp') as HTMLElement; expect(hostBindingEl.id).toEqual('after-content'); }); - }); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 849bbb5b7c..4d1ee8b3b6 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -10,7 +10,7 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {RendererStyleFlags2, RendererType2} from '../../src/render/api'; import {AttributeMarker, defineComponent, defineDirective, templateRefExtractor} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template, elementStylingMap, directiveInject} from '../../src/render3/instructions'; +import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template, elementStylingMap, directiveInject} from '../../src/render3/instructions'; import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3, RText, RComment, RNode, RendererStyleFlags3, ProceduralRenderer3} from '../../src/render3/interfaces/renderer'; import {NO_CHANGE} from '../../src/render3/tokens'; @@ -446,8 +446,10 @@ describe('render3 integration test', () => { } }, factory: () => cmptInstance = new TodoComponentHostBinding, - hostVars: 1, hostBindings: function(rf: RenderFlags, ctx: any, elementIndex: number): void { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { // host bindings elementProperty(elementIndex, 'title', bind(ctx.title)); @@ -1379,9 +1381,11 @@ describe('render3 integration test', () => { factory: function HostBindingDir_Factory() { return hostBindingDir = new HostBindingDir(); }, - hostVars: 1, hostBindings: function HostBindingDir_HostBindings( rf: RenderFlags, ctx: any, elIndex: number) { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementAttribute(elIndex, 'aria-label', bind(ctx.label)); } diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index fa6e2de6f6..191419f791 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -10,7 +10,7 @@ import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, El import {ViewEncapsulation} from '../../src/metadata'; import {AttributeMarker, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load, query, queryRefresh} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding} from '../../src/render3/instructions'; +import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement} from '../../src/render3/interfaces/renderer'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; @@ -1836,9 +1836,11 @@ describe('ViewContainerRef', () => { consts: 0, vars: 0, template: (rf: RenderFlags, cmp: HostBindingCmpt) => {}, - hostVars: 1, attributes: ['id', 'attribute'], hostBindings: function(rf: RenderFlags, ctx: HostBindingCmpt, elIndex: number) { + if (rf & RenderFlags.Create) { + allocHostVars(1); + } if (rf & RenderFlags.Update) { elementProperty(elIndex, 'title', bind(ctx.title)); }