feat(ivy): introduce "allocHostVars" instruction as a replacement for "hostVars" field (FW-692) (#27299)

PR Close #27299
This commit is contained in:
Andrew Kushnir 2018-11-27 12:05:26 -08:00 committed by Igor Minar
parent 0df914e1e9
commit a088b8c203
25 changed files with 329 additions and 144 deletions

View File

@ -146,11 +146,13 @@ describe('compiler compliance: bindings', () => {
selectors: [["", "hostBindingDir", ""]], selectors: [["", "hostBindingDir", ""]],
factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); }, factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); },
hostBindings: function HostBindingDir_HostBindings(rf, ctx, elIndex) { hostBindings: function HostBindingDir_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵallocHostVars(1);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind(ctx.dirId)); $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind(ctx.dirId));
} }
}, }
hostVars: 1
}); });
`; `;
@ -191,11 +193,13 @@ describe('compiler compliance: bindings', () => {
selectors: [["host-binding-comp"]], selectors: [["host-binding-comp"]],
factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); }, factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); },
hostBindings: function HostBindingComp_HostBindings(rf, ctx, elIndex) { hostBindings: function HostBindingComp_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵallocHostVars(3);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, ctx.id))); $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, ctx.id)));
} }
}, },
hostVars: 3,
consts: 0, consts: 0,
vars: 0, vars: 0,
template: function HostBindingComp_Template(rf, ctx) {}, template: function HostBindingComp_Template(rf, ctx) {},
@ -237,11 +241,13 @@ describe('compiler compliance: bindings', () => {
selectors: [["", "hostAttributeDir", ""]], selectors: [["", "hostAttributeDir", ""]],
factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); }, factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); },
hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) { hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵallocHostVars(1);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementAttribute(elIndex, "required", $r3$.ɵbind(ctx.required)); $r3$.ɵelementAttribute(elIndex, "required", $r3$.ɵbind(ctx.required));
} }
}, }
hostVars: 1
}); });
`; `;

View File

@ -773,6 +773,7 @@ describe('compiler compliance: styling', () => {
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵallocHostVars(4);
$r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx); $r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx);
} }
if (rf & 2) { if (rf & 2) {
@ -831,6 +832,7 @@ describe('compiler compliance: styling', () => {
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵallocHostVars(6);
$r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx); $r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx);
} }
if (rf & 2) { if (rf & 2) {
@ -896,6 +898,7 @@ describe('compiler compliance: styling', () => {
function WidthDirective_HostBindings(rf, ctx, elIndex) { function WidthDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵallocHostVars(2);
$r3$.ɵelementStyling(_c0, _c1, null, ctx); $r3$.ɵelementStyling(_c0, _c1, null, ctx);
} }
if (rf & 2) { if (rf & 2) {
@ -907,6 +910,7 @@ describe('compiler compliance: styling', () => {
function HeightDirective_HostBindings(rf, ctx, elIndex) { function HeightDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵallocHostVars(2);
$r3$.ɵelementStyling(_c2, _c3, null, ctx); $r3$.ɵelementStyling(_c2, _c3, null, ctx);
} }
if (rf & 2) { if (rf & 2) {

View File

@ -512,6 +512,7 @@ describe('ngtsc behavioral tests', () => {
const hostBindingsFn = ` const hostBindingsFn = `
hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) { hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
i0.ɵallocHostVars(3);
i0.ɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); }); 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.ɵlistener("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); });
i0.ɵelementStyling(_c0, null, null, ctx); i0.ɵelementStyling(_c0, null, null, ctx);

View File

@ -66,6 +66,8 @@ export class Identifiers {
static disableBindings: o.ExternalReference = {name: 'ɵdisableBindings', moduleName: CORE}; 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 getCurrentView: o.ExternalReference = {name: 'ɵgetCurrentView', moduleName: CORE};
static restoreView: o.ExternalReference = {name: 'ɵrestoreView', moduleName: CORE}; static restoreView: o.ExternalReference = {name: 'ɵrestoreView', moduleName: CORE};

View File

@ -62,8 +62,8 @@ function baseDirectiveFields(
definitionMap.set('contentQueriesRefresh', createContentQueriesRefreshFunction(meta)); definitionMap.set('contentQueriesRefresh', createContentQueriesRefreshFunction(meta));
// Initialize hostVars to number of bound host properties (interpolations illegal) // Initialize hostVarsCount to number of bound host properties (interpolations illegal)
let hostVars = Object.keys(meta.host.properties).length; const hostVarsCount = Object.keys(meta.host.properties).length;
const elVarExp = o.variable('elIndex'); const elVarExp = o.variable('elIndex');
const contextVarExp = o.variable(CONTEXT_NAME); const contextVarExp = o.variable(CONTEXT_NAME);
@ -94,18 +94,9 @@ function baseDirectiveFields(
// e.g. `hostBindings: (rf, ctx, elIndex) => { ... } // e.g. `hostBindings: (rf, ctx, elIndex) => { ... }
definitionMap.set( definitionMap.set(
'hostBindings', createHostBindingsFunction( 'hostBindings',
meta, elVarExp, contextVarExp, styleBuilder, bindingParser, constantPool, createHostBindingsFunction(
(slots: number) => { meta, elVarExp, contextVarExp, styleBuilder, bindingParser, constantPool, hostVarsCount));
const originalSlots = hostVars;
hostVars += slots;
return originalSlots;
}));
if (hostVars) {
// e.g. `hostVars: 2
definitionMap.set('hostVars', o.literal(hostVars));
}
// e.g 'inputs: {a: 'a'}` // e.g 'inputs: {a: 'a'}`
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs)); definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs));
@ -645,12 +636,12 @@ function createViewQueriesFunction(
function createHostBindingsFunction( function createHostBindingsFunction(
meta: R3DirectiveMetadata, elVarExp: o.ReadVarExpr, bindingContext: o.ReadVarExpr, meta: R3DirectiveMetadata, elVarExp: o.ReadVarExpr, bindingContext: o.ReadVarExpr,
styleBuilder: StylingBuilder, bindingParser: BindingParser, constantPool: ConstantPool, styleBuilder: StylingBuilder, bindingParser: BindingParser, constantPool: ConstantPool,
allocatePureFunctionSlots: (slots: number) => number): o.Expression|null { hostVarsCount: number): o.Expression|null {
const createStatements: o.Statement[] = []; const createStatements: o.Statement[] = [];
const updateStatements: o.Statement[] = []; const updateStatements: o.Statement[] = [];
let totalHostVarsCount = hostVarsCount;
const hostBindingSourceSpan = meta.typeSourceSpan; const hostBindingSourceSpan = meta.typeSourceSpan;
const directiveSummary = metadataAsSummary(meta); const directiveSummary = metadataAsSummary(meta);
// Calculate host event bindings // Calculate host event bindings
@ -670,9 +661,13 @@ function createHostBindingsFunction(
}; };
if (bindings) { if (bindings) {
const hostVarsCountFn = (numSlots: number): number => {
totalHostVarsCount += numSlots;
return hostVarsCount;
};
const valueConverter = new ValueConverter( const valueConverter = new ValueConverter(
constantPool, 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')); /* pipes are illegal here */ () => error('Unexpected pipe'));
for (const binding of bindings) { 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) { if (createStatements.length > 0 || updateStatements.length > 0) {
const hostBindingsFnName = meta.name ? `${meta.name}_HostBindings` : null; const hostBindingsFnName = meta.name ? `${meta.name}_HostBindings` : null;
const statements: o.Statement[] = []; const statements: o.Statement[] = [];

View File

@ -85,6 +85,7 @@ export {
reference as ɵreference, reference as ɵreference,
enableBindings as ɵenableBindings, enableBindings as ɵenableBindings,
disableBindings as ɵdisableBindings, disableBindings as ɵdisableBindings,
allocHostVars as ɵallocHostVars,
elementAttribute as ɵelementAttribute, elementAttribute as ɵelementAttribute,
elementContainerStart as ɵelementContainerStart, elementContainerStart as ɵelementContainerStart,
elementContainerEnd as ɵelementContainerEnd, elementContainerEnd as ɵelementContainerEnd,

View File

@ -17,13 +17,13 @@ import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di'; import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {publishDefaultGlobalUtils} from './global_utils'; import {publishDefaultGlobalUtils} from './global_utils';
import {queueInitHooks, queueLifecycleHooks} from './hooks'; import {queueInitHooks, queueLifecycleHooks} from './hooks';
import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, prefillHostVars, queueComponentIndexForCheck, refreshDescendantViews} from './instructions'; import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
import {ComponentDef, ComponentType} from './interfaces/definition'; import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node'; import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player'; import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; 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 {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'; import {defaultScheduler, getRootView, readPatchedLView, stringify} from './util';
@ -195,7 +195,13 @@ export function createRootComponent<T>(
hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef)); 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; return component;
} }

View File

@ -73,14 +73,6 @@ export function defineComponent<T>(componentDefinition: {
*/ */
vars: number; 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. * Static attributes to set on host element.
* *
@ -262,7 +254,6 @@ export function defineComponent<T>(componentDefinition: {
providersResolver: null, providersResolver: null,
consts: componentDefinition.consts, consts: componentDefinition.consts,
vars: componentDefinition.vars, vars: componentDefinition.vars,
hostVars: componentDefinition.hostVars || 0,
factory: componentDefinition.factory, factory: componentDefinition.factory,
template: componentDefinition.template || null !, template: componentDefinition.template || null !,
hostBindings: componentDefinition.hostBindings || null, hostBindings: componentDefinition.hostBindings || null,
@ -586,14 +577,6 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
*/ */
features?: DirectiveDefFeature[]; 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. * Function executed by the parent template to allow child directive to apply host bindings.
*/ */

View File

@ -76,7 +76,6 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
superHostBindings(rf, ctx, elementIndex); superHostBindings(rf, ctx, elementIndex);
prevHostBindings(rf, ctx, elementIndex); prevHostBindings(rf, ctx, elementIndex);
}; };
(definition as any).hostVars += superDef.hostVars;
} else { } else {
definition.hostBindings = superHostBindings; definition.hostBindings = superHostBindings;
} }

View File

@ -21,6 +21,7 @@ export {CssSelectorList} from './interfaces/projection';
// clang-format off // clang-format off
export { export {
allocHostVars,
bind, bind,
interpolation1, interpolation1,
interpolation2, interpolation2,

View File

@ -16,7 +16,6 @@ import {Sanitizer} from '../sanitization/security';
import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {Type} from '../type'; import {Type} from '../type';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect';
import {noop} from '../util/noop';
import {assertDataInRange, assertDefined, assertEqual, assertHasParent, assertLessThan, assertNotEqual, assertPreviousIsParent} from './assert'; import {assertDataInRange, assertDefined, assertEqual, assertHasParent, assertLessThan, assertNotEqual, assertPreviousIsParent} from './assert';
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from './bindings'; 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 {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation'; import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; 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 {createStylingContextTemplate, renderStyleAndClassBindings, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {BoundPlayerFactory} from './styling/player_factory'; import {BoundPlayerFactory} from './styling/player_factory';
import {getStylingContext} from './styling/util'; 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'; 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 * A permanent marker promise which signifies that the current CD tree is
* clean. * clean.
@ -125,10 +123,12 @@ export function setHostBindings(tView: TView, viewData: LView): void {
setBindingRoot(bindingRootIndex); setBindingRoot(bindingRootIndex);
} else { } else {
// If it's not a number, it's a host binding function that needs to be executed. // If it's not a number, it's a host binding function that needs to be executed.
viewData[BINDING_INDEX] = bindingRootIndex; if (instruction !== null) {
instruction( viewData[BINDING_INDEX] = bindingRootIndex;
RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]), instruction(
currentElementIndex); RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]),
currentElementIndex);
}
currentDirectiveIndex++; currentDirectiveIndex++;
} }
} }
@ -613,6 +613,7 @@ function createDirectivesAndLocals(
previousOrParentTNode, localRefs || null); previousOrParentTNode, localRefs || null);
} }
instantiateAllDirectives(tView, viewData, previousOrParentTNode); instantiateAllDirectives(tView, viewData, previousOrParentTNode);
invokeDirectivesHostBindings(tView, viewData, previousOrParentTNode);
saveResolvedLocalsInData(viewData, previousOrParentTNode, localRefExtractor); 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. // 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'); ngDevMode && assertEqual(getFirstTemplatePass(), true, 'should run on first template pass only');
const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null; const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
let totalHostVars = 0;
if (directives) { if (directives) {
initNodeFlags(tNode, tView.data.length, directives.length); initNodeFlags(tNode, tView.data.length, directives.length);
// When the same token is provided by several directives on the same node, some rules apply in // 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; const directiveDefIdx = tView.data.length;
baseResolveDirective(tView, viewData, def, def.factory); baseResolveDirective(tView, viewData, def, def.factory);
totalHostVars += def.hostVars;
saveNameToExportMap(tView.data !.length - 1, def, exportsMap); saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
// Init hooks are queued now so ngOnInit is called in host components before // 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); 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<any>;
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. * 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). * after directives are matched (so all directives are saved, then bindings).
* Because we are updating the blueprint, we only need to do this once. * 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++) { for (let i = 0; i < totalHostVars; i++) {
lView.push(NO_CHANGE); lView.push(NO_CHANGE);
tView.blueprint.push(NO_CHANGE); tView.blueprint.push(NO_CHANGE);
@ -1534,10 +1559,6 @@ function postProcessBaseDirective<T>(
'directives should be created before any bindings'); 'directives should be created before any bindings');
ngDevMode && assertPreviousIsParent(getIsParent()); ngDevMode && assertPreviousIsParent(getIsParent());
if (def.hostBindings) {
def.hostBindings(RenderFlags.Create, directive, previousOrParentTNode.index);
}
attachPatchData(directive, lView); attachPatchData(directive, lView);
if (native) { if (native) {
attachPatchData(native, lView); attachPatchData(native, lView);
@ -1594,13 +1615,21 @@ export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void
(tView.components || (tView.components = [])).push(previousOrParentTNode.index); (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<any>| ComponentDef<any>): void { function queueHostBindingForCheck(
tView: TView, def: DirectiveDef<any>| ComponentDef<any>, hostVars: number): void {
ngDevMode && ngDevMode &&
assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.'); assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.');
tView.expandoInstructions !.push(def.hostBindings || noop); const expando = tView.expandoInstructions !;
if (def.hostVars) tView.expandoInstructions !.push(def.hostVars); // 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. */ /** Caches local names and their matching directive indices for query and template lookups. */
@ -1661,8 +1690,6 @@ function baseResolveDirective<T>(
const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null); const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null);
tView.blueprint.push(nodeInjectorFactory); tView.blueprint.push(nodeInjectorFactory);
viewData.push(nodeInjectorFactory); viewData.push(nodeInjectorFactory);
queueHostBindingForCheck(tView, def);
} }
function addComponentLogic<T>( function addComponentLogic<T>(
@ -2495,6 +2522,19 @@ export function bind<T>(value: T): T|NO_CHANGE {
return bindingUpdated(lView, lView[BINDING_INDEX]++, value) ? value : 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. * Create interpolation bindings with a variable number of expressions.
* *

View File

@ -136,14 +136,6 @@ export interface DirectiveDef<T> extends BaseDef<T> {
/** Refreshes content queries associated with directives in a given view */ /** Refreshes content queries associated with directives in a given view */
contentQueriesRefresh: ((directiveIndex: number, queryIndex: number) => void)|null; 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. */ /** Refreshes host bindings on the associated directive. */
hostBindings: HostBindingsFunction<T>|null; hostBindings: HostBindingsFunction<T>|null;

View File

@ -346,7 +346,7 @@ export interface TView {
* *
* See VIEW_DATA.md for more information. * See VIEW_DATA.md for more information.
*/ */
expandoInstructions: (number|HostBindingsFunction<any>)[]|null; expandoInstructions: (number|HostBindingsFunction<any>|null)[]|null;
/** /**
* Full registry of directives and components that may be found in this view. * Full registry of directives and components that may be found in this view.

View File

@ -46,6 +46,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵnamespaceSVG': r3.namespaceSVG, 'ɵnamespaceSVG': r3.namespaceSVG,
'ɵenableBindings': r3.enableBindings, 'ɵenableBindings': r3.enableBindings,
'ɵdisableBindings': r3.disableBindings, 'ɵdisableBindings': r3.disableBindings,
'ɵallocHostVars': r3.allocHostVars,
'ɵelementStart': r3.elementStart, 'ɵelementStart': r3.elementStart,
'ɵelementEnd': r3.elementEnd, 'ɵelementEnd': r3.elementEnd,
'ɵelement': r3.element, 'ɵelement': r3.element,

View File

@ -8,6 +8,7 @@
import {assertDefined} from './assert'; import {assertDefined} from './assert';
import {executeHooks} from './hooks'; import {executeHooks} from './hooks';
import {ComponentDef, DirectiveDef} from './interfaces/definition';
import {TElementNode, TNode, TNodeFlags, TViewNode} from './interfaces/node'; import {TElementNode, TNode, TNodeFlags, TViewNode} from './interfaces/node';
import {LQueries} from './interfaces/query'; import {LQueries} from './interfaces/query';
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, HOST_NODE, LView, LViewFlags, OpaqueViewState, QUERIES, TVIEW} from './interfaces/view'; 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--; elementDepthCount--;
} }
let currentDirectiveDef: DirectiveDef<any>|ComponentDef<any>|null = null;
export function getCurrentDirectiveDef(): DirectiveDef<any>|ComponentDef<any>|null {
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
return currentDirectiveDef;
}
export function setCurrentDirectiveDef(def: DirectiveDef<any>| ComponentDef<any>| null): void {
currentDirectiveDef = def;
}
/** /**
* Stores whether directives should be matched to elements. * Stores whether directives should be matched to elements.
* *

View File

@ -842,6 +842,9 @@
{ {
"name": "invertObject" "name": "invertObject"
}, },
{
"name": "invokeDirectivesHostBindings"
},
{ {
"name": "isClassBased" "name": "isClassBased"
}, },
@ -968,9 +971,6 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "noop"
},
{ {
"name": "onChangesWrapper" "name": "onChangesWrapper"
}, },
@ -983,9 +983,6 @@
{ {
"name": "postProcessDirective" "name": "postProcessDirective"
}, },
{
"name": "prefillHostVars"
},
{ {
"name": "prepareInitialFlag" "name": "prepareInitialFlag"
}, },
@ -1001,9 +998,6 @@
{ {
"name": "queueDestroyHooks" "name": "queueDestroyHooks"
}, },
{
"name": "queueHostBindingForCheck"
},
{ {
"name": "queueInitHooks" "name": "queueInitHooks"
}, },
@ -1091,6 +1085,9 @@
{ {
"name": "setContextPlayersDirty" "name": "setContextPlayersDirty"
}, },
{
"name": "setCurrentDirectiveDef"
},
{ {
"name": "setDirty" "name": "setDirty"
}, },

View File

@ -377,24 +377,15 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "noop"
},
{ {
"name": "onChangesWrapper" "name": "onChangesWrapper"
}, },
{ {
"name": "postProcessBaseDirective" "name": "postProcessBaseDirective"
}, },
{
"name": "prefillHostVars"
},
{ {
"name": "queueComponentIndexForCheck" "name": "queueComponentIndexForCheck"
}, },
{
"name": "queueHostBindingForCheck"
},
{ {
"name": "readElementValue" "name": "readElementValue"
}, },
@ -431,6 +422,9 @@
{ {
"name": "setBindingRoot" "name": "setBindingRoot"
}, },
{
"name": "setCurrentDirectiveDef"
},
{ {
"name": "setFirstTemplatePass" "name": "setFirstTemplatePass"
}, },

View File

@ -1142,9 +1142,6 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "noop"
},
{ {
"name": "noop$1" "name": "noop$1"
}, },
@ -1184,9 +1181,6 @@
{ {
"name": "postProcessBaseDirective" "name": "postProcessBaseDirective"
}, },
{
"name": "prefillHostVars"
},
{ {
"name": "projectionNodeStack" "name": "projectionNodeStack"
}, },
@ -1208,9 +1202,6 @@
{ {
"name": "queueDestroyHooks" "name": "queueDestroyHooks"
}, },
{
"name": "queueHostBindingForCheck"
},
{ {
"name": "queueInitHooks" "name": "queueInitHooks"
}, },
@ -1292,6 +1283,9 @@
{ {
"name": "setCheckNoChangesMode" "name": "setCheckNoChangesMode"
}, },
{
"name": "setCurrentDirectiveDef"
},
{ {
"name": "setCurrentInjector" "name": "setCurrentInjector"
}, },

View File

@ -872,6 +872,9 @@
{ {
"name": "invertObject" "name": "invertObject"
}, },
{
"name": "invokeDirectivesHostBindings"
},
{ {
"name": "isComponent" "name": "isComponent"
}, },
@ -986,9 +989,6 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "noop"
},
{ {
"name": "onChangesWrapper" "name": "onChangesWrapper"
}, },
@ -1001,9 +1001,6 @@
{ {
"name": "postProcessDirective" "name": "postProcessDirective"
}, },
{
"name": "prefillHostVars"
},
{ {
"name": "prepareInitialFlag" "name": "prepareInitialFlag"
}, },
@ -1019,9 +1016,6 @@
{ {
"name": "queueDestroyHooks" "name": "queueDestroyHooks"
}, },
{
"name": "queueHostBindingForCheck"
},
{ {
"name": "queueInitHooks" "name": "queueInitHooks"
}, },
@ -1112,6 +1106,9 @@
{ {
"name": "setContextPlayersDirty" "name": "setContextPlayersDirty"
}, },
{
"name": "setCurrentDirectiveDef"
},
{ {
"name": "setDirty" "name": "setDirty"
}, },

View File

@ -2105,6 +2105,9 @@
{ {
"name": "invertObject" "name": "invertObject"
}, },
{
"name": "invokeDirectivesHostBindings"
},
{ {
"name": "isArray" "name": "isArray"
}, },
@ -2333,9 +2336,6 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "noop"
},
{ {
"name": "noop$1" "name": "noop$1"
}, },
@ -2399,9 +2399,6 @@
{ {
"name": "postProcessDirective" "name": "postProcessDirective"
}, },
{
"name": "prefillHostVars"
},
{ {
"name": "prepareInitialFlag" "name": "prepareInitialFlag"
}, },
@ -2426,9 +2423,6 @@
{ {
"name": "queueDestroyHooks" "name": "queueDestroyHooks"
}, },
{
"name": "queueHostBindingForCheck"
},
{ {
"name": "queueInitHooks" "name": "queueInitHooks"
}, },
@ -2546,6 +2540,9 @@
{ {
"name": "setContextPlayersDirty" "name": "setContextPlayersDirty"
}, },
{
"name": "setCurrentDirectiveDef"
},
{ {
"name": "setCurrentInjector" "name": "setCurrentInjector"
}, },

View File

@ -7,7 +7,7 @@
*/ */
import {Inject, InjectionToken} from '../../src/core'; 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'; import {ComponentFixture, createComponent} from './render_util';
@ -309,11 +309,13 @@ describe('InheritDefinitionFeature', () => {
type: SuperDirective, type: SuperDirective,
selectors: [['', 'superDir', '']], selectors: [['', 'superDir', '']],
hostBindings: (rf: RenderFlags, ctx: SuperDirective, elementIndex: number) => { hostBindings: (rf: RenderFlags, ctx: SuperDirective, elementIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elementIndex, 'id', bind(ctx.id)); elementProperty(elementIndex, 'id', bind(ctx.id));
} }
}, },
hostVars: 1,
factory: () => new SuperDirective(), factory: () => new SuperDirective(),
}); });
} }
@ -325,11 +327,13 @@ describe('InheritDefinitionFeature', () => {
type: SubDirective, type: SubDirective,
selectors: [['', 'subDir', '']], selectors: [['', 'subDir', '']],
hostBindings: (rf: RenderFlags, ctx: SubDirective, elementIndex: number) => { hostBindings: (rf: RenderFlags, ctx: SubDirective, elementIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elementIndex, 'title', bind(ctx.title)); elementProperty(elementIndex, 'title', bind(ctx.title));
} }
}, },
hostVars: 1,
factory: () => subDir = new SubDirective(), factory: () => subDir = new SubDirective(),
features: [InheritDefinitionFeature] features: [InheritDefinitionFeature]
}); });

View File

@ -13,7 +13,7 @@ import {defineComponent} from '../../src/render3/definition';
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di'; import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
import {ProvidersFeature, defineDirective, elementProperty, load, templateRefExtractor} from '../../src/render3/index'; 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 {isProceduralRenderer, RElement} from '../../src/render3/interfaces/renderer';
import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node';
import {getNativeByIndex} from '../../src/render3/util'; import {getNativeByIndex} from '../../src/render3/util';
@ -654,8 +654,10 @@ describe('di', () => {
type: HostBindingDir, type: HostBindingDir,
selectors: [['', 'hostBindingDir', '']], selectors: [['', 'hostBindingDir', '']],
factory: () => hostBindingDir = new HostBindingDir(), factory: () => hostBindingDir = new HostBindingDir(),
hostVars: 1,
hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => { hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elementIndex, 'id', bind(ctx.id)); elementProperty(elementIndex, 'id', bind(ctx.id));
} }

View File

@ -9,7 +9,7 @@
import {ElementRef, EventEmitter} from '@angular/core'; import {ElementRef, EventEmitter} from '@angular/core';
import {AttributeMarker, defineComponent, template, defineDirective, ProvidersFeature, NgOnChangesFeature, QueryList} from '../../src/render3/index'; 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 {query, queryRefresh} from '../../src/render3/query';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {pureFunction1, pureFunction2} from '../../src/render3/pure_function'; import {pureFunction1, pureFunction2} from '../../src/render3/pure_function';
@ -49,8 +49,10 @@ describe('host bindings', () => {
type: HostBindingDir, type: HostBindingDir,
selectors: [['', 'hostBindingDir', '']], selectors: [['', 'hostBindingDir', '']],
factory: () => hostBindingDir = new HostBindingDir(), factory: () => hostBindingDir = new HostBindingDir(),
hostVars: 1,
hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => { hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elementIndex, 'id', bind(ctx.id)); elementProperty(elementIndex, 'id', bind(ctx.id));
} }
@ -68,8 +70,10 @@ describe('host bindings', () => {
factory: () => new HostBindingComp(), factory: () => new HostBindingComp(),
consts: 0, consts: 0,
vars: 0, vars: 0,
hostVars: 1,
hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'id', bind(ctx.id)); elementProperty(elIndex, 'id', bind(ctx.id));
} }
@ -89,8 +93,10 @@ describe('host bindings', () => {
type: Directive, type: Directive,
selectors: [['', 'dir', '']], selectors: [['', 'dir', '']],
factory: () => directiveInstance = new Directive, factory: () => directiveInstance = new Directive,
hostVars: 1,
hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => { hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elementIndex, 'className', bind(ctx.klass)); elementProperty(elementIndex, 'className', bind(ctx.klass));
} }
@ -139,8 +145,10 @@ describe('host bindings', () => {
() => new CompWithProviders(directiveInject(ServiceOne), directiveInject(ServiceTwo)), () => new CompWithProviders(directiveInject(ServiceOne), directiveInject(ServiceTwo)),
consts: 0, consts: 0,
vars: 0, vars: 0,
hostVars: 1,
hostBindings: (rf: RenderFlags, ctx: CompWithProviders, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: CompWithProviders, elIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'id', bind(ctx.id)); elementProperty(elIndex, 'id', bind(ctx.id));
} }
@ -173,8 +181,10 @@ describe('host bindings', () => {
factory: () => new HostTitleComp(), factory: () => new HostTitleComp(),
consts: 0, consts: 0,
vars: 0, vars: 0,
hostVars: 1,
hostBindings: (rf: RenderFlags, ctx: HostTitleComp, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: HostTitleComp, elIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'title', bind(ctx.title)); elementProperty(elIndex, 'title', bind(ctx.title));
} }
@ -207,6 +217,66 @@ describe('host bindings', () => {
expect(hostBindingDiv.id).toEqual('bar'); 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) => {}
});
}
/**
* <host-binding-comp></host-binding-comp>
* <host-binding-comp></host-binding-comp>
* */
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<HTMLElement>;
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', it('should support dirs with host bindings on the same node as dirs without host bindings',
() => { () => {
const SomeDir = createDirective('someDir'); const SomeDir = createDirective('someDir');
@ -253,9 +323,11 @@ describe('host bindings', () => {
template: (rf: RenderFlags, ctx: InitHookComp) => {}, template: (rf: RenderFlags, ctx: InitHookComp) => {},
consts: 0, consts: 0,
vars: 0, vars: 0,
hostVars: 1,
features: [NgOnChangesFeature], features: [NgOnChangesFeature],
hostBindings: (rf: RenderFlags, ctx: InitHookComp, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: InitHookComp, elIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'title', bind(ctx.value)); elementProperty(elIndex, 'title', bind(ctx.value));
} }
@ -420,9 +492,11 @@ describe('host bindings', () => {
factory: () => hostBindingComp = new HostBindingComp(), factory: () => hostBindingComp = new HostBindingComp(),
consts: 0, consts: 0,
vars: 0, vars: 0,
hostVars: 8,
hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => {
// LView: [..., id, dir, title, ctx.id, pf1, ctx.title, ctx.otherTitle, pf2] // LView: [..., id, dir, title, ctx.id, pf1, ctx.title, ctx.otherTitle, pf2]
if (rf & RenderFlags.Create) {
allocHostVars(8);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'id', bind(pureFunction1(3, ff, ctx.id))); elementProperty(elIndex, 'id', bind(pureFunction1(3, ff, ctx.id)));
elementProperty(elIndex, 'dir', bind(ctx.dir)); elementProperty(elIndex, 'dir', bind(ctx.dir));
@ -496,9 +570,11 @@ describe('host bindings', () => {
factory: () => hostBindingComp = new HostBindingComp(), factory: () => hostBindingComp = new HostBindingComp(),
consts: 0, consts: 0,
vars: 0, vars: 0,
hostVars: 3,
hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => {
// LView: [..., id, ctx.id, pf1] // LView: [..., id, ctx.id, pf1]
if (rf & RenderFlags.Create) {
allocHostVars(3);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'id', bind(pureFunction1(1, ff, ctx.id))); elementProperty(elIndex, 'id', bind(pureFunction1(1, ff, ctx.id)));
} }
@ -525,9 +601,11 @@ describe('host bindings', () => {
type: HostBindingDir, type: HostBindingDir,
selectors: [['', 'hostDir', '']], selectors: [['', 'hostDir', '']],
factory: () => hostBindingDir = new HostBindingDir(), factory: () => hostBindingDir = new HostBindingDir(),
hostVars: 3,
hostBindings: (rf: RenderFlags, ctx: HostBindingDir, elIndex: number) => { 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) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'title', bind(pureFunction1(1, ff1, ctx.title))); elementProperty(elIndex, 'title', bind(pureFunction1(1, ff1, ctx.title)));
} }
@ -559,6 +637,69 @@ describe('host bindings', () => {
expect(hostElement.id).toBe('red,green'); 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(); });
}
}
});
}
// <button hostListenerDir hostDir>Click</button>
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', () => { it('should support ternary expressions in host bindings', () => {
let hostBindingComp !: HostBindingComp; let hostBindingComp !: HostBindingComp;
@ -587,9 +728,11 @@ describe('host bindings', () => {
factory: () => hostBindingComp = new HostBindingComp(), factory: () => hostBindingComp = new HostBindingComp(),
consts: 0, consts: 0,
vars: 0, vars: 0,
hostVars: 6,
hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => {
// LView: [..., id, title, ctx.id, pf1, ctx.title, pf1] // LView: [..., id, title, ctx.id, pf1, ctx.title, pf1]
if (rf & RenderFlags.Create) {
allocHostVars(6);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty( elementProperty(
elIndex, 'id', bind(ctx.condition ? pureFunction1(2, ff, ctx.id) : 'green')); elIndex, 'id', bind(ctx.condition ? pureFunction1(2, ff, ctx.id) : 'green'));
@ -677,8 +820,10 @@ describe('host bindings', () => {
factory: () => new HostBindingWithContentChildren(), factory: () => new HostBindingWithContentChildren(),
consts: 0, consts: 0,
vars: 0, vars: 0,
hostVars: 1,
hostBindings: (rf: RenderFlags, ctx: HostBindingWithContentChildren, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: HostBindingWithContentChildren, elIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'id', bind(ctx.foos.length)); elementProperty(elIndex, 'id', bind(ctx.foos.length));
} }
@ -734,8 +879,10 @@ describe('host bindings', () => {
factory: () => new HostBindingWithContentHooks(), factory: () => new HostBindingWithContentHooks(),
consts: 0, consts: 0,
vars: 0, vars: 0,
hostVars: 1,
hostBindings: (rf: RenderFlags, ctx: HostBindingWithContentHooks, elIndex: number) => { hostBindings: (rf: RenderFlags, ctx: HostBindingWithContentHooks, elIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'id', bind(ctx.myValue)); elementProperty(elIndex, 'id', bind(ctx.myValue));
} }
@ -755,5 +902,4 @@ describe('host bindings', () => {
const hostBindingEl = fixture.hostElement.querySelector('host-binding-comp') as HTMLElement; const hostBindingEl = fixture.hostElement.querySelector('host-binding-comp') as HTMLElement;
expect(hostBindingEl.id).toEqual('after-content'); expect(hostBindingEl.id).toEqual('after-content');
}); });
}); });

View File

@ -10,7 +10,7 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {RendererStyleFlags2, RendererType2} from '../../src/render/api'; import {RendererStyleFlags2, RendererType2} from '../../src/render/api';
import {AttributeMarker, defineComponent, defineDirective, templateRefExtractor} from '../../src/render3/index'; 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 {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3, RText, RComment, RNode, RendererStyleFlags3, ProceduralRenderer3} from '../../src/render3/interfaces/renderer'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3, RText, RComment, RNode, RendererStyleFlags3, ProceduralRenderer3} from '../../src/render3/interfaces/renderer';
import {NO_CHANGE} from '../../src/render3/tokens'; import {NO_CHANGE} from '../../src/render3/tokens';
@ -446,8 +446,10 @@ describe('render3 integration test', () => {
} }
}, },
factory: () => cmptInstance = new TodoComponentHostBinding, factory: () => cmptInstance = new TodoComponentHostBinding,
hostVars: 1,
hostBindings: function(rf: RenderFlags, ctx: any, elementIndex: number): void { hostBindings: function(rf: RenderFlags, ctx: any, elementIndex: number): void {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
// host bindings // host bindings
elementProperty(elementIndex, 'title', bind(ctx.title)); elementProperty(elementIndex, 'title', bind(ctx.title));
@ -1379,9 +1381,11 @@ describe('render3 integration test', () => {
factory: function HostBindingDir_Factory() { factory: function HostBindingDir_Factory() {
return hostBindingDir = new HostBindingDir(); return hostBindingDir = new HostBindingDir();
}, },
hostVars: 1,
hostBindings: function HostBindingDir_HostBindings( hostBindings: function HostBindingDir_HostBindings(
rf: RenderFlags, ctx: any, elIndex: number) { rf: RenderFlags, ctx: any, elIndex: number) {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementAttribute(elIndex, 'aria-label', bind(ctx.label)); elementAttribute(elIndex, 'aria-label', bind(ctx.label));
} }

View File

@ -10,7 +10,7 @@ import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, El
import {ViewEncapsulation} from '../../src/metadata'; import {ViewEncapsulation} from '../../src/metadata';
import {AttributeMarker, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load, query, queryRefresh} from '../../src/render3/index'; 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 {RenderFlags} from '../../src/render3/interfaces/definition';
import {RElement} from '../../src/render3/interfaces/renderer'; import {RElement} from '../../src/render3/interfaces/renderer';
import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound';
@ -1836,9 +1836,11 @@ describe('ViewContainerRef', () => {
consts: 0, consts: 0,
vars: 0, vars: 0,
template: (rf: RenderFlags, cmp: HostBindingCmpt) => {}, template: (rf: RenderFlags, cmp: HostBindingCmpt) => {},
hostVars: 1,
attributes: ['id', 'attribute'], attributes: ['id', 'attribute'],
hostBindings: function(rf: RenderFlags, ctx: HostBindingCmpt, elIndex: number) { hostBindings: function(rf: RenderFlags, ctx: HostBindingCmpt, elIndex: number) {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(elIndex, 'title', bind(ctx.title)); elementProperty(elIndex, 'title', bind(ctx.title));
} }