-
Notifications
You must be signed in to change notification settings - Fork 25.8k
/
Copy pathshared.ts
1972 lines (1810 loc) Β· 83.3 KB
/
shared.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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @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 {Injector} from '../../di/injector';
import {ErrorHandler} from '../../error_handler';
import {RuntimeError, RuntimeErrorCode} from '../../errors';
import {DehydratedView} from '../../hydration/interfaces';
import {SKIP_HYDRATION_ATTR_NAME} from '../../hydration/skip_hydration';
import {PRESERVE_HOST_CONTENT, PRESERVE_HOST_CONTENT_DEFAULT} from '../../hydration/tokens';
import {processTextNodeMarkersBeforeHydration, retrieveHydrationInfo} from '../../hydration/utils';
import {DoCheck, OnChanges, OnInit} from '../../interface/lifecycle_hooks';
import {SchemaMetadata} from '../../metadata/schema';
import {ViewEncapsulation} from '../../metadata/view';
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import {assertDefined, assertEqual, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert';
import {escapeCommentText} from '../../util/dom';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
import {stringify} from '../../util/stringify';
import {assertFirstCreatePass, assertFirstUpdatePass, assertLContainer, assertLView, assertTNodeForLView, assertTNodeForTView} from '../assert';
import {attachPatchData} from '../context_discovery';
import {getFactoryDef} from '../definition_factory';
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
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, HostBindingsFunction, HostDirectiveBindingMap, HostDirectiveDefs, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {NodeInjectorFactory} from '../interfaces/injector';
import {getUniqueLViewId} from '../interfaces/lview_tracking';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from '../interfaces/node';
import {Renderer} from '../interfaces/renderer';
import {RComment, RElement, RNode, RText} from '../interfaces/renderer_dom';
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, EMBEDDED_VIEW_INJECTOR, ENVIRONMENT, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, HYDRATION, ID, InitPhaseState, INJECTOR, LView, LViewEnvironment, LViewFlags, NEXT, ON_DESTROY_HOOKS, PARENT, REACTIVE_HOST_BINDING_CONSUMER, REACTIVE_TEMPLATE_CONSUMER, RENDERER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
import {assertPureTNodeType, assertTNodeType} from '../node_assert';
import {clearElementContents, updateTextNode} from '../node_manipulation';
import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher';
import {profiler, ProfilerEvent} from '../profiler';
import {commitLViewConsumerIfHasProducers, getReactiveLViewConsumer} from '../reactive_lview_consumer';
import {enterView, getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, leaveView, setBindingIndex, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setIsInCheckNoChangesMode, setSelectedIndex} from '../state';
import {NO_CHANGE} from '../tokens';
import {mergeHostAttrs} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER} from '../util/misc_utils';
import {renderStringify} from '../util/stringify_utils';
import {getFirstLContainer, getLViewParent, getNextLContainer} from '../util/view_traversal_utils';
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreationMode, resetPreOrderHookFlags, unwrapLView, updateTransplantedViewCount, viewAttachedToChangeDetector} from '../util/view_utils';
import {selectIndexInternal} from './advance';
import {Ι΅Ι΅directiveInject} from './di';
import {handleUnknownPropertyError, isPropertyValid, matchingSchemas} from './element_validation';
/**
* Invoke `HostBindingsFunction`s for view.
*
* 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 processHostBindingOpCodes(tView: TView, lView: LView): void {
const hostBindingOpCodes = tView.hostBindingOpCodes;
if (hostBindingOpCodes === null) return;
const consumer = getReactiveLViewConsumer(lView, REACTIVE_HOST_BINDING_CONSUMER);
try {
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];
consumer.runInContext(hostBindingFn, RenderFlags.Update, context);
}
}
} finally {
lView[REACTIVE_HOST_BINDING_CONSUMER] === null &&
commitLViewConsumerIfHasProducers(lView, REACTIVE_HOST_BINDING_CONSUMER);
setSelectedIndex(-1);
}
}
/** Refreshes all content queries declared by directives in a given view */
function refreshContentQueries(tView: TView, lView: LView): void {
const contentQueries = tView.contentQueries;
if (contentQueries !== null) {
for (let i = 0; i < contentQueries.length; i += 2) {
const queryStartIdx = contentQueries[i];
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);
directiveDef.contentQueries!(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx);
}
}
}
}
/** Refreshes child components in the current view (update mode). */
function refreshChildComponents(hostLView: LView, components: number[]): void {
for (let i = 0; i < components.length; i++) {
refreshComponent(hostLView, components[i]);
}
}
/** Renders child components in the current view (creation mode). */
function renderChildComponents(hostLView: LView, components: number[]): void {
for (let i = 0; i < components.length; i++) {
renderComponent(hostLView, components[i]);
}
}
export function createLView<T>(
parentLView: LView|null, tView: TView, context: T|null, flags: LViewFlags, host: RElement|null,
tHostNode: TNode|null, environment: LViewEnvironment|null, renderer: Renderer|null,
injector: Injector|null, embeddedViewInjector: Injector|null,
hydrationInfo: DehydratedView|null): LView {
const lView = tView.blueprint.slice() as LView;
lView[HOST] = host;
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
if (embeddedViewInjector !== null ||
(parentLView && (parentLView[FLAGS] & LViewFlags.HasEmbeddedViewInjector))) {
lView[FLAGS] |= LViewFlags.HasEmbeddedViewInjector;
}
resetPreOrderHookFlags(lView);
ngDevMode && tView.declTNode && parentLView && assertTNodeForLView(tView.declTNode, parentLView);
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
lView[CONTEXT] = context;
lView[ENVIRONMENT] = (environment || parentLView && parentLView[ENVIRONMENT])!;
ngDevMode && assertDefined(lView[ENVIRONMENT], 'LViewEnvironment is required');
lView[RENDERER] = (renderer || parentLView && parentLView[RENDERER])!;
ngDevMode && assertDefined(lView[RENDERER], 'Renderer is required');
lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null;
lView[T_HOST] = tHostNode;
lView[ID] = getUniqueLViewId();
lView[HYDRATION] = hydrationInfo;
lView[EMBEDDED_VIEW_INJECTOR as any] = embeddedViewInjector;
lView[REACTIVE_TEMPLATE_CONSUMER] = null;
ngDevMode &&
assertEqual(
tView.type == TViewType.Embedded ? parentLView !== null : true, true,
'Embedded views must have parentLView');
lView[DECLARATION_COMPONENT_VIEW] =
tView.type == TViewType.Embedded ? parentLView![DECLARATION_COMPONENT_VIEW] : lView;
return lView;
}
/**
* Create and stores the TNode, and hooks it up to the tree.
*
* @param tView The current `TView`.
* @param index The index at which the TNode should be saved (null if view, since they are not
* saved).
* @param type The type of TNode to create
* @param native The native element for this node, if applicable
* @param name The tag name of the associated native element, if applicable
* @param attrs Any attrs for the native element, if applicable
*/
export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType.Element|TNodeType.Text, name: string|null,
attrs: TAttributes|null): TElementNode;
export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType.Container, name: string|null,
attrs: TAttributes|null): TContainerNode;
export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType.Projection, name: null,
attrs: TAttributes|null): TProjectionNode;
export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType.ElementContainer, name: string|null,
attrs: TAttributes|null): TElementContainerNode;
export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType.Icu, name: null,
attrs: TAttributes|null): TElementContainerNode;
export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null):
TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&TIcuContainerNode {
ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in
// `view_engine_compatibility` for additional context.
assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.');
// Keep this function short, so that the VM will inline it.
ngDevMode && assertPureTNodeType(type);
let tNode = tView.data[index] as TNode;
if (tNode === null) {
tNode = createTNodeAtIndex(tView, index, type, name, attrs);
if (isInI18nBlock()) {
// If we are in i18n block then all elements should be pre declared through `Placeholder`
// See `TNodeType.Placeholder` and `LFrame.inI18n` for more context.
// If the `TNode` was not pre-declared than it means it was not mentioned which means it was
// removed, so we mark it as detached.
tNode.flags |= TNodeFlags.isDetached;
}
} else if (tNode.type & TNodeType.Placeholder) {
tNode.type = type;
tNode.value = name;
tNode.attrs = attrs;
const parent = getCurrentParentTNode();
tNode.injectorIndex = parent === null ? -1 : parent.injectorIndex;
ngDevMode && assertTNodeForTView(tNode, tView);
ngDevMode && assertEqual(index, tNode.index, 'Expecting same index');
}
setCurrentTNode(tNode, true);
return tNode as TElementNode & TContainerNode & TElementContainerNode & TProjectionNode &
TIcuContainerNode;
}
export function createTNodeAtIndex(
tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null) {
const currentTNode = getCurrentTNodePlaceholderOk();
const isParent = isCurrentTNodeParent();
const parent = isParent ? currentTNode : currentTNode && currentTNode.parent;
// Parents cannot cross component boundaries because components will be used in multiple places.
const tNode = tView.data[index] =
createTNode(tView, parent as TElementNode | TContainerNode, type, index, name, attrs);
// Assign a pointer to the first child node of a given view. The first node is not always the one
// at index 0, in case of i18n, index 0 can be the instruction `i18nStart` and the first node has
// the index 1 or more, so we can't just check node index.
if (tView.firstChild === null) {
tView.firstChild = tNode;
}
if (currentTNode !== null) {
if (isParent) {
// FIXME(misko): This logic looks unnecessarily complicated. Could we simplify?
if (currentTNode.child == null && tNode.parent !== null) {
// We are in the same view, which means we are adding content node to the parent view.
currentTNode.child = tNode;
}
} else {
if (currentTNode.next === null) {
// In the case of i18n the `currentTNode` may already be linked, in which case we don't want
// to break the links which i18n created.
currentTNode.next = tNode;
tNode.prev = currentTNode;
}
}
}
return tNode;
}
/**
* When elements are created dynamically after a view blueprint is created (e.g. through
* i18nApply()), we need to adjust the blueprint for future
* template passes.
*
* @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, initialValue: any): number {
if (numSlotsToAlloc === 0) return -1;
if (ngDevMode) {
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');
assertFirstUpdatePass(tView);
}
const allocIdx = lView.length;
for (let i = 0; i < numSlotsToAlloc; i++) {
lView.push(initialValue);
tView.blueprint.push(initialValue);
tView.data.push(null);
}
return allocIdx;
}
//////////////////////////
//// Render
//////////////////////////
/**
* Processes a view in the creation mode. This includes a number of steps in a specific order:
* - creating view query functions (if any);
* - executing a template function in the creation mode;
* - updating static queries (if any);
* - creating child components defined in a given view.
*/
export function renderView<T>(tView: TView, lView: LView<T>, context: T): void {
ngDevMode && assertEqual(isCreationMode(lView), true, 'Should be run in creation mode');
enterView(lView);
try {
const viewQuery = tView.viewQuery;
if (viewQuery !== null) {
executeViewQueryFn<T>(RenderFlags.Create, viewQuery, context);
}
// Execute a template associated with this view, if it exists. A template function might not be
// defined for the root component views.
const templateFn = tView.template;
if (templateFn !== null) {
executeTemplate<T>(tView, lView, templateFn, RenderFlags.Create, context);
}
// This needs to be set before children are processed to support recursive components.
// This must be set to false immediately after the first creation run because in an
// ngFor loop, all the views will be created together before update mode runs and turns
// off firstCreatePass. If we don't set it here, instances will perform directive
// matching, etc again and again.
if (tView.firstCreatePass) {
tView.firstCreatePass = false;
}
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
// content queries are resolved during change detection (i.e. update mode), after embedded
// views are refreshed (see block above).
if (tView.staticContentQueries) {
refreshContentQueries(tView, lView);
}
// We must materialize query results before child components are processed
// in case a child component has projected a container. The LContainer needs
// to exist so the embedded views are properly attached by the container.
if (tView.staticViewQueries) {
executeViewQueryFn<T>(RenderFlags.Update, tView.viewQuery!, context);
}
// Render child component views.
const components = tView.components;
if (components !== null) {
renderChildComponents(lView, components);
}
} catch (error) {
// If we didn't manage to get past the first template pass due to
// an error, mark the view as corrupted so we can try to recover.
if (tView.firstCreatePass) {
tView.incompleteFirstPass = true;
tView.firstCreatePass = false;
}
throw error;
} finally {
lView[FLAGS] &= ~LViewFlags.CreationMode;
leaveView();
}
}
/**
* Processes a view in update mode. This includes a number of steps in a specific order:
* - executing a template function in update mode;
* - executing hooks;
* - refreshing queries;
* - setting host bindings;
* - refreshing child (embedded and component) views.
*/
export function refreshView<T>(
tView: TView, lView: LView, templateFn: ComponentTemplate<{}>|null, context: T) {
ngDevMode && assertEqual(isCreationMode(lView), false, 'Should be run in update mode');
const flags = lView[FLAGS];
if ((flags & LViewFlags.Destroyed) === LViewFlags.Destroyed) return;
// Check no changes mode is a dev only mode used to verify that bindings have not changed
// since they were assigned. We do not want to execute lifecycle hooks in that mode.
const isInCheckNoChangesPass = ngDevMode && isInCheckNoChangesMode();
!isInCheckNoChangesPass && lView[ENVIRONMENT].effectManager?.flush();
enterView(lView);
try {
resetPreOrderHookFlags(lView);
setBindingIndex(tView.bindingStartIndex);
if (templateFn !== null) {
executeTemplate(tView, lView, templateFn, RenderFlags.Update, context);
}
const hooksInitPhaseCompleted =
(flags & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted;
// execute pre-order hooks (OnInit, OnChanges, DoCheck)
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
if (!isInCheckNoChangesPass) {
if (hooksInitPhaseCompleted) {
const preOrderCheckHooks = tView.preOrderCheckHooks;
if (preOrderCheckHooks !== null) {
executeCheckHooks(lView, preOrderCheckHooks, null);
}
} else {
const preOrderHooks = tView.preOrderHooks;
if (preOrderHooks !== null) {
executeInitAndCheckHooks(lView, preOrderHooks, InitPhaseState.OnInitHooksToBeRun, null);
}
incrementInitPhaseFlags(lView, InitPhaseState.OnInitHooksToBeRun);
}
}
// First mark transplanted views that are declared in this lView as needing a refresh at their
// insertion points. This is needed to avoid the situation where the template is defined in this
// `LView` but its declaration appears after the insertion component.
markTransplantedViewsForRefresh(lView);
refreshEmbeddedViews(lView);
// Content query results must be refreshed before content hooks are called.
if (tView.contentQueries !== null) {
refreshContentQueries(tView, lView);
}
// execute content hooks (AfterContentInit, AfterContentChecked)
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
if (!isInCheckNoChangesPass) {
if (hooksInitPhaseCompleted) {
const contentCheckHooks = tView.contentCheckHooks;
if (contentCheckHooks !== null) {
executeCheckHooks(lView, contentCheckHooks);
}
} else {
const contentHooks = tView.contentHooks;
if (contentHooks !== null) {
executeInitAndCheckHooks(
lView, contentHooks, InitPhaseState.AfterContentInitHooksToBeRun);
}
incrementInitPhaseFlags(lView, InitPhaseState.AfterContentInitHooksToBeRun);
}
}
processHostBindingOpCodes(tView, lView);
// Refresh child component views.
const components = tView.components;
if (components !== null) {
refreshChildComponents(lView, components);
}
// View queries must execute after refreshing child components because a template in this view
// could be inserted in a child component. If the view query executes before child component
// refresh, the template might not yet be inserted.
const viewQuery = tView.viewQuery;
if (viewQuery !== null) {
executeViewQueryFn<T>(RenderFlags.Update, viewQuery, context);
}
// execute view hooks (AfterViewInit, AfterViewChecked)
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
if (!isInCheckNoChangesPass) {
if (hooksInitPhaseCompleted) {
const viewCheckHooks = tView.viewCheckHooks;
if (viewCheckHooks !== null) {
executeCheckHooks(lView, viewCheckHooks);
}
} else {
const viewHooks = tView.viewHooks;
if (viewHooks !== null) {
executeInitAndCheckHooks(lView, viewHooks, InitPhaseState.AfterViewInitHooksToBeRun);
}
incrementInitPhaseFlags(lView, InitPhaseState.AfterViewInitHooksToBeRun);
}
}
if (tView.firstUpdatePass === true) {
// We need to make sure that we only flip the flag on successful `refreshView` only
// Don't do this in `finally` block.
// If we did this in `finally` block then an exception could block the execution of styling
// instructions which in turn would be unable to insert themselves into the styling linked
// list. The result of this would be that if the exception would not be throw on subsequent CD
// the styling would be unable to process it data and reflect to the DOM.
tView.firstUpdatePass = false;
}
// Do not reset the dirty state when running in check no changes mode. We don't want components
// to behave differently depending on whether check no changes is enabled or not. For example:
// Marking an OnPush component as dirty from within the `ngAfterViewInit` hook in order to
// refresh a `NgClass` binding should work. If we would reset the dirty state in the check
// no changes cycle, the component would be not be dirty for the next update pass. This would
// be different in production mode where the component dirty state is not reset.
if (!isInCheckNoChangesPass) {
lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass);
}
if (lView[FLAGS] & LViewFlags.RefreshTransplantedView) {
lView[FLAGS] &= ~LViewFlags.RefreshTransplantedView;
updateTransplantedViewCount(lView[PARENT] as LContainer, -1);
}
} finally {
leaveView();
}
}
function executeTemplate<T>(
tView: TView, lView: LView<T>, templateFn: ComponentTemplate<T>, rf: RenderFlags, context: T) {
const consumer = getReactiveLViewConsumer(lView, REACTIVE_TEMPLATE_CONSUMER);
const prevSelectedIndex = getSelectedIndex();
const isUpdatePhase = rf & RenderFlags.Update;
try {
setSelectedIndex(-1);
if (isUpdatePhase && lView.length > HEADER_OFFSET) {
// When we're updating, inherently select 0 so we don't
// have to generate that instruction for most update blocks.
selectIndexInternal(tView, lView, HEADER_OFFSET, !!ngDevMode && isInCheckNoChangesMode());
}
const preHookType =
isUpdatePhase ? ProfilerEvent.TemplateUpdateStart : ProfilerEvent.TemplateCreateStart;
profiler(preHookType, context as unknown as {});
consumer.runInContext(templateFn, rf, context);
} finally {
if (lView[REACTIVE_TEMPLATE_CONSUMER] === null) {
commitLViewConsumerIfHasProducers(lView, REACTIVE_TEMPLATE_CONSUMER);
}
setSelectedIndex(prevSelectedIndex);
const postHookType =
isUpdatePhase ? ProfilerEvent.TemplateUpdateEnd : ProfilerEvent.TemplateCreateEnd;
profiler(postHookType, context as unknown as {});
}
}
//////////////////////////
//// Element
//////////////////////////
export function executeContentQueries(tView: TView, tNode: TNode, lView: LView) {
if (isContentQueryHost(tNode)) {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
for (let directiveIndex = start; directiveIndex < end; directiveIndex++) {
const def = tView.data[directiveIndex] as DirectiveDef<any>;
if (def.contentQueries) {
def.contentQueries(RenderFlags.Create, lView[directiveIndex], directiveIndex);
}
}
}
}
/**
* Creates directive instances.
*/
export function createDirectivesInstances(tView: TView, lView: LView, tNode: TDirectiveHostNode) {
if (!getBindingsEnabled()) return;
instantiateAllDirectives(tView, lView, tNode, getNativeByTNode(tNode, lView));
if ((tNode.flags & TNodeFlags.hasHostBindings) === TNodeFlags.hasHostBindings) {
invokeDirectivesHostBindings(tView, lView, tNode);
}
}
/**
* Takes a list of local names and indices and pushes the resolved local variable values
* to LView in the same order as they are loaded in the template with load().
*/
export function saveResolvedLocalsInData(
viewData: LView, tNode: TDirectiveHostNode,
localRefExtractor: LocalRefExtractor = getNativeByTNode): void {
const localNames = tNode.localNames;
if (localNames !== null) {
let localIndex = tNode.index + 1;
for (let i = 0; i < localNames.length; i += 2) {
const index = localNames[i + 1] as number;
const value = index === -1 ?
localRefExtractor(
tNode as TElementNode | TContainerNode | TElementContainerNode, viewData) :
viewData[index];
viewData[localIndex++] = value;
}
}
}
/**
* Gets TView from a template function or creates a new TView
* if it doesn't already exist.
*
* @param def ComponentDef
* @returns TView
*/
export function getOrCreateComponentTView(def: ComponentDef<any>): TView {
const tView = def.tView;
// Create a TView if there isn't one, or recreate it if the first create pass didn't
// complete successfully since we can't know for sure whether it's in a usable shape.
if (tView === null || tView.incompleteFirstPass) {
// Declaration node here is null since this function is called when we dynamically create a
// component and hence there is no declaration.
const declTNode = null;
return def.tView = createTView(
TViewType.Component, declTNode, def.template, def.decls, def.vars, def.directiveDefs,
def.pipeDefs, def.viewQuery, def.schemas, def.consts, def.id);
}
return tView;
}
/**
* Creates a TView instance
*
* @param type Type of `TView`.
* @param declTNode Declaration location of this `TView`.
* @param templateFn Template function
* @param decls The number of nodes, local refs, and pipes in this template
* @param directives Registry of directives for this view
* @param pipes Registry of pipes for this view
* @param viewQuery View queries for this view
* @param schemas Schemas for this view
* @param consts Constants for this view
*/
export function createTView(
type: TViewType, declTNode: TNode|null, templateFn: ComponentTemplate<any>|null, decls: number,
vars: number, directives: DirectiveDefListOrFactory|null, pipes: PipeDefListOrFactory|null,
viewQuery: ViewQueriesFunction<any>|null, schemas: SchemaMetadata[]|null,
constsOrFactory: TConstantsOrFactory|null, ssrId: string|null): TView {
ngDevMode && ngDevMode.tView++;
const bindingStartIndex = HEADER_OFFSET + decls;
// This length does not yet contain host bindings from child directives because at this point,
// we don't know which directives are active on this template. As soon as a directive is matched
// that has a host binding, we will update the blueprint with that def's hostVars count.
const initialViewLength = bindingStartIndex + vars;
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
const consts = typeof constsOrFactory === 'function' ? constsOrFactory() : constsOrFactory;
const tView = blueprint[TVIEW as any] = {
type: type,
blueprint: blueprint,
template: templateFn,
queries: null,
viewQuery: viewQuery,
declTNode: declTNode,
data: blueprint.slice().fill(null, bindingStartIndex),
bindingStartIndex: bindingStartIndex,
expandoStartIndex: initialViewLength,
hostBindingOpCodes: null,
firstCreatePass: true,
firstUpdatePass: true,
staticViewQueries: false,
staticContentQueries: false,
preOrderHooks: null,
preOrderCheckHooks: null,
contentHooks: null,
contentCheckHooks: null,
viewHooks: null,
viewCheckHooks: null,
destroyHooks: null,
cleanup: null,
contentQueries: null,
components: null,
directiveRegistry: typeof directives === 'function' ? directives() : directives,
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
firstChild: null,
schemas: schemas,
consts: consts,
incompleteFirstPass: false,
ssrId,
};
if (ngDevMode) {
// For performance reasons it is important that the tView retains the same shape during runtime.
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
// prevent class transitions.
Object.seal(tView);
}
return tView;
}
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
const blueprint = [];
for (let i = 0; i < initialViewLength; i++) {
blueprint.push(i < bindingStartIndex ? null : NO_CHANGE);
}
return blueprint as LView;
}
/**
* Locates the host native element, used for bootstrapping existing nodes into rendering pipeline.
*
* @param renderer the renderer used to locate the element.
* @param elementOrSelector Render element or CSS selector to locate the element.
* @param encapsulation View Encapsulation defined for component that requests host element.
* @param injector Root view injector instance.
*/
export function locateHostElement(
renderer: Renderer, elementOrSelector: RElement|string, encapsulation: ViewEncapsulation,
injector: Injector): RElement {
// Note: we use default value for the `PRESERVE_HOST_CONTENT` here even though it's a
// tree-shakable one (providedIn:'root'). This code path can be triggered during dynamic
// component creation (after calling ViewContainerRef.createComponent) when an injector
// instance can be provided. The injector instance might be disconnected from the main DI
// tree, thus the `PRESERVE_HOST_CONTENT` would not be able to instantiate. In this case, the
// default value will be used.
const preserveHostContent = injector.get(PRESERVE_HOST_CONTENT, PRESERVE_HOST_CONTENT_DEFAULT);
// When using native Shadow DOM, do not clear host element to allow native slot
// projection.
const preserveContent = preserveHostContent || encapsulation === ViewEncapsulation.ShadowDom;
const rootElement = renderer.selectRootElement(elementOrSelector, preserveContent);
applyRootElementTransform(rootElement as HTMLElement);
return rootElement;
}
/**
* Applies any root element transformations that are needed. If hydration is enabled,
* this will process corrupted text nodes.
*
* @param rootElement the app root HTML Element
*/
export function applyRootElementTransform(rootElement: HTMLElement) {
_applyRootElementTransformImpl(rootElement as HTMLElement);
}
/**
* Reference to a function that applies transformations to the root HTML element
* of an app. When hydration is enabled, this processes any corrupt text nodes
* so they are properly hydratable on the client.
*
* @param rootElement the app root HTML Element
*/
let _applyRootElementTransformImpl: typeof applyRootElementTransformImpl =
(rootElement: HTMLElement) => null;
/**
* Processes text node markers before hydration begins. This replaces any special comment
* nodes that were added prior to serialization are swapped out to restore proper text
* nodes before hydration.
*
* @param rootElement the app root HTML Element
*/
export function applyRootElementTransformImpl(rootElement: HTMLElement) {
if (rootElement.hasAttribute(SKIP_HYDRATION_ATTR_NAME)) {
// Handle a situation when the `ngSkipHydration` attribute is applied
// to the root node of an application. In this case, we should clear
// the contents and render everything from scratch.
clearElementContents(rootElement as RElement);
} else {
processTextNodeMarkersBeforeHydration(rootElement);
}
}
/**
* Sets the implementation for the `applyRootElementTransform` function.
*/
export function enableApplyRootElementTransformImpl() {
_applyRootElementTransformImpl = applyRootElementTransformImpl;
}
/**
* Saves context for this cleanup function in LView.cleanupInstances.
*
* On the first template pass, saves in TView:
* - Cleanup function
* - Index of context we just saved in LView.cleanupInstances
*/
export function storeCleanupWithContext(
tView: TView, lView: LView, context: any, cleanupFn: Function): void {
const lCleanup = getOrCreateLViewCleanup(lView);
// Historically the `storeCleanupWithContext` was used to register both framework-level and
// user-defined cleanup callbacks, but over time those two types of cleanups were separated.
// This dev mode checks assures that user-level cleanup callbacks are _not_ stored in data
// structures reserved for framework-specific hooks.
ngDevMode &&
assertDefined(
context, 'Cleanup context is mandatory when registering framework-level destroy hooks');
lCleanup.push(context);
if (tView.firstCreatePass) {
getOrCreateTViewCleanup(tView).push(cleanupFn, lCleanup.length - 1);
} else {
// Make sure that no new framework-level cleanup functions are registered after the first
// template pass is done (and TView data structures are meant to fully constructed).
if (ngDevMode) {
Object.freeze(getOrCreateTViewCleanup(tView));
}
}
}
/**
* Constructs a TNode object from the arguments.
*
* @param tView `TView` to which this `TNode` belongs
* @param tParent Parent `TNode`
* @param type The type of the node
* @param index The index of the TNode in TView.data, adjusted for HEADER_OFFSET
* @param tagName The tag name of the node
* @param attrs The attributes defined on this node
* @returns the TNode object
*/
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Container,
index: number, tagName: string|null, attrs: TAttributes|null): TContainerNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Element|TNodeType.Text,
index: number, tagName: string|null, attrs: TAttributes|null): TElementNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.ElementContainer,
index: number, tagName: string|null, attrs: TAttributes|null): TElementContainerNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Icu, index: number,
tagName: string|null, attrs: TAttributes|null): TIcuContainerNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Projection,
index: number, tagName: string|null, attrs: TAttributes|null): TProjectionNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number,
tagName: string|null, attrs: TAttributes|null): TNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number,
value: string|null, attrs: TAttributes|null): TNode {
ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in
// `view_engine_compatibility` for additional context.
assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.');
ngDevMode && assertNotSame(attrs, undefined, '\'undefined\' is not valid value for \'attrs\'');
ngDevMode && ngDevMode.tNode++;
ngDevMode && tParent && assertTNodeForTView(tParent, tView);
let injectorIndex = tParent ? tParent.injectorIndex : -1;
const tNode = {
type,
index,
insertBeforeIndex: null,
injectorIndex,
directiveStart: -1,
directiveEnd: -1,
directiveStylingLast: -1,
componentOffset: -1,
propertyBindings: null,
flags: 0,
providerIndexes: 0,
value: value,
attrs: attrs,
mergedAttrs: null,
localNames: null,
initialInputs: undefined,
inputs: null,
outputs: null,
tView: null,
next: null,
prev: null,
projectionNext: null,
child: null,
parent: tParent,
projection: null,
styles: null,
stylesWithoutHost: null,
residualStyles: undefined,
classes: null,
classesWithoutHost: null,
residualClasses: undefined,
classBindings: 0 as any,
styleBindings: 0 as any,
};
if (ngDevMode) {
// For performance reasons it is important that the tNode retains the same shape during runtime.
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
// prevent class transitions.
Object.seal(tNode);
}
return tNode;
}
/**
* Generates the `PropertyAliases` data structure from the provided input/output mapping.
* @param aliasMap Input/output mapping from the directive definition.
* @param directiveIndex Index of the directive.
* @param propertyAliases Object in which to store the results.
* @param hostDirectiveAliasMap Object used to alias or filter out properties for host directives.
* If the mapping is provided, it'll act as an allowlist, as well as a mapping of what public
* name inputs/outputs should be exposed under.
*/
function generatePropertyAliases(
aliasMap: {[publicName: string]: string}, directiveIndex: number,
propertyAliases: PropertyAliases|null,
hostDirectiveAliasMap: HostDirectiveBindingMap|null): PropertyAliases|null {
for (let publicName in aliasMap) {
if (aliasMap.hasOwnProperty(publicName)) {
propertyAliases = propertyAliases === null ? {} : propertyAliases;
const internalName = aliasMap[publicName];
// If there are no host directive mappings, we want to remap using the alias map from the
// definition itself. If there is an alias map, it has two functions:
// 1. It serves as an allowlist of bindings that are exposed by the host directives. Only the
// ones inside the host directive map will be exposed on the host.
// 2. The public name of the property is aliased using the host directive alias map, rather
// than the alias map from the definition.
if (hostDirectiveAliasMap === null) {
addPropertyAlias(propertyAliases, directiveIndex, publicName, internalName);
} else if (hostDirectiveAliasMap.hasOwnProperty(publicName)) {
addPropertyAlias(
propertyAliases, directiveIndex, hostDirectiveAliasMap[publicName], internalName);
}
}
}
return propertyAliases;
}
function addPropertyAlias(
propertyAliases: PropertyAliases, directiveIndex: number, publicName: string,
internalName: string) {
if (propertyAliases.hasOwnProperty(publicName)) {
propertyAliases[publicName].push(directiveIndex, internalName);
} else {
propertyAliases[publicName] = [directiveIndex, internalName];
}
}
/**
* Initializes data structures required to work with directive inputs and outputs.
* Initialization is done for all directives matched on a given TNode.
*/
function initializeInputAndOutputAliases(
tView: TView, tNode: TNode, hostDirectiveDefinitionMap: HostDirectiveDefs|null): void {
ngDevMode && assertFirstCreatePass(tView);
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
const tViewData = tView.data;
const tNodeAttrs = tNode.attrs;
const inputsFromAttrs: InitialInputData = [];
let inputsStore: PropertyAliases|null = null;
let outputsStore: PropertyAliases|null = null;
for (let directiveIndex = start; directiveIndex < end; directiveIndex++) {
const directiveDef = tViewData[directiveIndex] as DirectiveDef<any>;
const aliasData =
hostDirectiveDefinitionMap ? hostDirectiveDefinitionMap.get(directiveDef) : null;
const aliasedInputs = aliasData ? aliasData.inputs : null;
const aliasedOutputs = aliasData ? aliasData.outputs : null;
inputsStore =
generatePropertyAliases(directiveDef.inputs, directiveIndex, inputsStore, aliasedInputs);
outputsStore =
generatePropertyAliases(directiveDef.outputs, directiveIndex, outputsStore, aliasedOutputs);
// 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">`).
// TODO(FW-1930): microsyntax expressions may also contain unbound/static attributes, which
// should be set for inline templates.
const initialInputs =
(inputsStore !== null && tNodeAttrs !== null && !isInlineTemplate(tNode)) ?
generateInitialInputs(inputsStore, directiveIndex, tNodeAttrs) :
null;
inputsFromAttrs.push(initialInputs);
}
if (inputsStore !== null) {
if (inputsStore.hasOwnProperty('class')) {
tNode.flags |= TNodeFlags.hasClassInput;
}
if (inputsStore.hasOwnProperty('style')) {
tNode.flags |= TNodeFlags.hasStyleInput;
}
}
tNode.initialInputs = inputsFromAttrs;
tNode.inputs = inputsStore;
tNode.outputs = outputsStore;
}
/**
* Mapping between attributes names that don't correspond to their element property names.
*
* Performance note: this function is written as a series of if checks (instead of, say, a property
* object lookup) for performance reasons - the series of `if` checks seems to be the fastest way of
* mapping property names. Do NOT change without benchmarking.
*
* Note: this mapping has to be kept in sync with the equally named mapping in the template
* type-checking machinery of ngtsc.
*/
function mapPropName(name: string): string {
if (name === 'class') return 'className';
if (name === 'for') return 'htmlFor';
if (name === 'formaction') return 'formAction';
if (name === 'innerHtml') return 'innerHTML';
if (name === 'readonly') return 'readOnly';
if (name === 'tabindex') return 'tabIndex';
return name;
}
export function elementPropertyInternal<T>(
tView: TView, tNode: TNode, lView: LView, propName: string, value: T, renderer: Renderer,
sanitizer: SanitizerFn|null|undefined, nativeOnly: boolean): void {
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
const element = getNativeByTNode(tNode, lView) as RElement | RComment;
let inputData = tNode.inputs;
let dataValue: PropertyAliasValue|undefined;
if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) {
setInputsForProperty(tView, lView, dataValue, propName, value);
if (isComponentHost(tNode)) markDirtyIfOnPush(lView, tNode.index);
if (ngDevMode) {
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
}
} else if (tNode.type & TNodeType.AnyRNode) {
propName = mapPropName(propName);
if (ngDevMode) {
validateAgainstEventProperties(propName);
if (!isPropertyValid(element, propName, tNode.value, tView.schemas)) {
handleUnknownPropertyError(propName, tNode.value, tNode.type, lView);
}