@angular/core 16.0.0-next.3 → 16.0.0-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2020/src/application_tokens.mjs +33 -1
- package/esm2020/src/change_detection/change_detector_ref.mjs +4 -4
- package/esm2020/src/compiler/compiler_facade_interface.mjs +1 -1
- package/esm2020/src/core.mjs +2 -2
- package/esm2020/src/core_private_export.mjs +2 -1
- package/esm2020/src/core_reactivity_export_internal.mjs +1 -1
- package/esm2020/src/core_render3_private_export.mjs +1 -2
- package/esm2020/src/di/r3_injector.mjs +8 -1
- package/esm2020/src/errors.mjs +1 -1
- package/esm2020/src/hydration/annotate.mjs +118 -5
- package/esm2020/src/hydration/api.mjs +14 -1
- package/esm2020/src/hydration/cleanup.mjs +54 -3
- package/esm2020/src/hydration/compression.mjs +69 -0
- package/esm2020/src/hydration/error_handling.mjs +357 -15
- package/esm2020/src/hydration/interfaces.mjs +17 -1
- package/esm2020/src/hydration/node_lookup_utils.mjs +199 -7
- package/esm2020/src/hydration/utils.mjs +16 -3
- package/esm2020/src/hydration/views.mjs +19 -15
- package/esm2020/src/linker/destroy_ref.mjs +5 -2
- package/esm2020/src/linker/view_container_ref.mjs +8 -7
- package/esm2020/src/metadata/directives.mjs +8 -3
- package/esm2020/src/render3/instructions/element.mjs +16 -9
- package/esm2020/src/render3/instructions/element_container.mjs +2 -5
- package/esm2020/src/render3/instructions/element_validation.mjs +2 -2
- package/esm2020/src/render3/instructions/projection.mjs +7 -4
- package/esm2020/src/render3/instructions/template.mjs +4 -7
- package/esm2020/src/render3/instructions/text.mjs +6 -6
- package/esm2020/src/render3/interfaces/public_definitions.mjs +1 -1
- package/esm2020/src/render3/interfaces/type_checks.mjs +4 -1
- package/esm2020/src/render3/node_manipulation.mjs +2 -2
- package/esm2020/src/render3/node_selector_matcher.mjs +17 -5
- package/esm2020/src/render3/util/view_utils.mjs +12 -1
- package/esm2020/src/render3/view_ref.mjs +1 -1
- package/esm2020/src/signals/index.mjs +2 -1
- package/esm2020/src/signals/src/computed.mjs +3 -3
- package/esm2020/src/signals/src/effect.mjs +1 -3
- package/esm2020/src/signals/src/signal.mjs +3 -3
- package/esm2020/src/signals/src/watch.mjs +3 -3
- package/esm2020/src/signals/src/weak_ref.mjs +18 -2
- package/esm2020/src/util/ng_dev_mode.mjs +2 -1
- package/esm2020/src/version.mjs +1 -1
- package/esm2020/testing/src/logger.mjs +3 -3
- package/esm2020/testing/src/ng_zone_mock.mjs +3 -3
- package/esm2020/testing/src/test_bed_compiler.mjs +12 -7
- package/fesm2015/core.mjs +1066 -178
- package/fesm2015/core.mjs.map +1 -1
- package/fesm2015/testing.mjs +816 -68
- package/fesm2015/testing.mjs.map +1 -1
- package/fesm2020/core.mjs +1051 -170
- package/fesm2020/core.mjs.map +1 -1
- package/fesm2020/testing.mjs +810 -67
- package/fesm2020/testing.mjs.map +1 -1
- package/index.d.ts +128 -216
- package/package.json +1 -1
- package/schematics/migrations/relative-link-resolution/bundle.js +7 -7
- package/schematics/migrations/router-link-with-href/bundle.js +10 -10
- package/schematics/ng-generate/standalone-migration/bundle.js +473 -376
- package/schematics/ng-generate/standalone-migration/bundle.js.map +2 -2
- package/testing/index.d.ts +1 -1
package/fesm2015/testing.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v16.0.0-next.
|
|
2
|
+
* @license Angular v16.0.0-next.4
|
|
3
3
|
* (c) 2010-2022 Google LLC. https://angular.io/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
@@ -8,6 +8,7 @@ import { getDebugNode, RendererFactory2 as RendererFactory2$1, InjectionToken as
|
|
|
8
8
|
import { __awaiter } from 'tslib';
|
|
9
9
|
import { ResourceLoader } from '@angular/compiler';
|
|
10
10
|
import { Subject, Subscription } from 'rxjs';
|
|
11
|
+
import { first } from 'rxjs/operators';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Wraps a test function in an asynchronous test zone. The test will automatically
|
|
@@ -1639,6 +1640,7 @@ function ngDevModeResetPerfCounters() {
|
|
|
1639
1640
|
hydratedNodes: 0,
|
|
1640
1641
|
hydratedComponents: 0,
|
|
1641
1642
|
dehydratedViewsRemoved: 0,
|
|
1643
|
+
dehydratedViewsCleanupRuns: 0,
|
|
1642
1644
|
};
|
|
1643
1645
|
// Make sure to refer to ngDevMode as ['ngDevMode'] for closure.
|
|
1644
1646
|
const allowNgDevModeTrue = locationString.indexOf('ngDevMode=false') === -1;
|
|
@@ -2447,12 +2449,19 @@ function isCssClassMatching(attrs, cssClassToMatch, isProjectionMode) {
|
|
|
2447
2449
|
ngDevMode &&
|
|
2448
2450
|
assertEqual(cssClassToMatch, cssClassToMatch.toLowerCase(), 'Class name expected to be lowercase.');
|
|
2449
2451
|
let i = 0;
|
|
2452
|
+
// Indicates whether we are processing value from the implicit
|
|
2453
|
+
// attribute section (i.e. before the first marker in the array).
|
|
2454
|
+
let isImplicitAttrsSection = true;
|
|
2450
2455
|
while (i < attrs.length) {
|
|
2451
2456
|
let item = attrs[i++];
|
|
2452
|
-
if (
|
|
2453
|
-
|
|
2454
|
-
if (
|
|
2455
|
-
|
|
2457
|
+
if (typeof item === 'string' && isImplicitAttrsSection) {
|
|
2458
|
+
const value = attrs[i++];
|
|
2459
|
+
if (isProjectionMode && item === 'class') {
|
|
2460
|
+
// We found a `class` attribute in the implicit attribute section,
|
|
2461
|
+
// check if it matches the value of the `cssClassToMatch` argument.
|
|
2462
|
+
if (classIndexOf(value.toLowerCase(), cssClassToMatch, 0) !== -1) {
|
|
2463
|
+
return true;
|
|
2464
|
+
}
|
|
2456
2465
|
}
|
|
2457
2466
|
}
|
|
2458
2467
|
else if (item === 1 /* AttributeMarker.Classes */) {
|
|
@@ -2464,6 +2473,11 @@ function isCssClassMatching(attrs, cssClassToMatch, isProjectionMode) {
|
|
|
2464
2473
|
}
|
|
2465
2474
|
return false;
|
|
2466
2475
|
}
|
|
2476
|
+
else if (typeof item === 'number') {
|
|
2477
|
+
// We've came across a first marker, which indicates
|
|
2478
|
+
// that the implicit attribute section is over.
|
|
2479
|
+
isImplicitAttrsSection = false;
|
|
2480
|
+
}
|
|
2467
2481
|
}
|
|
2468
2482
|
return false;
|
|
2469
2483
|
}
|
|
@@ -3280,6 +3294,9 @@ function isComponentDef(def) {
|
|
|
3280
3294
|
function isRootView(target) {
|
|
3281
3295
|
return (target[FLAGS] & 256 /* LViewFlags.IsRoot */) !== 0;
|
|
3282
3296
|
}
|
|
3297
|
+
function isProjectionTNode(tNode) {
|
|
3298
|
+
return (tNode.type & 16 /* TNodeType.Projection */) === 16 /* TNodeType.Projection */;
|
|
3299
|
+
}
|
|
3283
3300
|
|
|
3284
3301
|
// [Assert functions do not constraint type when they are guarded by a truthy
|
|
3285
3302
|
// expression.](https://github.com/microsoft/TypeScript/issues/37295)
|
|
@@ -3693,6 +3710,17 @@ function storeLViewOnDestroy(lView, onDestroyCallback) {
|
|
|
3693
3710
|
}
|
|
3694
3711
|
lView[ON_DESTROY_HOOKS].push(onDestroyCallback);
|
|
3695
3712
|
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Removes previously registered LView-specific destroy callback.
|
|
3715
|
+
*/
|
|
3716
|
+
function removeLViewOnDestroy(lView, onDestroyCallback) {
|
|
3717
|
+
if (lView[ON_DESTROY_HOOKS] === null)
|
|
3718
|
+
return;
|
|
3719
|
+
const destroyCBIdx = lView[ON_DESTROY_HOOKS].indexOf(onDestroyCallback);
|
|
3720
|
+
if (destroyCBIdx !== -1) {
|
|
3721
|
+
lView[ON_DESTROY_HOOKS].splice(destroyCBIdx, 1);
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3696
3724
|
|
|
3697
3725
|
const instructionState = {
|
|
3698
3726
|
lFrame: createLFrame(null),
|
|
@@ -8795,6 +8823,7 @@ class R3Injector extends EnvironmentInjector {
|
|
|
8795
8823
|
}
|
|
8796
8824
|
onDestroy(callback) {
|
|
8797
8825
|
this._onDestroyHooks.push(callback);
|
|
8826
|
+
return () => this.removeOnDestroy(callback);
|
|
8798
8827
|
}
|
|
8799
8828
|
runInContext(fn) {
|
|
8800
8829
|
this.assertNotDestroyed();
|
|
@@ -8969,6 +8998,12 @@ class R3Injector extends EnvironmentInjector {
|
|
|
8969
8998
|
return this.injectorDefTypes.has(providedIn);
|
|
8970
8999
|
}
|
|
8971
9000
|
}
|
|
9001
|
+
removeOnDestroy(callback) {
|
|
9002
|
+
const destroyCBIdx = this._onDestroyHooks.indexOf(callback);
|
|
9003
|
+
if (destroyCBIdx !== -1) {
|
|
9004
|
+
this._onDestroyHooks.splice(destroyCBIdx, 1);
|
|
9005
|
+
}
|
|
9006
|
+
}
|
|
8972
9007
|
}
|
|
8973
9008
|
function injectableDefOrInjectorDefFactory(token) {
|
|
8974
9009
|
// Most tokens will have an injectable def directly on them, which specifies a factory directly.
|
|
@@ -9154,6 +9189,38 @@ const PACKAGE_ROOT_URL = new InjectionToken('Application Packages Root URL');
|
|
|
9154
9189
|
* @publicApi
|
|
9155
9190
|
*/
|
|
9156
9191
|
const ANIMATION_MODULE_TYPE = new InjectionToken('AnimationModuleType');
|
|
9192
|
+
// TODO(crisbeto): link to CSP guide here.
|
|
9193
|
+
/**
|
|
9194
|
+
* Token used to configure the [Content Security Policy](https://web.dev/strict-csp/) nonce that
|
|
9195
|
+
* Angular will apply when inserting inline styles. If not provided, Angular will look up its value
|
|
9196
|
+
* from the `ngCspNonce` attribute of the application root node.
|
|
9197
|
+
*
|
|
9198
|
+
* @publicApi
|
|
9199
|
+
*/
|
|
9200
|
+
const CSP_NONCE = new InjectionToken('CSP nonce', {
|
|
9201
|
+
providedIn: 'root',
|
|
9202
|
+
factory: () => {
|
|
9203
|
+
var _a;
|
|
9204
|
+
// Ideally we wouldn't have to use `querySelector` here since we know that the nonce will be on
|
|
9205
|
+
// the root node, but because the token value is used in renderers, it has to be available
|
|
9206
|
+
// *very* early in the bootstrapping process. This should be a fairly shallow search, because
|
|
9207
|
+
// the app won't have been added to the DOM yet. Some approaches that were considered:
|
|
9208
|
+
// 1. Find the root node through `ApplicationRef.components[i].location` - normally this would
|
|
9209
|
+
// be enough for our purposes, but the token is injected very early so the `components` array
|
|
9210
|
+
// isn't populated yet.
|
|
9211
|
+
// 2. Find the root `LView` through the current `LView` - renderers are a prerequisite to
|
|
9212
|
+
// creating the `LView`. This means that no `LView` will have been entered when this factory is
|
|
9213
|
+
// invoked for the root component.
|
|
9214
|
+
// 3. Have the token factory return `() => string` which is invoked when a nonce is requested -
|
|
9215
|
+
// the slightly later execution does allow us to get an `LView` reference, but the fact that
|
|
9216
|
+
// it is a function means that it could be executed at *any* time (including immediately) which
|
|
9217
|
+
// may lead to weird bugs.
|
|
9218
|
+
// 4. Have the `ComponentFactory` read the attribute and provide it to the injector under the
|
|
9219
|
+
// hood - has the same problem as #1 and #2 in that the renderer is used to query for the root
|
|
9220
|
+
// node and the nonce value needs to be available when the renderer is created.
|
|
9221
|
+
return ((_a = getDocument().body.querySelector('[ngCspNonce]')) === null || _a === void 0 ? void 0 : _a.getAttribute('ngCspNonce')) || null;
|
|
9222
|
+
},
|
|
9223
|
+
});
|
|
9157
9224
|
|
|
9158
9225
|
function escapeTransferStateContent(text) {
|
|
9159
9226
|
const escapedText = {
|
|
@@ -9296,6 +9363,19 @@ function retrieveTransferredState(doc, appId) {
|
|
|
9296
9363
|
return initialState;
|
|
9297
9364
|
}
|
|
9298
9365
|
|
|
9366
|
+
/** Encodes that the node lookup should start from the host node of this component. */
|
|
9367
|
+
const REFERENCE_NODE_HOST = 'h';
|
|
9368
|
+
/** Encodes that the node lookup should start from the document body node. */
|
|
9369
|
+
const REFERENCE_NODE_BODY = 'b';
|
|
9370
|
+
/**
|
|
9371
|
+
* Describes navigation steps that the runtime logic need to perform,
|
|
9372
|
+
* starting from a given (known) element.
|
|
9373
|
+
*/
|
|
9374
|
+
var NodeNavigationStep;
|
|
9375
|
+
(function (NodeNavigationStep) {
|
|
9376
|
+
NodeNavigationStep["FirstChild"] = "f";
|
|
9377
|
+
NodeNavigationStep["NextSibling"] = "n";
|
|
9378
|
+
})(NodeNavigationStep || (NodeNavigationStep = {}));
|
|
9299
9379
|
/**
|
|
9300
9380
|
* Keys within serialized view data structure to represent various
|
|
9301
9381
|
* parts. See the `SerializedView` interface below for additional information.
|
|
@@ -9303,8 +9383,11 @@ function retrieveTransferredState(doc, appId) {
|
|
|
9303
9383
|
const ELEMENT_CONTAINERS = 'e';
|
|
9304
9384
|
const TEMPLATES = 't';
|
|
9305
9385
|
const CONTAINERS = 'c';
|
|
9386
|
+
const MULTIPLIER = 'x';
|
|
9306
9387
|
const NUM_ROOT_NODES = 'r';
|
|
9307
9388
|
const TEMPLATE_ID = 'i'; // as it's also an "id"
|
|
9389
|
+
const NODES = 'n';
|
|
9390
|
+
const DISCONNECTED_NODES = 'd';
|
|
9308
9391
|
|
|
9309
9392
|
/**
|
|
9310
9393
|
* The name of the key used in the TransferState collection,
|
|
@@ -9497,14 +9580,28 @@ function getSerializedContainerViews(hydrationInfo, index) {
|
|
|
9497
9580
|
* by calculating the sum of root nodes in all dehydrated views in this container.
|
|
9498
9581
|
*/
|
|
9499
9582
|
function calcSerializedContainerSize(hydrationInfo, index) {
|
|
9500
|
-
var _a;
|
|
9583
|
+
var _a, _b;
|
|
9501
9584
|
const views = (_a = getSerializedContainerViews(hydrationInfo, index)) !== null && _a !== void 0 ? _a : [];
|
|
9502
9585
|
let numNodes = 0;
|
|
9503
9586
|
for (let view of views) {
|
|
9504
|
-
numNodes += view[NUM_ROOT_NODES];
|
|
9587
|
+
numNodes += view[NUM_ROOT_NODES] * ((_b = view[MULTIPLIER]) !== null && _b !== void 0 ? _b : 1);
|
|
9505
9588
|
}
|
|
9506
9589
|
return numNodes;
|
|
9507
9590
|
}
|
|
9591
|
+
/**
|
|
9592
|
+
* Checks whether a node is annotated as "disconnected", i.e. not present
|
|
9593
|
+
* in the DOM at serialization time. We should not attempt hydration for
|
|
9594
|
+
* such nodes and instead, use a regular "creation mode".
|
|
9595
|
+
*/
|
|
9596
|
+
function isDisconnectedNode(hydrationInfo, index) {
|
|
9597
|
+
var _a;
|
|
9598
|
+
// Check if we are processing disconnected info for the first time.
|
|
9599
|
+
if (typeof hydrationInfo.disconnectedNodes === 'undefined') {
|
|
9600
|
+
const nodeIds = hydrationInfo.data[DISCONNECTED_NODES];
|
|
9601
|
+
hydrationInfo.disconnectedNodes = nodeIds ? (new Set(nodeIds)) : null;
|
|
9602
|
+
}
|
|
9603
|
+
return !!((_a = hydrationInfo.disconnectedNodes) === null || _a === void 0 ? void 0 : _a.has(index));
|
|
9604
|
+
}
|
|
9508
9605
|
|
|
9509
9606
|
/**
|
|
9510
9607
|
* Represents a component created by a `ComponentFactory`.
|
|
@@ -9685,7 +9782,7 @@ class Version {
|
|
|
9685
9782
|
/**
|
|
9686
9783
|
* @publicApi
|
|
9687
9784
|
*/
|
|
9688
|
-
const VERSION = new Version('16.0.0-next.
|
|
9785
|
+
const VERSION = new Version('16.0.0-next.4');
|
|
9689
9786
|
|
|
9690
9787
|
// This default value is when checking the hierarchy for a token.
|
|
9691
9788
|
//
|
|
@@ -14379,41 +14476,446 @@ function detectChanges(component) {
|
|
|
14379
14476
|
detectChangesInternal(view[TVIEW], view, component);
|
|
14380
14477
|
}
|
|
14381
14478
|
|
|
14479
|
+
const AT_THIS_LOCATION = '<-- AT THIS LOCATION';
|
|
14382
14480
|
/**
|
|
14383
|
-
*
|
|
14384
|
-
*
|
|
14481
|
+
* Retrieves a user friendly string for a given TNodeType for use in
|
|
14482
|
+
* friendly error messages
|
|
14483
|
+
*
|
|
14484
|
+
* @param tNodeType
|
|
14485
|
+
* @returns
|
|
14385
14486
|
*/
|
|
14386
|
-
function
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
|
|
14487
|
+
function getFriendlyStringFromTNodeType(tNodeType) {
|
|
14488
|
+
switch (tNodeType) {
|
|
14489
|
+
case 4 /* TNodeType.Container */:
|
|
14490
|
+
return 'view container';
|
|
14491
|
+
case 2 /* TNodeType.Element */:
|
|
14492
|
+
return 'element';
|
|
14493
|
+
case 8 /* TNodeType.ElementContainer */:
|
|
14494
|
+
return 'ng-container';
|
|
14495
|
+
case 32 /* TNodeType.Icu */:
|
|
14496
|
+
return 'icu';
|
|
14497
|
+
case 64 /* TNodeType.Placeholder */:
|
|
14498
|
+
return 'i18n';
|
|
14499
|
+
case 16 /* TNodeType.Projection */:
|
|
14500
|
+
return 'projection';
|
|
14501
|
+
case 1 /* TNodeType.Text */:
|
|
14502
|
+
return 'text';
|
|
14503
|
+
default:
|
|
14504
|
+
// This should not happen as we cover all possible TNode types above.
|
|
14505
|
+
return '<unknown>';
|
|
14506
|
+
}
|
|
14507
|
+
}
|
|
14508
|
+
/**
|
|
14509
|
+
* Validates that provided nodes match during the hydration process.
|
|
14510
|
+
*/
|
|
14511
|
+
function validateMatchingNode(node, nodeType, tagName, lView, tNode, isViewContainerAnchor = false) {
|
|
14512
|
+
var _a, _b, _c;
|
|
14513
|
+
if (!node ||
|
|
14514
|
+
(node.nodeType !== nodeType ||
|
|
14515
|
+
(node.nodeType === Node.ELEMENT_NODE &&
|
|
14516
|
+
node.tagName.toLowerCase() !== (tagName === null || tagName === void 0 ? void 0 : tagName.toLowerCase())))) {
|
|
14517
|
+
const expectedNode = shortRNodeDescription(nodeType, tagName, null);
|
|
14518
|
+
let header = `During hydration Angular expected ${expectedNode} but `;
|
|
14519
|
+
const hostComponentDef = getDeclarationComponentDef(lView);
|
|
14520
|
+
const componentClassName = (_a = hostComponentDef === null || hostComponentDef === void 0 ? void 0 : hostComponentDef.type) === null || _a === void 0 ? void 0 : _a.name;
|
|
14521
|
+
const expected = `Angular expected this DOM:\n\n${describeExpectedDom(lView, tNode, isViewContainerAnchor)}\n\n`;
|
|
14522
|
+
let actual = '';
|
|
14523
|
+
if (!node) {
|
|
14524
|
+
// No node found during hydration.
|
|
14525
|
+
header += `the node was not found.\n\n`;
|
|
14526
|
+
}
|
|
14527
|
+
else {
|
|
14528
|
+
const actualNode = shortRNodeDescription(node.nodeType, (_b = node.tagName) !== null && _b !== void 0 ? _b : null, (_c = node.textContent) !== null && _c !== void 0 ? _c : null);
|
|
14529
|
+
header += `found ${actualNode}.\n\n`;
|
|
14530
|
+
actual = `Actual DOM is:\n\n${describeDomFromNode(node)}\n\n`;
|
|
14531
|
+
}
|
|
14532
|
+
const footer = getHydrationErrorFooter(componentClassName);
|
|
14533
|
+
const message = header + expected + actual + getHydrationAttributeNote() + footer;
|
|
14534
|
+
throw new RuntimeError(500 /* RuntimeErrorCode.HYDRATION_NODE_MISMATCH */, message);
|
|
14393
14535
|
}
|
|
14394
14536
|
}
|
|
14395
14537
|
/**
|
|
14396
|
-
*
|
|
14538
|
+
* Validates that a given node has sibling nodes
|
|
14397
14539
|
*/
|
|
14398
14540
|
function validateSiblingNodeExists(node) {
|
|
14399
14541
|
validateNodeExists(node);
|
|
14400
14542
|
if (!node.nextSibling) {
|
|
14401
|
-
|
|
14402
|
-
|
|
14543
|
+
const header = 'During hydration Angular expected more sibling nodes to be present.\n\n';
|
|
14544
|
+
const actual = `Actual DOM is:\n\n${describeDomFromNode(node)}\n\n`;
|
|
14545
|
+
const footer = getHydrationErrorFooter();
|
|
14546
|
+
const message = header + actual + footer;
|
|
14547
|
+
throw new RuntimeError(501 /* RuntimeErrorCode.HYDRATION_MISSING_SIBLINGS */, message);
|
|
14403
14548
|
}
|
|
14404
14549
|
}
|
|
14550
|
+
/**
|
|
14551
|
+
* Validates that a node exists or throws
|
|
14552
|
+
*/
|
|
14405
14553
|
function validateNodeExists(node) {
|
|
14406
14554
|
if (!node) {
|
|
14407
|
-
|
|
14408
|
-
|
|
14555
|
+
throw new RuntimeError(502 /* RuntimeErrorCode.HYDRATION_MISSING_NODE */, `Hydration expected an element to be present at this location.`);
|
|
14556
|
+
}
|
|
14557
|
+
}
|
|
14558
|
+
/**
|
|
14559
|
+
* Builds the hydration error message when a node is not found
|
|
14560
|
+
*
|
|
14561
|
+
* @param lView the LView where the node exists
|
|
14562
|
+
* @param tNode the TNode
|
|
14563
|
+
*/
|
|
14564
|
+
function nodeNotFoundError(lView, tNode) {
|
|
14565
|
+
const header = 'During serialization, Angular was unable to find an element in the DOM:\n\n';
|
|
14566
|
+
const expected = `${describeExpectedDom(lView, tNode, false)}\n\n`;
|
|
14567
|
+
const footer = getHydrationErrorFooter();
|
|
14568
|
+
throw new RuntimeError(502 /* RuntimeErrorCode.HYDRATION_MISSING_NODE */, header + expected + footer);
|
|
14569
|
+
}
|
|
14570
|
+
/**
|
|
14571
|
+
* Builds a hydration error message when a node is not found at a path location
|
|
14572
|
+
*
|
|
14573
|
+
* @param host the Host Node
|
|
14574
|
+
* @param path the path to the node
|
|
14575
|
+
*/
|
|
14576
|
+
function nodeNotFoundAtPathError(host, path) {
|
|
14577
|
+
const header = `During hydration Angular was unable to locate a node ` +
|
|
14578
|
+
`using the "${path}" path, starting from the ${describeRNode(host)} node.\n\n`;
|
|
14579
|
+
const footer = getHydrationErrorFooter();
|
|
14580
|
+
throw new RuntimeError(502 /* RuntimeErrorCode.HYDRATION_MISSING_NODE */, header + footer);
|
|
14581
|
+
}
|
|
14582
|
+
/**
|
|
14583
|
+
* Builds the hydration error message in the case that dom nodes are created outside of
|
|
14584
|
+
* the Angular context and are being used as projected nodes
|
|
14585
|
+
*
|
|
14586
|
+
* @param lView the LView
|
|
14587
|
+
* @param tNode the TNode
|
|
14588
|
+
* @returns an error
|
|
14589
|
+
*/
|
|
14590
|
+
function unsupportedProjectionOfDomNodes(rNode) {
|
|
14591
|
+
const header = 'During serialization, Angular detected DOM nodes ' +
|
|
14592
|
+
'that were created outside of Angular context and provided as projectable nodes ' +
|
|
14593
|
+
'(likely via `ViewContainerRef.createComponent` or `createComponent` APIs). ' +
|
|
14594
|
+
'Hydration is not supported for such cases, consider refactoring the code to avoid ' +
|
|
14595
|
+
'this pattern or using `ngSkipHydration` on the host element of the component.\n\n';
|
|
14596
|
+
const actual = `${describeDomFromNode(rNode)}\n\n`;
|
|
14597
|
+
const message = header + actual + getHydrationAttributeNote();
|
|
14598
|
+
return new RuntimeError(503 /* RuntimeErrorCode.UNSUPPORTED_PROJECTION_DOM_NODES */, message);
|
|
14599
|
+
}
|
|
14600
|
+
/**
|
|
14601
|
+
* Builds the hydration error message in the case that ngSkipHydration was used on a
|
|
14602
|
+
* node that is not a component host element or host binding
|
|
14603
|
+
*
|
|
14604
|
+
* @param rNode the HTML Element
|
|
14605
|
+
* @returns an error
|
|
14606
|
+
*/
|
|
14607
|
+
function invalidSkipHydrationHost(rNode) {
|
|
14608
|
+
const header = 'The `ngSkipHydration` flag is applied on a node ' +
|
|
14609
|
+
'that doesn\'t act as a component host. Hydration can be ' +
|
|
14610
|
+
'skipped only on per-component basis.\n\n';
|
|
14611
|
+
const actual = `${describeDomFromNode(rNode)}\n\n`;
|
|
14612
|
+
const footer = 'Please move the `ngSkipHydration` attribute to the component host element.';
|
|
14613
|
+
const message = header + actual + footer;
|
|
14614
|
+
return new RuntimeError(504 /* RuntimeErrorCode.INVALID_SKIP_HYDRATION_HOST */, message);
|
|
14615
|
+
}
|
|
14616
|
+
/**
|
|
14617
|
+
* Builds the hydration error message in the case that a user is attempting to enable
|
|
14618
|
+
* hydration on internationalized nodes, which is not yet supported.
|
|
14619
|
+
*
|
|
14620
|
+
* @param rNode the HTML Element
|
|
14621
|
+
* @returns an error
|
|
14622
|
+
*/
|
|
14623
|
+
function notYetSupportedI18nBlockError(rNode) {
|
|
14624
|
+
const header = 'Hydration for nodes marked with `i18n` is not yet supported. ' +
|
|
14625
|
+
'You can opt-out a component that uses `i18n` in a template using ' +
|
|
14626
|
+
'the `ngSkipHydration` attribute or fall back to the previous ' +
|
|
14627
|
+
'hydration logic (which re-creates the application structure).\n\n';
|
|
14628
|
+
const actual = `${describeDomFromNode(rNode)}\n\n`;
|
|
14629
|
+
const message = header + actual;
|
|
14630
|
+
return new RuntimeError(518 /* RuntimeErrorCode.HYDRATION_I18N_NOT_YET_SUPPORTED */, message);
|
|
14631
|
+
}
|
|
14632
|
+
// Stringification methods
|
|
14633
|
+
/**
|
|
14634
|
+
* Stringifies a given TNode's attributes
|
|
14635
|
+
*
|
|
14636
|
+
* @param tNode a provided TNode
|
|
14637
|
+
* @returns string
|
|
14638
|
+
*/
|
|
14639
|
+
function stringifyTNodeAttrs(tNode) {
|
|
14640
|
+
const results = [];
|
|
14641
|
+
if (tNode.attrs) {
|
|
14642
|
+
for (let i = 0; i < tNode.attrs.length;) {
|
|
14643
|
+
const attrName = tNode.attrs[i++];
|
|
14644
|
+
// Once we reach the first flag, we know that the list of
|
|
14645
|
+
// attributes is over.
|
|
14646
|
+
if (typeof attrName == 'number') {
|
|
14647
|
+
break;
|
|
14648
|
+
}
|
|
14649
|
+
const attrValue = tNode.attrs[i++];
|
|
14650
|
+
results.push(`${attrName}="${shorten(attrValue)}"`);
|
|
14651
|
+
}
|
|
14652
|
+
}
|
|
14653
|
+
return results.join(' ');
|
|
14654
|
+
}
|
|
14655
|
+
/**
|
|
14656
|
+
* The list of internal attributes that should be filtered out while
|
|
14657
|
+
* producing an error message.
|
|
14658
|
+
*/
|
|
14659
|
+
const internalAttrs = new Set(['ngh', 'ng-version', 'ng-server-context']);
|
|
14660
|
+
/**
|
|
14661
|
+
* Stringifies an HTML Element's attributes
|
|
14662
|
+
*
|
|
14663
|
+
* @param rNode an HTML Element
|
|
14664
|
+
* @returns string
|
|
14665
|
+
*/
|
|
14666
|
+
function stringifyRNodeAttrs(rNode) {
|
|
14667
|
+
const results = [];
|
|
14668
|
+
for (let i = 0; i < rNode.attributes.length; i++) {
|
|
14669
|
+
const attr = rNode.attributes[i];
|
|
14670
|
+
if (internalAttrs.has(attr.name))
|
|
14671
|
+
continue;
|
|
14672
|
+
results.push(`${attr.name}="${shorten(attr.value)}"`);
|
|
14673
|
+
}
|
|
14674
|
+
return results.join(' ');
|
|
14675
|
+
}
|
|
14676
|
+
// Methods for Describing the DOM
|
|
14677
|
+
/**
|
|
14678
|
+
* Converts a tNode to a helpful readable string value for use in error messages
|
|
14679
|
+
*
|
|
14680
|
+
* @param tNode a given TNode
|
|
14681
|
+
* @param innerContent the content of the node
|
|
14682
|
+
* @returns string
|
|
14683
|
+
*/
|
|
14684
|
+
function describeTNode(tNode, innerContent = '…') {
|
|
14685
|
+
switch (tNode.type) {
|
|
14686
|
+
case 1 /* TNodeType.Text */:
|
|
14687
|
+
const content = tNode.value ? `(${tNode.value})` : '';
|
|
14688
|
+
return `#text${content}`;
|
|
14689
|
+
case 2 /* TNodeType.Element */:
|
|
14690
|
+
const attrs = stringifyTNodeAttrs(tNode);
|
|
14691
|
+
const tag = tNode.value.toLowerCase();
|
|
14692
|
+
return `<${tag}${attrs ? ' ' + attrs : ''}>${innerContent}</${tag}>`;
|
|
14693
|
+
case 8 /* TNodeType.ElementContainer */:
|
|
14694
|
+
return '<!-- ng-container -->';
|
|
14695
|
+
case 4 /* TNodeType.Container */:
|
|
14696
|
+
return '<!-- container -->';
|
|
14697
|
+
default:
|
|
14698
|
+
const typeAsString = getFriendlyStringFromTNodeType(tNode.type);
|
|
14699
|
+
return `#node(${typeAsString})`;
|
|
14700
|
+
}
|
|
14701
|
+
}
|
|
14702
|
+
/**
|
|
14703
|
+
* Converts an RNode to a helpful readable string value for use in error messages
|
|
14704
|
+
*
|
|
14705
|
+
* @param rNode a given RNode
|
|
14706
|
+
* @param innerContent the content of the node
|
|
14707
|
+
* @returns string
|
|
14708
|
+
*/
|
|
14709
|
+
function describeRNode(rNode, innerContent = '…') {
|
|
14710
|
+
var _a;
|
|
14711
|
+
const node = rNode;
|
|
14712
|
+
switch (node.nodeType) {
|
|
14713
|
+
case Node.ELEMENT_NODE:
|
|
14714
|
+
const tag = node.tagName.toLowerCase();
|
|
14715
|
+
const attrs = stringifyRNodeAttrs(node);
|
|
14716
|
+
return `<${tag}${attrs ? ' ' + attrs : ''}>${innerContent}</${tag}>`;
|
|
14717
|
+
case Node.TEXT_NODE:
|
|
14718
|
+
const content = node.textContent ? shorten(node.textContent) : '';
|
|
14719
|
+
return `#text${content ? `(${content})` : ''}`;
|
|
14720
|
+
case Node.COMMENT_NODE:
|
|
14721
|
+
return `<!-- ${shorten((_a = node.textContent) !== null && _a !== void 0 ? _a : '')} -->`;
|
|
14722
|
+
default:
|
|
14723
|
+
return `#node(${node.nodeType})`;
|
|
14724
|
+
}
|
|
14725
|
+
}
|
|
14726
|
+
/**
|
|
14727
|
+
* Builds the string containing the expected DOM present given the LView and TNode
|
|
14728
|
+
* values for a readable error message
|
|
14729
|
+
*
|
|
14730
|
+
* @param lView the lView containing the DOM
|
|
14731
|
+
* @param tNode the tNode
|
|
14732
|
+
* @param isViewContainerAnchor boolean
|
|
14733
|
+
* @returns string
|
|
14734
|
+
*/
|
|
14735
|
+
function describeExpectedDom(lView, tNode, isViewContainerAnchor) {
|
|
14736
|
+
const spacer = ' ';
|
|
14737
|
+
let content = '';
|
|
14738
|
+
if (tNode.prev) {
|
|
14739
|
+
content += spacer + '…\n';
|
|
14740
|
+
content += spacer + describeTNode(tNode.prev) + '\n';
|
|
14741
|
+
}
|
|
14742
|
+
else if (tNode.type && tNode.type & 12 /* TNodeType.AnyContainer */) {
|
|
14743
|
+
content += spacer + '…\n';
|
|
14744
|
+
}
|
|
14745
|
+
if (isViewContainerAnchor) {
|
|
14746
|
+
content += spacer + describeTNode(tNode) + '\n';
|
|
14747
|
+
content += spacer + `<!-- container --> ${AT_THIS_LOCATION}\n`;
|
|
14748
|
+
}
|
|
14749
|
+
else {
|
|
14750
|
+
content += spacer + describeTNode(tNode) + ` ${AT_THIS_LOCATION}\n`;
|
|
14751
|
+
}
|
|
14752
|
+
content += spacer + '…\n';
|
|
14753
|
+
const parentRNode = tNode.type ? getParentRElement(lView[TVIEW], tNode, lView) : null;
|
|
14754
|
+
if (parentRNode) {
|
|
14755
|
+
content = describeRNode(parentRNode, '\n' + content);
|
|
14756
|
+
}
|
|
14757
|
+
return content;
|
|
14758
|
+
}
|
|
14759
|
+
/**
|
|
14760
|
+
* Builds the string containing the DOM present around a given RNode for a
|
|
14761
|
+
* readable error message
|
|
14762
|
+
*
|
|
14763
|
+
* @param node the RNode
|
|
14764
|
+
* @returns string
|
|
14765
|
+
*/
|
|
14766
|
+
function describeDomFromNode(node) {
|
|
14767
|
+
const spacer = ' ';
|
|
14768
|
+
let content = '';
|
|
14769
|
+
const currentNode = node;
|
|
14770
|
+
if (currentNode.previousSibling) {
|
|
14771
|
+
content += spacer + '…\n';
|
|
14772
|
+
content += spacer + describeRNode(currentNode.previousSibling) + '\n';
|
|
14773
|
+
}
|
|
14774
|
+
content += spacer + describeRNode(currentNode) + ` ${AT_THIS_LOCATION}\n`;
|
|
14775
|
+
if (node.nextSibling) {
|
|
14776
|
+
content += spacer + '…\n';
|
|
14777
|
+
}
|
|
14778
|
+
if (node.parentNode) {
|
|
14779
|
+
content = describeRNode(currentNode.parentNode, '\n' + content);
|
|
14780
|
+
}
|
|
14781
|
+
return content;
|
|
14782
|
+
}
|
|
14783
|
+
/**
|
|
14784
|
+
* Shortens the description of a given RNode by its type for readability
|
|
14785
|
+
*
|
|
14786
|
+
* @param nodeType the type of node
|
|
14787
|
+
* @param tagName the node tag name
|
|
14788
|
+
* @param textContent the text content in the node
|
|
14789
|
+
* @returns string
|
|
14790
|
+
*/
|
|
14791
|
+
function shortRNodeDescription(nodeType, tagName, textContent) {
|
|
14792
|
+
switch (nodeType) {
|
|
14793
|
+
case Node.ELEMENT_NODE:
|
|
14794
|
+
return `<${tagName.toLowerCase()}>`;
|
|
14795
|
+
case Node.TEXT_NODE:
|
|
14796
|
+
const content = textContent ? ` (with the "${shorten(textContent)}" content)` : '';
|
|
14797
|
+
return `a text node${content}`;
|
|
14798
|
+
case Node.COMMENT_NODE:
|
|
14799
|
+
return 'a comment node';
|
|
14800
|
+
default:
|
|
14801
|
+
return `#node(nodeType=${nodeType})`;
|
|
14409
14802
|
}
|
|
14410
14803
|
}
|
|
14804
|
+
/**
|
|
14805
|
+
* Builds the footer hydration error message
|
|
14806
|
+
*
|
|
14807
|
+
* @param componentClassName the name of the component class
|
|
14808
|
+
* @returns string
|
|
14809
|
+
*/
|
|
14810
|
+
function getHydrationErrorFooter(componentClassName) {
|
|
14811
|
+
const componentInfo = componentClassName ? `the "${componentClassName}"` : 'corresponding';
|
|
14812
|
+
return `To fix this problem:\n` +
|
|
14813
|
+
` * check ${componentInfo} component for hydration-related issues\n` +
|
|
14814
|
+
` * or skip hydration by adding the \`ngSkipHydration\` attribute ` +
|
|
14815
|
+
`to its host node in a template`;
|
|
14816
|
+
}
|
|
14817
|
+
/**
|
|
14818
|
+
* An attribute related note for hydration errors
|
|
14819
|
+
*/
|
|
14820
|
+
function getHydrationAttributeNote() {
|
|
14821
|
+
return 'Note: attributes are only displayed to better represent the DOM' +
|
|
14822
|
+
' but have no effect on hydration mismatches.\n\n';
|
|
14823
|
+
}
|
|
14824
|
+
// Node string utility functions
|
|
14825
|
+
/**
|
|
14826
|
+
* Strips all newlines out of a given string
|
|
14827
|
+
*
|
|
14828
|
+
* @param input a string to be cleared of new line characters
|
|
14829
|
+
* @returns
|
|
14830
|
+
*/
|
|
14831
|
+
function stripNewlines(input) {
|
|
14832
|
+
return input.replace(/\s+/gm, '');
|
|
14833
|
+
}
|
|
14834
|
+
/**
|
|
14835
|
+
* Reduces a string down to a maximum length of characters with ellipsis for readability
|
|
14836
|
+
*
|
|
14837
|
+
* @param input a string input
|
|
14838
|
+
* @param maxLength a maximum length in characters
|
|
14839
|
+
* @returns string
|
|
14840
|
+
*/
|
|
14841
|
+
function shorten(input, maxLength = 50) {
|
|
14842
|
+
if (!input) {
|
|
14843
|
+
return '';
|
|
14844
|
+
}
|
|
14845
|
+
input = stripNewlines(input);
|
|
14846
|
+
return input.length > maxLength ? `${input.substring(0, maxLength - 1)}…` : input;
|
|
14847
|
+
}
|
|
14848
|
+
|
|
14849
|
+
/**
|
|
14850
|
+
* Regexp that extracts a reference node information from the compressed node location.
|
|
14851
|
+
* The reference node is represented as either:
|
|
14852
|
+
* - a number which points to an LView slot
|
|
14853
|
+
* - the `b` char which indicates that the lookup should start from the `document.body`
|
|
14854
|
+
* - the `h` char to start lookup from the component host node (`lView[HOST]`)
|
|
14855
|
+
*/
|
|
14856
|
+
const REF_EXTRACTOR_REGEXP = new RegExp(`^(\\d+)*(${REFERENCE_NODE_BODY}|${REFERENCE_NODE_HOST})*(.*)`);
|
|
14857
|
+
/**
|
|
14858
|
+
* Helper function that takes a reference node location and a set of navigation steps
|
|
14859
|
+
* (from the reference node) to a target node and outputs a string that represents
|
|
14860
|
+
* a location.
|
|
14861
|
+
*
|
|
14862
|
+
* For example, given: referenceNode = 'b' (body) and path = ['firstChild', 'firstChild',
|
|
14863
|
+
* 'nextSibling'], the function returns: `bf2n`.
|
|
14864
|
+
*/
|
|
14865
|
+
function compressNodeLocation(referenceNode, path) {
|
|
14866
|
+
const result = [referenceNode];
|
|
14867
|
+
for (const segment of path) {
|
|
14868
|
+
const lastIdx = result.length - 1;
|
|
14869
|
+
if (lastIdx > 0 && result[lastIdx - 1] === segment) {
|
|
14870
|
+
// An empty string in a count slot represents 1 occurrence of an instruction.
|
|
14871
|
+
const value = (result[lastIdx] || 1);
|
|
14872
|
+
result[lastIdx] = value + 1;
|
|
14873
|
+
}
|
|
14874
|
+
else {
|
|
14875
|
+
// Adding a new segment to the path.
|
|
14876
|
+
// Using an empty string in a counter field to avoid encoding `1`s
|
|
14877
|
+
// into the path, since they are implicit (e.g. `f1n1` vs `fn`), so
|
|
14878
|
+
// it's enough to have a single char in this case.
|
|
14879
|
+
result.push(segment, '');
|
|
14880
|
+
}
|
|
14881
|
+
}
|
|
14882
|
+
return result.join('');
|
|
14883
|
+
}
|
|
14884
|
+
/**
|
|
14885
|
+
* Helper function that reverts the `compressNodeLocation` and transforms a given
|
|
14886
|
+
* string into an array where at 0th position there is a reference node info and
|
|
14887
|
+
* after that it contains information (in pairs) about a navigation step and the
|
|
14888
|
+
* number of repetitions.
|
|
14889
|
+
*
|
|
14890
|
+
* For example, the path like 'bf2n' will be transformed to:
|
|
14891
|
+
* ['b', 'firstChild', 2, 'nextSibling', 1].
|
|
14892
|
+
*
|
|
14893
|
+
* This information is later consumed by the code that navigates the DOM to find
|
|
14894
|
+
* a given node by its location.
|
|
14895
|
+
*/
|
|
14896
|
+
function decompressNodeLocation(path) {
|
|
14897
|
+
const matches = path.match(REF_EXTRACTOR_REGEXP);
|
|
14898
|
+
const [_, refNodeId, refNodeName, rest] = matches;
|
|
14899
|
+
// If a reference node is represented by an index, transform it to a number.
|
|
14900
|
+
const ref = refNodeId ? parseInt(refNodeId, 10) : refNodeName;
|
|
14901
|
+
const steps = [];
|
|
14902
|
+
// Match all segments in a path.
|
|
14903
|
+
for (const [_, step, count] of rest.matchAll(/(f|n)(\d*)/g)) {
|
|
14904
|
+
const repeat = parseInt(count, 10) || 1;
|
|
14905
|
+
steps.push(step, repeat);
|
|
14906
|
+
}
|
|
14907
|
+
return [ref, ...steps];
|
|
14908
|
+
}
|
|
14411
14909
|
|
|
14412
14910
|
/** Whether current TNode is a first node in an <ng-container>. */
|
|
14413
14911
|
function isFirstElementInNgContainer(tNode) {
|
|
14414
14912
|
var _a;
|
|
14415
14913
|
return !tNode.prev && ((_a = tNode.parent) === null || _a === void 0 ? void 0 : _a.type) === 8 /* TNodeType.ElementContainer */;
|
|
14416
14914
|
}
|
|
14915
|
+
/** Returns an instruction index (subtracting HEADER_OFFSET). */
|
|
14916
|
+
function getNoOffsetIndex(tNode) {
|
|
14917
|
+
return tNode.index - HEADER_OFFSET;
|
|
14918
|
+
}
|
|
14417
14919
|
/**
|
|
14418
14920
|
* Locate a node in DOM tree that corresponds to a given TNode.
|
|
14419
14921
|
*
|
|
@@ -14426,7 +14928,13 @@ function isFirstElementInNgContainer(tNode) {
|
|
|
14426
14928
|
function locateNextRNode(hydrationInfo, tView, lView, tNode) {
|
|
14427
14929
|
var _a;
|
|
14428
14930
|
let native = null;
|
|
14429
|
-
|
|
14931
|
+
const noOffsetIndex = getNoOffsetIndex(tNode);
|
|
14932
|
+
const nodes = hydrationInfo.data[NODES];
|
|
14933
|
+
if (nodes === null || nodes === void 0 ? void 0 : nodes[noOffsetIndex]) {
|
|
14934
|
+
// We know the exact location of the node.
|
|
14935
|
+
native = locateRNodeByPath(nodes[noOffsetIndex], lView);
|
|
14936
|
+
}
|
|
14937
|
+
else if (tView.firstChild === tNode) {
|
|
14430
14938
|
// We create a first node in this view, so we use a reference
|
|
14431
14939
|
// to the first child in this DOM segment.
|
|
14432
14940
|
native = hydrationInfo.firstChild;
|
|
@@ -14439,7 +14947,7 @@ function locateNextRNode(hydrationInfo, tView, lView, tNode) {
|
|
|
14439
14947
|
assertDefined(previousTNode, 'Unexpected state: current TNode does not have a connection ' +
|
|
14440
14948
|
'to the previous node or a parent node.');
|
|
14441
14949
|
if (isFirstElementInNgContainer(tNode)) {
|
|
14442
|
-
const noOffsetParentIndex = tNode.parent
|
|
14950
|
+
const noOffsetParentIndex = getNoOffsetIndex(tNode.parent);
|
|
14443
14951
|
native = getSegmentHead(hydrationInfo, noOffsetParentIndex);
|
|
14444
14952
|
}
|
|
14445
14953
|
else {
|
|
@@ -14453,7 +14961,7 @@ function locateNextRNode(hydrationInfo, tView, lView, tNode) {
|
|
|
14453
14961
|
// represented in the DOM as `<div></div>...<!--container-->`.
|
|
14454
14962
|
// In this case, there are nodes *after* this element and we need to skip
|
|
14455
14963
|
// all of them to reach an element that we are looking for.
|
|
14456
|
-
const noOffsetPrevSiblingIndex = previousTNode
|
|
14964
|
+
const noOffsetPrevSiblingIndex = getNoOffsetIndex(previousTNode);
|
|
14457
14965
|
const segmentHead = getSegmentHead(hydrationInfo, noOffsetPrevSiblingIndex);
|
|
14458
14966
|
if (previousTNode.type === 2 /* TNodeType.Element */ && segmentHead) {
|
|
14459
14967
|
const numRootNodesToSkip = calcSerializedContainerSize(hydrationInfo, noOffsetPrevSiblingIndex);
|
|
@@ -14481,6 +14989,183 @@ function siblingAfter(skip, from) {
|
|
|
14481
14989
|
}
|
|
14482
14990
|
return currentNode;
|
|
14483
14991
|
}
|
|
14992
|
+
/**
|
|
14993
|
+
* Helper function to produce a string representation of the navigation steps
|
|
14994
|
+
* (in terms of `nextSibling` and `firstChild` navigations). Used in error
|
|
14995
|
+
* messages in dev mode.
|
|
14996
|
+
*/
|
|
14997
|
+
function stringifyNavigationInstructions(instructions) {
|
|
14998
|
+
const container = [];
|
|
14999
|
+
let i = 0;
|
|
15000
|
+
while (i < instructions.length) {
|
|
15001
|
+
const step = instructions[i++];
|
|
15002
|
+
const repeat = instructions[i++];
|
|
15003
|
+
for (let r = 0; r < repeat; r++) {
|
|
15004
|
+
container.push(step === NodeNavigationStep.FirstChild ? 'firstChild' : 'nextSibling');
|
|
15005
|
+
}
|
|
15006
|
+
}
|
|
15007
|
+
return container.join('.');
|
|
15008
|
+
}
|
|
15009
|
+
/**
|
|
15010
|
+
* Helper function that navigates from a starting point node (the `from` node)
|
|
15011
|
+
* using provided set of navigation instructions (within `path` argument).
|
|
15012
|
+
*/
|
|
15013
|
+
function navigateToNode(from, instructions) {
|
|
15014
|
+
let node = from;
|
|
15015
|
+
let i = 0;
|
|
15016
|
+
while (i < instructions.length) {
|
|
15017
|
+
const step = instructions[i++];
|
|
15018
|
+
const repeat = instructions[i++];
|
|
15019
|
+
for (let r = 0; r < repeat; r++) {
|
|
15020
|
+
if (ngDevMode && !node) {
|
|
15021
|
+
throw nodeNotFoundAtPathError(from, stringifyNavigationInstructions(instructions));
|
|
15022
|
+
}
|
|
15023
|
+
switch (step) {
|
|
15024
|
+
case NodeNavigationStep.FirstChild:
|
|
15025
|
+
node = node.firstChild;
|
|
15026
|
+
break;
|
|
15027
|
+
case NodeNavigationStep.NextSibling:
|
|
15028
|
+
node = node.nextSibling;
|
|
15029
|
+
break;
|
|
15030
|
+
}
|
|
15031
|
+
}
|
|
15032
|
+
}
|
|
15033
|
+
if (ngDevMode && !node) {
|
|
15034
|
+
throw nodeNotFoundAtPathError(from, stringifyNavigationInstructions(instructions));
|
|
15035
|
+
}
|
|
15036
|
+
return node;
|
|
15037
|
+
}
|
|
15038
|
+
/**
|
|
15039
|
+
* Locates an RNode given a set of navigation instructions (which also contains
|
|
15040
|
+
* a starting point node info).
|
|
15041
|
+
*/
|
|
15042
|
+
function locateRNodeByPath(path, lView) {
|
|
15043
|
+
const [referenceNode, ...navigationInstructions] = decompressNodeLocation(path);
|
|
15044
|
+
let ref;
|
|
15045
|
+
if (referenceNode === REFERENCE_NODE_HOST) {
|
|
15046
|
+
ref = lView[0];
|
|
15047
|
+
}
|
|
15048
|
+
else if (referenceNode === REFERENCE_NODE_BODY) {
|
|
15049
|
+
ref = ɵɵresolveBody(lView[0]);
|
|
15050
|
+
}
|
|
15051
|
+
else {
|
|
15052
|
+
const parentElementId = Number(referenceNode);
|
|
15053
|
+
ref = unwrapRNode(lView[parentElementId + HEADER_OFFSET]);
|
|
15054
|
+
}
|
|
15055
|
+
return navigateToNode(ref, navigationInstructions);
|
|
15056
|
+
}
|
|
15057
|
+
/**
|
|
15058
|
+
* Generate a list of DOM navigation operations to get from node `start` to node `finish`.
|
|
15059
|
+
*
|
|
15060
|
+
* Note: assumes that node `start` occurs before node `finish` in an in-order traversal of the DOM
|
|
15061
|
+
* tree. That is, we should be able to get from `start` to `finish` purely by using `.firstChild`
|
|
15062
|
+
* and `.nextSibling` operations.
|
|
15063
|
+
*/
|
|
15064
|
+
function navigateBetween(start, finish) {
|
|
15065
|
+
if (start === finish) {
|
|
15066
|
+
return [];
|
|
15067
|
+
}
|
|
15068
|
+
else if (start.parentElement == null || finish.parentElement == null) {
|
|
15069
|
+
return null;
|
|
15070
|
+
}
|
|
15071
|
+
else if (start.parentElement === finish.parentElement) {
|
|
15072
|
+
return navigateBetweenSiblings(start, finish);
|
|
15073
|
+
}
|
|
15074
|
+
else {
|
|
15075
|
+
// `finish` is a child of its parent, so the parent will always have a child.
|
|
15076
|
+
const parent = finish.parentElement;
|
|
15077
|
+
const parentPath = navigateBetween(start, parent);
|
|
15078
|
+
const childPath = navigateBetween(parent.firstChild, finish);
|
|
15079
|
+
if (!parentPath || !childPath)
|
|
15080
|
+
return null;
|
|
15081
|
+
return [
|
|
15082
|
+
// First navigate to `finish`'s parent
|
|
15083
|
+
...parentPath,
|
|
15084
|
+
// Then to its first child.
|
|
15085
|
+
NodeNavigationStep.FirstChild,
|
|
15086
|
+
// And finally from that node to `finish` (maybe a no-op if we're already there).
|
|
15087
|
+
...childPath,
|
|
15088
|
+
];
|
|
15089
|
+
}
|
|
15090
|
+
}
|
|
15091
|
+
/**
|
|
15092
|
+
* Calculates a path between 2 sibling nodes (generates a number of `NextSibling` navigations).
|
|
15093
|
+
*/
|
|
15094
|
+
function navigateBetweenSiblings(start, finish) {
|
|
15095
|
+
const nav = [];
|
|
15096
|
+
let node = null;
|
|
15097
|
+
for (node = start; node != null && node !== finish; node = node.nextSibling) {
|
|
15098
|
+
nav.push(NodeNavigationStep.NextSibling);
|
|
15099
|
+
}
|
|
15100
|
+
return node === null ? [] : nav;
|
|
15101
|
+
}
|
|
15102
|
+
/**
|
|
15103
|
+
* Calculates a path between 2 nodes in terms of `nextSibling` and `firstChild`
|
|
15104
|
+
* navigations:
|
|
15105
|
+
* - the `from` node is a known node, used as an starting point for the lookup
|
|
15106
|
+
* (the `fromNodeName` argument is a string representation of the node).
|
|
15107
|
+
* - the `to` node is a node that the runtime logic would be looking up,
|
|
15108
|
+
* using the path generated by this function.
|
|
15109
|
+
*/
|
|
15110
|
+
function calcPathBetween(from, to, fromNodeName) {
|
|
15111
|
+
const path = navigateBetween(from, to);
|
|
15112
|
+
return path === null ? null : compressNodeLocation(fromNodeName, path);
|
|
15113
|
+
}
|
|
15114
|
+
/**
|
|
15115
|
+
* Invoked at serialization time (on the server) when a set of navigation
|
|
15116
|
+
* instructions needs to be generated for a TNode.
|
|
15117
|
+
*/
|
|
15118
|
+
function calcPathForNode(tNode, lView) {
|
|
15119
|
+
const parentTNode = tNode.parent;
|
|
15120
|
+
let parentIndex;
|
|
15121
|
+
let parentRNode;
|
|
15122
|
+
let referenceNodeName;
|
|
15123
|
+
if (parentTNode === null) {
|
|
15124
|
+
// No parent TNode - use host element as a reference node.
|
|
15125
|
+
parentIndex = referenceNodeName = REFERENCE_NODE_HOST;
|
|
15126
|
+
parentRNode = lView[HOST];
|
|
15127
|
+
}
|
|
15128
|
+
else {
|
|
15129
|
+
// Use parent TNode as a reference node.
|
|
15130
|
+
parentIndex = parentTNode.index;
|
|
15131
|
+
parentRNode = unwrapRNode(lView[parentIndex]);
|
|
15132
|
+
referenceNodeName = renderStringify(parentIndex - HEADER_OFFSET);
|
|
15133
|
+
}
|
|
15134
|
+
let rNode = unwrapRNode(lView[tNode.index]);
|
|
15135
|
+
if (tNode.type & 12 /* TNodeType.AnyContainer */) {
|
|
15136
|
+
// For <ng-container> nodes, instead of serializing a reference
|
|
15137
|
+
// to the anchor comment node, serialize a location of the first
|
|
15138
|
+
// DOM element. Paired with the container size (serialized as a part
|
|
15139
|
+
// of `ngh.containers`), it should give enough information for runtime
|
|
15140
|
+
// to hydrate nodes in this container.
|
|
15141
|
+
const firstRNode = getFirstNativeNode(lView, tNode);
|
|
15142
|
+
// If container is not empty, use a reference to the first element,
|
|
15143
|
+
// otherwise, rNode would point to an anchor comment node.
|
|
15144
|
+
if (firstRNode) {
|
|
15145
|
+
rNode = firstRNode;
|
|
15146
|
+
}
|
|
15147
|
+
}
|
|
15148
|
+
let path = calcPathBetween(parentRNode, rNode, referenceNodeName);
|
|
15149
|
+
if (path === null && parentRNode !== rNode) {
|
|
15150
|
+
// Searching for a path between elements within a host node failed.
|
|
15151
|
+
// Trying to find a path to an element starting from the `document.body` instead.
|
|
15152
|
+
//
|
|
15153
|
+
// Important note: this type of reference is relatively unstable, since Angular
|
|
15154
|
+
// may not be able to control parts of the page that the runtime logic navigates
|
|
15155
|
+
// through. This is mostly needed to cover "portals" use-case (like menus, dialog boxes,
|
|
15156
|
+
// etc), where nodes are content-projected (including direct DOM manipulations) outside
|
|
15157
|
+
// of the host node. The better solution is to provide APIs to work with "portals",
|
|
15158
|
+
// at which point this code path would not be needed.
|
|
15159
|
+
const body = parentRNode.ownerDocument.body;
|
|
15160
|
+
path = calcPathBetween(body, rNode, REFERENCE_NODE_BODY);
|
|
15161
|
+
if (path === null) {
|
|
15162
|
+
// If the path is still empty, it's likely that this node is detached and
|
|
15163
|
+
// won't be found during hydration.
|
|
15164
|
+
throw nodeNotFoundError(lView, tNode);
|
|
15165
|
+
}
|
|
15166
|
+
}
|
|
15167
|
+
return path;
|
|
15168
|
+
}
|
|
14484
15169
|
|
|
14485
15170
|
function templateFirstCreatePass(index, tView, lView, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex) {
|
|
14486
15171
|
var _a, _b;
|
|
@@ -14558,7 +15243,7 @@ function createContainerAnchorImpl(tView, lView, tNode, index) {
|
|
|
14558
15243
|
*/
|
|
14559
15244
|
function locateOrCreateContainerAnchorImpl(tView, lView, tNode, index) {
|
|
14560
15245
|
const hydrationInfo = lView[HYDRATION];
|
|
14561
|
-
const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1();
|
|
15246
|
+
const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode(hydrationInfo, index);
|
|
14562
15247
|
lastNodeWasCreated(isNodeCreationMode);
|
|
14563
15248
|
// Regular creation mode.
|
|
14564
15249
|
if (isNodeCreationMode) {
|
|
@@ -14567,11 +15252,8 @@ function locateOrCreateContainerAnchorImpl(tView, lView, tNode, index) {
|
|
|
14567
15252
|
// Hydration mode, looking up existing elements in DOM.
|
|
14568
15253
|
const currentRNode = locateNextRNode(hydrationInfo, tView, lView, tNode);
|
|
14569
15254
|
ngDevMode && validateNodeExists(currentRNode);
|
|
15255
|
+
setSegmentHead(hydrationInfo, index, currentRNode);
|
|
14570
15256
|
const viewContainerSize = calcSerializedContainerSize(hydrationInfo, index);
|
|
14571
|
-
// If this container is non-empty, store the first node as a segment head,
|
|
14572
|
-
// otherwise, this node is an anchor and segment head doesn't exist (thus `null`).
|
|
14573
|
-
const segmentHead = viewContainerSize > 0 ? currentRNode : null;
|
|
14574
|
-
setSegmentHead(hydrationInfo, index, segmentHead);
|
|
14575
15257
|
const comment = siblingAfter(viewContainerSize, currentRNode);
|
|
14576
15258
|
if (ngDevMode) {
|
|
14577
15259
|
validateMatchingNode(comment, Node.COMMENT_NODE, null, lView, tNode);
|
|
@@ -14827,7 +15509,7 @@ let _locateOrCreateElementNode = (tView, lView, tNode, renderer, name, index) =>
|
|
|
14827
15509
|
*/
|
|
14828
15510
|
function locateOrCreateElementNodeImpl(tView, lView, tNode, renderer, name, index) {
|
|
14829
15511
|
const hydrationInfo = lView[HYDRATION];
|
|
14830
|
-
const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1();
|
|
15512
|
+
const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode(hydrationInfo, index);
|
|
14831
15513
|
lastNodeWasCreated(isNodeCreationMode);
|
|
14832
15514
|
// Regular creation mode.
|
|
14833
15515
|
if (isNodeCreationMode) {
|
|
@@ -14851,10 +15533,17 @@ function locateOrCreateElementNodeImpl(tView, lView, tNode, renderer, name, inde
|
|
|
14851
15533
|
// Checks if the skip hydration attribute is present during hydration so we know to
|
|
14852
15534
|
// skip attempting to hydrate this block.
|
|
14853
15535
|
if (hydrationInfo && hasNgSkipHydrationAttr(tNode)) {
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
|
|
14857
|
-
|
|
15536
|
+
if (isComponentHost(tNode)) {
|
|
15537
|
+
enterSkipHydrationBlock(tNode);
|
|
15538
|
+
// Since this isn't hydratable, we need to empty the node
|
|
15539
|
+
// so there's no duplicate content after render
|
|
15540
|
+
clearElementContents(renderer, native);
|
|
15541
|
+
}
|
|
15542
|
+
else if (ngDevMode) {
|
|
15543
|
+
// If this is not a component host, throw an error.
|
|
15544
|
+
// Hydration can be skipped on per-component basis only.
|
|
15545
|
+
throw invalidSkipHydrationHost(native);
|
|
15546
|
+
}
|
|
14858
15547
|
}
|
|
14859
15548
|
return native;
|
|
14860
15549
|
}
|
|
@@ -14988,10 +15677,7 @@ function locateOrCreateElementContainerNode(tView, lView, tNode, index) {
|
|
|
14988
15677
|
ngDevMode &&
|
|
14989
15678
|
assertNumber(ngContainerSize, 'Unexpected state: hydrating an <ng-container>, ' +
|
|
14990
15679
|
'but no hydration info is available.');
|
|
14991
|
-
|
|
14992
|
-
// otherwise, this node is an anchor and segment head doesn't exist (thus `null`).
|
|
14993
|
-
const segmentHead = ngContainerSize > 0 ? currentRNode : null;
|
|
14994
|
-
setSegmentHead(hydrationInfo, index, segmentHead);
|
|
15680
|
+
setSegmentHead(hydrationInfo, index, currentRNode);
|
|
14995
15681
|
comment = siblingAfter(ngContainerSize, currentRNode);
|
|
14996
15682
|
if (ngDevMode) {
|
|
14997
15683
|
validateMatchingNode(comment, Node.COMMENT_NODE, null, lView, tNode);
|
|
@@ -15373,7 +16059,10 @@ function ɵɵprojection(nodeIndex, selectorIndex = 0, attrs) {
|
|
|
15373
16059
|
tProjectionNode.projection = selectorIndex;
|
|
15374
16060
|
// `<ng-content>` has no content
|
|
15375
16061
|
setCurrentTNodeAsNotParent();
|
|
15376
|
-
|
|
16062
|
+
const hydrationInfo = lView[HYDRATION];
|
|
16063
|
+
const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1();
|
|
16064
|
+
if (isNodeCreationMode &&
|
|
16065
|
+
(tProjectionNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */) {
|
|
15377
16066
|
// re-distribution of projectable nodes is stored on a component's view level
|
|
15378
16067
|
applyProjection(tView, lView, tProjectionNode);
|
|
15379
16068
|
}
|
|
@@ -17336,7 +18025,7 @@ function ɵɵtext(index, value = '') {
|
|
|
17336
18025
|
const tNode = tView.firstCreatePass ?
|
|
17337
18026
|
getOrCreateTNode(tView, adjustedIndex, 1 /* TNodeType.Text */, value, null) :
|
|
17338
18027
|
tView.data[adjustedIndex];
|
|
17339
|
-
const textNative = _locateOrCreateTextNode(tView, lView, tNode, value);
|
|
18028
|
+
const textNative = _locateOrCreateTextNode(tView, lView, tNode, value, index);
|
|
17340
18029
|
lView[adjustedIndex] = textNative;
|
|
17341
18030
|
if (wasLastNodeCreated()) {
|
|
17342
18031
|
appendChild(tView, lView, textNative, tNode);
|
|
@@ -17344,7 +18033,7 @@ function ɵɵtext(index, value = '') {
|
|
|
17344
18033
|
// Text nodes are self closing.
|
|
17345
18034
|
setCurrentTNode(tNode, false);
|
|
17346
18035
|
}
|
|
17347
|
-
let _locateOrCreateTextNode = (tView, lView, tNode, value) => {
|
|
18036
|
+
let _locateOrCreateTextNode = (tView, lView, tNode, value, index) => {
|
|
17348
18037
|
lastNodeWasCreated(true);
|
|
17349
18038
|
return createTextNode(lView[RENDERER], value);
|
|
17350
18039
|
};
|
|
@@ -17352,9 +18041,9 @@ let _locateOrCreateTextNode = (tView, lView, tNode, value) => {
|
|
|
17352
18041
|
* Enables hydration code path (to lookup existing elements in DOM)
|
|
17353
18042
|
* in addition to the regular creation mode of text nodes.
|
|
17354
18043
|
*/
|
|
17355
|
-
function locateOrCreateTextNodeImpl(tView, lView, tNode, value) {
|
|
18044
|
+
function locateOrCreateTextNodeImpl(tView, lView, tNode, value, index) {
|
|
17356
18045
|
const hydrationInfo = lView[HYDRATION];
|
|
17357
|
-
const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1();
|
|
18046
|
+
const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode(hydrationInfo, index);
|
|
17358
18047
|
lastNodeWasCreated(isNodeCreationMode);
|
|
17359
18048
|
// Regular creation mode.
|
|
17360
18049
|
if (isNodeCreationMode) {
|
|
@@ -22465,6 +23154,54 @@ function removeDehydratedView(dehydratedView, renderer) {
|
|
|
22465
23154
|
}
|
|
22466
23155
|
}
|
|
22467
23156
|
}
|
|
23157
|
+
/**
|
|
23158
|
+
* Walks over all views within this LContainer invokes dehydrated views
|
|
23159
|
+
* cleanup function for each one.
|
|
23160
|
+
*/
|
|
23161
|
+
function cleanupLContainer(lContainer) {
|
|
23162
|
+
removeDehydratedViews(lContainer);
|
|
23163
|
+
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
|
|
23164
|
+
cleanupLView(lContainer[i]);
|
|
23165
|
+
}
|
|
23166
|
+
}
|
|
23167
|
+
/**
|
|
23168
|
+
* Walks over `LContainer`s and components registered within
|
|
23169
|
+
* this LView and invokes dehydrated views cleanup function for each one.
|
|
23170
|
+
*/
|
|
23171
|
+
function cleanupLView(lView) {
|
|
23172
|
+
const tView = lView[TVIEW];
|
|
23173
|
+
for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
|
|
23174
|
+
if (isLContainer(lView[i])) {
|
|
23175
|
+
const lContainer = lView[i];
|
|
23176
|
+
cleanupLContainer(lContainer);
|
|
23177
|
+
}
|
|
23178
|
+
else if (Array.isArray(lView[i])) {
|
|
23179
|
+
// This is a component, enter the `cleanupLView` recursively.
|
|
23180
|
+
cleanupLView(lView[i]);
|
|
23181
|
+
}
|
|
23182
|
+
}
|
|
23183
|
+
}
|
|
23184
|
+
/**
|
|
23185
|
+
* Walks over all views registered within the ApplicationRef and removes
|
|
23186
|
+
* all dehydrated views from all `LContainer`s along the way.
|
|
23187
|
+
*/
|
|
23188
|
+
function cleanupDehydratedViews(appRef) {
|
|
23189
|
+
// Wait once an app becomes stable and cleanup all views that
|
|
23190
|
+
// were not claimed during the application bootstrap process.
|
|
23191
|
+
// The timing is similar to when we kick off serialization on the server.
|
|
23192
|
+
return appRef.isStable.pipe(first((isStable) => isStable)).toPromise().then(() => {
|
|
23193
|
+
const viewRefs = appRef._views;
|
|
23194
|
+
for (const viewRef of viewRefs) {
|
|
23195
|
+
const lView = getComponentLViewForHydration(viewRef);
|
|
23196
|
+
// An `lView` might be `null` if a `ViewRef` represents
|
|
23197
|
+
// an embedded view (not a component view).
|
|
23198
|
+
if (lView !== null && lView[HOST] !== null) {
|
|
23199
|
+
cleanupLView(lView);
|
|
23200
|
+
ngDevMode && ngDevMode.dehydratedViewsCleanupRuns++;
|
|
23201
|
+
}
|
|
23202
|
+
}
|
|
23203
|
+
});
|
|
23204
|
+
}
|
|
22468
23205
|
|
|
22469
23206
|
/**
|
|
22470
23207
|
* Given a current DOM node and a serialized information about the views
|
|
@@ -22472,22 +23209,27 @@ function removeDehydratedView(dehydratedView, renderer) {
|
|
|
22472
23209
|
* dehydrated views.
|
|
22473
23210
|
*/
|
|
22474
23211
|
function locateDehydratedViewsInContainer(currentRNode, serializedViews) {
|
|
23212
|
+
var _a;
|
|
22475
23213
|
const dehydratedViews = [];
|
|
22476
23214
|
for (const serializedView of serializedViews) {
|
|
22477
|
-
|
|
22478
|
-
|
|
22479
|
-
|
|
22480
|
-
|
|
22481
|
-
|
|
22482
|
-
|
|
22483
|
-
|
|
22484
|
-
|
|
22485
|
-
|
|
22486
|
-
|
|
22487
|
-
|
|
22488
|
-
|
|
23215
|
+
// Repeats a view multiple times as needed, based on the serialized information
|
|
23216
|
+
// (for example, for *ngFor-produced views).
|
|
23217
|
+
for (let i = 0; i < ((_a = serializedView[MULTIPLIER]) !== null && _a !== void 0 ? _a : 1); i++) {
|
|
23218
|
+
const view = {
|
|
23219
|
+
data: serializedView,
|
|
23220
|
+
firstChild: null,
|
|
23221
|
+
};
|
|
23222
|
+
if (serializedView[NUM_ROOT_NODES] > 0) {
|
|
23223
|
+
// Keep reference to the first node in this view,
|
|
23224
|
+
// so it can be accessed while invoking template instructions.
|
|
23225
|
+
view.firstChild = currentRNode;
|
|
23226
|
+
// Move over to the next node after this view, which can
|
|
23227
|
+
// either be a first node of the next view or an anchor comment
|
|
23228
|
+
// node after the last view in a container.
|
|
23229
|
+
currentRNode = siblingAfter(serializedView[NUM_ROOT_NODES], currentRNode);
|
|
23230
|
+
}
|
|
23231
|
+
dehydratedViews.push(view);
|
|
22489
23232
|
}
|
|
22490
|
-
dehydratedViews.push(view);
|
|
22491
23233
|
}
|
|
22492
23234
|
return [currentRNode, dehydratedViews];
|
|
22493
23235
|
}
|
|
@@ -22871,21 +23613,22 @@ function locateOrCreateAnchorNode(lContainer, hostLView, hostTNode, slotValue) {
|
|
|
22871
23613
|
if (lContainer[NATIVE] && lContainer[DEHYDRATED_VIEWS])
|
|
22872
23614
|
return;
|
|
22873
23615
|
const hydrationInfo = hostLView[HYDRATION];
|
|
22874
|
-
const
|
|
23616
|
+
const noOffsetIndex = hostTNode.index - HEADER_OFFSET;
|
|
23617
|
+
const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock(hostTNode) ||
|
|
23618
|
+
isDisconnectedNode(hydrationInfo, noOffsetIndex);
|
|
22875
23619
|
// Regular creation mode.
|
|
22876
23620
|
if (isNodeCreationMode) {
|
|
22877
23621
|
return createAnchorNode(lContainer, hostLView, hostTNode, slotValue);
|
|
22878
23622
|
}
|
|
22879
23623
|
// Hydration mode, looking up an anchor node and dehydrated views in DOM.
|
|
22880
|
-
const
|
|
22881
|
-
const
|
|
22882
|
-
const serializedViews = (_a = hydrationInfo.data[CONTAINERS]) === null || _a === void 0 ? void 0 : _a[index];
|
|
23624
|
+
const currentRNode = getSegmentHead(hydrationInfo, noOffsetIndex);
|
|
23625
|
+
const serializedViews = (_a = hydrationInfo.data[CONTAINERS]) === null || _a === void 0 ? void 0 : _a[noOffsetIndex];
|
|
22883
23626
|
ngDevMode &&
|
|
22884
23627
|
assertDefined(serializedViews, 'Unexpected state: no hydration info available for a given TNode, ' +
|
|
22885
23628
|
'which represents a view container.');
|
|
22886
23629
|
const [commentNode, dehydratedViews] = locateDehydratedViewsInContainer(currentRNode, serializedViews);
|
|
22887
23630
|
if (ngDevMode) {
|
|
22888
|
-
validateMatchingNode(commentNode, Node.COMMENT_NODE, null, hostLView, hostTNode);
|
|
23631
|
+
validateMatchingNode(commentNode, Node.COMMENT_NODE, null, hostLView, hostTNode, true);
|
|
22889
23632
|
// Do not throw in case this node is already claimed (thus `false` as a second
|
|
22890
23633
|
// argument). If this container is created based on an `<ng-template>`, the comment
|
|
22891
23634
|
// node would be already claimed from the `template` instruction. If an element acts
|
|
@@ -24729,10 +25472,11 @@ class TestBedCompiler {
|
|
|
24729
25472
|
}
|
|
24730
25473
|
}
|
|
24731
25474
|
queueTypesFromModulesArray(arr) {
|
|
24732
|
-
// Because we may encounter the same NgModule
|
|
24733
|
-
// NgModule
|
|
24734
|
-
// encountered. In some test setups, this caching
|
|
24735
|
-
|
|
25475
|
+
// Because we may encounter the same NgModule or a standalone Component while processing
|
|
25476
|
+
// the dependencies of an NgModule or a standalone Component, we cache them in this set so we
|
|
25477
|
+
// can skip ones that have already been seen encountered. In some test setups, this caching
|
|
25478
|
+
// resulted in 10X runtime improvement.
|
|
25479
|
+
const processedDefs = new Set();
|
|
24736
25480
|
const queueTypesFromModulesArrayRecur = (arr) => {
|
|
24737
25481
|
var _a;
|
|
24738
25482
|
for (const value of arr) {
|
|
@@ -24741,10 +25485,10 @@ class TestBedCompiler {
|
|
|
24741
25485
|
}
|
|
24742
25486
|
else if (hasNgModuleDef(value)) {
|
|
24743
25487
|
const def = value.ɵmod;
|
|
24744
|
-
if (
|
|
25488
|
+
if (processedDefs.has(def)) {
|
|
24745
25489
|
continue;
|
|
24746
25490
|
}
|
|
24747
|
-
|
|
25491
|
+
processedDefs.add(def);
|
|
24748
25492
|
// Look through declarations, imports, and exports, and queue
|
|
24749
25493
|
// everything found there.
|
|
24750
25494
|
this.queueTypeArray(maybeUnwrapFn(def.declarations), value);
|
|
@@ -24757,6 +25501,10 @@ class TestBedCompiler {
|
|
|
24757
25501
|
else if (isStandaloneComponent(value)) {
|
|
24758
25502
|
this.queueType(value, null);
|
|
24759
25503
|
const def = getComponentDef(value);
|
|
25504
|
+
if (processedDefs.has(def)) {
|
|
25505
|
+
continue;
|
|
25506
|
+
}
|
|
25507
|
+
processedDefs.add(def);
|
|
24760
25508
|
const dependencies = maybeUnwrapFn((_a = def.dependencies) !== null && _a !== void 0 ? _a : []);
|
|
24761
25509
|
dependencies.forEach((dependency) => {
|
|
24762
25510
|
// Note: in AOT, the `dependencies` might also contain regular
|