refactor(ivy): move directives into separate array (#22918)
PR Close #22918
This commit is contained in:

committed by
Matias Niemelä

parent
34981063ec
commit
49396ca2ae
@ -30,6 +30,7 @@ export {
|
||||
T as ɵT,
|
||||
V as ɵV,
|
||||
Q as ɵQ,
|
||||
d as ɵd,
|
||||
P as ɵP,
|
||||
b as ɵb,
|
||||
i1 as ɵi1,
|
||||
@ -79,9 +80,9 @@ export {
|
||||
bypassSanitizationTrustScript as ɵbypassSanitizationTrustScript,
|
||||
bypassSanitizationTrustUrl as ɵbypassSanitizationTrustUrl,
|
||||
bypassSanitizationTrustResourceUrl as ɵbypassSanitizationTrustResourceUrl,
|
||||
sanitizeHtml as ɵsanitizeHtml,
|
||||
sanitizeStyle as ɵsanitizeStyle,
|
||||
sanitizeUrl as ɵsanitizeUrl,
|
||||
sanitizeResourceUrl as ɵsanitizeResourceUrl,
|
||||
sanitizeHtml as ɵsanitizeHtml,
|
||||
sanitizeStyle as ɵsanitizeStyle,
|
||||
sanitizeUrl as ɵsanitizeUrl,
|
||||
sanitizeResourceUrl as ɵsanitizeResourceUrl,
|
||||
} from './sanitization/sanitization';
|
||||
// clang-format on
|
||||
|
@ -140,9 +140,9 @@ export function renderComponent<T>(
|
||||
try {
|
||||
// Create element node at index 0 in data array
|
||||
elementNode = hostElement(hostNode, componentDef);
|
||||
// Create directive instance with factory() and store at index 1 in data array (el is 0)
|
||||
// Create directive instance with factory() and store at index 0 in directives array
|
||||
component = rootContext.component =
|
||||
baseDirectiveCreate(1, componentDef.factory(), componentDef) as T;
|
||||
baseDirectiveCreate(0, componentDef.factory(), componentDef) as T;
|
||||
initChangeDetectorIfExisting(elementNode.nodeInjector, component);
|
||||
} finally {
|
||||
// We must not use leaveView here because it will set creationMode to false too early,
|
||||
@ -172,8 +172,8 @@ export function renderComponent<T>(
|
||||
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
|
||||
const elementNode = _getComponentHostLElementNode(component);
|
||||
|
||||
// Root component is always created at dir index 1, after host element at 0
|
||||
queueInitHooks(1, def.onInit, def.doCheck, elementNode.view.tView);
|
||||
// Root component is always created at dir index 0
|
||||
queueInitHooks(0, def.onInit, def.doCheck, elementNode.view.tView);
|
||||
queueLifecycleHooks(elementNode.tNode !.flags, elementNode.view);
|
||||
}
|
||||
|
||||
|
@ -316,10 +316,11 @@ function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode):
|
||||
const hostInjector = hostNode.nodeInjector;
|
||||
const existingRef = hostInjector && hostInjector.changeDetectorRef;
|
||||
|
||||
return existingRef ? existingRef :
|
||||
createViewRef(
|
||||
hostNode.data as LView,
|
||||
hostNode.view.data[hostNode.tNode !.flags >> TNodeFlags.INDX_SHIFT]);
|
||||
return existingRef ?
|
||||
existingRef :
|
||||
createViewRef(
|
||||
hostNode.data as LView,
|
||||
hostNode.view.directives ![hostNode.tNode !.flags >> TNodeFlags.INDX_SHIFT]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,13 +395,13 @@ export function getOrCreateInjectable<T>(
|
||||
// nothing to the "left" of it so it doesn't need a mask.
|
||||
const start = flags >> TNodeFlags.INDX_SHIFT;
|
||||
|
||||
const tData = node.view.tView.data;
|
||||
const defs = node.view.tView.directives !;
|
||||
for (let i = start, ii = start + size; i < ii; i++) {
|
||||
// Get the definition for the directive at this index and, if it is injectable (diPublic),
|
||||
// and matches the given token, return the directive instance.
|
||||
const directiveDef = tData[i] as DirectiveDef<any>;
|
||||
const directiveDef = defs[i] as DirectiveDef<any>;
|
||||
if (directiveDef.diPublic && directiveDef.type == token) {
|
||||
return getDirectiveInstance(node.view.data[i]);
|
||||
return getDirectiveInstance(node.view.directives ![i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -530,7 +531,7 @@ export const QUERY_READ_FROM_NODE =
|
||||
(new ReadFromInjectorFn<any>((injector: LInjector, node: LNode, directiveIdx: number) => {
|
||||
ngDevMode && assertNodeOfPossibleTypes(node, LNodeType.Container, LNodeType.Element);
|
||||
if (directiveIdx > -1) {
|
||||
return node.view.data[directiveIdx];
|
||||
return node.view.directives ![directiveIdx];
|
||||
} else if (node.type === LNodeType.Element) {
|
||||
return getOrCreateElementRef(injector);
|
||||
} else if (node.type === LNodeType.Container) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {assertEqual} from './assert';
|
||||
import {DirectiveDef} from './interfaces/definition';
|
||||
import {TNodeFlags} from './interfaces/node';
|
||||
import {HookData, LView, LifecycleStage, TView} from './interfaces/view';
|
||||
@ -24,15 +25,15 @@ import {HookData, LView, LifecycleStage, TView} from './interfaces/view';
|
||||
*/
|
||||
export function queueInitHooks(
|
||||
index: number, onInit: (() => void) | null, doCheck: (() => void) | null, tView: TView): void {
|
||||
if (tView.firstTemplatePass === true) {
|
||||
if (onInit != null) {
|
||||
(tView.initHooks || (tView.initHooks = [])).push(index, onInit);
|
||||
}
|
||||
ngDevMode &&
|
||||
assertEqual(tView.firstTemplatePass, true, 'Should only be called on first template pass');
|
||||
if (onInit) {
|
||||
(tView.initHooks || (tView.initHooks = [])).push(index, onInit);
|
||||
}
|
||||
|
||||
if (doCheck != null) {
|
||||
(tView.initHooks || (tView.initHooks = [])).push(index, doCheck);
|
||||
(tView.checkHooks || (tView.checkHooks = [])).push(index, doCheck);
|
||||
}
|
||||
if (doCheck) {
|
||||
(tView.initHooks || (tView.initHooks = [])).push(index, doCheck);
|
||||
(tView.checkHooks || (tView.checkHooks = [])).push(index, doCheck);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +51,7 @@ export function queueLifecycleHooks(flags: number, currentView: LView): void {
|
||||
// directiveCreate) so we can preserve the current hook order. Content, view, and destroy
|
||||
// hooks for projected components and directives must be called *before* their hosts.
|
||||
for (let i = start; i < end; i++) {
|
||||
const def = (tView.data[i] as DirectiveDef<any>);
|
||||
const def = (tView.directives ![i] as DirectiveDef<any>);
|
||||
queueContentHooks(def, tView, i);
|
||||
queueViewHooks(def, tView, i);
|
||||
queueDestroyHooks(def, tView, i);
|
||||
@ -60,11 +61,11 @@ export function queueLifecycleHooks(flags: number, currentView: LView): void {
|
||||
|
||||
/** Queues afterContentInit and afterContentChecked hooks on TView */
|
||||
function queueContentHooks(def: DirectiveDef<any>, tView: TView, i: number): void {
|
||||
if (def.afterContentInit != null) {
|
||||
if (def.afterContentInit) {
|
||||
(tView.contentHooks || (tView.contentHooks = [])).push(i, def.afterContentInit);
|
||||
}
|
||||
|
||||
if (def.afterContentChecked != null) {
|
||||
if (def.afterContentChecked) {
|
||||
(tView.contentHooks || (tView.contentHooks = [])).push(i, def.afterContentChecked);
|
||||
(tView.contentCheckHooks || (tView.contentCheckHooks = [])).push(i, def.afterContentChecked);
|
||||
}
|
||||
@ -72,11 +73,11 @@ function queueContentHooks(def: DirectiveDef<any>, tView: TView, i: number): voi
|
||||
|
||||
/** Queues afterViewInit and afterViewChecked hooks on TView */
|
||||
function queueViewHooks(def: DirectiveDef<any>, tView: TView, i: number): void {
|
||||
if (def.afterViewInit != null) {
|
||||
if (def.afterViewInit) {
|
||||
(tView.viewHooks || (tView.viewHooks = [])).push(i, def.afterViewInit);
|
||||
}
|
||||
|
||||
if (def.afterViewChecked != null) {
|
||||
if (def.afterViewChecked) {
|
||||
(tView.viewHooks || (tView.viewHooks = [])).push(i, def.afterViewChecked);
|
||||
(tView.viewCheckHooks || (tView.viewCheckHooks = [])).push(i, def.afterViewChecked);
|
||||
}
|
||||
@ -96,7 +97,7 @@ function queueDestroyHooks(def: DirectiveDef<any>, tView: TView, i: number): voi
|
||||
*/
|
||||
export function executeInitHooks(currentView: LView, tView: TView, creationMode: boolean): void {
|
||||
if (currentView.lifecycleStage === LifecycleStage.INIT) {
|
||||
executeHooks(currentView.data, tView.initHooks, tView.checkHooks, creationMode);
|
||||
executeHooks(currentView.directives !, tView.initHooks, tView.checkHooks, creationMode);
|
||||
currentView.lifecycleStage = LifecycleStage.AFTER_INIT;
|
||||
}
|
||||
}
|
||||
@ -110,7 +111,7 @@ export function executeHooks(
|
||||
data: any[], allHooks: HookData | null, checkHooks: HookData | null,
|
||||
creationMode: boolean): void {
|
||||
const hooksToCall = creationMode ? allHooks : checkHooks;
|
||||
if (hooksToCall != null) {
|
||||
if (hooksToCall) {
|
||||
callHooks(data, hooksToCall);
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ export {
|
||||
listener as L,
|
||||
store as st,
|
||||
load as ld,
|
||||
loadDirective as d,
|
||||
|
||||
projection as P,
|
||||
projectionDef as pD,
|
||||
|
@ -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 {
|
||||
|
@ -7,12 +7,13 @@
|
||||
*/
|
||||
|
||||
import {LContainer} from './container';
|
||||
import {ComponentTemplate, DirectiveDef, PipeDef} from './definition';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, PipeDef} from './definition';
|
||||
import {LElementNode, LViewNode, TNode} from './node';
|
||||
import {LQueries} from './query';
|
||||
import {Renderer3} from './renderer';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* `LView` stores all of the information needed to process the instructions as
|
||||
* they are invoked from the template. Each embedded view and component view has its
|
||||
@ -131,7 +132,7 @@ export interface LView {
|
||||
* This array stores all element/text/container nodes created inside this view
|
||||
* and their bindings. Stored as an array rather than a linked list so we can
|
||||
* look up nodes directly in the case of forward declaration or bindings
|
||||
* (e.g. E(1))..
|
||||
* (e.g. E(1)).
|
||||
*
|
||||
* All bindings for a given view are stored in the order in which they
|
||||
* appear in the template, starting with `bindingStartIndex`.
|
||||
@ -140,6 +141,14 @@ export interface LView {
|
||||
*/
|
||||
readonly 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[].
|
||||
*/
|
||||
directives: any[]|null;
|
||||
|
||||
/**
|
||||
* The static data for this view. We need a reference to this so we can easily walk up the
|
||||
* node tree in DI and get the TView.data array associated with a node (where the
|
||||
@ -210,9 +219,17 @@ export interface LViewOrLContainer {
|
||||
* Stored on the template function as ngPrivateData.
|
||||
*/
|
||||
export interface TView {
|
||||
/** Static data equivalent of LView.data[]. Contains TNodes and directive defs. */
|
||||
/** Static data equivalent of LView.data[]. Contains TNodes. */
|
||||
data: TData;
|
||||
|
||||
/**
|
||||
* Directive and component defs for this view
|
||||
*
|
||||
* Defs are stored at the same index in TView.directives[] as their instances
|
||||
* are stored in LView.directives[]. This simplifies lookup in DI.
|
||||
*/
|
||||
directives: (ComponentDef<any>|DirectiveDef<any>)[]|null;
|
||||
|
||||
/** Whether or not this template has been processed. */
|
||||
firstTemplatePass: boolean;
|
||||
|
||||
@ -278,8 +295,22 @@ export interface TView {
|
||||
destroyHooks: HookData|null;
|
||||
|
||||
/**
|
||||
* A list of element indices for child components that will need to be refreshed when the
|
||||
* current view has finished its check.
|
||||
* Array of pipe ngOnDestroy hooks that should be executed when this view is destroyed.
|
||||
*
|
||||
* Even indices: Index of pipe in data
|
||||
* Odd indices: Hook function
|
||||
*
|
||||
* These must be stored separately from directive destroy hooks because their contexts
|
||||
* are stored in data.
|
||||
*/
|
||||
pipeDestroyHooks: HookData|null;
|
||||
|
||||
/**
|
||||
* A list of directive and element indices for child components that will need to be
|
||||
* refreshed when the current view has finished its check.
|
||||
*
|
||||
* Even indices: Directive indices
|
||||
* Odd indices: Element indices
|
||||
*/
|
||||
components: number[]|null;
|
||||
|
||||
@ -340,11 +371,11 @@ export const enum LifecycleStage {
|
||||
* 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/pipe's definition is stored here at the same index
|
||||
* as its directive/pipe instance in the data array. Any nodes that do not have static
|
||||
* in the data array. Each pipe's definition is stored here at the same index
|
||||
* as its pipe instance in the data array. Any nodes that do not have static
|
||||
* data store a null value in tData to avoid a sparse array.
|
||||
*/
|
||||
export type TData = (TNode | DirectiveDef<any>| PipeDef<any>| null)[];
|
||||
export type TData = (TNode | PipeDef<any>| null)[];
|
||||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
|
@ -361,6 +361,7 @@ export function getParentState(state: LViewOrLContainer, rootView: LView): LView
|
||||
function cleanUpView(view: LView): void {
|
||||
removeListeners(view);
|
||||
executeOnDestroys(view);
|
||||
executePipeOnDestroys(view);
|
||||
}
|
||||
|
||||
/** Removes listeners and unsubscribes from output subscriptions */
|
||||
@ -384,7 +385,15 @@ function executeOnDestroys(view: LView): void {
|
||||
const tView = view.tView;
|
||||
let destroyHooks: HookData|null;
|
||||
if (tView != null && (destroyHooks = tView.destroyHooks) != null) {
|
||||
callHooks(view.data, destroyHooks);
|
||||
callHooks(view.directives !, destroyHooks);
|
||||
}
|
||||
}
|
||||
|
||||
/** Calls pipe destroy hooks for this view */
|
||||
function executePipeOnDestroys(view: LView): void {
|
||||
const pipeDestroyHooks = view.tView && view.tView.pipeDestroyHooks;
|
||||
if (pipeDestroyHooks) {
|
||||
callHooks(view.data !, pipeDestroyHooks);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,8 +25,8 @@ export function pipe<T>(index: number, pipeDef: PipeDef<T>, firstInstance?: T):
|
||||
const tView = getTView();
|
||||
if (tView.firstTemplatePass) {
|
||||
tView.data[index] = pipeDef;
|
||||
if (pipeDef.onDestroy != null) {
|
||||
(tView.destroyHooks || (tView.destroyHooks = [])).push(index, pipeDef.onDestroy);
|
||||
if (pipeDef.onDestroy) {
|
||||
(tView.pipeDestroyHooks || (tView.pipeDestroyHooks = [])).push(index, pipeDef.onDestroy);
|
||||
}
|
||||
}
|
||||
const pipeInstance = pipeDef.pure && firstInstance ? firstInstance : pipeDef.n();
|
||||
|
@ -194,11 +194,11 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null {
|
||||
* @returns Index of a found directive or null when none found.
|
||||
*/
|
||||
function geIdxOfMatchingDirective(node: LNode, type: Type<any>): number|null {
|
||||
const tData = node.view.tView.data;
|
||||
const defs = node.view.tView.directives !;
|
||||
const flags = node.tNode !.flags;
|
||||
for (let i = flags >> TNodeFlags.INDX_SHIFT, ii = i + (flags & TNodeFlags.SIZE_MASK); i < ii;
|
||||
i++) {
|
||||
const def = tData[i] as DirectiveDef<any>;
|
||||
const def = defs[i] as DirectiveDef<any>;
|
||||
if (def.diPublic && def.type === type) {
|
||||
return i;
|
||||
}
|
||||
@ -214,7 +214,7 @@ function readFromNodeInjector(
|
||||
} else {
|
||||
const matchingIdx = geIdxOfMatchingDirective(node, read as Type<any>);
|
||||
if (matchingIdx !== null) {
|
||||
return node.view.data[matchingIdx];
|
||||
return node.view.directives ![matchingIdx];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
Reference in New Issue
Block a user