@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.
Files changed (59) hide show
  1. package/esm2020/src/application_tokens.mjs +33 -1
  2. package/esm2020/src/change_detection/change_detector_ref.mjs +4 -4
  3. package/esm2020/src/compiler/compiler_facade_interface.mjs +1 -1
  4. package/esm2020/src/core.mjs +2 -2
  5. package/esm2020/src/core_private_export.mjs +2 -1
  6. package/esm2020/src/core_reactivity_export_internal.mjs +1 -1
  7. package/esm2020/src/core_render3_private_export.mjs +1 -2
  8. package/esm2020/src/di/r3_injector.mjs +8 -1
  9. package/esm2020/src/errors.mjs +1 -1
  10. package/esm2020/src/hydration/annotate.mjs +118 -5
  11. package/esm2020/src/hydration/api.mjs +14 -1
  12. package/esm2020/src/hydration/cleanup.mjs +54 -3
  13. package/esm2020/src/hydration/compression.mjs +69 -0
  14. package/esm2020/src/hydration/error_handling.mjs +357 -15
  15. package/esm2020/src/hydration/interfaces.mjs +17 -1
  16. package/esm2020/src/hydration/node_lookup_utils.mjs +199 -7
  17. package/esm2020/src/hydration/utils.mjs +16 -3
  18. package/esm2020/src/hydration/views.mjs +19 -15
  19. package/esm2020/src/linker/destroy_ref.mjs +5 -2
  20. package/esm2020/src/linker/view_container_ref.mjs +8 -7
  21. package/esm2020/src/metadata/directives.mjs +8 -3
  22. package/esm2020/src/render3/instructions/element.mjs +16 -9
  23. package/esm2020/src/render3/instructions/element_container.mjs +2 -5
  24. package/esm2020/src/render3/instructions/element_validation.mjs +2 -2
  25. package/esm2020/src/render3/instructions/projection.mjs +7 -4
  26. package/esm2020/src/render3/instructions/template.mjs +4 -7
  27. package/esm2020/src/render3/instructions/text.mjs +6 -6
  28. package/esm2020/src/render3/interfaces/public_definitions.mjs +1 -1
  29. package/esm2020/src/render3/interfaces/type_checks.mjs +4 -1
  30. package/esm2020/src/render3/node_manipulation.mjs +2 -2
  31. package/esm2020/src/render3/node_selector_matcher.mjs +17 -5
  32. package/esm2020/src/render3/util/view_utils.mjs +12 -1
  33. package/esm2020/src/render3/view_ref.mjs +1 -1
  34. package/esm2020/src/signals/index.mjs +2 -1
  35. package/esm2020/src/signals/src/computed.mjs +3 -3
  36. package/esm2020/src/signals/src/effect.mjs +1 -3
  37. package/esm2020/src/signals/src/signal.mjs +3 -3
  38. package/esm2020/src/signals/src/watch.mjs +3 -3
  39. package/esm2020/src/signals/src/weak_ref.mjs +18 -2
  40. package/esm2020/src/util/ng_dev_mode.mjs +2 -1
  41. package/esm2020/src/version.mjs +1 -1
  42. package/esm2020/testing/src/logger.mjs +3 -3
  43. package/esm2020/testing/src/ng_zone_mock.mjs +3 -3
  44. package/esm2020/testing/src/test_bed_compiler.mjs +12 -7
  45. package/fesm2015/core.mjs +1066 -178
  46. package/fesm2015/core.mjs.map +1 -1
  47. package/fesm2015/testing.mjs +816 -68
  48. package/fesm2015/testing.mjs.map +1 -1
  49. package/fesm2020/core.mjs +1051 -170
  50. package/fesm2020/core.mjs.map +1 -1
  51. package/fesm2020/testing.mjs +810 -67
  52. package/fesm2020/testing.mjs.map +1 -1
  53. package/index.d.ts +128 -216
  54. package/package.json +1 -1
  55. package/schematics/migrations/relative-link-resolution/bundle.js +7 -7
  56. package/schematics/migrations/router-link-with-href/bundle.js +10 -10
  57. package/schematics/ng-generate/standalone-migration/bundle.js +473 -376
  58. package/schematics/ng-generate/standalone-migration/bundle.js.map +2 -2
  59. package/testing/index.d.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Angular v16.0.0-next.3
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 (isProjectionMode && item === 'class') {
2453
- item = attrs[i];
2454
- if (classIndexOf(item.toLowerCase(), cssClassToMatch, 0) !== -1) {
2455
- return true;
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.3');
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
- * Verifies whether a given node matches an expected criteria,
14384
- * based on internal data structure state.
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 validateMatchingNode(node, nodeType, tagName, lView, tNode) {
14387
- validateNodeExists(node);
14388
- if (node.nodeType !== nodeType ||
14389
- node.nodeType === Node.ELEMENT_NODE &&
14390
- node.tagName.toLowerCase() !== (tagName === null || tagName === void 0 ? void 0 : tagName.toLowerCase())) {
14391
- // TODO: improve error message and use RuntimeError instead.
14392
- throw new Error(`Unexpected node found during hydration.`);
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
- * Verifies whether next sibling node exists.
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
- // TODO: improve error message and use RuntimeError instead.
14402
- throw new Error(`Unexpected state: insufficient number of sibling nodes.`);
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
- // TODO: improve error message and use RuntimeError instead.
14408
- throw new Error(`Hydration expected an element to be present at this location.`);
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
- if (tView.firstChild === tNode) {
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.index - HEADER_OFFSET;
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.index - HEADER_OFFSET;
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
- enterSkipHydrationBlock(tNode);
14855
- // Since this isn't hydratable, we need to empty the node
14856
- // so there's no duplicate content after render
14857
- clearElementContents(renderer, native);
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
- // If this container is non-empty, store the first node as a segment head,
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
- if ((tProjectionNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */) {
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
- const view = {
22478
- data: serializedView,
22479
- firstChild: null,
22480
- };
22481
- if (serializedView[NUM_ROOT_NODES] > 0) {
22482
- // Keep reference to the first node in this view,
22483
- // so it can be accessed while invoking template instructions.
22484
- view.firstChild = currentRNode;
22485
- // Move over to the next node after this view, which can
22486
- // either be a first node of the next view or an anchor comment
22487
- // node after the last view in a container.
22488
- currentRNode = siblingAfter(serializedView[NUM_ROOT_NODES], currentRNode);
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 isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock(hostTNode);
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 index = hostTNode.index - HEADER_OFFSET;
22881
- const currentRNode = getSegmentHead(hydrationInfo, index);
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 while processing the imports and exports of an
24733
- // NgModule tree, we cache them in this set so we can skip ones that have already been seen
24734
- // encountered. In some test setups, this caching resulted in 10X runtime improvement.
24735
- const processedNgModuleDefs = new Set();
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 (processedNgModuleDefs.has(def)) {
25488
+ if (processedDefs.has(def)) {
24745
25489
  continue;
24746
25490
  }
24747
- processedNgModuleDefs.add(def);
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