forked from angular/angular
/
shared.ts
1596 lines (1473 loc) · 67.1 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 {consumerAfterComputation, consumerBeforeComputation, setActiveConsumer} from '@angular/core/primitives/signals';
import {Injector} from '../../di/injector';
import {ErrorHandler} from '../../error_handler';
import {RuntimeError, RuntimeErrorCode} from '../../errors';
import {DehydratedView} from '../../hydration/interfaces';
import {hasSkipHydrationAttrOnRElement} from '../../hydration/skip_hydration';
import {PRESERVE_HOST_CONTENT, PRESERVE_HOST_CONTENT_DEFAULT} from '../../hydration/tokens';
import {processTextNodeMarkersBeforeHydration} from '../../hydration/utils';
import {DoCheck, OnChanges, OnInit} from '../../interface/lifecycle_hooks';
import {Writable} from '../../interface/type';
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, assertLView, assertNoDuplicateDirectives, 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 {CONTAINER_HEADER_OFFSET, LContainer} 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} 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, INJECTOR, LView, LViewEnvironment, LViewFlags, NEXT, PARENT, REACTIVE_HOST_BINDING_CONSUMER, REACTIVE_TEMPLATE_CONSUMER, RENDERER, T_HOST, TData, 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 {getReactiveLViewConsumer} from '../reactive_lview_consumer';
import {getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, isInSkipHydrationBlock, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, 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 {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, resetPreOrderHookFlags, unwrapLView} 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);
consumer.dirty = false;
const prevConsumer = consumerBeforeComputation(consumer);
try {
const context = lView[directiveIdx];
hostBindingFn(RenderFlags.Update, context);
} finally {
consumerAfterComputation(consumer, prevConsumer);
}
}
}
} finally {
setSelectedIndex(-1);
}
}
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<T> {
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;
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 as LView<T>;
}
/**
* 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;
}
export 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 {});
const effectiveConsumer = isUpdatePhase ? consumer : null;
const prevConsumer = consumerBeforeComputation(effectiveConsumer);
try {
if (effectiveConsumer !== null) {
effectiveConsumer.dirty = false;
}
templateFn(rf, context);
} finally {
consumerAfterComputation(effectiveConsumer, prevConsumer);
}
} finally {
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 prevConsumer = setActiveConsumer(null);
try {
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);
}
}
} finally {
setActiveConsumer(prevConsumer);
}
}
}
/**
* 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 (hasSkipHydrationAttrOnRElement(rootElement)) {
// 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;
let flags = 0;
if (isInSkipHydrationBlock()) {
flags |= TNodeFlags.inSkipHydrationBlock;
}
const tNode = {
type,
index,
insertBeforeIndex: null,
injectorIndex,
directiveStart: -1,
directiveEnd: -1,
directiveStylingLast: -1,
componentOffset: -1,
propertyBindings: null,
flags,
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);
}
ngDevMode.rendererSetProperty++;
}
// It is assumed that the sanitizer is only added when the compiler determines that the
// property is risky, so sanitization can be done without further checks.
value = sanitizer != null ? (sanitizer(value, tNode.value || '', propName) as any) : value;
renderer.setProperty(element as RElement, propName, value);
} else if (tNode.type & TNodeType.AnyContainer) {
// If the node is a container and the property didn't
// match any of the inputs or schemas we should throw.
if (ngDevMode && !matchingSchemas(tView.schemas, tNode.value)) {
handleUnknownPropertyError(propName, tNode.value, tNode.type, lView);
}
}
}
/** If node is an OnPush component, marks its LView dirty. */
export function markDirtyIfOnPush(lView: LView, viewIndex: number): void {
ngDevMode && assertLView(lView);
const childComponentLView = getComponentLViewByIndex(viewIndex, lView);
if (!(childComponentLView[FLAGS] & LViewFlags.CheckAlways)) {
childComponentLView[FLAGS] |= LViewFlags.Dirty;
}
}
function setNgReflectProperty(
lView: LView, element: RElement|RComment, type: TNodeType, attrName: string, value: any) {
const renderer = lView[RENDERER];
attrName = normalizeDebugBindingName(attrName);
const debugValue = normalizeDebugBindingValue(value);
if (type & TNodeType.AnyRNode) {
if (value == null) {
renderer.removeAttribute((element as RElement), attrName);
} else {
renderer.setAttribute((element as RElement), attrName, debugValue);
}
} else {
const textContent =
escapeCommentText(`bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`);
renderer.setValue((element as RComment), textContent);
}
}
export function setNgReflectProperties(
lView: LView, element: RElement|RComment, type: TNodeType, dataValue: PropertyAliasValue,
value: any) {
if (type & (TNodeType.AnyRNode | TNodeType.Container)) {
/**
* dataValue is an array containing runtime input or output names for the directives:
* i+0: directive instance index
* i+1: privateName
*
* e.g. [0, 'change', 'change-minified']
* we want to set the reflected property with the privateName: dataValue[i+1]
*/
for (let i = 0; i < dataValue.length; i += 2) {
setNgReflectProperty(lView, element, type, dataValue[i + 1] as string, value);
}
}
}
/**
* Resolve the matched directives on a node.
*/
export function resolveDirectives(
tView: TView, lView: LView, tNode: TElementNode|TContainerNode|TElementContainerNode,
localRefs: string[]|null): void {
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
// tsickle.
ngDevMode && assertFirstCreatePass(tView);
if (getBindingsEnabled()) {
const exportsMap: ({[key: string]: number}|null) = localRefs === null ? null : {'': -1};
const matchResult = findDirectiveDefMatches(tView, tNode);
let directiveDefs: DirectiveDef<unknown>[]|null;
let hostDirectiveDefs: HostDirectiveDefs|null;
if (matchResult === null) {
directiveDefs = hostDirectiveDefs = null;
} else {
[directiveDefs, hostDirectiveDefs] = matchResult;
}
if (directiveDefs !== null) {
initializeDirectives(tView, lView, tNode, directiveDefs, exportsMap, hostDirectiveDefs);
}
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
}
// Merge the template attrs last so that they have the highest priority.
tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, tNode.attrs);
}
/** Initializes the data structures necessary for a list of directives to be instantiated. */
export function initializeDirectives(
tView: TView, lView: LView<unknown>, tNode: TElementNode|TContainerNode|TElementContainerNode,
directives: DirectiveDef<unknown>[], exportsMap: {[key: string]: number;}|null,
hostDirectiveDefs: HostDirectiveDefs|null) {
ngDevMode && assertFirstCreatePass(tView);
// Publishes the directive types to DI so they can be injected. Needs to
// happen in a separate pass before the TNode flags have been initialized.
for (let i = 0; i < directives.length; i++) {
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, lView), tView, directives[i].type);
}
initTNodeFlags(tNode, tView.data.length, directives.length);
// When the same token is provided by several directives on the same node, some rules apply in
// the viewEngine:
// - viewProviders have priority over providers
// - the last directive in NgModule.declarations has priority over the previous one
// So to match these rules, the order in which providers are added in the arrays is very
// important.
for (let i = 0; i < directives.length; i++) {
const def = directives[i];
if (def.providersResolver) def.providersResolver(def);
}
let preOrderHooksFound = false;
let preOrderCheckHooksFound = false;
let directiveIdx = allocExpando(tView, lView, directives.length, null);
ngDevMode &&
assertSame(
directiveIdx, tNode.directiveStart,
'TNode.directiveStart should point to just allocated space');
for (let i = 0; i < directives.length; i++) {
const def = directives[i];
// Merge the attrs in the order of matches. This assumes that the first directive is the
// component itself, so that the component has the least priority.
tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs);
configureViewWithDirective(tView, tNode, lView, directiveIdx, def);
saveNameToExportMap(directiveIdx, def, exportsMap);
if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery;
if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0)
tNode.flags |= TNodeFlags.hasHostBindings;
const lifeCycleHooks: Partial<OnChanges&OnInit&DoCheck> = def.type.prototype;
// Only push a node index into the preOrderHooks array if this is the first
// pre-order hook found on this node.
if (!preOrderHooksFound &&
(lifeCycleHooks.ngOnChanges || lifeCycleHooks.ngOnInit || lifeCycleHooks.ngDoCheck)) {
// We will push the actual hook function into this array later during dir instantiation.
// We cannot do it now because we must ensure hooks are registered in the same
// order that directives are created (i.e. injection order).
(tView.preOrderHooks ??= []).push(tNode.index);
preOrderHooksFound = true;
}
if (!preOrderCheckHooksFound && (lifeCycleHooks.ngOnChanges || lifeCycleHooks.ngDoCheck)) {
(tView.preOrderCheckHooks ??= []).push(tNode.index);
preOrderCheckHooksFound = true;
}
directiveIdx++;
}
initializeInputAndOutputAliases(tView, tNode, hostDirectiveDefs);
}
/**
* Add `hostBindings` to the `TView.hostBindingOpCodes`.
*
* @param tView `TView` to which the `hostBindings` should be added.
* @param tNode `TNode` the element which contains the directive
* @param directiveIdx Directive index in view.
* @param directiveVarsIdx Where will the directive's vars be stored
* @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add.
*/
export function registerHostBindingOpCodes(
tView: TView, tNode: TNode, directiveIdx: number, directiveVarsIdx: number,
def: ComponentDef<any>|DirectiveDef<any>): void {
ngDevMode && assertFirstCreatePass(tView);
const hostBindings = def.hostBindings;
if (hostBindings) {
let hostBindingOpCodes = tView.hostBindingOpCodes;
if (hostBindingOpCodes === null) {
hostBindingOpCodes = tView.hostBindingOpCodes = [] as any as HostBindingOpCodes;
}
const elementIndx = ~tNode.index;
if (lastSelectedElementIdx(hostBindingOpCodes) != elementIndx) {
// Conditionally add select element so that we are more efficient in execution.
// NOTE: this is strictly not necessary and it trades code size for runtime perf.
// (We could just always add it.)
hostBindingOpCodes.push(elementIndx);
}
hostBindingOpCodes.push(directiveIdx, directiveVarsIdx, hostBindings);
}
}
/**
* Returns the last selected element index in the `HostBindingOpCodes`
*
* For perf reasons we don't need to update the selected element index in `HostBindingOpCodes` only
* if it changes. This method returns the last index (or '0' if not found.)
*
* Selected element index are only the ones which are negative.
*/
function lastSelectedElementIdx(hostBindingOpCodes: HostBindingOpCodes): number {
let i = hostBindingOpCodes.length;
while (i > 0) {
const value = hostBindingOpCodes[--i];
if (typeof value === 'number' && value < 0) {
return value;
}
}
return 0;
}
/**
* Instantiate all the directives that were previously resolved on the current node.
*/
function instantiateAllDirectives(
tView: TView, lView: LView, tNode: TDirectiveHostNode, native: RNode) {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
// The component view needs to be created before creating the node injector
// since it is used to inject some special symbols like `ChangeDetectorRef`.
if (isComponentHost(tNode)) {
ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode);
addComponentLogic(