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", ""]],
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
}
});
`;

View File

@ -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) {

View File

@ -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);

View File

@ -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};

View File

@ -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[] = [];

View File

@ -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,

View File

@ -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;
}

View File

@ -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.
*/

View File

@ -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;
}

View File

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

View File

@ -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.
*

View File

@ -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;

View File

@ -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.

View File

@ -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,

View File

@ -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.
*

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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]
});

View File

@ -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));
}

View File

@ -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');
});
});

View File

@ -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));
}

View File

@ -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));
}