refactor(ivy): move directives into separate array (#22918)

PR Close #22918
This commit is contained in:
Kara Erickson
2018-03-21 15:10:34 -07:00
committed by Matias Niemelä
parent 34981063ec
commit 49396ca2ae
28 changed files with 544 additions and 441 deletions

View File

@ -48,7 +48,7 @@ export type Sanitizer = (value: any) => string;
*
* Saved here to avoid re-instantiating an array on every change detection run.
*/
const rootDirectiveIndices = [1, 0];
const rootDirectiveIndices = [0, 0];
/**
@ -95,9 +95,8 @@ let isParent: boolean;
* Static data that corresponds to the instance-specific data array on an LView.
*
* Each node's static data is stored in tData at the same index that it's stored
* in the data array. Each directive's definition is stored here at the same index
* as its directive instance in the data array. Any nodes that do not have static
* data store a null value in tData to avoid a sparse array.
* in the data array. Any nodes that do not have static data store a null value in
* tData to avoid a sparse array.
*/
let tData: TData;
@ -134,6 +133,14 @@ export function getCreationMode(): boolean {
*/
let data: any[];
/**
* An array of directive instances in the current view.
*
* These must be stored separately from LNodes because their presence is
* unknown at compile-time and thus space cannot be reserved in data[].
*/
let directives: any[]|null;
/**
* Points to the next binding index to read or write to.
*/
@ -188,6 +195,7 @@ const enum BindingDirection {
export function enterView(newView: LView, host: LElementNode | LViewNode | null): LView {
const oldView = currentView;
data = newView && newView.data;
directives = newView && newView.directives;
bindingIndex = newView && newView.bindingStartIndex || 0;
tData = newView && newView.tView.data;
creationMode = newView && (newView.flags & LViewFlags.CreationMode) === LViewFlags.CreationMode;
@ -214,8 +222,7 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
export function leaveView(newView: LView): void {
if (!checkNoChangesMode) {
executeHooks(
currentView.data, currentView.tView.viewHooks, currentView.tView.viewCheckHooks,
creationMode);
directives !, currentView.tView.viewHooks, currentView.tView.viewCheckHooks, creationMode);
}
// Views should be clean and in update mode after being checked, so these bits are cleared
currentView.flags &= ~(LViewFlags.CreationMode | LViewFlags.Dirty);
@ -229,7 +236,6 @@ function refreshDirectives() {
const tView = currentView.tView;
// This needs to be set before children are processed to support recursive components
// so to refresh the component, refresh() needs to be called with (1, 0)
tView.firstTemplatePass = firstTemplatePass = false;
setHostBindings(tView.hostBindings);
@ -239,11 +245,11 @@ function refreshDirectives() {
/** Sets the host bindings for the current view. */
function setHostBindings(bindings: number[] | null): void {
if (bindings != null) {
const defs = currentView.tView.directives !;
for (let i = 0; i < bindings.length; i += 2) {
const dirIndex = bindings[i];
const elementIndex = bindings[i | 1];
const def = tData[dirIndex] as DirectiveDef<any>;
def.hostBindings && def.hostBindings(dirIndex, elementIndex);
const def = defs[dirIndex] as DirectiveDef<any>;
def.hostBindings && def.hostBindings(dirIndex, bindings[i | 1]);
}
}
}
@ -251,8 +257,8 @@ function setHostBindings(bindings: number[] | null): void {
/** Refreshes child components in the current view. */
function refreshChildComponents(components: number[] | null): void {
if (components != null) {
for (let i = 0; i < components.length; i++) {
componentRefresh(components[i] + 1, components[i]);
for (let i = 0; i < components.length; i += 2) {
componentRefresh(components[i], components[i | 1]);
}
}
}
@ -261,7 +267,7 @@ function executeInitAndContentHooks(): void {
if (!checkNoChangesMode) {
const tView = currentView.tView;
executeInitHooks(currentView, tView, creationMode);
executeHooks(currentView.data, tView.contentHooks, tView.contentCheckHooks, creationMode);
executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode);
}
}
@ -274,6 +280,7 @@ export function createLView(
flags: flags | LViewFlags.CreationMode | LViewFlags.Attached,
node: null !, // until we initialize it in createNode.
data: [],
directives: null,
tView: tView,
cleanup: null,
renderer: renderer,
@ -451,9 +458,10 @@ export function renderComponentOrTemplate<T>(
} else {
executeInitAndContentHooks();
// Element was stored at 0 and directive was stored at 1 in renderComponent
// Element was stored at 0 in data and directive was stored at 0 in directives
// in renderComponent()
setHostBindings(rootDirectiveIndices);
componentRefresh(1, 0);
componentRefresh(0, 0);
}
} finally {
if (rendererFactory.end) {
@ -497,10 +505,14 @@ export function elementStart(
let hostComponentDef: ComponentDef<any>|null = null;
let name = nameOrComponentType as string;
let directiveIndex = getNextDirectiveIndex(index);
if (isHostElement) {
hostComponentDef = firstTemplatePass ?
(nameOrComponentType as ComponentType<any>).ngComponentDef :
tData[index + 1] as ComponentDef<any>;
currentView.tView.directives ![directiveIndex] as ComponentDef<any>;
name = hostComponentDef !.tag;
}
@ -524,8 +536,8 @@ export function elementStart(
node = createLNode(index, LNodeType.Element, native, componentView);
if (node.tNode == null) {
const localNames: (string | number)[]|null =
findMatchingLocalNames(hostComponentDef, localRefs, isHostElement ? index + 1 : -1, '');
const localNames: (string | number)[]|null = findMatchingLocalNames(
hostComponentDef, localRefs, isHostElement ? directiveIndex : -1, '');
ngDevMode && assertDataInRange(index - 1);
node.tNode = tData[index] = createTNode(name, attrs || null, null, localNames);
}
@ -533,37 +545,34 @@ export function elementStart(
if (attrs) setUpAttributes(native, attrs);
appendChild(node.parent !, native, currentView);
const elementIndex = index;
if (hostComponentDef) {
// TODO(mhevery): This assumes that the directives come in correct order, which
// is not guaranteed. Must be refactored to take it into account.
const instance = hostComponentDef.factory();
directiveCreate(++index, instance, hostComponentDef, null);
directiveCreate(directiveIndex, index, instance, hostComponentDef, null);
initChangeDetectorIfExisting(node.nodeInjector, instance);
queueComponentIndexForCheck(elementIndex);
if (hostComponentDef.hostBindings) queueHostBindingForCheck(index, elementIndex);
queueComponentIndexForCheck(directiveIndex, index);
directiveIndex++;
}
hack_declareDirectives(index, elementIndex, directiveTypes, localRefs);
hack_declareDirectives(directiveIndex, index, directiveTypes, localRefs);
}
}
return native;
}
/** Stores index of component's host element so it will be queued for view refresh during CD. */
function queueComponentIndexForCheck(elIndex: number): void {
function queueComponentIndexForCheck(dirIndex: number, elIndex: number): void {
if (firstTemplatePass) {
(currentView.tView.components || (currentView.tView.components = [])).push(elIndex);
(currentView.tView.components || (currentView.tView.components = [])).push(dirIndex, elIndex);
}
}
/** Stores index of directive and host element so it will be queued for binding refresh during CD.
*/
function queueHostBindingForCheck(dirIndex: number, elIndex: number): void {
if (firstTemplatePass) {
(currentView.tView.hostBindings || (currentView.tView.hostBindings = [
])).push(dirIndex, elIndex);
}
ngDevMode &&
assertEqual(firstTemplatePass, true, 'Should only be called in first template pass.');
(currentView.tView.hostBindings || (currentView.tView.hostBindings = [])).push(dirIndex, elIndex);
}
/** Sets the context for a ChangeDetectorRef to the given instance. */
@ -578,20 +587,19 @@ export function initChangeDetectorIfExisting(injector: LInjector | null, instanc
* come in the correct order for DI.
*/
function hack_declareDirectives(
index: number, elIndex: number, directiveTypes: DirectiveType<any>[] | null | undefined,
index: number, elementIndex: number, directiveTypes: DirectiveType<any>[] | null | undefined,
localRefs: string[] | null | undefined, ) {
if (directiveTypes) {
const defs = currentView.tView.directives !;
// TODO(mhevery): This assumes that the directives come in correct order, which
// is not guaranteed. Must be refactored to take it into account.
for (let i = 0; i < directiveTypes.length; i++) {
index++;
const directiveType = directiveTypes[i];
const directiveDef =
firstTemplatePass ? directiveType.ngDirectiveDef : tData[index] as DirectiveDef<any>;
const localNames =
firstTemplatePass ? findMatchingLocalNames(directiveDef, localRefs, index) : null;
directiveCreate(index, directiveDef.factory(), directiveDef, localNames);
if (directiveDef.hostBindings) queueHostBindingForCheck(index, elIndex);
firstTemplatePass ? directiveType.ngDirectiveDef : defs[index] as DirectiveDef<any>;
directiveCreate(index, elementIndex, directiveDef.factory(), directiveDef, localRefs);
index++;
}
}
}
@ -633,6 +641,7 @@ function getOrCreateTView(template: ComponentTemplate<any>): TView {
export function createTView(): TView {
return {
data: [],
directives: null,
firstTemplatePass: true,
initHooks: null,
checkHooks: null,
@ -641,6 +650,7 @@ export function createTView(): TView {
viewHooks: null,
viewCheckHooks: null,
destroyHooks: null,
pipeDestroyHooks: null,
hostBindings: null,
components: null
};
@ -758,8 +768,8 @@ export function listener(
*/
function createOutput(outputs: PropertyAliasValue, listener: Function): void {
for (let i = 0; i < outputs.length; i += 2) {
ngDevMode && assertDataInRange(outputs[i] as number);
const subscription = data[outputs[i] as number][outputs[i | 1]].subscribe(listener);
ngDevMode && assertDataInRange(outputs[i] as number, directives !);
const subscription = directives ![outputs[i] as number][outputs[i | 1]].subscribe(listener);
cleanup !.push(subscription.unsubscribe, subscription);
}
}
@ -874,8 +884,8 @@ function createTNode(
*/
function setInputsForProperty(inputs: PropertyAliasValue, value: any): void {
for (let i = 0; i < inputs.length; i += 2) {
ngDevMode && assertDataInRange(inputs[i] as number);
data[inputs[i] as number][inputs[i | 1]] = value;
ngDevMode && assertDataInRange(inputs[i] as number, directives !);
directives ![inputs[i] as number][inputs[i | 1]] = value;
}
}
@ -894,9 +904,10 @@ function generatePropertyAliases(
if (size > 0) {
const start = tNodeFlags >> TNodeFlags.INDX_SHIFT;
const isInput = direction === BindingDirection.Input;
const defs = currentView.tView.directives !;
for (let i = start, ii = start + size; i < ii; i++) {
const directiveDef = tData ![i] as DirectiveDef<any>;
const directiveDef = defs[i] as DirectiveDef<any>;
const propertyAliasMap: {[publicName: string]: string} =
isInput ? directiveDef.inputs : directiveDef.outputs;
for (let publicName in propertyAliasMap) {
@ -1090,32 +1101,38 @@ export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
* NOTE: directives can be created in order other than the index order. They can also
* be retrieved before they are created in which case the value will be null.
*
* @param index Each directive in a `View` will have a unique index. Directives can
* be created or retrieved out of order.
* @param index Index to save the directive in the directives array
* @param elementIndex Index of the host element in the data array
* @param directive The directive instance.
* @param directiveDef DirectiveDef object which contains information about the template.
* @param localNames Names under which a query can retrieve the directive instance
* @param localRefs Names under which a query can retrieve the directive instance
*/
export function directiveCreate<T>(
index: number, directive: T, directiveDef: DirectiveDef<T>,
localNames?: (string | number)[] | null): T {
index: number, elementIndex: number, directive: T, directiveDef: DirectiveDef<T>,
localRefs?: string[] | null): T {
const instance = baseDirectiveCreate(index, directive, directiveDef);
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
const tNode: TNode|null = previousOrParentNode.tNode !;
if (firstTemplatePass && localNames) {
tNode.localNames = tNode.localNames ? tNode.localNames.concat(localNames) : localNames;
if (firstTemplatePass) {
// Init hooks are queued now so ngOnInit is called in host components before
// any projected components.
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
if (directiveDef.hostBindings) queueHostBindingForCheck(index, elementIndex);
if (localRefs) {
const localNames = findMatchingLocalNames(directiveDef, localRefs, index);
tNode.localNames =
localNames && tNode.localNames ? tNode.localNames.concat(localNames) : localNames;
}
}
if (tNode && tNode.attrs) {
setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode);
setInputsFromAttrs<T>(index, instance, directiveDef.inputs, tNode);
}
// Init hooks are queued now so ngOnInit is called in host components before
// any projected components.
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
return instance;
}
@ -1127,25 +1144,27 @@ export function directiveCreate<T>(
*/
export function baseDirectiveCreate<T>(
index: number, directive: T, directiveDef: DirectiveDef<T>): T {
let instance;
ngDevMode &&
assertNull(currentView.bindingStartIndex, 'directives should be created before any bindings');
ngDevMode && assertPreviousIsParent();
if (firstTemplatePass) {
const flags = previousOrParentNode.tNode !.flags;
previousOrParentNode.tNode !.flags =
(flags & TNodeFlags.SIZE_MASK) === 0 ? (index << TNodeFlags.INDX_SHIFT) | 1 : flags + 1;
}
ngDevMode && assertDataInRange(index - 1);
Object.defineProperty(
directive, NG_HOST_SYMBOL, {enumerable: false, value: previousOrParentNode});
data[index] = instance = directive;
if (directives == null) currentView.directives = directives = [];
if (index >= tData.length) {
tData[index] = directiveDef !;
ngDevMode && assertDataNext(index, directives);
directives[index] = directive;
if (firstTemplatePass) {
const defs = currentView.tView.directives || (currentView.tView.directives = []);
ngDevMode && assertDataNext(index, defs);
defs[index] = (directiveDef);
const flags = previousOrParentNode.tNode !.flags;
previousOrParentNode.tNode !.flags =
(flags & TNodeFlags.SIZE_MASK) === 0 ? (index << TNodeFlags.INDX_SHIFT) | 1 : flags + 1;
}
const diPublic = directiveDef !.diPublic;
@ -1158,19 +1177,19 @@ export function baseDirectiveCreate<T>(
(previousOrParentNode as LElementNode).native, directiveDef !.attributes as string[]);
}
return instance;
return directive;
}
/**
* Sets initial input properties on directive instances from attribute data
*
* @param directiveIndex Index of the directive in directives array
* @param instance Instance of the directive on which to set the initial inputs
* @param inputs The list of inputs from the directive def
* @param tNode The static data for this node
*/
function setInputsFromAttrs<T>(instance: T, inputs: {[key: string]: string}, tNode: TNode): void {
const directiveIndex = (previousOrParentNode.tNode !.flags & TNodeFlags.SIZE_MASK) - 1;
function setInputsFromAttrs<T>(
directiveIndex: number, instance: T, inputs: {[key: string]: string}, tNode: TNode): void {
let initialInputData = tNode.initialInputs as InitialInputData | undefined;
if (initialInputData === undefined || directiveIndex >= initialInputData.length) {
initialInputData = generateInitialInputs(directiveIndex, inputs, tNode);
@ -1266,7 +1285,7 @@ export function container(
// Containers are added to the current view tree instead of their embedded views
// because views can be removed and re-inserted.
addToViewTree(node.data);
hack_declareDirectives(index, index, directiveTypes, localRefs);
hack_declareDirectives(getNextDirectiveIndex(index), index, directiveTypes, localRefs);
isParent = false;
ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container);
@ -1279,6 +1298,12 @@ export function container(
}
}
/** Retrieves the next directive index to write */
function getNextDirectiveIndex(index: number): number {
return firstTemplatePass ? (directives ? directives.length : 0) :
(tData[index] as TNode).flags >> TNodeFlags.INDX_SHIFT;
}
/**
* Sets a container up to receive views.
*
@ -1461,10 +1486,10 @@ export function componentRefresh<T>(directiveIndex: number, elementIndex: number
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
if (viewAttached(hostView) && hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
ngDevMode && assertDataInRange(directiveIndex);
const template = (tData[directiveIndex] as ComponentDef<any>).template;
ngDevMode && assertDataInRange(directiveIndex, directives !);
const template = (currentView.tView.directives ![directiveIndex] as ComponentDef<any>).template;
detectChangesInternal(
hostView, element, template, getDirectiveInstance<T>(data[directiveIndex]));
hostView, element, template, getDirectiveInstance<T>(directives ![directiveIndex]));
}
}
@ -1770,7 +1795,7 @@ export function detectChanges<T>(component: T): void {
const hostNode = _getComponentHostLElementNode(component);
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
const componentIndex = hostNode.tNode !.flags >> TNodeFlags.INDX_SHIFT;
const template = (hostNode.view.tView.data[componentIndex] as ComponentDef<T>).template;
const template = (hostNode.view.tView.directives ![componentIndex] as ComponentDef<T>).template;
detectChangesInternal(hostNode.data as LView, hostNode, template, component);
}
@ -2035,10 +2060,17 @@ export function store<T>(index: number, value: T): void {
/** Retrieves a value from the `data`. */
export function load<T>(index: number): T {
ngDevMode && assertDataInRange(index, data);
ngDevMode && assertDataInRange(index);
return data[index];
}
/** Retrieves a value from the `directives` array. */
export function loadDirective<T>(index: number): T {
ngDevMode && assertNotNull(directives, 'Directives array should be defined if reading a dir.');
ngDevMode && assertDataInRange(index, directives !);
return directives ![index];
}
/** Gets the current binding value and increments the binding index. */
export function consumeBinding(): any {
ngDevMode && assertDataInRange(bindingIndex);
@ -2087,7 +2119,7 @@ export function getTView(): TView {
}
export function getDirectiveInstance<T>(instanceOrArray: T | [T]): T {
// Directives with content queries store an array in data[directiveIndex]
// Directives with content queries store an array in directives[directiveIndex]
// with the instance as the first index
return Array.isArray(instanceOrArray) ? instanceOrArray[0] : instanceOrArray;
}
@ -2105,8 +2137,9 @@ function assertDataInRange(index: number, arr?: any[]) {
assertLessThan(index, arr ? arr.length : 0, 'index expected to be a valid data index');
}
function assertDataNext(index: number) {
assertEqual(data.length, index, 'index expected to be at the end of data');
function assertDataNext(index: number, arr?: any[]) {
if (arr == null) arr = data;
assertEqual(arr.length, index, 'index expected to be at the end of arr');
}
export function _getComponentHostLElementNode<T>(component: T): LElementNode {