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

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