/
view_utils.ts
287 lines (259 loc) Β· 10.5 KB
/
view_utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {RuntimeError, RuntimeErrorCode} from '../../errors';
import {assertDefined, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertLessThan} from '../../util/assert';
import {assertTNode, assertTNodeForLView} from '../assert';
import {HAS_CHILD_VIEWS_TO_REFRESH, LContainer, TYPE} from '../interfaces/container';
import {TConstants, TNode} from '../interfaces/node';
import {RNode} from '../interfaces/renderer_dom';
import {isLContainer, isLView} from '../interfaces/type_checks';
import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, ON_DESTROY_HOOKS, PARENT, PREORDER_HOOK_FLAGS, PreOrderHookFlags, TData, TView} from '../interfaces/view';
/**
* For efficiency reasons we often put several different data types (`RNode`, `LView`, `LContainer`)
* in same location in `LView`. This is because we don't want to pre-allocate space for it
* because the storage is sparse. This file contains utilities for dealing with such data types.
*
* How do we know what is stored at a given location in `LView`.
* - `Array.isArray(value) === false` => `RNode` (The normal storage value)
* - `Array.isArray(value) === true` => then the `value[0]` represents the wrapped value.
* - `typeof value[TYPE] === 'object'` => `LView`
* - This happens when we have a component at a given location
* - `typeof value[TYPE] === true` => `LContainer`
* - This happens when we have `LContainer` binding at a given location.
*
*
* NOTE: it is assumed that `Array.isArray` and `typeof` operations are very efficient.
*/
/**
* Returns `RNode`.
* @param value wrapped value of `RNode`, `LView`, `LContainer`
*/
export function unwrapRNode(value: RNode|LView|LContainer): RNode {
while (Array.isArray(value)) {
value = value[HOST] as any;
}
return value as RNode;
}
/**
* Returns `LView` or `null` if not found.
* @param value wrapped value of `RNode`, `LView`, `LContainer`
*/
export function unwrapLView(value: RNode|LView|LContainer): LView|null {
while (Array.isArray(value)) {
// This check is same as `isLView()` but we don't call at as we don't want to call
// `Array.isArray()` twice and give JITer more work for inlining.
if (typeof value[TYPE] === 'object') return value as LView;
value = value[HOST] as any;
}
return null;
}
/**
* Retrieves an element value from the provided `viewData`, by unwrapping
* from any containers, component views, or style contexts.
*/
export function getNativeByIndex(index: number, lView: LView): RNode {
ngDevMode && assertIndexInRange(lView, index);
ngDevMode && assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Expected to be past HEADER_OFFSET');
return unwrapRNode(lView[index]);
}
/**
* Retrieve an `RNode` for a given `TNode` and `LView`.
*
* This function guarantees in dev mode to retrieve a non-null `RNode`.
*
* @param tNode
* @param lView
*/
export function getNativeByTNode(tNode: TNode, lView: LView): RNode {
ngDevMode && assertTNodeForLView(tNode, lView);
ngDevMode && assertIndexInRange(lView, tNode.index);
const node: RNode = unwrapRNode(lView[tNode.index]);
return node;
}
/**
* Retrieve an `RNode` or `null` for a given `TNode` and `LView`.
*
* Some `TNode`s don't have associated `RNode`s. For example `Projection`
*
* @param tNode
* @param lView
*/
export function getNativeByTNodeOrNull(tNode: TNode|null, lView: LView): RNode|null {
const index = tNode === null ? -1 : tNode.index;
if (index !== -1) {
ngDevMode && assertTNodeForLView(tNode!, lView);
const node: RNode|null = unwrapRNode(lView[index]);
return node;
}
return null;
}
// fixme(misko): The return Type should be `TNode|null`
export function getTNode(tView: TView, index: number): TNode {
ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode');
ngDevMode && assertLessThan(index, tView.data.length, 'wrong index for TNode');
const tNode = tView.data[index] as TNode;
ngDevMode && tNode !== null && assertTNode(tNode);
return tNode;
}
/** Retrieves a value from any `LView` or `TData`. */
export function load<T>(view: LView|TData, index: number): T {
ngDevMode && assertIndexInRange(view, index);
return view[index];
}
export function getComponentLViewByIndex(nodeIndex: number, hostView: LView): LView {
// Could be an LView or an LContainer. If LContainer, unwrap to find LView.
ngDevMode && assertIndexInRange(hostView, nodeIndex);
const slotValue = hostView[nodeIndex];
const lView = isLView(slotValue) ? slotValue : slotValue[HOST];
return lView;
}
/** Checks whether a given view is in creation mode */
export function isCreationMode(view: LView): boolean {
return (view[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode;
}
/**
* Returns a boolean for whether the view is attached to the change detection tree.
*
* Note: This determines whether a view should be checked, not whether it's inserted
* into a container. For that, you'll want `viewAttachedToContainer` below.
*/
export function viewAttachedToChangeDetector(view: LView): boolean {
return (view[FLAGS] & LViewFlags.Attached) === LViewFlags.Attached;
}
/** Returns a boolean for whether the view is attached to a container. */
export function viewAttachedToContainer(view: LView): boolean {
return isLContainer(view[PARENT]);
}
/** Returns a constant from `TConstants` instance. */
export function getConstant<T>(consts: TConstants|null, index: null|undefined): null;
export function getConstant<T>(consts: TConstants, index: number): T|null;
export function getConstant<T>(consts: TConstants|null, index: number|null|undefined): T|null;
export function getConstant<T>(consts: TConstants|null, index: number|null|undefined): T|null {
if (index === null || index === undefined) return null;
ngDevMode && assertIndexInRange(consts!, index);
return consts![index] as unknown as T;
}
/**
* Resets the pre-order hook flags of the view.
* @param lView the LView on which the flags are reset
*/
export function resetPreOrderHookFlags(lView: LView) {
lView[PREORDER_HOOK_FLAGS] = 0 as PreOrderHookFlags;
}
/**
* Adds the `RefreshView` flag from the lView and updates HAS_CHILD_VIEWS_TO_REFRESH flag of
* parents.
*/
export function markViewForRefresh(lView: LView) {
if (lView[FLAGS] & LViewFlags.RefreshView) {
return;
}
lView[FLAGS] |= LViewFlags.RefreshView;
if (viewAttachedToChangeDetector(lView)) {
markAncestorsForTraversal(lView);
}
}
/**
* Walks up the LView hierarchy.
* @param nestingLevel Number of times to walk up in hierarchy.
* @param currentView View from which to start the lookup.
*/
export function walkUpViews(nestingLevel: number, currentView: LView): LView {
while (nestingLevel > 0) {
ngDevMode &&
assertDefined(
currentView[DECLARATION_VIEW],
'Declaration view should be defined if nesting level is greater than 0.');
currentView = currentView[DECLARATION_VIEW]!;
nestingLevel--;
}
return currentView;
}
/**
* Updates the `DESCENDANT_VIEWS_TO_REFRESH` counter on the parents of the `LView` as well as the
* parents above that whose
* 1. counter goes from 0 to 1, indicating that there is a new child that has a view to refresh
* or
* 2. counter goes from 1 to 0, indicating there are no more descendant views to refresh
* When attaching/re-attaching an `LView` to the change detection tree, we need to ensure that the
* views above it are traversed during change detection if this one is marked for refresh or has
* some child or descendant that needs to be refreshed.
*/
export function updateAncestorTraversalFlagsOnAttach(lView: LView) {
if (lView[FLAGS] & (LViewFlags.RefreshView | LViewFlags.HasChildViewsToRefresh)) {
markAncestorsForTraversal(lView);
}
}
/**
* Ensures views above the given `lView` are traversed during change detection even when they are
* not dirty.
*
* This is done by setting the `HAS_CHILD_VIEWS_TO_REFRESH` flag up to the root, stopping when the
* flag is already `true` or the `lView` is detached.
*/
export function markAncestorsForTraversal(lView: LView) {
let parent = lView[PARENT];
while (parent !== null) {
// We stop adding markers to the ancestors once we reach one that already has the marker. This
// is to avoid needlessly traversing all the way to the root when the marker already exists.
if ((isLContainer(parent) && parent[HAS_CHILD_VIEWS_TO_REFRESH] ||
(isLView(parent) && parent[FLAGS] & LViewFlags.HasChildViewsToRefresh))) {
break;
}
if (isLContainer(parent)) {
parent[HAS_CHILD_VIEWS_TO_REFRESH] = true;
} else {
parent[FLAGS] |= LViewFlags.HasChildViewsToRefresh;
if (!viewAttachedToChangeDetector(parent)) {
break;
}
}
parent = parent[PARENT];
}
}
/**
* Marks the component or root view of an LView for refresh.
*
* This function locates the declaration component view of a given LView and marks it for refresh.
* With this, we get component-level change detection granularity. Marking the `LView` itself for
* refresh would be view-level granularity.
*
* Note that when an LView is a root view, the DECLARATION_COMPONENT_VIEW will be the root view
* itself. This is a bit confusing since the TView.type is `Root`, rather than `Component`, but this
* is actually what we need for host bindings in a root view.
*/
export function markViewDirtyFromSignal(lView: LView): void {
const declarationComponentView = lView[DECLARATION_COMPONENT_VIEW];
declarationComponentView[FLAGS] |= LViewFlags.RefreshView;
if (viewAttachedToChangeDetector(declarationComponentView)) {
markAncestorsForTraversal(declarationComponentView);
}
}
/**
* Stores a LView-specific destroy callback.
*/
export function storeLViewOnDestroy(lView: LView, onDestroyCallback: () => void) {
if ((lView[FLAGS] & LViewFlags.Destroyed) === LViewFlags.Destroyed) {
throw new RuntimeError(
RuntimeErrorCode.VIEW_ALREADY_DESTROYED, ngDevMode && 'View has already been destroyed.');
}
if (lView[ON_DESTROY_HOOKS] === null) {
lView[ON_DESTROY_HOOKS] = [];
}
lView[ON_DESTROY_HOOKS].push(onDestroyCallback);
}
/**
* Removes previously registered LView-specific destroy callback.
*/
export function removeLViewOnDestroy(lView: LView, onDestroyCallback: () => void) {
if (lView[ON_DESTROY_HOOKS] === null) return;
const destroyCBIdx = lView[ON_DESTROY_HOOKS].indexOf(onDestroyCallback);
if (destroyCBIdx !== -1) {
lView[ON_DESTROY_HOOKS].splice(destroyCBIdx, 1);
}
}