Skip to content

Commit 8fdb1e0

Browse files
karaIgorMinar
authored andcommitted
refactor(core): store directive defs in static data (#20855)
PR Close #20855
1 parent f3d38ce commit 8fdb1e0

17 files changed

+397
-384
lines changed

packages/core/src/render3/component.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,12 @@ export function renderComponent<T>(
143143
const renderer = opts.renderer || document;
144144
const componentDef = componentType.ngComponentDef;
145145
let component: T;
146-
const oldView = enterView(createViewState(-1, renderer), null);
146+
const oldView = enterView(createViewState(-1, renderer, []), null);
147147
try {
148-
elementHost(opts.host || componentDef.tag);
149-
component = directiveCreate(0, componentDef.n(), componentDef);
148+
// Create element node at index 0 in data array
149+
elementHost(opts.host || componentDef.tag, componentDef);
150+
// Create directive instance with n() and store at index 1 in data array (el is 0)
151+
component = directiveCreate(1, componentDef.n(), componentDef);
150152
} finally {
151153
leaveView(oldView);
152154
}
@@ -165,7 +167,9 @@ export function detectChanges<T>(component: T) {
165167
ngDevMode && assertNotNull(hostNode.data, 'hostNode.data');
166168
const oldView = enterView(hostNode.view !, hostNode);
167169
try {
168-
(component.constructor as ComponentType<T>).ngComponentDef.r(0, 0);
170+
// Element was stored at 0 and directive was stored at 1 in renderComponent
171+
// so to refresh the component, r() needs to be called with (1, 0)
172+
(component.constructor as ComponentType<T>).ngComponentDef.r(1, 0);
169173
isDirty = false;
170174
} finally {
171175
leaveView(oldView);

packages/core/src/render3/di.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {ComponentFactory, ComponentRef as IComponentRef, ElementRef as IElementRef, EmbeddedViewRef as IEmbeddedViewRef, Injector, NgModuleRef as INgModuleRef, TemplateRef as ITemplateRef, Type, ViewContainerRef as IViewContainerRef, ViewRef as IViewRef} from '../core';
1010
import {BLOOM_SIZE, NG_ELEMENT_ID, getOrCreateNodeInjector} from './instructions';
1111
import {LContainer, LNodeFlags, LNodeInjector} from './interfaces';
12-
import {ComponentTemplate} from './public_interfaces';
12+
import {ComponentTemplate, DirectiveDef} from './public_interfaces';
1313
import {stringify} from './util';
1414

1515
export const enum InjectFlags {
@@ -43,13 +43,11 @@ export function inject<T>(token: Type<T>, flags?: InjectFlags): T {
4343
if (size !== 0) {
4444
size = size >> LNodeFlags.SIZE_SHIFT;
4545
const start = flags >> LNodeFlags.INDX_SHIFT;
46-
const directives = node.view.directives;
47-
if (directives) {
48-
for (let i = start, ii = start + size; i < ii; i++) {
49-
const def = directives[(i << 1) | 1];
50-
if (def.diPublic && def.type == token) {
51-
return directives[i << 1];
52-
}
46+
const ngStaticData = node.view.ngStaticData;
47+
for (let i = start, ii = start + size; i < ii; i++) {
48+
const def = ngStaticData[i] as DirectiveDef<any>;
49+
if (def.diPublic && def.type == token) {
50+
return node.view.data[i];
5351
}
5452
}
5553
}

packages/core/src/render3/instructions.ts

Lines changed: 80 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import './ng_dev_mode';
1010

1111
import {Type} from '../core';
1212
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull} from './assert';
13-
import {CSSSelector, ContainerState, InitialInputData, InitialInputs, LContainer, LContainerStatic, LElement, LNode, LNodeFlags, LNodeInjector, LNodeStatic, LProjection, LText, LView, MinificationData, MinificationDataValue, ProjectionState, QueryState, ViewState} from './interfaces';
13+
import {
14+
CSSSelector, ContainerState, InitialInputData, InitialInputs, LContainer, LContainerStatic, LElement, LNode,
15+
LNodeFlags, LNodeInjector, LNodeStatic, LProjection, LText, LView, MinificationData, MinificationDataValue,
16+
ProjectionState, QueryState, ViewState, NgStaticData
17+
} from './interfaces';
1418
import {assertNodeType} from './node_assert';
1519
import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation';
1620
import {isNodeMatchingSelector} from './node_selector_matcher';
@@ -48,12 +52,12 @@ let isParent: boolean;
4852
* in the data array. Any nodes that do not have static data store a null
4953
* value to avoid a sparse array.
5054
*/
51-
let ngStaticData: (LNodeStatic | null)[];
55+
let ngStaticData: NgStaticData;
5256

5357
/**
5458
* State of the current view being processed.
5559
*/
56-
let currentView: ViewState = createViewState(null !, null !);
60+
let currentView: ViewState = createViewState(null !, null !, []);
5761

5862
let currentQuery: QueryState|null;
5963

@@ -68,18 +72,6 @@ let creationMode: boolean;
6872
*/
6973
let data: any[];
7074

71-
/**
72-
* An array of directives in the current view
73-
*
74-
* even indices: contain the directive instance.
75-
* odd indices: contain the directive def
76-
*
77-
* We must store the directive def (rather than token | null)
78-
* because we need to be able to access the inputs and outputs
79-
* of directives that aren't diPublic.
80-
*/
81-
let directives: any[];
82-
8375
/**
8476
* Points to the next binding index to read or write to.
8577
*/
@@ -117,9 +109,9 @@ let cleanup: any[]|null;
117109
*/
118110
export function enterView(newViewState: ViewState, host: LElement | LView | null): ViewState {
119111
const oldViewState = currentView;
120-
directives = newViewState.directives;
121112
data = newViewState.data;
122113
bindingIndex = newViewState.bindingStartIndex || 0;
114+
ngStaticData = newViewState.ngStaticData;
123115

124116
if (creationMode = !data) {
125117
// Absence of data implies creationMode.
@@ -139,13 +131,13 @@ export function enterView(newViewState: ViewState, host: LElement | LView | null
139131

140132
export const leaveView: (newViewState: ViewState) => void = enterView as any;
141133

142-
export function createViewState(viewId: number, renderer: Renderer3): ViewState {
134+
export function createViewState(viewId: number, renderer: Renderer3, ngStaticData: NgStaticData): ViewState {
143135
const newView = {
144136
parent: currentView,
145137
id: viewId, // -1 for component views
146138
node: null !, // until we initialize it in createNode.
147139
data: null !, // Hack use as a marker for creationMode
148-
directives: [],
140+
ngStaticData: ngStaticData,
149141
cleanup: null,
150142
renderer: renderer,
151143
child: null,
@@ -206,10 +198,10 @@ export function createLNode(
206198
data[index] = node;
207199

208200
// Every node adds a value to the static data array to avoid a sparse array
209-
if (ngStaticData && index >= ngStaticData.length) {
201+
if (index >= ngStaticData.length) {
210202
ngStaticData[index] = null;
211-
} else if (ngStaticData) {
212-
node.staticData = ngStaticData[index];
203+
} else {
204+
node.staticData = ngStaticData[index] as LNodeStatic;
213205
}
214206

215207
// Now link ourselves into the tree.
@@ -248,9 +240,9 @@ export function createLNode(
248240
export function renderTemplate<T>(host: LElement, template: ComponentTemplate<T>, context: T) {
249241
const hostView = host.data !;
250242
ngDevMode && assertNotEqual(hostView, null, 'hostView');
243+
hostView.ngStaticData = getTemplateStatic(template);
251244
const oldView = enterView(hostView, host);
252245
try {
253-
ngStaticData = template.ngStaticData || (template.ngStaticData = [] as never);
254246
template(context, creationMode);
255247
} finally {
256248
leaveView(oldView);
@@ -345,13 +337,19 @@ export function elementCreate(
345337
throw 'for now name is required';
346338
} else {
347339
native = renderer.createElement(name);
340+
341+
let componentView: ViewState | null = null;
342+
if (isHostElement) {
343+
const ngStaticData = getTemplateStatic((nameOrComponentDef as ComponentDef<any>).template);
344+
componentView = addToViewTree(createViewState(-1, renderer, ngStaticData));
345+
}
346+
348347
// Only component views should be added to the view tree directly. Embedded views are
349348
// accessed through their containers because they may be removed / re-added later.
350-
node = createLNode(
351-
index, LNodeFlags.Element, native,
352-
isHostElement ? addToViewTree(createViewState(-1, renderer)) : null);
349+
node = createLNode(index, LNodeFlags.Element, native, componentView);
353350

354351
if (node.staticData == null) {
352+
ngDevMode && assertDataInRange(index - 1);
355353
node.staticData = ngStaticData[index] = createStaticData(name, attrs || null, null);
356354
}
357355

@@ -362,6 +360,17 @@ export function elementCreate(
362360
return native;
363361
}
364362

363+
/**
364+
* Gets static data from a template function or creates a new static
365+
* data array if it doesn't already exist.
366+
*
367+
* @param template The template from which to get static data
368+
* @returns NgStaticData
369+
*/
370+
function getTemplateStatic(template: ComponentTemplate<any>): NgStaticData {
371+
return template.ngStaticData || (template.ngStaticData = [] as never);
372+
}
373+
365374
function setUpAttributes(native: RElement, attrs: string[]): void {
366375
ngDevMode && assertEqual(attrs.length % 2, 0, 'attrs.length % 2');
367376
const isFnRenderer = (renderer as Renderer3Fn).setAttribute;
@@ -381,7 +390,7 @@ export function createError(text: string, token: any) {
381390
*
382391
* @param elementOrSelector Render element or CSS selector to locate the element.
383392
*/
384-
export function elementHost(elementOrSelector: RElement | string) {
393+
export function elementHost(elementOrSelector: RElement | string, def: ComponentDef<any>) {
385394
ngDevMode && assertDataInRange(-1);
386395
const rNode = typeof elementOrSelector === 'string' ?
387396
((renderer as Renderer3Fn).selectRootElement ?
@@ -395,7 +404,7 @@ export function elementHost(elementOrSelector: RElement | string) {
395404
throw createError('Host node is required:', elementOrSelector);
396405
}
397406
}
398-
createLNode(0, LNodeFlags.Element, rNode, createViewState(-1, renderer));
407+
createLNode(0, LNodeFlags.Element, rNode, createViewState(-1, renderer, getTemplateStatic(def.template)));
399408
}
400409

401410

@@ -446,9 +455,9 @@ export function listenerCreate(
446455
*/
447456
function outputCreate(outputs: (number | string)[], listener: Function): void {
448457
for (let i = 0; i < outputs.length; i += 2) {
449-
ngDevMode && assertDirectivesInRange((outputs[i] as number) << 1);
458+
ngDevMode && assertDataInRange(outputs[i] as number);
450459
const subscription =
451-
directives[(outputs[i] as number) << 1][outputs[i | 1]].subscribe(listener);
460+
data[outputs[i] as number][outputs[i | 1]].subscribe(listener);
452461
cleanup !.push(subscription.unsubscribe, subscription);
453462
}
454463
}
@@ -512,8 +521,7 @@ export function elementProperty<T>(index: number, propName: string, value: T | N
512521

513522
let staticData: LNodeStatic|null = node.staticData !;
514523
// if staticData.inputs is undefined, a listener has created output staticData, but inputs haven't
515-
// yet been
516-
// checked
524+
// yet been checked
517525
if (staticData.inputs === undefined) {
518526
// mark inputs as checked
519527
staticData.inputs = null;
@@ -541,7 +549,8 @@ function createStaticData(
541549
attrs,
542550
initialInputs: undefined,
543551
inputs: undefined,
544-
outputs: undefined, containerStatic
552+
outputs: undefined,
553+
containerStatic: containerStatic
545554
};
546555
}
547556

@@ -551,8 +560,8 @@ function createStaticData(
551560
*/
552561
function setInputsForProperty(inputs: (number | string)[], value: any): void {
553562
for (let i = 0; i < inputs.length; i += 2) {
554-
ngDevMode && assertDirectivesInRange(inputs[i] as number << 1);
555-
directives[(inputs[i] as number) << 1][inputs[i | 1]] = value;
563+
ngDevMode && assertDataInRange(inputs[i] as number);
564+
data[inputs[i] as number][inputs[i | 1]] = value;
556565
}
557566
}
558567

@@ -568,7 +577,7 @@ function generateMinifiedData(flags: number, data: LNodeStatic, isInputData = fa
568577
const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT;
569578

570579
for (let i = start, ii = start + size; i < ii; i++) {
571-
const directiveDef: DirectiveDef<any> = directives[(i << 1) | 1];
580+
const directiveDef: DirectiveDef<any> = ngStaticData ![i] as DirectiveDef<any>;
572581
const minifiedPropertyMap: {[minifiedKey: string]: string} =
573582
isInputData ? directiveDef.inputs : directiveDef.outputs;
574583
for (let unminifiedKey in minifiedPropertyMap) {
@@ -716,11 +725,10 @@ export function directiveCreate<T>(index: number, directive: T, directiveDef: Di
716725
export function directiveCreate<T>(
717726
index: number, directive?: T, directiveDef?: DirectiveDef<T>): T {
718727
let instance;
719-
const index2 = index << 1;
720728
if (directive == null) {
721729
// return existing
722-
ngDevMode && assertDirectivesInRange(index2);
723-
instance = directives[index2];
730+
ngDevMode && assertDataInRange(index);
731+
instance = data[index];
724732
} else {
725733
ngDevMode && assertEqual(currentView.bindingStartIndex, null, 'bindingStartIndex');
726734
ngDevMode && assertPreviousIsParent();
@@ -734,19 +742,26 @@ export function directiveCreate<T>(
734742
}
735743
previousOrParentNode !.flags = flags;
736744

737-
ngDevMode && assertDirectivesInRange(index2 - 1);
745+
ngDevMode && assertDataInRange(index - 1);
738746
Object.defineProperty(
739747
directive, NG_HOST_SYMBOL, {enumerable: false, value: previousOrParentNode});
740-
directives[index2] = instance = directive;
741-
directives[index2 | 1] = directiveDef;
748+
data[index] = instance = directive;
749+
750+
if (index >= ngStaticData.length) {
751+
ngStaticData[index] = directiveDef !;
752+
}
753+
742754
const diPublic = directiveDef !.diPublic;
743755
if (diPublic) {
744756
diPublic(directiveDef !);
745757
}
746-
const nodeBindings: LNodeStatic|null = previousOrParentNode.staticData;
747-
if (nodeBindings && nodeBindings.attrs)
748-
setInputsFromAttrs<T>(instance, directiveDef !.inputs, nodeBindings);
758+
759+
const staticData: LNodeStatic|null = previousOrParentNode.staticData !;
760+
if (staticData && staticData.attrs) {
761+
setInputsFromAttrs<T>(instance, directiveDef !.inputs, staticData);
762+
}
749763
}
764+
750765
return instance;
751766
}
752767

@@ -936,31 +951,33 @@ export function viewCreate(viewBlockId: number): boolean {
936951
enterView((existingView as LView).data, previousOrParentNode as LView);
937952
} else {
938953
// When we create a new View, we always reset the state of the instructions.
939-
const newViewState = createViewState(viewBlockId, renderer);
954+
const newViewState = createViewState(viewBlockId, renderer, initViewStaticData(viewBlockId, container));
940955
enterView(newViewState, createLNode(null, LNodeFlags.View, null, newViewState));
941956
containerState.nextIndex++;
942957
}
943-
setNgStaticDataForView(viewBlockId);
944958

945959
return !viewUpdateMode;
946960
}
947961

948962
/**
949-
* Each embedded view needs to set the global ngStaticData variable to the static data for that
950-
* view.
951-
* Otherwise, the view's static data for a particular node would overwrite the static
952-
* data for a node in the view above it with the same index (since it's in the same template).
963+
* Initialize the static data for the active view.
964+
*
965+
* Each embedded view needs to set the global ngStaticData variable to the static data for
966+
* that view. Otherwise, the view's static data for a particular node would overwrite
967+
* the staticdata for a node in the view above it with the same index (since it's in the
968+
* same template).
953969
*
954970
* @param viewIndex The index of the view's static data in containerStatic
971+
* @param parent The parent container in which to look for the view's static data
972+
* @returns NgStaticData
955973
*/
956-
function setNgStaticDataForView(viewIndex: number): void {
957-
ngDevMode && assertNodeType(previousOrParentNode.parent !, LNodeFlags.Container);
958-
const containerStatic =
959-
(previousOrParentNode.parent !.staticData as LContainerStatic).containerStatic;
974+
function initViewStaticData(viewIndex: number, parent: LContainer): NgStaticData {
975+
ngDevMode && assertNodeType(parent, LNodeFlags.Container);
976+
const containerStatic = (parent !.staticData as LContainerStatic).containerStatic;
960977
if (viewIndex >= containerStatic.length || containerStatic[viewIndex] == null) {
961978
containerStatic[viewIndex] = [];
962979
}
963-
ngStaticData = containerStatic[viewIndex];
980+
return containerStatic[viewIndex];
964981
}
965982

966983
/**
@@ -998,18 +1015,14 @@ export const refreshComponent:
9981015
const element = data ![elementIndex] as LElement;
9991016
ngDevMode && assertNodeType(element, LNodeFlags.Element);
10001017
ngDevMode && assertNotEqual(element.data, null, 'isComponent');
1001-
ngDevMode && assertDirectivesInRange(directiveIndex << 1);
1018+
ngDevMode && assertDataInRange(directiveIndex);
10021019
const hostView = element.data !;
10031020
ngDevMode && assertNotEqual(hostView, null, 'hostView');
1004-
const directive = directives[directiveIndex << 1];
1021+
const directive = data[directiveIndex];
10051022
const oldView = enterView(hostView, element);
1006-
const oldNgStaticData = ngStaticData;
10071023
try {
1008-
const _template = template || this !.template;
1009-
ngStaticData = _template.ngStaticData || (_template.ngStaticData = [] as never);
1010-
_template(directive, creationMode);
1024+
(template || this !.template)(directive, creationMode);
10111025
} finally {
1012-
ngStaticData = oldNgStaticData;
10131026
leaveView(oldView);
10141027
}
10151028
};
@@ -1557,6 +1570,11 @@ function valueInData<T>(data: any[], index: number, value?: T): T {
15571570
if (value === undefined) {
15581571
value = data[index];
15591572
} else {
1573+
// We don't store any static data for local variables, so the first time
1574+
// we see the template, we should store as null to avoid a sparse array
1575+
if (index >= ngStaticData.length) {
1576+
ngStaticData[index] = null;
1577+
}
15601578
data[index] = value;
15611579
}
15621580
return value !;
@@ -1585,6 +1603,3 @@ function assertDataInRange(index: number, arr?: any[]) {
15851603
assertLessThan(arr ? arr.length : 0, index, 'data.length');
15861604
}
15871605

1588-
function assertDirectivesInRange(index: number) {
1589-
assertLessThan(directives ? directives.length : 0, index, 'directives.length');
1590-
}

0 commit comments

Comments
 (0)