refactor(core): Replace `ExpandoInstructions` with `HostBindingOpCodes` (#39301)

The `ExpandoInstructions` was unnecessarily convoluted way to solve the
problem of calling the `HostBindingFunction`s on components and
directives. The code was complicated and hard to fallow.

The replacement is a simplified way to achieve the same thing, which
is also more efficient in space and speed.

PR Close #39301
This commit is contained in:
Misko Hevery 2020-10-15 17:04:41 -07:00 committed by Alex Rickabaugh
parent 08f3d62391
commit 68d4de6770
17 changed files with 307 additions and 346 deletions

View File

@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 3037,
"main-es2015": 448676,
"main-es2015": 448085,
"polyfills-es2015": 52415
}
}

View File

@ -30,7 +30,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 136096,
"main-es2015": 135506,
"polyfills-es2015": 37248
}
}
@ -39,7 +39,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2289,
"main-es2015": 242460,
"main-es2015": 241945,
"polyfills-es2015": 36938,
"5-es2015": 751
}

View File

@ -198,57 +198,6 @@ The above will create the following layout:
| ... | ... | ...
The `EXPANDO` section needs additional information for information stored in `TView.expandoInstructions`
| Index | `TView.expandoInstructions` | Meaning
| ----: | ---------------------------:| -------
| 0 | -10 | Negative numbers signify pointers to elements. In this case 10 (`<child>`)
| 1 | 2 | Injector size. Number of values to skip to get to Host Bindings.
| 2 | Child.ɵcmp.hostBindings | The function to call. (Only when `hostVars` is not `0`)
| 3 | Child.ɵcmp.hostVars | Number of host bindings to process. (Only when `hostVars` is not `0`)
| 4 | Tooltip.ɵdir.hostBindings | The function to call. (Only when `hostVars` is not `0`)
| 5 | Tooltip.ɵdir.hostVars | Number of host bindings to process. (Only when `hostVars` is not `0`)
The reason for this layout is to make the host binding update efficient using this pseudo code:
```typescript
let currentDirectiveIndex = -1;
let currentElementIndex = -1;
// This is global state which is used internally by hostBindings to know where the offset is
let bindingRootIndex = tView.expandoStartIndex;
for(var i = 0; i < tView.expandoInstructions.length; i++) {
let instruction = tView.expandoInstructions[i];
if (typeof instruction === 'number') {
// Numbers are used to update the indices.
if (instruction < 0) {
// Negative numbers means that we are starting new EXPANDO block and need to update current element and directive index
bindingRootIndex += BLOOM_OFFSET;
currentDirectiveIndex = bindingRootIndex;
currentElementIndex = -instruction;
} else {
bindingRootIndex += instruction;
}
} else {
// We know that we are hostBinding function so execute it.
instruction(currentDirectiveIndex, currentElementIndex);
currentDirectiveIndex++;
}
}
```
The above code should execute as:
| Instruction | `bindingRootIndex` | `currentDirectiveIndex` | `currentElementIndex`
| ----------: | -----------------: | ----------------------: | --------------------:
| (initial) | `11` | `-1` | `-1`
| `-10` | `19` | `\* new Child() *\ 19` | `\* <child> *\ 10`
| `2` | `21` | `\* new Child() *\ 19` | `\* <child> *\ 10`
| `Child.ɵcmp.hostBindings` | invoke with => | `\* new Child() *\ 19` | `\* <child> *\ 10`
| | `21` | `\* new Tooltip() *\ 20` | `\* <child> *\ 10`
| `Child.ɵcmp.hostVars` | `22` | `\* new Tooltip() *\ 20` | `\* <child> *\ 10`
| `Tooltip.ɵdir.hostBindings` | invoke with => | `\* new Tooltip() *\ 20` | `\* <child> *\ 10`
| | `22` | `21` | `\* <child> *\ 10`
| `Tooltip.ɵdir.hostVars` | `22` | `21` | `\* <child> *\ 10`
## `EXPANDO` and Injection
`EXPANDO` will also store the injection information for the element.

View File

@ -16,9 +16,9 @@ import {assertComponentType} from './assert';
import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {registerPostOrderHooks} from './hooks';
import {addHostBindingsToExpandoInstructions, addToViewTree, CLEAN_PROMISE, createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, growHostVarsSpace, initTNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, renderView} from './instructions/shared';
import {addToViewTree, CLEAN_PROMISE, createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, initTNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, registerHostBindingOpCodes, renderView} from './instructions/shared';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeType} from './interfaces/node';
import {TElementNode, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player';
import {domRendererFactory3, RElement, Renderer3, RendererFactory3} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view';
@ -230,7 +230,9 @@ export function createRootComponent<T>(
// We want to generate an empty QueryList for root content queries for backwards
// compatibility with ViewEngine.
if (componentDef.contentQueries) {
componentDef.contentQueries(RenderFlags.Create, component, rootLView.length - 1);
const tNode = getCurrentTNode()!;
ngDevMode && assertDefined(tNode, 'TNode expected');
componentDef.contentQueries(RenderFlags.Create, component, tNode.directiveStart);
}
const rootTNode = getCurrentTNode()!;
@ -240,8 +242,9 @@ export function createRootComponent<T>(
setSelectedIndex(rootTNode.index);
const rootTView = rootLView[TVIEW];
addHostBindingsToExpandoInstructions(rootTView, componentDef);
growHostVarsSpace(rootTView, rootLView, componentDef.hostVars);
registerHostBindingOpCodes(
rootTView, rootTNode, rootLView, rootTNode.directiveStart, rootTNode.directiveEnd,
componentDef);
invokeHostBindingsInCreationMode(componentDef, component);
}
@ -274,13 +277,12 @@ export function createRootContext(
* ```
*/
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
const rootTView = readPatchedLView(component)![TVIEW];
const dirIndex = rootTView.data.length - 1;
// TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on
// LNode).
registerPostOrderHooks(
rootTView, {directiveStart: dirIndex, directiveEnd: dirIndex + 1} as TNode);
const lView = readPatchedLView(component)!;
ngDevMode && assertDefined(lView, 'LView is required');
const tView = lView[TVIEW];
const tNode = getCurrentTNode()!;
ngDevMode && assertDefined(tNode, 'TNode is required');
registerPostOrderHooks(tView, tNode);
}
/**

View File

@ -7,7 +7,7 @@
*/
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from '../interface/lifecycle_hooks';
import {assertEqual, assertNotEqual} from '../util/assert';
import {assertDefined, assertEqual, assertNotEqual} from '../util/assert';
import {assertFirstCreatePass} from './assert';
import {NgOnChangesFeatureImpl} from './features/ng_onchanges_feature';
import {DirectiveDef} from './interfaces/definition';
@ -77,6 +77,7 @@ export function registerPostOrderHooks(tView: TView, tNode: TNode): void {
// hooks for projected components and directives must be called *before* their hosts.
for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) {
const directiveDef = tView.data[i] as DirectiveDef<any>;
ngDevMode && assertDefined(directiveDef, 'Expecting DirectiveDef');
const lifecycleHooks: AfterContentInit&AfterContentChecked&AfterViewInit&AfterViewChecked&
OnDestroy = directiveDef.type.prototype;
const {

View File

@ -154,7 +154,7 @@ export function i18nStartFirstCreatePass(
function createTNodeAndAddOpCode(
tView: TView, rootTNode: TNode|null, existingTNodes: TNode[], lView: LView,
createOpCodes: I18nCreateOpCodes, text: string|null, isICU: boolean): TNode {
const i18nNodeIdx = allocExpando(tView, lView, 1);
const i18nNodeIdx = allocExpando(tView, lView, 1, null);
let opCode = i18nNodeIdx << I18nCreateOpCode.SHIFT;
let parentTNode = getCurrentParentTNode();
@ -423,7 +423,7 @@ export function icuStart(
let bindingMask = 0;
const tIcu: TIcu = {
type: icuExpression.type,
currentCaseLViewIndex: allocExpando(tView, lView, 1),
currentCaseLViewIndex: allocExpando(tView, lView, 1, null),
anchorIdx,
cases: [],
create: [],
@ -596,7 +596,7 @@ function walkIcuTree(
let bindingMask = 0;
let currentNode = parentNode.firstChild;
while (currentNode) {
const newIndex = allocExpando(tView, lView, 1);
const newIndex = allocExpando(tView, lView, 1, null);
switch (currentNode.nodeType) {
case Node.ELEMENT_NODE:
const element = currentNode as Element;

View File

@ -22,7 +22,7 @@ import {SelectorFlags} from '../interfaces/projection';
import {LQueries, TQueries} from '../interfaces/query';
import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer';
import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, ExpandoInstructions, FLAGS, HEADER_OFFSET, HookData, HOST, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view';
import {attachDebugObject} from '../util/debug_utils';
import {getParentInjectorIndex, getParentInjectorView} from '../util/injector_utils';
import {unwrapRNode} from '../util/view_utils';
@ -115,38 +115,38 @@ function nameSuffix(text: string|null|undefined): string {
*/
export const TViewConstructor = class TView implements ITView {
constructor(
public type: TViewType, //
public blueprint: LView, //
public template: ComponentTemplate<{}>|null, //
public queries: TQueries|null, //
public viewQuery: ViewQueriesFunction<{}>|null, //
public declTNode: ITNode|null, //
public data: TData, //
public bindingStartIndex: number, //
public expandoStartIndex: number, //
public expandoInstructions: ExpandoInstructions|null, //
public firstCreatePass: boolean, //
public firstUpdatePass: boolean, //
public staticViewQueries: boolean, //
public staticContentQueries: boolean, //
public preOrderHooks: HookData|null, //
public preOrderCheckHooks: HookData|null, //
public contentHooks: HookData|null, //
public contentCheckHooks: HookData|null, //
public viewHooks: HookData|null, //
public viewCheckHooks: HookData|null, //
public destroyHooks: DestroyHookData|null, //
public cleanup: any[]|null, //
public contentQueries: number[]|null, //
public components: number[]|null, //
public directiveRegistry: DirectiveDefList|null, //
public pipeRegistry: PipeDefList|null, //
public firstChild: ITNode|null, //
public schemas: SchemaMetadata[]|null, //
public consts: TConstants|null, //
public incompleteFirstPass: boolean, //
public _decls: number, //
public _vars: number, //
public type: TViewType,
public blueprint: LView,
public template: ComponentTemplate<{}>|null,
public queries: TQueries|null,
public viewQuery: ViewQueriesFunction<{}>|null,
public declTNode: ITNode|null,
public data: TData,
public bindingStartIndex: number,
public expandoStartIndex: number,
public hostBindingOpCodes: HostBindingOpCodes|null,
public firstCreatePass: boolean,
public firstUpdatePass: boolean,
public staticViewQueries: boolean,
public staticContentQueries: boolean,
public preOrderHooks: HookData|null,
public preOrderCheckHooks: HookData|null,
public contentHooks: HookData|null,
public contentCheckHooks: HookData|null,
public viewHooks: HookData|null,
public viewCheckHooks: HookData|null,
public destroyHooks: DestroyHookData|null,
public cleanup: any[]|null,
public contentQueries: number[]|null,
public components: number[]|null,
public directiveRegistry: DirectiveDefList|null,
public pipeRegistry: PipeDefList|null,
public firstChild: ITNode|null,
public schemas: SchemaMetadata[]|null,
public consts: TConstants|null,
public incompleteFirstPass: boolean,
public _decls: number,
public _vars: number,
) {}

View File

@ -12,7 +12,7 @@ import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../me
import {ViewEncapsulation} from '../../metadata/view';
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/sanitizer';
import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert';
import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
@ -24,13 +24,13 @@ import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} f
import {throwMultipleComponentError} from '../errors';
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks';
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {NodeInjectorFactory, NodeInjectorOffset} from '../interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode} from '../interfaces/node';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, HostBindingsFunction, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {NodeInjectorFactory} from '../interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from '../interfaces/node';
import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {isComponentDef, isComponentHost, isContentQueryHost, isRootView} from '../interfaces/type_checks';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
import {assertPureTNodeType, assertTNodeType} from '../node_assert';
import {updateTextNode} from '../node_manipulation';
import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher';
@ -53,65 +53,31 @@ import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint,
const _CLEAN_PROMISE = (() => Promise.resolve(null))();
/**
* Process the `TView.expandoInstructions`. (Execute the `hostBindings`.)
* Invoke `HostBindingsFunction`s for view.
*
* @param tView `TView` containing the `expandoInstructions`
* @param lView `LView` associated with the `TView`
* This methods executes `TView.hostBindingOpCodes`. It is used to execute the
* `HostBindingsFunction`s associated with the current `LView`.
*
* @param tView Current `TView`.
* @param lView Current `LView`.
*/
export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lView: LView): void {
ngDevMode && assertSame(tView, lView[TVIEW], '`LView` is not associated with the `TView`!');
export function processHostBindingOpCodes(tView: TView, lView: LView): void {
const hostBindingOpCodes = tView.hostBindingOpCodes;
if (hostBindingOpCodes === null) return;
try {
const expandoInstructions = tView.expandoInstructions;
if (expandoInstructions !== null) {
let bindingRootIndex = tView.expandoStartIndex;
let currentDirectiveIndex = -1;
let currentElementIndex = -1;
// TODO(misko): PERF It is possible to get here with `TView.expandoInstructions` containing no
// functions to execute. This is wasteful as there is no work to be done, but we still need
// to iterate over the instructions.
// In example of this is in this test: `host_binding_spec.ts`
// `fit('should not cause problems if detectChanges is called when a property updates', ...`
// In the above test we get here with expando [0, 0, 1] which requires a lot of processing but
// there is no function to execute.
for (let i = 0; i < expandoInstructions.length; i++) {
const instruction = expandoInstructions[i];
if (typeof instruction === 'number') {
if (instruction <= 0) {
// Negative numbers mean that we are starting new EXPANDO block and need to update
// the current element and directive index.
currentElementIndex = ~instruction;
setSelectedIndex(currentElementIndex);
// Injector block and providers are taken into account.
const providerCount = (expandoInstructions[++i] as number);
bindingRootIndex += NodeInjectorOffset.SIZE + providerCount;
currentDirectiveIndex = bindingRootIndex;
} else {
// This is either the injector size (so the binding root can skip over directives
// and get to the first set of host bindings on this node) or the host var count
// (to get to the next set of host bindings on this node).
bindingRootIndex += instruction;
}
} else {
// If it's not a number, it's a host binding function that needs to be executed.
if (instruction !== null) {
ngDevMode &&
assertLessThan(
currentDirectiveIndex, TNodeProviderIndexes.CptViewProvidersCountShifter,
'Reached the max number of host bindings');
setBindingRootForHostBindings(bindingRootIndex, currentDirectiveIndex);
const hostCtx = lView[currentDirectiveIndex];
instruction(RenderFlags.Update, hostCtx);
}
// TODO(misko): PERF Relying on incrementing the `currentDirectiveIndex` here is
// sub-optimal. The implications are that if we have a lot of directives but none of them
// have host bindings we nevertheless need to iterate over the expando instructions to
// update the counter. It would be much better if we could encode the
// `currentDirectiveIndex` into the `expandoInstruction` array so that we only need to
// iterate over those directives which actually have `hostBindings`.
currentDirectiveIndex++;
}
for (let i = 0; i < hostBindingOpCodes.length; i++) {
const opCode = hostBindingOpCodes[i] as number;
if (opCode < 0) {
// Negative numbers are element indexes.
setSelectedIndex(~opCode);
} else {
// Positive numbers are NumberTuple which store bindingRootIndex and directiveIndex.
const directiveIdx = opCode;
const bindingRootIndx = hostBindingOpCodes[++i] as number;
const hostBindingFn = hostBindingOpCodes[++i] as HostBindingsFunction<any>;
setBindingRootForHostBindings(bindingRootIndx, directiveIdx);
const context = lView[directiveIdx];
hostBindingFn(RenderFlags.Update, context);
}
}
} finally {
@ -119,6 +85,7 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
}
}
/** Refreshes all content queries declared by directives in a given view */
function refreshContentQueries(tView: TView, lView: LView): void {
const contentQueries = tView.contentQueries;
@ -128,6 +95,7 @@ function refreshContentQueries(tView: TView, lView: LView): void {
const directiveDefIdx = contentQueries[i + 1];
if (directiveDefIdx !== -1) {
const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>;
ngDevMode && assertDefined(directiveDef, 'DirectiveDef not found.');
ngDevMode &&
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
setCurrentQueryIndex(queryStartIdx);
@ -279,10 +247,14 @@ export function createTNodeAtIndex(
* @param tView `TView` associated with `LView`
* @param lView The `LView` containing the blueprint to adjust
* @param numSlotsToAlloc The number of slots to alloc in the LView, should be >0
* @param initialValue Initial value to store in blueprint
*/
export function allocExpando(tView: TView, lView: LView, numSlotsToAlloc: number): number {
export function allocExpando(
tView: TView, lView: LView, numSlotsToAlloc: number, initialValue: any): number {
if (numSlotsToAlloc === 0) return -1;
if (ngDevMode) {
assertGreaterThan(numSlotsToAlloc, 0, 'The number of slots to alloc should be greater than 0');
assertFirstCreatePass(tView);
assertSame(tView, lView[TVIEW], '`LView` must be associated with `TView`!');
assertEqual(tView.data.length, lView.length, 'Expecting LView to be same size as TView');
assertEqual(
tView.data.length, tView.blueprint.length, 'Expecting Blueprint to be same size as TView');
@ -290,21 +262,9 @@ export function allocExpando(tView: TView, lView: LView, numSlotsToAlloc: number
}
const allocIdx = lView.length;
for (let i = 0; i < numSlotsToAlloc; i++) {
tView.blueprint.push(null);
lView.push(initialValue);
tView.blueprint.push(initialValue);
tView.data.push(null);
lView.push(null);
}
// We should only increment the expando start index if there aren't already directives
// and injectors saved in the "expando" section
if (!tView.expandoInstructions) {
tView.expandoStartIndex += numSlotsToAlloc;
} else {
// Since we're adding the dynamic nodes into the expando section, we need to let the host
// bindings know that they should skip x slots
// FIXME(misko): Refactor `expandoInstructions` so that it does not rely on relative binding
// offsets, but absolute values which Means we would not have to store it here.
tView.expandoInstructions.push(numSlotsToAlloc);
}
return allocIdx;
}
@ -454,7 +414,7 @@ export function refreshView<T>(
}
}
setHostBindingsByExecutingExpandoInstructions(tView, lView);
processHostBindingOpCodes(tView, lView);
// Refresh child component views.
const components = tView.components;
@ -653,7 +613,7 @@ export function createTView(
const consts = typeof constsOrFactory === 'function' ? constsOrFactory() : constsOrFactory;
const tView = blueprint[TVIEW as any] = ngDevMode ?
new TViewConstructor(
type,
type, // type: TViewType,
blueprint, // blueprint: LView,
templateFn, // template: ComponentTemplate<{}>|null,
null, // queries: TQueries|null
@ -662,24 +622,24 @@ export function createTView(
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
bindingStartIndex, // bindingStartIndex: number,
initialViewLength, // expandoStartIndex: number,
null, // expandoInstructions: ExpandoInstructions|null,
true, // firstCreatePass: boolean,
true, // firstUpdatePass: boolean,
false, // staticViewQueries: boolean,
false, // staticContentQueries: boolean,
null, // preOrderHooks: HookData|null,
null, // preOrderCheckHooks: HookData|null,
null, // contentHooks: HookData|null,
null, // contentCheckHooks: HookData|null,
null, // viewHooks: HookData|null,
null, // viewCheckHooks: HookData|null,
null, // destroyHooks: DestroyHookData|null,
null, // cleanup: any[]|null,
null, // contentQueries: number[]|null,
null, // components: number[]|null,
typeof directives === 'function' ?
directives() :
directives, // directiveRegistry: DirectiveDefList|null,
null, // hostBindingOpCodes: HostBindingOpCodes,
true, // firstCreatePass: boolean,
true, // firstUpdatePass: boolean,
false, // staticViewQueries: boolean,
false, // staticContentQueries: boolean,
null, // preOrderHooks: HookData|null,
null, // preOrderCheckHooks: HookData|null,
null, // contentHooks: HookData|null,
null, // contentCheckHooks: HookData|null,
null, // viewHooks: HookData|null,
null, // viewCheckHooks: HookData|null,
null, // destroyHooks: DestroyHookData|null,
null, // cleanup: any[]|null,
null, // contentQueries: number[]|null,
null, // components: number[]|null,
typeof directives === 'function' ? //
directives() : //
directives, // directiveRegistry: DirectiveDefList|null,
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
null, // firstChild: TNode|null,
schemas, // schemas: SchemaMetadata[]|null,
@ -698,7 +658,7 @@ export function createTView(
data: blueprint.slice().fill(null, bindingStartIndex),
bindingStartIndex: bindingStartIndex,
expandoStartIndex: initialViewLength,
expandoInstructions: null,
hostBindingOpCodes: null,
firstCreatePass: true,
firstUpdatePass: true,
staticViewQueries: false,
@ -945,14 +905,14 @@ function initializeInputAndOutputAliases(tView: TView, tNode: TNode): void {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
const defs = tView.data;
const tViewData = tView.data;
const tNodeAttrs = tNode.attrs;
const inputsFromAttrs: InitialInputData = ngDevMode ? new TNodeInitialInputs() : [];
let inputsStore: PropertyAliases|null = null;
let outputsStore: PropertyAliases|null = null;
for (let i = start; i < end; i++) {
const directiveDef = defs[i] as DirectiveDef<any>;
const directiveDef = tViewData[i] as DirectiveDef<any>;
const directiveInputs = directiveDef.inputs;
// Do not use unbound attributes as inputs to structural directives, since structural
// directive inputs can only be set using microsyntax (e.g. `<div *dir="exp">`).
@ -1146,10 +1106,15 @@ export function instantiateRootComponent<T>(tView: TView, lView: LView, def: Com
const rootTNode = getCurrentTNode()!;
if (tView.firstCreatePass) {
if (def.providersResolver) def.providersResolver(def);
generateExpandoInstructionBlock(tView, rootTNode, 1);
baseResolveDirective(tView, lView, def);
const directiveIndex = allocExpando(tView, lView, 1, null);
ngDevMode &&
assertEqual(
directiveIndex, rootTNode.directiveStart,
'Because this is a root component the allocated expando should match the TNode component.');
configureViewWithDirective(tView, rootTNode, lView, directiveIndex, def);
}
const directive = getNodeInjectable(lView, tView, lView.length - 1, rootTNode as TElementNode);
const directive =
getNodeInjectable(lView, tView, rootTNode.directiveStart, rootTNode as TElementNode);
attachPatchData(directive, lView);
const native = getNativeByTNode(rootTNode, lView);
if (native) {
@ -1174,7 +1139,6 @@ export function resolveDirectives(
const exportsMap: ({[key: string]: number}|null) = localRefs === null ? null : {'': -1};
if (directiveDefs !== null) {
let totalDirectiveHostVars = 0;
hasDirectives = true;
initTNodeFlags(tNode, tView.data.length, directiveDefs.length);
// When the same token is provided by several directives on the same node, some rules apply in
@ -1187,18 +1151,22 @@ export function resolveDirectives(
const def = directiveDefs[i];
if (def.providersResolver) def.providersResolver(def);
}
generateExpandoInstructionBlock(tView, tNode, directiveDefs.length);
let preOrderHooksFound = false;
let preOrderCheckHooksFound = false;
let directiveIdx = allocExpando(tView, lView, directiveDefs.length, null);
ngDevMode &&
assertSame(
directiveIdx, tNode.directiveStart,
'TNode.directiveStart should point to just allocated space');
for (let i = 0; i < directiveDefs.length; i++) {
const def = directiveDefs[i];
// Merge the attrs in the order of matches. This assumes that the first directive is the
// component itself, so that the component has the least priority.
tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs);
baseResolveDirective(tView, lView, def);
saveNameToExportMap(tView.data!.length - 1, def, exportsMap);
configureViewWithDirective(tView, tNode, lView, directiveIdx, def);
saveNameToExportMap(directiveIdx, def, exportsMap);
if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery;
if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0)
@ -1221,12 +1189,10 @@ export function resolveDirectives(
preOrderCheckHooksFound = true;
}
addHostBindingsToExpandoInstructions(tView, def);
totalDirectiveHostVars += def.hostVars;
directiveIdx++;
}
initializeInputAndOutputAliases(tView, tNode);
growHostVarsSpace(tView, lView, totalDirectiveHostVars);
}
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
}
@ -1236,51 +1202,57 @@ export function resolveDirectives(
}
/**
* Add `hostBindings` to the `TView.expandoInstructions`.
* Add `hostBindings` to the `TView.hostBindingOpCodes`.
*
* @param tView `TView` to which the `hostBindings` should be added.
* @param tNode `TNode` the element which contains the directive
* @param lView `LView` current `LView`
* @param directiveIdx Directive index in view.
* @param directiveVarsIdx Where will the directive's vars be stored
* @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add.
*/
export function addHostBindingsToExpandoInstructions(
tView: TView, def: ComponentDef<any>|DirectiveDef<any>): void {
export function registerHostBindingOpCodes(
tView: TView, tNode: TNode, lView: LView, directiveIdx: number, directiveVarsIdx: number,
def: ComponentDef<any>|DirectiveDef<any>): void {
ngDevMode && assertFirstCreatePass(tView);
const expando = tView.expandoInstructions!;
// TODO(misko): PERF we are adding `hostBindings` even if there is nothing to add! This is
// suboptimal for performance. `def.hostBindings` may be null,
// but we still need to push null to the array as a placeholder
// to ensure the directive counter is incremented (so host
// binding functions always line up with the corrective directive).
// This is suboptimal for performance. See `currentDirectiveIndex`
// comment in `setHostBindingsByExecutingExpandoInstructions` for more
// details. expando.push(def.hostBindings);
expando.push(def.hostBindings);
const hostVars = def.hostVars;
if (hostVars !== 0) {
expando.push(def.hostVars);
const hostBindings = def.hostBindings;
if (hostBindings) {
let hostBindingOpCodes = tView.hostBindingOpCodes;
if (hostBindingOpCodes === null) {
hostBindingOpCodes = tView.hostBindingOpCodes = [] as any as HostBindingOpCodes;
}
const elementIndx = ~tNode.index;
if (lastSelectedElementIdx(hostBindingOpCodes) != elementIndx) {
// Conditionally add select element so that we are more efficient in execution.
// NOTE: this is strictly not necessary and it trades code size for runtime perf.
// (We could just always add it.)
hostBindingOpCodes.push(elementIndx);
}
hostBindingOpCodes.push(directiveIdx, directiveVarsIdx, hostBindings);
}
}
/**
* Grow the `LView`, blueprint and `TView.data` to accommodate the `hostBindings`.
* Returns the last selected element index in the `HostBindingOpCodes`
*
* To support locality we don't know ahead of time how many `hostVars` of the containing directives
* we need to allocate. For this reason we allow growing these data structures as we discover more
* directives to accommodate them.
* For perf reasons we don't need to update the selected element index in `HostBindingOpCodes` only
* if it changes. This method returns the last index (or '0' if not found.)
*
* @param tView `TView` which needs to be grown.
* @param lView `LView` which needs to be grown.
* @param count Size by which we need to grow the data structures.
* Selected element index are only the ones which are negative.
*/
export function growHostVarsSpace(tView: TView, lView: LView, count: number) {
ngDevMode && assertFirstCreatePass(tView);
ngDevMode && assertSame(tView, lView[TVIEW], '`LView` must be associated with `TView`!');
for (let i = 0; i < count; i++) {
lView.push(NO_CHANGE);
tView.blueprint.push(NO_CHANGE);
tView.data.push(null);
function lastSelectedElementIdx(hostBindingOpCodes: HostBindingOpCodes): number {
let i = hostBindingOpCodes.length;
while (i > 0) {
const value = hostBindingOpCodes[--i];
if (typeof value === 'number' && value < 0) {
return value;
}
}
return 0;
}
/**
* Instantiate all the directives that were previously resolved on the current node.
*/
@ -1321,7 +1293,6 @@ function instantiateAllDirectives(
function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode) {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
const expando = tView.expandoInstructions!;
const firstCreatePass = tView.firstCreatePass;
const elementIndex = tNode.index;
const currentDirectiveIndex = getCurrentDirectiveIndex();
@ -1333,8 +1304,6 @@ function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode)
setCurrentDirectiveIndex(dirIndex);
if (def.hostBindings !== null || def.hostVars !== 0 || def.hostAttrs !== null) {
invokeHostBindingsInCreationMode(def, directive);
} else if (firstCreatePass) {
expando.push(null);
}
}
} finally {
@ -1355,26 +1324,6 @@ export function invokeHostBindingsInCreationMode(def: DirectiveDef<any>, directi
}
}
/**
* Generates a new block in TView.expandoInstructions for this node.
*
* Each expando block starts with the element index (turned negative so we can distinguish
* it from the hostVar count) and the directive count. See more in VIEW_DATA.md.
*/
export function generateExpandoInstructionBlock(
tView: TView, tNode: TNode, directiveCount: number): void {
ngDevMode &&
assertEqual(
tView.firstCreatePass, true,
'Expando block should only be generated on first create pass.');
const elementIndex = ~tNode.index;
const providerStartIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const providerCount = tView.data.length - providerStartIndex;
(tView.expandoInstructions || (tView.expandoInstructions = []))
.push(elementIndex, providerCount, directiveCount);
}
/**
* Matches the current node against all available selectors.
* If a component is matched (at most one), it is returned in first position in the array.
@ -1450,15 +1399,15 @@ function cacheMatchingLocalNames(
* to their directive instances.
*/
function saveNameToExportMap(
index: number, def: DirectiveDef<any>|ComponentDef<any>,
directiveIdx: number, def: DirectiveDef<any>|ComponentDef<any>,
exportsMap: {[key: string]: number}|null) {
if (exportsMap) {
if (def.exportAs) {
for (let i = 0; i < def.exportAs.length; i++) {
exportsMap[def.exportAs[i]] = index;
exportsMap[def.exportAs[i]] = directiveIdx;
}
}
if (isComponentDef(def)) exportsMap[''] = index;
if (isComponentDef(def)) exportsMap[''] = directiveIdx;
}
}
@ -1479,13 +1428,32 @@ export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives:
tNode.providerIndexes = index;
}
function baseResolveDirective<T>(tView: TView, viewData: LView, def: DirectiveDef<T>) {
tView.data.push(def);
/**
* Setup directive for instantiation.
*
* We need to create a `NodeInjectorFactory` which is then inserted in both the `Blueprint` as well
* as `LView`. `TView` gets the `DirectiveDef`.
*
* @param tView `TView`
* @param tNode `TNode`
* @param lView `LView`
* @param directiveIndex Index where the directive will be stored in the Expando.
* @param def `DirectiveDef`
*/
function configureViewWithDirective<T>(
tView: TView, tNode: TNode, lView: LView, directiveIndex: number, def: DirectiveDef<T>): void {
ngDevMode &&
assertGreaterThanOrEqual(directiveIndex, HEADER_OFFSET, 'Must be in Expando section');
tView.data[directiveIndex] = def;
const directiveFactory =
def.factory || ((def as {factory: Function}).factory = getFactoryDef(def.type, true));
const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null);
tView.blueprint.push(nodeInjectorFactory);
viewData.push(nodeInjectorFactory);
tView.blueprint[directiveIndex] = nodeInjectorFactory;
lView[directiveIndex] = nodeInjectorFactory;
registerHostBindingOpCodes(
tView, tNode, lView, directiveIndex, allocExpando(tView, lView, def.hostVars, NO_CHANGE),
def);
}
function addComponentLogic<T>(lView: LView, hostTNode: TElementNode, def: ComponentDef<T>): void {

View File

@ -409,11 +409,17 @@ export interface TNode {
/**
* Stores starting index of the directives.
*
* NOTE: The first directive is always component (if present).
*/
directiveStart: number;
/**
* Stores final exclusive index of the directives.
*
* The area right behind the `directiveStart-directiveEnd` range is used to allocate the
* `HostBindingFunction` `vars` (or null if no bindings.) Therefore `directiveEnd` is used to set
* `LFrame.bindingRootIndex` before `HostBindingFunction` is executed.
*/
directiveEnd: number;

View File

@ -427,11 +427,69 @@ export const enum PreOrderHookFlags {
}
/**
* Set of instructions used to process host bindings efficiently.
* Stores a set of OpCodes to process `HostBindingsFunction` associated with a current view.
*
* In order to invoke `HostBindingsFunction` we need:
* 1. 'elementIdx`: Index to the element associated with the `HostBindingsFunction`.
* 2. 'directiveIdx`: Index to the directive associated with the `HostBindingsFunction`. (This will
* become the context for the `HostBindingsFunction` invocation.)
* 3. `bindingRootIdx`: Location where the bindings for the `HostBindingsFunction` start. Internally
* `HostBindingsFunction` binding indexes start from `0` so we need to add `bindingRootIdx` to
* it.
* 4. `HostBindingsFunction`: A host binding function to execute.
*
* The above information needs to be encoded into the `HostBindingOpCodes` in an efficient manner.
*
* 1. `elementIdx` is encoded into the `HostBindingOpCodes` as `~elementIdx` (so a negative number);
* 2. `directiveIdx`
* 3. `bindingRootIdx`
* 4. `HostBindingsFunction` is passed in as is.
*
* The `HostBindingOpCodes` array contains:
* - negative number to select the element index.
* - followed by 1 or more of:
* - a number to select the directive index
* - a number to select the bindingRoot index
* - and a function to invoke.
*
* ## Example
*
* ```
* const hostBindingOpCodes = [
* ~30, // Select element 30
* 40, 45, MyDir.ɵdir.hostBindings // Invoke host bindings on MyDir on element 30;
* // directiveIdx = 40; bindingRootIdx = 45;
* 50, 55, OtherDir.ɵdir.hostBindings // Invoke host bindings on OtherDire on element 30
* // directiveIdx = 50; bindingRootIdx = 55;
* ]
* ```
*
* ## Pseudocode
* ```
* const hostBindingOpCodes = tView.hostBindingOpCodes;
* if (hostBindingOpCodes === null) return;
* for (let i = 0; i < hostBindingOpCodes.length; i++) {
* const opCode = hostBindingOpCodes[i] as number;
* if (opCode < 0) {
* // Negative numbers are element indexes.
* setSelectedIndex(~opCode);
* } else {
* // Positive numbers are NumberTuple which store bindingRootIndex and directiveIndex.
* const directiveIdx = opCode;
* const bindingRootIndx = hostBindingOpCodes[++i] as number;
* const hostBindingFn = hostBindingOpCodes[++i] as HostBindingsFunction<any>;
* setBindingRootForHostBindings(bindingRootIndx, directiveIdx);
* const context = lView[directiveIdx];
* hostBindingFn(RenderFlags.Update, context);
* }
* }
* ```
*
* See VIEW_DATA.md for more information.
*/
export interface ExpandoInstructions extends Array<number|HostBindingsFunction<any>|null> {}
export interface HostBindingOpCodes extends Array<number|HostBindingsFunction<any>> {
__brand__: 'HostBindingOpCodes';
debug?: string[];
}
/**
* Explicitly marks `TView` as a specific type in `ngDevMode`
@ -572,13 +630,11 @@ export interface TView {
firstChild: TNode|null;
/**
* Set of instructions used to process host bindings efficiently.
* Stores the OpCodes to be replayed during change-detection to process the `HostBindings`
*
* See VIEW_DATA.md for more information.
* See `HostBindingOpCodes` for encoding details.
*/
// TODO(misko): `expandoInstructions` should be renamed to `hostBindingsInstructions` since they
// keep track of `hostBindings` which need to be executed.
expandoInstructions: ExpandoInstructions|null;
hostBindingOpCodes: HostBindingOpCodes|null;
/**
* Full registry of directives and components that may be found in this view.

View File

@ -570,7 +570,7 @@ function createTQuery(tView: TView, metadata: TQueryMetadata, nodeIndex: number)
function saveContentQueryAndDirectiveIndex(tView: TView, directiveIndex: number) {
const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []);
const lastSavedDirectiveIndex =
tView.contentQueries.length ? tViewContentQueries[tViewContentQueries.length - 1] : -1;
tViewContentQueries.length ? tViewContentQueries[tViewContentQueries.length - 1] : -1;
if (directiveIndex !== lastSavedDirectiveIndex) {
tViewContentQueries.push(tView.queries!.length - 1, directiveIndex);
}

View File

@ -69,10 +69,10 @@
"name": "addComponentLogic"
},
{
"name": "addHostBindingsToExpandoInstructions"
"name": "addToViewTree"
},
{
"name": "addToViewTree"
"name": "allocExpando"
},
{
"name": "allocLFrame"
@ -86,9 +86,6 @@
{
"name": "autoRegisterModuleById"
},
{
"name": "baseResolveDirective"
},
{
"name": "callHook"
},
@ -104,6 +101,9 @@
{
"name": "concatStringsWithSpace"
},
{
"name": "configureViewWithDirective"
},
{
"name": "createLFrame"
},
@ -146,9 +146,6 @@
{
"name": "findAttrIndexInNode"
},
{
"name": "generateExpandoInstructionBlock"
},
{
"name": "generateInitialInputs"
},
@ -206,9 +203,6 @@
{
"name": "getTView"
},
{
"name": "growHostVarsSpace"
},
{
"name": "hasTagAndTypeMatch"
},
@ -284,9 +278,6 @@
{
"name": "nativeAppendOrInsertBefore"
},
{
"name": "nativeInsertBefore"
},
{
"name": "nextNgElementId"
},
@ -305,6 +296,9 @@
{
"name": "refreshView"
},
{
"name": "registerHostBindingOpCodes"
},
{
"name": "rememberChangeHistoryAndInvokeOnChangesHook"
},
@ -350,9 +344,6 @@
{
"name": "setUpAttributes"
},
{
"name": "unwrapRNode"
},
{
"name": "updateTransplantedViewCount"
},

View File

@ -698,15 +698,15 @@
{
"name": "addComponentLogic"
},
{
"name": "addHostBindingsToExpandoInstructions"
},
{
"name": "addToArray"
},
{
"name": "addToViewTree"
},
{
"name": "allocExpando"
},
{
"name": "allocLFrame"
},
@ -734,9 +734,6 @@
{
"name": "baseElement"
},
{
"name": "baseResolveDirective"
},
{
"name": "bindingUpdated"
},
@ -785,6 +782,9 @@
{
"name": "config"
},
{
"name": "configureViewWithDirective"
},
{
"name": "connectableObservableDescriptor"
},
@ -941,9 +941,6 @@
{
"name": "fromArray"
},
{
"name": "generateExpandoInstructionBlock"
},
{
"name": "generateInitialInputs"
},
@ -1082,9 +1079,6 @@
{
"name": "getTView"
},
{
"name": "growHostVarsSpace"
},
{
"name": "handleError"
},
@ -1421,6 +1415,9 @@
{
"name": "registerDestroyHooksIfSupported"
},
{
"name": "registerHostBindingOpCodes"
},
{
"name": "registerPostOrderHooks"
},

View File

@ -56,6 +56,9 @@
{
"name": "_renderCompCount"
},
{
"name": "allocExpando"
},
{
"name": "allocLFrame"
},
@ -176,9 +179,6 @@
{
"name": "nativeAppendOrInsertBefore"
},
{
"name": "nativeInsertBefore"
},
{
"name": "nextNgElementId"
},
@ -194,6 +194,9 @@
{
"name": "refreshView"
},
{
"name": "registerHostBindingOpCodes"
},
{
"name": "rememberChangeHistoryAndInvokeOnChangesHook"
},
@ -224,16 +227,10 @@
{
"name": "setSelectedIndex"
},
{
"name": "unwrapRNode"
},
{
"name": "updateTransplantedViewCount"
},
{
"name": "viewAttachedToChangeDetector"
},
{
"name": "ɵɵtext"
}
]

View File

@ -920,9 +920,6 @@
{
"name": "addComponentLogic"
},
{
"name": "addHostBindingsToExpandoInstructions"
},
{
"name": "addToArray"
},
@ -935,6 +932,9 @@
{
"name": "advanceActivatedRouteNodeAndItsChildren"
},
{
"name": "allocExpando"
},
{
"name": "allocLFrame"
},
@ -965,9 +965,6 @@
{
"name": "baseElement"
},
{
"name": "baseResolveDirective"
},
{
"name": "bindingUpdated"
},
@ -1016,6 +1013,9 @@
{
"name": "config"
},
{
"name": "configureViewWithDirective"
},
{
"name": "connectableObservableDescriptor"
},
@ -1244,9 +1244,6 @@
{
"name": "fromArray"
},
{
"name": "generateExpandoInstructionBlock"
},
{
"name": "generateInitialInputs"
},
@ -1427,9 +1424,6 @@
{
"name": "getToken"
},
{
"name": "growHostVarsSpace"
},
{
"name": "handleError"
},
@ -1760,6 +1754,9 @@
{
"name": "refreshView"
},
{
"name": "registerHostBindingOpCodes"
},
{
"name": "registerPostOrderHooks"
},

View File

@ -176,15 +176,15 @@
{
"name": "addComponentLogic"
},
{
"name": "addHostBindingsToExpandoInstructions"
},
{
"name": "addToArray"
},
{
"name": "addToViewTree"
},
{
"name": "allocExpando"
},
{
"name": "allocLFrame"
},
@ -209,9 +209,6 @@
{
"name": "attachPatchData"
},
{
"name": "baseResolveDirective"
},
{
"name": "bindingUpdated"
},
@ -242,6 +239,9 @@
{
"name": "concatStringsWithSpace"
},
{
"name": "configureViewWithDirective"
},
{
"name": "createDirectivesInstances"
},
@ -323,9 +323,6 @@
{
"name": "forwardRef"
},
{
"name": "generateExpandoInstructionBlock"
},
{
"name": "generateInitialInputs"
},
@ -431,9 +428,6 @@
{
"name": "getTView"
},
{
"name": "growHostVarsSpace"
},
{
"name": "handleError"
},
@ -605,6 +599,9 @@
{
"name": "refreshView"
},
{
"name": "registerHostBindingOpCodes"
},
{
"name": "registerPostOrderHooks"
},

View File

@ -119,7 +119,7 @@ const ShapeOfTView: ShapeOf<TView> = {
staticViewQueries: true,
staticContentQueries: true,
firstChild: true,
expandoInstructions: true,
hostBindingOpCodes: true,
directiveRegistry: true,
pipeRegistry: true,
preOrderHooks: true,