feat(ivy): introduce "allocHostVars" instruction as a replacement for "hostVars" field (FW-692) (#27299)
PR Close #27299
This commit is contained in:

committed by
Igor Minar

parent
0df914e1e9
commit
a088b8c203
@ -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.
|
||||
*
|
||||
|
Reference in New Issue
Block a user