feat(ivy): introduce "allocHostVars" instruction as a replacement for "hostVars" field (FW-692) (#27299)
PR Close #27299
This commit is contained in:
parent
0df914e1e9
commit
a088b8c203
|
@ -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
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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[] = [];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<T>(
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,14 +73,6 @@ export function defineComponent<T>(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<T>(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<T>(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.
|
||||
*/
|
||||
|
|
|
@ -76,7 +76,6 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
|
|||
superHostBindings(rf, ctx, elementIndex);
|
||||
prevHostBindings(rf, ctx, elementIndex);
|
||||
};
|
||||
(definition as any).hostVars += superDef.hostVars;
|
||||
} else {
|
||||
definition.hostBindings = superHostBindings;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export {CssSelectorList} from './interfaces/projection';
|
|||
|
||||
// clang-format off
|
||||
export {
|
||||
allocHostVars,
|
||||
bind,
|
||||
interpolation1,
|
||||
interpolation2,
|
||||
|
|
|
@ -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<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.
|
||||
*
|
||||
|
@ -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<T>(
|
|||
'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<any>| ComponentDef<any>): void {
|
||||
function queueHostBindingForCheck(
|
||||
tView: TView, def: DirectiveDef<any>| ComponentDef<any>, 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<T>(
|
|||
const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null);
|
||||
tView.blueprint.push(nodeInjectorFactory);
|
||||
viewData.push(nodeInjectorFactory);
|
||||
|
||||
queueHostBindingForCheck(tView, def);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
|
|
@ -136,14 +136,6 @@ export interface DirectiveDef<T> extends BaseDef<T> {
|
|||
/** 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<T>|null;
|
||||
|
||||
|
|
|
@ -346,7 +346,7 @@ export interface TView {
|
|||
*
|
||||
* 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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<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.
|
||||
*
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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]
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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) => {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <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',
|
||||
() => {
|
||||
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(); });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// <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', () => {
|
||||
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');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue