@angular/core 16.0.0-next.2 → 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 (73) hide show
  1. package/esm2020/src/application_ref.mjs +95 -87
  2. package/esm2020/src/application_tokens.mjs +59 -21
  3. package/esm2020/src/change_detection/change_detector_ref.mjs +4 -4
  4. package/esm2020/src/compiler/compiler_facade_interface.mjs +1 -1
  5. package/esm2020/src/core.mjs +2 -2
  6. package/esm2020/src/core_private_export.mjs +3 -3
  7. package/esm2020/src/core_reactivity_export_internal.mjs +1 -1
  8. package/esm2020/src/core_render3_private_export.mjs +1 -2
  9. package/esm2020/src/di/contextual.mjs +37 -0
  10. package/esm2020/src/di/index.mjs +2 -1
  11. package/esm2020/src/di/r3_injector.mjs +8 -1
  12. package/esm2020/src/errors.mjs +1 -1
  13. package/esm2020/src/hydration/annotate.mjs +250 -6
  14. package/esm2020/src/hydration/api.mjs +22 -1
  15. package/esm2020/src/hydration/cleanup.mjs +101 -0
  16. package/esm2020/src/hydration/compression.mjs +69 -0
  17. package/esm2020/src/hydration/error_handling.mjs +362 -12
  18. package/esm2020/src/hydration/interfaces.mjs +25 -2
  19. package/esm2020/src/hydration/node_lookup_utils.mjs +217 -17
  20. package/esm2020/src/hydration/skip_hydration.mjs +16 -1
  21. package/esm2020/src/hydration/utils.mjs +95 -7
  22. package/esm2020/src/hydration/views.mjs +84 -0
  23. package/esm2020/src/linker/destroy_ref.mjs +5 -2
  24. package/esm2020/src/linker/template_ref.mjs +17 -2
  25. package/esm2020/src/linker/view_container_ref.mjs +111 -35
  26. package/esm2020/src/metadata/directives.mjs +8 -3
  27. package/esm2020/src/render3/component_ref.mjs +2 -2
  28. package/esm2020/src/render3/definition.mjs +114 -45
  29. package/esm2020/src/render3/instructions/element.mjs +31 -14
  30. package/esm2020/src/render3/instructions/element_container.mjs +9 -13
  31. package/esm2020/src/render3/instructions/element_validation.mjs +2 -2
  32. package/esm2020/src/render3/instructions/projection.mjs +7 -4
  33. package/esm2020/src/render3/instructions/shared.mjs +53 -16
  34. package/esm2020/src/render3/instructions/template.mjs +54 -6
  35. package/esm2020/src/render3/instructions/text.mjs +6 -6
  36. package/esm2020/src/render3/interfaces/container.mjs +3 -2
  37. package/esm2020/src/render3/interfaces/public_definitions.mjs +1 -1
  38. package/esm2020/src/render3/interfaces/type_checks.mjs +5 -2
  39. package/esm2020/src/render3/interfaces/view.mjs +1 -1
  40. package/esm2020/src/render3/jit/module.mjs +3 -2
  41. package/esm2020/src/render3/ng_module_ref.mjs +9 -5
  42. package/esm2020/src/render3/node_manipulation.mjs +2 -2
  43. package/esm2020/src/render3/node_selector_matcher.mjs +17 -5
  44. package/esm2020/src/render3/util/discovery_utils.mjs +3 -2
  45. package/esm2020/src/render3/util/view_utils.mjs +12 -1
  46. package/esm2020/src/render3/view_ref.mjs +1 -1
  47. package/esm2020/src/signals/index.mjs +2 -1
  48. package/esm2020/src/signals/src/computed.mjs +3 -3
  49. package/esm2020/src/signals/src/effect.mjs +1 -3
  50. package/esm2020/src/signals/src/signal.mjs +3 -3
  51. package/esm2020/src/signals/src/watch.mjs +3 -3
  52. package/esm2020/src/signals/src/weak_ref.mjs +18 -2
  53. package/esm2020/src/util/ng_dev_mode.mjs +3 -1
  54. package/esm2020/src/version.mjs +1 -1
  55. package/esm2020/src/zone/ng_zone.mjs +62 -1
  56. package/esm2020/testing/src/logger.mjs +3 -3
  57. package/esm2020/testing/src/ng_zone_mock.mjs +3 -3
  58. package/esm2020/testing/src/test_bed_compiler.mjs +15 -10
  59. package/fesm2015/core.mjs +3339 -1754
  60. package/fesm2015/core.mjs.map +1 -1
  61. package/fesm2015/testing.mjs +2619 -1376
  62. package/fesm2015/testing.mjs.map +1 -1
  63. package/fesm2020/core.mjs +3348 -1760
  64. package/fesm2020/core.mjs.map +1 -1
  65. package/fesm2020/testing.mjs +2569 -1322
  66. package/fesm2020/testing.mjs.map +1 -1
  67. package/index.d.ts +564 -682
  68. package/package.json +1 -1
  69. package/schematics/migrations/relative-link-resolution/bundle.js +7 -7
  70. package/schematics/migrations/router-link-with-href/bundle.js +10 -10
  71. package/schematics/ng-generate/standalone-migration/bundle.js +644 -448
  72. package/schematics/ng-generate/standalone-migration/bundle.js.map +3 -3
  73. package/testing/index.d.ts +1 -1
@@ -1,12 +1,13 @@
1
1
  /**
2
- * @license Angular v16.0.0-next.2
2
+ * @license Angular v16.0.0-next.4
3
3
  * (c) 2010-2022 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
6
6
 
7
- import { getDebugNode, RendererFactory2 as RendererFactory2$1, InjectionToken as InjectionToken$1, ɵstringify, ɵReflectionCapabilities, Directive, Component, Pipe, NgModule, ɵgetInjectableDef, resolveForwardRef as resolveForwardRef$1, ɵNG_COMP_DEF, ɵRender3NgModuleRef, ApplicationInitStatus, LOCALE_ID as LOCALE_ID$1, ɵDEFAULT_LOCALE_ID, ɵsetLocaleId, ɵRender3ComponentFactory, ɵcompileComponent, ɵNG_DIR_DEF, ɵcompileDirective, ɵNG_PIPE_DEF, ɵcompilePipe, ɵNG_MOD_DEF, ɵtransitiveScopesFor, ɵpatchComponentDefWithScope, ɵNG_INJ_DEF, ɵcompileNgModuleDefs, NgZone, Compiler, COMPILER_OPTIONS, ɵNgModuleFactory, ɵisEnvironmentProviders, ModuleWithComponentFactories, ɵconvertToBitFlags, Injector as Injector$1, InjectFlags as InjectFlags$1, ɵsetAllowDuplicateNgModuleIdsForTest, ɵresetCompiledComponents, ɵsetUnknownElementStrictMode as ɵsetUnknownElementStrictMode$1, ɵsetUnknownPropertyStrictMode as ɵsetUnknownPropertyStrictMode$1, ɵgetUnknownElementStrictMode as ɵgetUnknownElementStrictMode$1, ɵgetUnknownPropertyStrictMode as ɵgetUnknownPropertyStrictMode$1, EnvironmentInjector as EnvironmentInjector$1, ɵflushModuleScopingQueueAsMuchAsPossible } from '@angular/core';
7
+ import { getDebugNode, RendererFactory2 as RendererFactory2$1, InjectionToken as InjectionToken$1, ɵstringify, ɵReflectionCapabilities, Directive, Component, Pipe, NgModule, ɵgetInjectableDef, resolveForwardRef as resolveForwardRef$1, ɵNG_COMP_DEF, ɵRender3NgModuleRef, ApplicationInitStatus, LOCALE_ID as LOCALE_ID$1, ɵDEFAULT_LOCALE_ID, ɵsetLocaleId, ɵRender3ComponentFactory, ɵcompileComponent, ɵNG_DIR_DEF, ɵcompileDirective, ɵNG_PIPE_DEF, ɵcompilePipe, ɵNG_MOD_DEF, ɵtransitiveScopesFor, ɵpatchComponentDefWithScope, ɵNG_INJ_DEF, ɵcompileNgModuleDefs, NgZone, ɵprovideNgZoneChangeDetection, Compiler, COMPILER_OPTIONS, ɵNgModuleFactory, ɵisEnvironmentProviders, ModuleWithComponentFactories, ɵconvertToBitFlags, Injector as Injector$1, InjectFlags as InjectFlags$1, ɵsetAllowDuplicateNgModuleIdsForTest, ɵresetCompiledComponents, ɵsetUnknownElementStrictMode as ɵsetUnknownElementStrictMode$1, ɵsetUnknownPropertyStrictMode as ɵsetUnknownPropertyStrictMode$1, ɵgetUnknownElementStrictMode as ɵgetUnknownElementStrictMode$1, ɵgetUnknownPropertyStrictMode as ɵgetUnknownPropertyStrictMode$1, EnvironmentInjector as EnvironmentInjector$1, ɵflushModuleScopingQueueAsMuchAsPossible } from '@angular/core';
8
8
  import { ResourceLoader } from '@angular/compiler';
9
9
  import { Subject, Subscription } from 'rxjs';
10
+ import { first } from 'rxjs/operators';
10
11
 
11
12
  /**
12
13
  * Wraps a test function in an asynchronous test zone. The test will automatically
@@ -1637,6 +1638,8 @@ function ngDevModeResetPerfCounters() {
1637
1638
  rendererCreateComment: 0,
1638
1639
  hydratedNodes: 0,
1639
1640
  hydratedComponents: 0,
1641
+ dehydratedViewsRemoved: 0,
1642
+ dehydratedViewsCleanupRuns: 0,
1640
1643
  };
1641
1644
  // Make sure to refer to ngDevMode as ['ngDevMode'] for closure.
1642
1645
  const allowNgDevModeTrue = locationString.indexOf('ngDevMode=false') === -1;
@@ -2197,15 +2200,659 @@ const NG_ELEMENT_ID = getClosureSafeProperty({ __NG_ELEMENT_ID__: getClosureSafe
2197
2200
  */
2198
2201
  const NG_ENV_ID = getClosureSafeProperty({ __NG_ENV_ID__: getClosureSafeProperty });
2199
2202
 
2200
- /** Counter used to generate unique IDs for component definitions. */
2201
- let componentDefCount = 0;
2203
+ /**
2204
+ * Returns an index of `classToSearch` in `className` taking token boundaries into account.
2205
+ *
2206
+ * `classIndexOf('AB A', 'A', 0)` will be 3 (not 0 since `AB!==A`)
2207
+ *
2208
+ * @param className A string containing classes (whitespace separated)
2209
+ * @param classToSearch A class name to locate
2210
+ * @param startingIndex Starting location of search
2211
+ * @returns an index of the located class (or -1 if not found)
2212
+ */
2213
+ function classIndexOf(className, classToSearch, startingIndex) {
2214
+ ngDevMode && assertNotEqual(classToSearch, '', 'can not look for "" string.');
2215
+ let end = className.length;
2216
+ while (true) {
2217
+ const foundIndex = className.indexOf(classToSearch, startingIndex);
2218
+ if (foundIndex === -1)
2219
+ return foundIndex;
2220
+ if (foundIndex === 0 || className.charCodeAt(foundIndex - 1) <= 32 /* CharCode.SPACE */) {
2221
+ // Ensure that it has leading whitespace
2222
+ const length = classToSearch.length;
2223
+ if (foundIndex + length === end ||
2224
+ className.charCodeAt(foundIndex + length) <= 32 /* CharCode.SPACE */) {
2225
+ // Ensure that it has trailing whitespace
2226
+ return foundIndex;
2227
+ }
2228
+ }
2229
+ // False positive, keep searching from where we left off.
2230
+ startingIndex = foundIndex + 1;
2231
+ }
2232
+ }
2233
+
2234
+ /**
2235
+ * Assigns all attribute values to the provided element via the inferred renderer.
2236
+ *
2237
+ * This function accepts two forms of attribute entries:
2238
+ *
2239
+ * default: (key, value):
2240
+ * attrs = [key1, value1, key2, value2]
2241
+ *
2242
+ * namespaced: (NAMESPACE_MARKER, uri, name, value)
2243
+ * attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value]
2244
+ *
2245
+ * The `attrs` array can contain a mix of both the default and namespaced entries.
2246
+ * The "default" values are set without a marker, but if the function comes across
2247
+ * a marker value then it will attempt to set a namespaced value. If the marker is
2248
+ * not of a namespaced value then the function will quit and return the index value
2249
+ * where it stopped during the iteration of the attrs array.
2250
+ *
2251
+ * See [AttributeMarker] to understand what the namespace marker value is.
2252
+ *
2253
+ * Note that this instruction does not support assigning style and class values to
2254
+ * an element. See `elementStart` and `elementHostAttrs` to learn how styling values
2255
+ * are applied to an element.
2256
+ * @param renderer The renderer to be used
2257
+ * @param native The element that the attributes will be assigned to
2258
+ * @param attrs The attribute array of values that will be assigned to the element
2259
+ * @returns the index value that was last accessed in the attributes array
2260
+ */
2261
+ function setUpAttributes(renderer, native, attrs) {
2262
+ let i = 0;
2263
+ while (i < attrs.length) {
2264
+ const value = attrs[i];
2265
+ if (typeof value === 'number') {
2266
+ // only namespaces are supported. Other value types (such as style/class
2267
+ // entries) are not supported in this function.
2268
+ if (value !== 0 /* AttributeMarker.NamespaceURI */) {
2269
+ break;
2270
+ }
2271
+ // we just landed on the marker value ... therefore
2272
+ // we should skip to the next entry
2273
+ i++;
2274
+ const namespaceURI = attrs[i++];
2275
+ const attrName = attrs[i++];
2276
+ const attrVal = attrs[i++];
2277
+ ngDevMode && ngDevMode.rendererSetAttribute++;
2278
+ renderer.setAttribute(native, attrName, attrVal, namespaceURI);
2279
+ }
2280
+ else {
2281
+ // attrName is string;
2282
+ const attrName = value;
2283
+ const attrVal = attrs[++i];
2284
+ // Standard attributes
2285
+ ngDevMode && ngDevMode.rendererSetAttribute++;
2286
+ if (isAnimationProp(attrName)) {
2287
+ renderer.setProperty(native, attrName, attrVal);
2288
+ }
2289
+ else {
2290
+ renderer.setAttribute(native, attrName, attrVal);
2291
+ }
2292
+ i++;
2293
+ }
2294
+ }
2295
+ // another piece of code may iterate over the same attributes array. Therefore
2296
+ // it may be helpful to return the exact spot where the attributes array exited
2297
+ // whether by running into an unsupported marker or if all the static values were
2298
+ // iterated over.
2299
+ return i;
2300
+ }
2301
+ /**
2302
+ * Test whether the given value is a marker that indicates that the following
2303
+ * attribute values in a `TAttributes` array are only the names of attributes,
2304
+ * and not name-value pairs.
2305
+ * @param marker The attribute marker to test.
2306
+ * @returns true if the marker is a "name-only" marker (e.g. `Bindings`, `Template` or `I18n`).
2307
+ */
2308
+ function isNameOnlyAttributeMarker(marker) {
2309
+ return marker === 3 /* AttributeMarker.Bindings */ || marker === 4 /* AttributeMarker.Template */ ||
2310
+ marker === 6 /* AttributeMarker.I18n */;
2311
+ }
2312
+ function isAnimationProp(name) {
2313
+ // Perf note: accessing charCodeAt to check for the first character of a string is faster as
2314
+ // compared to accessing a character at index 0 (ex. name[0]). The main reason for this is that
2315
+ // charCodeAt doesn't allocate memory to return a substring.
2316
+ return name.charCodeAt(0) === 64 /* CharCode.AT_SIGN */;
2317
+ }
2318
+ /**
2319
+ * Merges `src` `TAttributes` into `dst` `TAttributes` removing any duplicates in the process.
2320
+ *
2321
+ * This merge function keeps the order of attrs same.
2322
+ *
2323
+ * @param dst Location of where the merged `TAttributes` should end up.
2324
+ * @param src `TAttributes` which should be appended to `dst`
2325
+ */
2326
+ function mergeHostAttrs(dst, src) {
2327
+ if (src === null || src.length === 0) {
2328
+ // do nothing
2329
+ }
2330
+ else if (dst === null || dst.length === 0) {
2331
+ // We have source, but dst is empty, just make a copy.
2332
+ dst = src.slice();
2333
+ }
2334
+ else {
2335
+ let srcMarker = -1 /* AttributeMarker.ImplicitAttributes */;
2336
+ for (let i = 0; i < src.length; i++) {
2337
+ const item = src[i];
2338
+ if (typeof item === 'number') {
2339
+ srcMarker = item;
2340
+ }
2341
+ else {
2342
+ if (srcMarker === 0 /* AttributeMarker.NamespaceURI */) {
2343
+ // Case where we need to consume `key1`, `key2`, `value` items.
2344
+ }
2345
+ else if (srcMarker === -1 /* AttributeMarker.ImplicitAttributes */ ||
2346
+ srcMarker === 2 /* AttributeMarker.Styles */) {
2347
+ // Case where we have to consume `key1` and `value` only.
2348
+ mergeHostAttribute(dst, srcMarker, item, null, src[++i]);
2349
+ }
2350
+ else {
2351
+ // Case where we have to consume `key1` only.
2352
+ mergeHostAttribute(dst, srcMarker, item, null, null);
2353
+ }
2354
+ }
2355
+ }
2356
+ }
2357
+ return dst;
2358
+ }
2359
+ /**
2360
+ * Append `key`/`value` to existing `TAttributes` taking region marker and duplicates into account.
2361
+ *
2362
+ * @param dst `TAttributes` to append to.
2363
+ * @param marker Region where the `key`/`value` should be added.
2364
+ * @param key1 Key to add to `TAttributes`
2365
+ * @param key2 Key to add to `TAttributes` (in case of `AttributeMarker.NamespaceURI`)
2366
+ * @param value Value to add or to overwrite to `TAttributes` Only used if `marker` is not Class.
2367
+ */
2368
+ function mergeHostAttribute(dst, marker, key1, key2, value) {
2369
+ let i = 0;
2370
+ // Assume that new markers will be inserted at the end.
2371
+ let markerInsertPosition = dst.length;
2372
+ // scan until correct type.
2373
+ if (marker === -1 /* AttributeMarker.ImplicitAttributes */) {
2374
+ markerInsertPosition = -1;
2375
+ }
2376
+ else {
2377
+ while (i < dst.length) {
2378
+ const dstValue = dst[i++];
2379
+ if (typeof dstValue === 'number') {
2380
+ if (dstValue === marker) {
2381
+ markerInsertPosition = -1;
2382
+ break;
2383
+ }
2384
+ else if (dstValue > marker) {
2385
+ // We need to save this as we want the markers to be inserted in specific order.
2386
+ markerInsertPosition = i - 1;
2387
+ break;
2388
+ }
2389
+ }
2390
+ }
2391
+ }
2392
+ // search until you find place of insertion
2393
+ while (i < dst.length) {
2394
+ const item = dst[i];
2395
+ if (typeof item === 'number') {
2396
+ // since `i` started as the index after the marker, we did not find it if we are at the next
2397
+ // marker
2398
+ break;
2399
+ }
2400
+ else if (item === key1) {
2401
+ // We already have same token
2402
+ if (key2 === null) {
2403
+ if (value !== null) {
2404
+ dst[i + 1] = value;
2405
+ }
2406
+ return;
2407
+ }
2408
+ else if (key2 === dst[i + 1]) {
2409
+ dst[i + 2] = value;
2410
+ return;
2411
+ }
2412
+ }
2413
+ // Increment counter.
2414
+ i++;
2415
+ if (key2 !== null)
2416
+ i++;
2417
+ if (value !== null)
2418
+ i++;
2419
+ }
2420
+ // insert at location.
2421
+ if (markerInsertPosition !== -1) {
2422
+ dst.splice(markerInsertPosition, 0, marker);
2423
+ i = markerInsertPosition + 1;
2424
+ }
2425
+ dst.splice(i++, 0, key1);
2426
+ if (key2 !== null) {
2427
+ dst.splice(i++, 0, key2);
2428
+ }
2429
+ if (value !== null) {
2430
+ dst.splice(i++, 0, value);
2431
+ }
2432
+ }
2433
+
2434
+ const NG_TEMPLATE_SELECTOR = 'ng-template';
2435
+ /**
2436
+ * Search the `TAttributes` to see if it contains `cssClassToMatch` (case insensitive)
2437
+ *
2438
+ * @param attrs `TAttributes` to search through.
2439
+ * @param cssClassToMatch class to match (lowercase)
2440
+ * @param isProjectionMode Whether or not class matching should look into the attribute `class` in
2441
+ * addition to the `AttributeMarker.Classes`.
2442
+ */
2443
+ function isCssClassMatching(attrs, cssClassToMatch, isProjectionMode) {
2444
+ // TODO(misko): The fact that this function needs to know about `isProjectionMode` seems suspect.
2445
+ // It is strange to me that sometimes the class information comes in form of `class` attribute
2446
+ // and sometimes in form of `AttributeMarker.Classes`. Some investigation is needed to determine
2447
+ // if that is the right behavior.
2448
+ ngDevMode &&
2449
+ assertEqual(cssClassToMatch, cssClassToMatch.toLowerCase(), 'Class name expected to be lowercase.');
2450
+ let i = 0;
2451
+ // Indicates whether we are processing value from the implicit
2452
+ // attribute section (i.e. before the first marker in the array).
2453
+ let isImplicitAttrsSection = true;
2454
+ while (i < attrs.length) {
2455
+ let item = attrs[i++];
2456
+ if (typeof item === 'string' && isImplicitAttrsSection) {
2457
+ const value = attrs[i++];
2458
+ if (isProjectionMode && item === 'class') {
2459
+ // We found a `class` attribute in the implicit attribute section,
2460
+ // check if it matches the value of the `cssClassToMatch` argument.
2461
+ if (classIndexOf(value.toLowerCase(), cssClassToMatch, 0) !== -1) {
2462
+ return true;
2463
+ }
2464
+ }
2465
+ }
2466
+ else if (item === 1 /* AttributeMarker.Classes */) {
2467
+ // We found the classes section. Start searching for the class.
2468
+ while (i < attrs.length && typeof (item = attrs[i++]) == 'string') {
2469
+ // while we have strings
2470
+ if (item.toLowerCase() === cssClassToMatch)
2471
+ return true;
2472
+ }
2473
+ return false;
2474
+ }
2475
+ else if (typeof item === 'number') {
2476
+ // We've came across a first marker, which indicates
2477
+ // that the implicit attribute section is over.
2478
+ isImplicitAttrsSection = false;
2479
+ }
2480
+ }
2481
+ return false;
2482
+ }
2483
+ /**
2484
+ * Checks whether the `tNode` represents an inline template (e.g. `*ngFor`).
2485
+ *
2486
+ * @param tNode current TNode
2487
+ */
2488
+ function isInlineTemplate(tNode) {
2489
+ return tNode.type === 4 /* TNodeType.Container */ && tNode.value !== NG_TEMPLATE_SELECTOR;
2490
+ }
2491
+ /**
2492
+ * Function that checks whether a given tNode matches tag-based selector and has a valid type.
2493
+ *
2494
+ * Matching can be performed in 2 modes: projection mode (when we project nodes) and regular
2495
+ * directive matching mode:
2496
+ * - in the "directive matching" mode we do _not_ take TContainer's tagName into account if it is
2497
+ * different from NG_TEMPLATE_SELECTOR (value different from NG_TEMPLATE_SELECTOR indicates that a
2498
+ * tag name was extracted from * syntax so we would match the same directive twice);
2499
+ * - in the "projection" mode, we use a tag name potentially extracted from the * syntax processing
2500
+ * (applicable to TNodeType.Container only).
2501
+ */
2502
+ function hasTagAndTypeMatch(tNode, currentSelector, isProjectionMode) {
2503
+ const tagNameToCompare = tNode.type === 4 /* TNodeType.Container */ && !isProjectionMode ? NG_TEMPLATE_SELECTOR : tNode.value;
2504
+ return currentSelector === tagNameToCompare;
2505
+ }
2506
+ /**
2507
+ * A utility function to match an Ivy node static data against a simple CSS selector
2508
+ *
2509
+ * @param node static data of the node to match
2510
+ * @param selector The selector to try matching against the node.
2511
+ * @param isProjectionMode if `true` we are matching for content projection, otherwise we are doing
2512
+ * directive matching.
2513
+ * @returns true if node matches the selector.
2514
+ */
2515
+ function isNodeMatchingSelector(tNode, selector, isProjectionMode) {
2516
+ ngDevMode && assertDefined(selector[0], 'Selector should have a tag name');
2517
+ let mode = 4 /* SelectorFlags.ELEMENT */;
2518
+ const nodeAttrs = tNode.attrs || [];
2519
+ // Find the index of first attribute that has no value, only a name.
2520
+ const nameOnlyMarkerIdx = getNameOnlyMarkerIndex(nodeAttrs);
2521
+ // When processing ":not" selectors, we skip to the next ":not" if the
2522
+ // current one doesn't match
2523
+ let skipToNextSelector = false;
2524
+ for (let i = 0; i < selector.length; i++) {
2525
+ const current = selector[i];
2526
+ if (typeof current === 'number') {
2527
+ // If we finish processing a :not selector and it hasn't failed, return false
2528
+ if (!skipToNextSelector && !isPositive(mode) && !isPositive(current)) {
2529
+ return false;
2530
+ }
2531
+ // If we are skipping to the next :not() and this mode flag is positive,
2532
+ // it's a part of the current :not() selector, and we should keep skipping
2533
+ if (skipToNextSelector && isPositive(current))
2534
+ continue;
2535
+ skipToNextSelector = false;
2536
+ mode = current | (mode & 1 /* SelectorFlags.NOT */);
2537
+ continue;
2538
+ }
2539
+ if (skipToNextSelector)
2540
+ continue;
2541
+ if (mode & 4 /* SelectorFlags.ELEMENT */) {
2542
+ mode = 2 /* SelectorFlags.ATTRIBUTE */ | mode & 1 /* SelectorFlags.NOT */;
2543
+ if (current !== '' && !hasTagAndTypeMatch(tNode, current, isProjectionMode) ||
2544
+ current === '' && selector.length === 1) {
2545
+ if (isPositive(mode))
2546
+ return false;
2547
+ skipToNextSelector = true;
2548
+ }
2549
+ }
2550
+ else {
2551
+ const selectorAttrValue = mode & 8 /* SelectorFlags.CLASS */ ? current : selector[++i];
2552
+ // special case for matching against classes when a tNode has been instantiated with
2553
+ // class and style values as separate attribute values (e.g. ['title', CLASS, 'foo'])
2554
+ if ((mode & 8 /* SelectorFlags.CLASS */) && tNode.attrs !== null) {
2555
+ if (!isCssClassMatching(tNode.attrs, selectorAttrValue, isProjectionMode)) {
2556
+ if (isPositive(mode))
2557
+ return false;
2558
+ skipToNextSelector = true;
2559
+ }
2560
+ continue;
2561
+ }
2562
+ const attrName = (mode & 8 /* SelectorFlags.CLASS */) ? 'class' : current;
2563
+ const attrIndexInNode = findAttrIndexInNode(attrName, nodeAttrs, isInlineTemplate(tNode), isProjectionMode);
2564
+ if (attrIndexInNode === -1) {
2565
+ if (isPositive(mode))
2566
+ return false;
2567
+ skipToNextSelector = true;
2568
+ continue;
2569
+ }
2570
+ if (selectorAttrValue !== '') {
2571
+ let nodeAttrValue;
2572
+ if (attrIndexInNode > nameOnlyMarkerIdx) {
2573
+ nodeAttrValue = '';
2574
+ }
2575
+ else {
2576
+ ngDevMode &&
2577
+ assertNotEqual(nodeAttrs[attrIndexInNode], 0 /* AttributeMarker.NamespaceURI */, 'We do not match directives on namespaced attributes');
2578
+ // we lowercase the attribute value to be able to match
2579
+ // selectors without case-sensitivity
2580
+ // (selectors are already in lowercase when generated)
2581
+ nodeAttrValue = nodeAttrs[attrIndexInNode + 1].toLowerCase();
2582
+ }
2583
+ const compareAgainstClassName = mode & 8 /* SelectorFlags.CLASS */ ? nodeAttrValue : null;
2584
+ if (compareAgainstClassName &&
2585
+ classIndexOf(compareAgainstClassName, selectorAttrValue, 0) !== -1 ||
2586
+ mode & 2 /* SelectorFlags.ATTRIBUTE */ && selectorAttrValue !== nodeAttrValue) {
2587
+ if (isPositive(mode))
2588
+ return false;
2589
+ skipToNextSelector = true;
2590
+ }
2591
+ }
2592
+ }
2593
+ }
2594
+ return isPositive(mode) || skipToNextSelector;
2595
+ }
2596
+ function isPositive(mode) {
2597
+ return (mode & 1 /* SelectorFlags.NOT */) === 0;
2598
+ }
2599
+ /**
2600
+ * Examines the attribute's definition array for a node to find the index of the
2601
+ * attribute that matches the given `name`.
2602
+ *
2603
+ * NOTE: This will not match namespaced attributes.
2604
+ *
2605
+ * Attribute matching depends upon `isInlineTemplate` and `isProjectionMode`.
2606
+ * The following table summarizes which types of attributes we attempt to match:
2607
+ *
2608
+ * ===========================================================================================================
2609
+ * Modes | Normal Attributes | Bindings Attributes | Template Attributes | I18n
2610
+ * Attributes
2611
+ * ===========================================================================================================
2612
+ * Inline + Projection | YES | YES | NO | YES
2613
+ * -----------------------------------------------------------------------------------------------------------
2614
+ * Inline + Directive | NO | NO | YES | NO
2615
+ * -----------------------------------------------------------------------------------------------------------
2616
+ * Non-inline + Projection | YES | YES | NO | YES
2617
+ * -----------------------------------------------------------------------------------------------------------
2618
+ * Non-inline + Directive | YES | YES | NO | YES
2619
+ * ===========================================================================================================
2620
+ *
2621
+ * @param name the name of the attribute to find
2622
+ * @param attrs the attribute array to examine
2623
+ * @param isInlineTemplate true if the node being matched is an inline template (e.g. `*ngFor`)
2624
+ * rather than a manually expanded template node (e.g `<ng-template>`).
2625
+ * @param isProjectionMode true if we are matching against content projection otherwise we are
2626
+ * matching against directives.
2627
+ */
2628
+ function findAttrIndexInNode(name, attrs, isInlineTemplate, isProjectionMode) {
2629
+ if (attrs === null)
2630
+ return -1;
2631
+ let i = 0;
2632
+ if (isProjectionMode || !isInlineTemplate) {
2633
+ let bindingsMode = false;
2634
+ while (i < attrs.length) {
2635
+ const maybeAttrName = attrs[i];
2636
+ if (maybeAttrName === name) {
2637
+ return i;
2638
+ }
2639
+ else if (maybeAttrName === 3 /* AttributeMarker.Bindings */ || maybeAttrName === 6 /* AttributeMarker.I18n */) {
2640
+ bindingsMode = true;
2641
+ }
2642
+ else if (maybeAttrName === 1 /* AttributeMarker.Classes */ || maybeAttrName === 2 /* AttributeMarker.Styles */) {
2643
+ let value = attrs[++i];
2644
+ // We should skip classes here because we have a separate mechanism for
2645
+ // matching classes in projection mode.
2646
+ while (typeof value === 'string') {
2647
+ value = attrs[++i];
2648
+ }
2649
+ continue;
2650
+ }
2651
+ else if (maybeAttrName === 4 /* AttributeMarker.Template */) {
2652
+ // We do not care about Template attributes in this scenario.
2653
+ break;
2654
+ }
2655
+ else if (maybeAttrName === 0 /* AttributeMarker.NamespaceURI */) {
2656
+ // Skip the whole namespaced attribute and value. This is by design.
2657
+ i += 4;
2658
+ continue;
2659
+ }
2660
+ // In binding mode there are only names, rather than name-value pairs.
2661
+ i += bindingsMode ? 1 : 2;
2662
+ }
2663
+ // We did not match the attribute
2664
+ return -1;
2665
+ }
2666
+ else {
2667
+ return matchTemplateAttribute(attrs, name);
2668
+ }
2669
+ }
2670
+ function isNodeMatchingSelectorList(tNode, selector, isProjectionMode = false) {
2671
+ for (let i = 0; i < selector.length; i++) {
2672
+ if (isNodeMatchingSelector(tNode, selector[i], isProjectionMode)) {
2673
+ return true;
2674
+ }
2675
+ }
2676
+ return false;
2677
+ }
2678
+ function getProjectAsAttrValue(tNode) {
2679
+ const nodeAttrs = tNode.attrs;
2680
+ if (nodeAttrs != null) {
2681
+ const ngProjectAsAttrIdx = nodeAttrs.indexOf(5 /* AttributeMarker.ProjectAs */);
2682
+ // only check for ngProjectAs in attribute names, don't accidentally match attribute's value
2683
+ // (attribute names are stored at even indexes)
2684
+ if ((ngProjectAsAttrIdx & 1) === 0) {
2685
+ return nodeAttrs[ngProjectAsAttrIdx + 1];
2686
+ }
2687
+ }
2688
+ return null;
2689
+ }
2690
+ function getNameOnlyMarkerIndex(nodeAttrs) {
2691
+ for (let i = 0; i < nodeAttrs.length; i++) {
2692
+ const nodeAttr = nodeAttrs[i];
2693
+ if (isNameOnlyAttributeMarker(nodeAttr)) {
2694
+ return i;
2695
+ }
2696
+ }
2697
+ return nodeAttrs.length;
2698
+ }
2699
+ function matchTemplateAttribute(attrs, name) {
2700
+ let i = attrs.indexOf(4 /* AttributeMarker.Template */);
2701
+ if (i > -1) {
2702
+ i++;
2703
+ while (i < attrs.length) {
2704
+ const attr = attrs[i];
2705
+ // Return in case we checked all template attrs and are switching to the next section in the
2706
+ // attrs array (that starts with a number that represents an attribute marker).
2707
+ if (typeof attr === 'number')
2708
+ return -1;
2709
+ if (attr === name)
2710
+ return i;
2711
+ i++;
2712
+ }
2713
+ }
2714
+ return -1;
2715
+ }
2716
+ /**
2717
+ * Checks whether a selector is inside a CssSelectorList
2718
+ * @param selector Selector to be checked.
2719
+ * @param list List in which to look for the selector.
2720
+ */
2721
+ function isSelectorInSelectorList(selector, list) {
2722
+ selectorListLoop: for (let i = 0; i < list.length; i++) {
2723
+ const currentSelectorInList = list[i];
2724
+ if (selector.length !== currentSelectorInList.length) {
2725
+ continue;
2726
+ }
2727
+ for (let j = 0; j < selector.length; j++) {
2728
+ if (selector[j] !== currentSelectorInList[j]) {
2729
+ continue selectorListLoop;
2730
+ }
2731
+ }
2732
+ return true;
2733
+ }
2734
+ return false;
2735
+ }
2736
+ function maybeWrapInNotSelector(isNegativeMode, chunk) {
2737
+ return isNegativeMode ? ':not(' + chunk.trim() + ')' : chunk;
2738
+ }
2739
+ function stringifyCSSSelector(selector) {
2740
+ let result = selector[0];
2741
+ let i = 1;
2742
+ let mode = 2 /* SelectorFlags.ATTRIBUTE */;
2743
+ let currentChunk = '';
2744
+ let isNegativeMode = false;
2745
+ while (i < selector.length) {
2746
+ let valueOrMarker = selector[i];
2747
+ if (typeof valueOrMarker === 'string') {
2748
+ if (mode & 2 /* SelectorFlags.ATTRIBUTE */) {
2749
+ const attrValue = selector[++i];
2750
+ currentChunk +=
2751
+ '[' + valueOrMarker + (attrValue.length > 0 ? '="' + attrValue + '"' : '') + ']';
2752
+ }
2753
+ else if (mode & 8 /* SelectorFlags.CLASS */) {
2754
+ currentChunk += '.' + valueOrMarker;
2755
+ }
2756
+ else if (mode & 4 /* SelectorFlags.ELEMENT */) {
2757
+ currentChunk += ' ' + valueOrMarker;
2758
+ }
2759
+ }
2760
+ else {
2761
+ //
2762
+ // Append current chunk to the final result in case we come across SelectorFlag, which
2763
+ // indicates that the previous section of a selector is over. We need to accumulate content
2764
+ // between flags to make sure we wrap the chunk later in :not() selector if needed, e.g.
2765
+ // ```
2766
+ // ['', Flags.CLASS, '.classA', Flags.CLASS | Flags.NOT, '.classB', '.classC']
2767
+ // ```
2768
+ // should be transformed to `.classA :not(.classB .classC)`.
2769
+ //
2770
+ // Note: for negative selector part, we accumulate content between flags until we find the
2771
+ // next negative flag. This is needed to support a case where `:not()` rule contains more than
2772
+ // one chunk, e.g. the following selector:
2773
+ // ```
2774
+ // ['', Flags.ELEMENT | Flags.NOT, 'p', Flags.CLASS, 'foo', Flags.CLASS | Flags.NOT, 'bar']
2775
+ // ```
2776
+ // should be stringified to `:not(p.foo) :not(.bar)`
2777
+ //
2778
+ if (currentChunk !== '' && !isPositive(valueOrMarker)) {
2779
+ result += maybeWrapInNotSelector(isNegativeMode, currentChunk);
2780
+ currentChunk = '';
2781
+ }
2782
+ mode = valueOrMarker;
2783
+ // According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative
2784
+ // mode is maintained for remaining chunks of a selector.
2785
+ isNegativeMode = isNegativeMode || !isPositive(mode);
2786
+ }
2787
+ i++;
2788
+ }
2789
+ if (currentChunk !== '') {
2790
+ result += maybeWrapInNotSelector(isNegativeMode, currentChunk);
2791
+ }
2792
+ return result;
2793
+ }
2794
+ /**
2795
+ * Generates string representation of CSS selector in parsed form.
2796
+ *
2797
+ * ComponentDef and DirectiveDef are generated with the selector in parsed form to avoid doing
2798
+ * additional parsing at runtime (for example, for directive matching). However in some cases (for
2799
+ * example, while bootstrapping a component), a string version of the selector is required to query
2800
+ * for the host element on the page. This function takes the parsed form of a selector and returns
2801
+ * its string representation.
2802
+ *
2803
+ * @param selectorList selector in parsed form
2804
+ * @returns string representation of a given selector
2805
+ */
2806
+ function stringifyCSSSelectorList(selectorList) {
2807
+ return selectorList.map(stringifyCSSSelector).join(',');
2808
+ }
2809
+ /**
2810
+ * Extracts attributes and classes information from a given CSS selector.
2811
+ *
2812
+ * This function is used while creating a component dynamically. In this case, the host element
2813
+ * (that is created dynamically) should contain attributes and classes specified in component's CSS
2814
+ * selector.
2815
+ *
2816
+ * @param selector CSS selector in parsed form (in a form of array)
2817
+ * @returns object with `attrs` and `classes` fields that contain extracted information
2818
+ */
2819
+ function extractAttrsAndClassesFromSelector(selector) {
2820
+ const attrs = [];
2821
+ const classes = [];
2822
+ let i = 1;
2823
+ let mode = 2 /* SelectorFlags.ATTRIBUTE */;
2824
+ while (i < selector.length) {
2825
+ let valueOrMarker = selector[i];
2826
+ if (typeof valueOrMarker === 'string') {
2827
+ if (mode === 2 /* SelectorFlags.ATTRIBUTE */) {
2828
+ if (valueOrMarker !== '') {
2829
+ attrs.push(valueOrMarker, selector[++i]);
2830
+ }
2831
+ }
2832
+ else if (mode === 8 /* SelectorFlags.CLASS */) {
2833
+ classes.push(valueOrMarker);
2834
+ }
2835
+ }
2836
+ else {
2837
+ // According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative
2838
+ // mode is maintained for remaining chunks of a selector. Since attributes and classes are
2839
+ // extracted only for "positive" part of the selector, we can stop here.
2840
+ if (!isPositive(mode))
2841
+ break;
2842
+ mode = valueOrMarker;
2843
+ }
2844
+ i++;
2845
+ }
2846
+ return { attrs, classes };
2847
+ }
2848
+
2202
2849
  /**
2203
2850
  * Create a component definition object.
2204
2851
  *
2205
2852
  *
2206
2853
  * # Example
2207
2854
  * ```
2208
- * class MyDirective {
2855
+ * class MyComponent {
2209
2856
  * // Generated by Angular Template Compiler
2210
2857
  * // [Symbol] syntax will not be supported by TypeScript until v2.7
2211
2858
  * static ɵcmp = defineComponent({
@@ -2220,61 +2867,32 @@ function ɵɵdefineComponent(componentDefinition) {
2220
2867
  // Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
2221
2868
  // See the `initNgDevMode` docstring for more information.
2222
2869
  (typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
2223
- const type = componentDefinition.type;
2224
- const standalone = componentDefinition.standalone === true;
2225
- const declaredInputs = {};
2870
+ const baseDef = getNgDirectiveDef(componentDefinition);
2226
2871
  const def = {
2227
- type: type,
2228
- providersResolver: null,
2872
+ ...baseDef,
2229
2873
  decls: componentDefinition.decls,
2230
2874
  vars: componentDefinition.vars,
2231
- factory: null,
2232
- template: componentDefinition.template || null,
2875
+ template: componentDefinition.template,
2233
2876
  consts: componentDefinition.consts || null,
2234
2877
  ngContentSelectors: componentDefinition.ngContentSelectors,
2235
- hostBindings: componentDefinition.hostBindings || null,
2236
- hostVars: componentDefinition.hostVars || 0,
2237
- hostAttrs: componentDefinition.hostAttrs || null,
2238
- contentQueries: componentDefinition.contentQueries || null,
2239
- declaredInputs: declaredInputs,
2240
- inputs: null,
2241
- outputs: null,
2242
- exportAs: componentDefinition.exportAs || null,
2243
2878
  onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
2244
2879
  directiveDefs: null,
2245
2880
  pipeDefs: null,
2246
- standalone,
2247
- dependencies: standalone && componentDefinition.dependencies || null,
2881
+ dependencies: baseDef.standalone && componentDefinition.dependencies || null,
2248
2882
  getStandaloneInjector: null,
2249
- selectors: componentDefinition.selectors || EMPTY_ARRAY,
2250
- viewQuery: componentDefinition.viewQuery || null,
2251
- features: componentDefinition.features || null,
2252
2883
  data: componentDefinition.data || {},
2253
2884
  encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
2254
- id: `c${componentDefCount++}`,
2255
2885
  styles: componentDefinition.styles || EMPTY_ARRAY,
2256
2886
  _: null,
2257
- setInput: null,
2258
2887
  schemas: componentDefinition.schemas || null,
2259
2888
  tView: null,
2260
- findHostDirectiveDefs: null,
2261
- hostDirectives: null,
2889
+ id: '',
2262
2890
  };
2891
+ initFeatures(def);
2263
2892
  const dependencies = componentDefinition.dependencies;
2264
- const feature = componentDefinition.features;
2265
- def.inputs = invertObject(componentDefinition.inputs, declaredInputs),
2266
- def.outputs = invertObject(componentDefinition.outputs),
2267
- feature && feature.forEach((fn) => fn(def));
2268
- def.directiveDefs = dependencies ?
2269
- (() => (typeof dependencies === 'function' ? dependencies() : dependencies)
2270
- .map(extractDirectiveDef)
2271
- .filter(nonNull)) :
2272
- null;
2273
- def.pipeDefs = dependencies ?
2274
- (() => (typeof dependencies === 'function' ? dependencies() : dependencies)
2275
- .map(getPipeDef$1)
2276
- .filter(nonNull)) :
2277
- null;
2893
+ def.directiveDefs = extractDefListOrFactory(dependencies, /* pipeDef */ false);
2894
+ def.pipeDefs = extractDefListOrFactory(dependencies, /* pipeDef */ true);
2895
+ def.id = getComponentId(def);
2278
2896
  return def;
2279
2897
  });
2280
2898
  }
@@ -2289,8 +2907,8 @@ function ɵɵdefineComponent(componentDefinition) {
2289
2907
  */
2290
2908
  function ɵɵsetComponentScope(type, directives, pipes) {
2291
2909
  const def = type.ɵcmp;
2292
- def.directiveDefs = () => (typeof directives === 'function' ? directives() : directives).map(extractDirectiveDef);
2293
- def.pipeDefs = () => (typeof pipes === 'function' ? pipes() : pipes).map(getPipeDef$1);
2910
+ def.directiveDefs = extractDefListOrFactory(directives, /* pipeDef */ false);
2911
+ def.pipeDefs = extractDefListOrFactory(pipes, /* pipeDef */ true);
2294
2912
  }
2295
2913
  function extractDirectiveDef(type) {
2296
2914
  return getComponentDef$1(type) || getDirectiveDef(type);
@@ -2425,7 +3043,13 @@ function invertObject(obj, secondary) {
2425
3043
  *
2426
3044
  * @codeGenApi
2427
3045
  */
2428
- const ɵɵdefineDirective = ɵɵdefineComponent;
3046
+ function ɵɵdefineDirective(directiveDefinition) {
3047
+ return noSideEffects(() => {
3048
+ const def = getNgDirectiveDef(directiveDefinition);
3049
+ initFeatures(def);
3050
+ return def;
3051
+ });
3052
+ }
2429
3053
  /**
2430
3054
  * Create a pipe definition object.
2431
3055
  *
@@ -2485,6 +3109,99 @@ function getNgModuleDef(type, throwNotFound) {
2485
3109
  }
2486
3110
  return ngModuleDef;
2487
3111
  }
3112
+ function getNgDirectiveDef(directiveDefinition) {
3113
+ const declaredInputs = {};
3114
+ return {
3115
+ type: directiveDefinition.type,
3116
+ providersResolver: null,
3117
+ factory: null,
3118
+ hostBindings: directiveDefinition.hostBindings || null,
3119
+ hostVars: directiveDefinition.hostVars || 0,
3120
+ hostAttrs: directiveDefinition.hostAttrs || null,
3121
+ contentQueries: directiveDefinition.contentQueries || null,
3122
+ declaredInputs,
3123
+ exportAs: directiveDefinition.exportAs || null,
3124
+ standalone: directiveDefinition.standalone === true,
3125
+ selectors: directiveDefinition.selectors || EMPTY_ARRAY,
3126
+ viewQuery: directiveDefinition.viewQuery || null,
3127
+ features: directiveDefinition.features || null,
3128
+ setInput: null,
3129
+ findHostDirectiveDefs: null,
3130
+ hostDirectives: null,
3131
+ inputs: invertObject(directiveDefinition.inputs, declaredInputs),
3132
+ outputs: invertObject(directiveDefinition.outputs),
3133
+ };
3134
+ }
3135
+ function initFeatures(definition) {
3136
+ definition.features?.forEach((fn) => fn(definition));
3137
+ }
3138
+ function extractDefListOrFactory(dependencies, pipeDef) {
3139
+ if (!dependencies) {
3140
+ return null;
3141
+ }
3142
+ const defExtractor = pipeDef ? getPipeDef$1 : extractDirectiveDef;
3143
+ return () => (typeof dependencies === 'function' ? dependencies() : dependencies)
3144
+ .map(dep => defExtractor(dep))
3145
+ .filter(nonNull);
3146
+ }
3147
+ /**
3148
+ * A map that contains the generated component IDs and type.
3149
+ */
3150
+ const GENERATED_COMP_IDS = new Map();
3151
+ /**
3152
+ * A method can returns a component ID from the component definition using a variant of DJB2 hash
3153
+ * algorithm.
3154
+ */
3155
+ function getComponentId(componentDef) {
3156
+ let hash = 0;
3157
+ // We cannot rely solely on the component selector as the same selector can be used in different
3158
+ // modules.
3159
+ //
3160
+ // `componentDef.style` is not used, due to it causing inconsistencies. Ex: when server
3161
+ // component styles has no sourcemaps and browsers do.
3162
+ //
3163
+ // Example:
3164
+ // https://github.com/angular/components/blob/d9f82c8f95309e77a6d82fd574c65871e91354c2/src/material/core/option/option.ts#L248
3165
+ // https://github.com/angular/components/blob/285f46dc2b4c5b127d356cb7c4714b221f03ce50/src/material/legacy-core/option/option.ts#L32
3166
+ const hashSelectors = [
3167
+ componentDef.selectors,
3168
+ componentDef.ngContentSelectors,
3169
+ componentDef.hostVars,
3170
+ componentDef.hostAttrs,
3171
+ componentDef.consts,
3172
+ componentDef.vars,
3173
+ componentDef.decls,
3174
+ componentDef.encapsulation,
3175
+ componentDef.standalone,
3176
+ // We cannot use 'componentDef.type.name' as the name of the symbol will change and will not
3177
+ // match in the server and browser bundles.
3178
+ Object.getOwnPropertyNames(componentDef.type.prototype),
3179
+ !!componentDef.contentQueries,
3180
+ !!componentDef.viewQuery,
3181
+ ].join('|');
3182
+ for (const char of hashSelectors) {
3183
+ hash = Math.imul(31, hash) + char.charCodeAt(0) << 0;
3184
+ }
3185
+ // Force positive number hash.
3186
+ // 2147483647 = equivalent of Integer.MAX_VALUE.
3187
+ hash += 2147483647 + 1;
3188
+ const compId = 'c' + hash;
3189
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
3190
+ if (GENERATED_COMP_IDS.has(compId)) {
3191
+ const previousCompDefType = GENERATED_COMP_IDS.get(compId);
3192
+ if (previousCompDefType !== componentDef.type) {
3193
+ // TODO: use `formatRuntimeError` to have an error code and we can later on create an error
3194
+ // guide to explain this further.
3195
+ console.warn(`Component ID generation collision detected. Components '${previousCompDefType.name}' and '${componentDef.type.name}' with selector '${stringifyCSSSelectorList(componentDef
3196
+ .selectors)}' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID.`);
3197
+ }
3198
+ }
3199
+ else {
3200
+ GENERATED_COMP_IDS.set(compId, componentDef.type);
3201
+ }
3202
+ }
3203
+ return compId;
3204
+ }
2488
3205
 
2489
3206
  // Below are constants for LView indices to help us look up LView members
2490
3207
  // without having to remember the specific indices.
@@ -2553,13 +3270,14 @@ const HAS_TRANSPLANTED_VIEWS = 2;
2553
3270
  const NATIVE = 7;
2554
3271
  const VIEW_REFS = 8;
2555
3272
  const MOVED_VIEWS = 9;
3273
+ const DEHYDRATED_VIEWS = 10;
2556
3274
  /**
2557
3275
  * Size of LContainer's header. Represents the index after which all views in the
2558
3276
  * container will be inserted. We need to keep a record of current views so we know
2559
3277
  * which views are already in the DOM (and don't need to be re-added) and so we can
2560
3278
  * remove views from the DOM when they are no longer required.
2561
3279
  */
2562
- const CONTAINER_HEADER_OFFSET = 10;
3280
+ const CONTAINER_HEADER_OFFSET = 11;
2563
3281
  // Note: This hack is necessary so we don't erroneously get a circular dependency
2564
3282
  // failure based on types.
2565
3283
  const unusedValueExportToPlacateAjd$3 = 1;
@@ -2588,11 +3306,14 @@ function isDirectiveHost(tNode) {
2588
3306
  return (tNode.flags & 1 /* TNodeFlags.isDirectiveHost */) === 1 /* TNodeFlags.isDirectiveHost */;
2589
3307
  }
2590
3308
  function isComponentDef(def) {
2591
- return def.template !== null;
3309
+ return !!def.template;
2592
3310
  }
2593
3311
  function isRootView(target) {
2594
3312
  return (target[FLAGS] & 256 /* LViewFlags.IsRoot */) !== 0;
2595
3313
  }
3314
+ function isProjectionTNode(tNode) {
3315
+ return (tNode.type & 16 /* TNodeType.Projection */) === 16 /* TNodeType.Projection */;
3316
+ }
2596
3317
 
2597
3318
  // [Assert functions do not constraint type when they are guarded by a truthy
2598
3319
  // expression.](https://github.com/microsoft/TypeScript/issues/37295)
@@ -3006,6 +3727,17 @@ function storeLViewOnDestroy(lView, onDestroyCallback) {
3006
3727
  }
3007
3728
  lView[ON_DESTROY_HOOKS].push(onDestroyCallback);
3008
3729
  }
3730
+ /**
3731
+ * Removes previously registered LView-specific destroy callback.
3732
+ */
3733
+ function removeLViewOnDestroy(lView, onDestroyCallback) {
3734
+ if (lView[ON_DESTROY_HOOKS] === null)
3735
+ return;
3736
+ const destroyCBIdx = lView[ON_DESTROY_HOOKS].indexOf(onDestroyCallback);
3737
+ if (destroyCBIdx !== -1) {
3738
+ lView[ON_DESTROY_HOOKS].splice(destroyCBIdx, 1);
3739
+ }
3740
+ }
3009
3741
 
3010
3742
  const instructionState = {
3011
3743
  lFrame: createLFrame(null),
@@ -3045,7 +3777,7 @@ function getBindingsEnabled() {
3045
3777
  * Returns true if currently inside a skip hydration block.
3046
3778
  * @returns boolean
3047
3779
  */
3048
- function isInSkipHydrationBlock() {
3780
+ function isInSkipHydrationBlock$1() {
3049
3781
  return instructionState.skipHydrationRootTNode !== null;
3050
3782
  }
3051
3783
  /**
@@ -3990,206 +4722,6 @@ function assertPureTNodeType(type) {
3990
4722
  }
3991
4723
  }
3992
4724
 
3993
- /**
3994
- * Assigns all attribute values to the provided element via the inferred renderer.
3995
- *
3996
- * This function accepts two forms of attribute entries:
3997
- *
3998
- * default: (key, value):
3999
- * attrs = [key1, value1, key2, value2]
4000
- *
4001
- * namespaced: (NAMESPACE_MARKER, uri, name, value)
4002
- * attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value]
4003
- *
4004
- * The `attrs` array can contain a mix of both the default and namespaced entries.
4005
- * The "default" values are set without a marker, but if the function comes across
4006
- * a marker value then it will attempt to set a namespaced value. If the marker is
4007
- * not of a namespaced value then the function will quit and return the index value
4008
- * where it stopped during the iteration of the attrs array.
4009
- *
4010
- * See [AttributeMarker] to understand what the namespace marker value is.
4011
- *
4012
- * Note that this instruction does not support assigning style and class values to
4013
- * an element. See `elementStart` and `elementHostAttrs` to learn how styling values
4014
- * are applied to an element.
4015
- * @param renderer The renderer to be used
4016
- * @param native The element that the attributes will be assigned to
4017
- * @param attrs The attribute array of values that will be assigned to the element
4018
- * @returns the index value that was last accessed in the attributes array
4019
- */
4020
- function setUpAttributes(renderer, native, attrs) {
4021
- let i = 0;
4022
- while (i < attrs.length) {
4023
- const value = attrs[i];
4024
- if (typeof value === 'number') {
4025
- // only namespaces are supported. Other value types (such as style/class
4026
- // entries) are not supported in this function.
4027
- if (value !== 0 /* AttributeMarker.NamespaceURI */) {
4028
- break;
4029
- }
4030
- // we just landed on the marker value ... therefore
4031
- // we should skip to the next entry
4032
- i++;
4033
- const namespaceURI = attrs[i++];
4034
- const attrName = attrs[i++];
4035
- const attrVal = attrs[i++];
4036
- ngDevMode && ngDevMode.rendererSetAttribute++;
4037
- renderer.setAttribute(native, attrName, attrVal, namespaceURI);
4038
- }
4039
- else {
4040
- // attrName is string;
4041
- const attrName = value;
4042
- const attrVal = attrs[++i];
4043
- // Standard attributes
4044
- ngDevMode && ngDevMode.rendererSetAttribute++;
4045
- if (isAnimationProp(attrName)) {
4046
- renderer.setProperty(native, attrName, attrVal);
4047
- }
4048
- else {
4049
- renderer.setAttribute(native, attrName, attrVal);
4050
- }
4051
- i++;
4052
- }
4053
- }
4054
- // another piece of code may iterate over the same attributes array. Therefore
4055
- // it may be helpful to return the exact spot where the attributes array exited
4056
- // whether by running into an unsupported marker or if all the static values were
4057
- // iterated over.
4058
- return i;
4059
- }
4060
- /**
4061
- * Test whether the given value is a marker that indicates that the following
4062
- * attribute values in a `TAttributes` array are only the names of attributes,
4063
- * and not name-value pairs.
4064
- * @param marker The attribute marker to test.
4065
- * @returns true if the marker is a "name-only" marker (e.g. `Bindings`, `Template` or `I18n`).
4066
- */
4067
- function isNameOnlyAttributeMarker(marker) {
4068
- return marker === 3 /* AttributeMarker.Bindings */ || marker === 4 /* AttributeMarker.Template */ ||
4069
- marker === 6 /* AttributeMarker.I18n */;
4070
- }
4071
- function isAnimationProp(name) {
4072
- // Perf note: accessing charCodeAt to check for the first character of a string is faster as
4073
- // compared to accessing a character at index 0 (ex. name[0]). The main reason for this is that
4074
- // charCodeAt doesn't allocate memory to return a substring.
4075
- return name.charCodeAt(0) === 64 /* CharCode.AT_SIGN */;
4076
- }
4077
- /**
4078
- * Merges `src` `TAttributes` into `dst` `TAttributes` removing any duplicates in the process.
4079
- *
4080
- * This merge function keeps the order of attrs same.
4081
- *
4082
- * @param dst Location of where the merged `TAttributes` should end up.
4083
- * @param src `TAttributes` which should be appended to `dst`
4084
- */
4085
- function mergeHostAttrs(dst, src) {
4086
- if (src === null || src.length === 0) {
4087
- // do nothing
4088
- }
4089
- else if (dst === null || dst.length === 0) {
4090
- // We have source, but dst is empty, just make a copy.
4091
- dst = src.slice();
4092
- }
4093
- else {
4094
- let srcMarker = -1 /* AttributeMarker.ImplicitAttributes */;
4095
- for (let i = 0; i < src.length; i++) {
4096
- const item = src[i];
4097
- if (typeof item === 'number') {
4098
- srcMarker = item;
4099
- }
4100
- else {
4101
- if (srcMarker === 0 /* AttributeMarker.NamespaceURI */) {
4102
- // Case where we need to consume `key1`, `key2`, `value` items.
4103
- }
4104
- else if (srcMarker === -1 /* AttributeMarker.ImplicitAttributes */ ||
4105
- srcMarker === 2 /* AttributeMarker.Styles */) {
4106
- // Case where we have to consume `key1` and `value` only.
4107
- mergeHostAttribute(dst, srcMarker, item, null, src[++i]);
4108
- }
4109
- else {
4110
- // Case where we have to consume `key1` only.
4111
- mergeHostAttribute(dst, srcMarker, item, null, null);
4112
- }
4113
- }
4114
- }
4115
- }
4116
- return dst;
4117
- }
4118
- /**
4119
- * Append `key`/`value` to existing `TAttributes` taking region marker and duplicates into account.
4120
- *
4121
- * @param dst `TAttributes` to append to.
4122
- * @param marker Region where the `key`/`value` should be added.
4123
- * @param key1 Key to add to `TAttributes`
4124
- * @param key2 Key to add to `TAttributes` (in case of `AttributeMarker.NamespaceURI`)
4125
- * @param value Value to add or to overwrite to `TAttributes` Only used if `marker` is not Class.
4126
- */
4127
- function mergeHostAttribute(dst, marker, key1, key2, value) {
4128
- let i = 0;
4129
- // Assume that new markers will be inserted at the end.
4130
- let markerInsertPosition = dst.length;
4131
- // scan until correct type.
4132
- if (marker === -1 /* AttributeMarker.ImplicitAttributes */) {
4133
- markerInsertPosition = -1;
4134
- }
4135
- else {
4136
- while (i < dst.length) {
4137
- const dstValue = dst[i++];
4138
- if (typeof dstValue === 'number') {
4139
- if (dstValue === marker) {
4140
- markerInsertPosition = -1;
4141
- break;
4142
- }
4143
- else if (dstValue > marker) {
4144
- // We need to save this as we want the markers to be inserted in specific order.
4145
- markerInsertPosition = i - 1;
4146
- break;
4147
- }
4148
- }
4149
- }
4150
- }
4151
- // search until you find place of insertion
4152
- while (i < dst.length) {
4153
- const item = dst[i];
4154
- if (typeof item === 'number') {
4155
- // since `i` started as the index after the marker, we did not find it if we are at the next
4156
- // marker
4157
- break;
4158
- }
4159
- else if (item === key1) {
4160
- // We already have same token
4161
- if (key2 === null) {
4162
- if (value !== null) {
4163
- dst[i + 1] = value;
4164
- }
4165
- return;
4166
- }
4167
- else if (key2 === dst[i + 1]) {
4168
- dst[i + 2] = value;
4169
- return;
4170
- }
4171
- }
4172
- // Increment counter.
4173
- i++;
4174
- if (key2 !== null)
4175
- i++;
4176
- if (value !== null)
4177
- i++;
4178
- }
4179
- // insert at location.
4180
- if (markerInsertPosition !== -1) {
4181
- dst.splice(markerInsertPosition, 0, marker);
4182
- i = markerInsertPosition + 1;
4183
- }
4184
- dst.splice(i++, 0, key1);
4185
- if (key2 !== null) {
4186
- dst.splice(i++, 0, key2);
4187
- }
4188
- if (value !== null) {
4189
- dst.splice(i++, 0, value);
4190
- }
4191
- }
4192
-
4193
4725
  /// Parent Injector Utils ///////////////////////////////////////////////////////////////
4194
4726
  function hasParentInjector(parentLocation) {
4195
4727
  return parentLocation !== NO_PARENT_INJECTOR;
@@ -8301,6 +8833,7 @@ class R3Injector extends EnvironmentInjector {
8301
8833
  }
8302
8834
  onDestroy(callback) {
8303
8835
  this._onDestroyHooks.push(callback);
8836
+ return () => this.removeOnDestroy(callback);
8304
8837
  }
8305
8838
  runInContext(fn) {
8306
8839
  this.assertNotDestroyed();
@@ -8475,6 +9008,12 @@ class R3Injector extends EnvironmentInjector {
8475
9008
  return this.injectorDefTypes.has(providedIn);
8476
9009
  }
8477
9010
  }
9011
+ removeOnDestroy(callback) {
9012
+ const destroyCBIdx = this._onDestroyHooks.indexOf(callback);
9013
+ if (destroyCBIdx !== -1) {
9014
+ this._onDestroyHooks.splice(destroyCBIdx, 1);
9015
+ }
9016
+ }
8478
9017
  }
8479
9018
  function injectableDefOrInjectorDefFactory(token) {
8480
9019
  // Most tokens will have an injectable def directly on them, which specifies a factory directly.
@@ -8597,35 +9136,41 @@ function forEachSingleProvider(providers, fn) {
8597
9136
  }
8598
9137
 
8599
9138
  /**
8600
- * A [DI token](guide/glossary#di-token "DI token definition") representing a unique string ID, used
9139
+ * A [DI token](guide/glossary#di-token "DI token definition") representing a string ID, used
8601
9140
  * primarily for prefixing application attributes and CSS styles when
8602
9141
  * {@link ViewEncapsulation#Emulated ViewEncapsulation.Emulated} is being used.
8603
9142
  *
8604
- * BY default, the value is randomly generated and assigned to the application by Angular.
8605
- * To provide a custom ID value, use a DI provider <!-- TODO: provider --> to configure
8606
- * the root {@link Injector} that uses this token.
9143
+ * The token is needed in cases when multiple applications are bootstrapped on a page
9144
+ * (for example, using `bootstrapApplication` calls). In this case, ensure that those applications
9145
+ * have different `APP_ID` value setup. For example:
9146
+ *
9147
+ * ```
9148
+ * bootstrapApplication(ComponentA, {
9149
+ * providers: [
9150
+ * { provide: APP_ID, useValue: 'app-a' },
9151
+ * // ... other providers ...
9152
+ * ]
9153
+ * });
9154
+ *
9155
+ * bootstrapApplication(ComponentB, {
9156
+ * providers: [
9157
+ * { provide: APP_ID, useValue: 'app-b' },
9158
+ * // ... other providers ...
9159
+ * ]
9160
+ * });
9161
+ * ```
9162
+ *
9163
+ * By default, when there is only one application bootstrapped, you don't need to provide the
9164
+ * `APP_ID` token (the `ng` will be used as an app ID).
8607
9165
  *
8608
9166
  * @publicApi
8609
9167
  */
8610
9168
  const APP_ID = new InjectionToken('AppId', {
8611
9169
  providedIn: 'root',
8612
- factory: _appIdRandomProviderFactory,
9170
+ factory: () => DEFAULT_APP_ID,
8613
9171
  });
8614
- function _appIdRandomProviderFactory() {
8615
- return `${_randomChar()}${_randomChar()}${_randomChar()}`;
8616
- }
8617
- /**
8618
- * Providers that generate a random `APP_ID_TOKEN`.
8619
- * @publicApi
8620
- */
8621
- const APP_ID_RANDOM_PROVIDER = {
8622
- provide: APP_ID,
8623
- useFactory: _appIdRandomProviderFactory,
8624
- deps: [],
8625
- };
8626
- function _randomChar() {
8627
- return String.fromCharCode(97 + Math.floor(Math.random() * 25));
8628
- }
9172
+ /** Default value of the `APP_ID` token. */
9173
+ const DEFAULT_APP_ID = 'ng';
8629
9174
  /**
8630
9175
  * A function that is executed when a platform is initialized.
8631
9176
  * @publicApi
@@ -8654,6 +9199,37 @@ const PACKAGE_ROOT_URL = new InjectionToken('Application Packages Root URL');
8654
9199
  * @publicApi
8655
9200
  */
8656
9201
  const ANIMATION_MODULE_TYPE = new InjectionToken('AnimationModuleType');
9202
+ // TODO(crisbeto): link to CSP guide here.
9203
+ /**
9204
+ * Token used to configure the [Content Security Policy](https://web.dev/strict-csp/) nonce that
9205
+ * Angular will apply when inserting inline styles. If not provided, Angular will look up its value
9206
+ * from the `ngCspNonce` attribute of the application root node.
9207
+ *
9208
+ * @publicApi
9209
+ */
9210
+ const CSP_NONCE = new InjectionToken('CSP nonce', {
9211
+ providedIn: 'root',
9212
+ factory: () => {
9213
+ // Ideally we wouldn't have to use `querySelector` here since we know that the nonce will be on
9214
+ // the root node, but because the token value is used in renderers, it has to be available
9215
+ // *very* early in the bootstrapping process. This should be a fairly shallow search, because
9216
+ // the app won't have been added to the DOM yet. Some approaches that were considered:
9217
+ // 1. Find the root node through `ApplicationRef.components[i].location` - normally this would
9218
+ // be enough for our purposes, but the token is injected very early so the `components` array
9219
+ // isn't populated yet.
9220
+ // 2. Find the root `LView` through the current `LView` - renderers are a prerequisite to
9221
+ // creating the `LView`. This means that no `LView` will have been entered when this factory is
9222
+ // invoked for the root component.
9223
+ // 3. Have the token factory return `() => string` which is invoked when a nonce is requested -
9224
+ // the slightly later execution does allow us to get an `LView` reference, but the fact that
9225
+ // it is a function means that it could be executed at *any* time (including immediately) which
9226
+ // may lead to weird bugs.
9227
+ // 4. Have the `ComponentFactory` read the attribute and provide it to the injector under the
9228
+ // hood - has the same problem as #1 and #2 in that the renderer is used to query for the root
9229
+ // node and the nonce value needs to be available when the renderer is created.
9230
+ return getDocument().body.querySelector('[ngCspNonce]')?.getAttribute('ngCspNonce') || null;
9231
+ },
9232
+ });
8657
9233
 
8658
9234
  function escapeTransferStateContent(text) {
8659
9235
  const escapedText = {
@@ -8772,966 +9348,642 @@ class TransferState {
8772
9348
  return JSON.stringify(this.store);
8773
9349
  }
8774
9350
  }
8775
- /** @nocollapse */
8776
- TransferState.ɵprov =
8777
- /** @pureOrBreakMyCode */ ɵɵdefineInjectable({
8778
- token: TransferState,
8779
- providedIn: 'root',
8780
- factory: initTransferState,
8781
- });
8782
- function retrieveTransferredState(doc, appId) {
8783
- // Locate the script tag with the JSON data transferred from the server.
8784
- // The id of the script tag is set to the Angular appId + 'state'.
8785
- const script = doc.getElementById(appId + '-state');
8786
- let initialState = {};
8787
- if (script && script.textContent) {
8788
- try {
8789
- // Avoid using any here as it triggers lint errors in google3 (any is not allowed).
8790
- initialState = JSON.parse(unescapeTransferStateContent(script.textContent));
8791
- }
8792
- catch (e) {
8793
- console.warn('Exception while restoring TransferState for app ' + appId, e);
8794
- }
8795
- }
8796
- return initialState;
8797
- }
8798
-
8799
- /* Represents a key in NghDom that holds information about <ng-container>s. */
8800
- const ELEMENT_CONTAINERS = 'e';
8801
-
8802
- /**
8803
- * The name of the key used in the TransferState collection,
8804
- * where hydration information is located.
8805
- */
8806
- const TRANSFER_STATE_TOKEN_ID = '__ɵnghData__';
8807
- /**
8808
- * Lookup key used to reference DOM hydration data (ngh) in `TransferState`.
8809
- */
8810
- const NGH_DATA_KEY = makeStateKey(TRANSFER_STATE_TOKEN_ID);
8811
- /**
8812
- * The name of the attribute that would be added to host component
8813
- * nodes and contain a reference to a particular slot in transferred
8814
- * state that contains the necessary hydration info for this component.
8815
- */
8816
- const NGH_ATTR_NAME = 'ngh';
8817
- /**
8818
- * Reference to a function that reads `ngh` attribute value from a given RNode
8819
- * and retrieves hydration information from the TransferState using that value
8820
- * as an index. Returns `null` by default, when hydration is not enabled.
8821
- *
8822
- * @param rNode Component's host element.
8823
- * @param injector Injector that this component has access to.
8824
- */
8825
- let _retrieveHydrationInfoImpl = (rNode, injector) => null;
8826
- function retrieveHydrationInfoImpl(rNode, injector) {
8827
- const nghAttrValue = rNode.getAttribute(NGH_ATTR_NAME);
8828
- if (nghAttrValue == null)
8829
- return null;
8830
- let data = {};
8831
- // An element might have an empty `ngh` attribute value (e.g. `<comp ngh="" />`),
8832
- // which means that no special annotations are required. Do not attempt to read
8833
- // from the TransferState in this case.
8834
- if (nghAttrValue !== '') {
8835
- const transferState = injector.get(TransferState, null, { optional: true });
8836
- if (transferState !== null) {
8837
- const nghData = transferState.get(NGH_DATA_KEY, []);
8838
- // The nghAttrValue is always a number referencing an index
8839
- // in the hydration TransferState data.
8840
- data = nghData[Number(nghAttrValue)];
8841
- // If the `ngh` attribute exists and has a non-empty value,
8842
- // the hydration info *must* be present in the TransferState.
8843
- // If there is no data for some reasons, this is an error.
8844
- ngDevMode && assertDefined(data, 'Unable to retrieve hydration info from the TransferState.');
8845
- }
8846
- }
8847
- const dehydratedView = {
8848
- data,
8849
- firstChild: rNode.firstChild ?? null,
8850
- };
8851
- // The `ngh` attribute is cleared from the DOM node now
8852
- // that the data has been retrieved.
8853
- rNode.removeAttribute(NGH_ATTR_NAME);
8854
- // Note: don't check whether this node was claimed for hydration,
8855
- // because this node might've been previously claimed while processing
8856
- // template instructions.
8857
- ngDevMode && markRNodeAsClaimedByHydration(rNode, /* checkIfAlreadyClaimed */ false);
8858
- ngDevMode && ngDevMode.hydratedComponents++;
8859
- return dehydratedView;
8860
- }
8861
- /**
8862
- * Sets the implementation for the `retrieveNghInfo` function.
8863
- */
8864
- function enableRetrieveHydrationInfoImpl() {
8865
- _retrieveHydrationInfoImpl = retrieveHydrationInfoImpl;
8866
- }
8867
- /**
8868
- * Retrieves hydration info by reading the value from the `ngh` attribute
8869
- * and accessing a corresponding slot in TransferState storage.
8870
- */
8871
- function retrieveHydrationInfo(rNode, injector) {
8872
- return _retrieveHydrationInfoImpl(rNode, injector);
8873
- }
8874
- /**
8875
- * Retrieves an instance of a component LView from a given ViewRef.
8876
- * Returns an instance of a component LView or `null` in case of an embedded view.
8877
- */
8878
- function getComponentLViewForHydration(viewRef) {
8879
- // Reading an internal field from `ViewRef` instance.
8880
- let lView = viewRef._lView;
8881
- const tView = lView[TVIEW];
8882
- // A registered ViewRef might represent an instance of an
8883
- // embedded view, in which case we do not need to annotate it.
8884
- if (tView.type === 2 /* TViewType.Embedded */) {
8885
- return null;
8886
- }
8887
- // Check if it's a root view and if so, retrieve component's
8888
- // LView from the first slot after the header.
8889
- if (isRootView(lView)) {
8890
- lView = lView[HEADER_OFFSET];
8891
- }
8892
- return lView;
8893
- }
8894
- /**
8895
- * Marks a node as "claimed" by hydration process.
8896
- * This is needed to make assessments in tests whether
8897
- * the hydration process handled all nodes.
8898
- */
8899
- function markRNodeAsClaimedByHydration(node, checkIfAlreadyClaimed = true) {
8900
- if (!ngDevMode) {
8901
- throw new Error('Calling `markRNodeAsClaimedByHydration` in prod mode ' +
8902
- 'is not supported and likely a mistake.');
8903
- }
8904
- if (checkIfAlreadyClaimed && isRNodeClaimedForHydration(node)) {
8905
- throw new Error('Trying to claim a node, which was claimed already.');
8906
- }
8907
- node.__claimed = true;
8908
- ngDevMode.hydratedNodes++;
8909
- }
8910
- function isRNodeClaimedForHydration(node) {
8911
- return !!node.__claimed;
8912
- }
8913
- function storeNgContainerInfo(hydrationInfo, index, firstChild) {
8914
- hydrationInfo.ngContainers ?? (hydrationInfo.ngContainers = {});
8915
- hydrationInfo.ngContainers[index] = { firstChild };
8916
- }
8917
- function getNgContainerSize(hydrationInfo, index) {
8918
- return hydrationInfo.data[ELEMENT_CONTAINERS]?.[index] ?? null;
8919
- }
8920
-
8921
- /**
8922
- * Represents a component created by a `ComponentFactory`.
8923
- * Provides access to the component instance and related objects,
8924
- * and provides the means of destroying the instance.
8925
- *
8926
- * @publicApi
8927
- */
8928
- class ComponentRef$1 {
8929
- }
8930
- /**
8931
- * Base class for a factory that can create a component dynamically.
8932
- * Instantiate a factory for a given type of component with `resolveComponentFactory()`.
8933
- * Use the resulting `ComponentFactory.create()` method to create a component of that type.
8934
- *
8935
- * @see [Dynamic Components](guide/dynamic-component-loader)
8936
- *
8937
- * @publicApi
8938
- *
8939
- * @deprecated Angular no longer requires Component factories. Please use other APIs where
8940
- * Component class can be used directly.
8941
- */
8942
- class ComponentFactory$1 {
8943
- }
8944
-
8945
- function noComponentFactoryError(component) {
8946
- const error = Error(`No component factory found for ${stringify(component)}. Did you add it to @NgModule.entryComponents?`);
8947
- error[ERROR_COMPONENT] = component;
8948
- return error;
8949
- }
8950
- const ERROR_COMPONENT = 'ngComponent';
8951
- function getComponent$1(error) {
8952
- return error[ERROR_COMPONENT];
8953
- }
8954
- class _NullComponentFactoryResolver {
8955
- resolveComponentFactory(component) {
8956
- throw noComponentFactoryError(component);
8957
- }
8958
- }
8959
- /**
8960
- * A simple registry that maps `Components` to generated `ComponentFactory` classes
8961
- * that can be used to create instances of components.
8962
- * Use to obtain the factory for a given component type,
8963
- * then use the factory's `create()` method to create a component of that type.
8964
- *
8965
- * Note: since v13, dynamic component creation via
8966
- * [`ViewContainerRef.createComponent`](api/core/ViewContainerRef#createComponent)
8967
- * does **not** require resolving component factory: component class can be used directly.
8968
- *
8969
- * @publicApi
8970
- *
8971
- * @deprecated Angular no longer requires Component factories. Please use other APIs where
8972
- * Component class can be used directly.
8973
- */
8974
- class ComponentFactoryResolver$1 {
8975
- }
8976
- ComponentFactoryResolver$1.NULL = ( /* @__PURE__ */new _NullComponentFactoryResolver());
8977
-
8978
- /**
8979
- * Creates an ElementRef from the most recent node.
8980
- *
8981
- * @returns The ElementRef instance to use
8982
- */
8983
- function injectElementRef() {
8984
- return createElementRef(getCurrentTNode(), getLView());
8985
- }
8986
- /**
8987
- * Creates an ElementRef given a node.
8988
- *
8989
- * @param tNode The node for which you'd like an ElementRef
8990
- * @param lView The view to which the node belongs
8991
- * @returns The ElementRef instance to use
8992
- */
8993
- function createElementRef(tNode, lView) {
8994
- return new ElementRef(getNativeByTNode(tNode, lView));
8995
- }
8996
- /**
8997
- * A wrapper around a native element inside of a View.
8998
- *
8999
- * An `ElementRef` is backed by a render-specific element. In the browser, this is usually a DOM
9000
- * element.
9001
- *
9002
- * @security Permitting direct access to the DOM can make your application more vulnerable to
9003
- * XSS attacks. Carefully review any use of `ElementRef` in your code. For more detail, see the
9004
- * [Security Guide](https://g.co/ng/security).
9005
- *
9006
- * @publicApi
9007
- */
9008
- // Note: We don't expose things like `Injector`, `ViewContainer`, ... here,
9009
- // i.e. users have to ask for what they need. With that, we can build better analysis tools
9010
- // and could do better codegen in the future.
9011
- class ElementRef {
9012
- constructor(nativeElement) {
9013
- this.nativeElement = nativeElement;
9351
+ /** @nocollapse */
9352
+ TransferState.ɵprov =
9353
+ /** @pureOrBreakMyCode */ ɵɵdefineInjectable({
9354
+ token: TransferState,
9355
+ providedIn: 'root',
9356
+ factory: initTransferState,
9357
+ });
9358
+ function retrieveTransferredState(doc, appId) {
9359
+ // Locate the script tag with the JSON data transferred from the server.
9360
+ // The id of the script tag is set to the Angular appId + 'state'.
9361
+ const script = doc.getElementById(appId + '-state');
9362
+ let initialState = {};
9363
+ if (script && script.textContent) {
9364
+ try {
9365
+ // Avoid using any here as it triggers lint errors in google3 (any is not allowed).
9366
+ initialState = JSON.parse(unescapeTransferStateContent(script.textContent));
9367
+ }
9368
+ catch (e) {
9369
+ console.warn('Exception while restoring TransferState for app ' + appId, e);
9370
+ }
9014
9371
  }
9372
+ return initialState;
9015
9373
  }
9374
+
9375
+ /** Encodes that the node lookup should start from the host node of this component. */
9376
+ const REFERENCE_NODE_HOST = 'h';
9377
+ /** Encodes that the node lookup should start from the document body node. */
9378
+ const REFERENCE_NODE_BODY = 'b';
9016
9379
  /**
9017
- * @internal
9018
- * @nocollapse
9380
+ * Describes navigation steps that the runtime logic need to perform,
9381
+ * starting from a given (known) element.
9019
9382
  */
9020
- ElementRef.__NG_ELEMENT_ID__ = injectElementRef;
9383
+ var NodeNavigationStep;
9384
+ (function (NodeNavigationStep) {
9385
+ NodeNavigationStep["FirstChild"] = "f";
9386
+ NodeNavigationStep["NextSibling"] = "n";
9387
+ })(NodeNavigationStep || (NodeNavigationStep = {}));
9021
9388
  /**
9022
- * Unwraps `ElementRef` and return the `nativeElement`.
9023
- *
9024
- * @param value value to unwrap
9025
- * @returns `nativeElement` if `ElementRef` otherwise returns value as is.
9389
+ * Keys within serialized view data structure to represent various
9390
+ * parts. See the `SerializedView` interface below for additional information.
9026
9391
  */
9027
- function unwrapElementRef(value) {
9028
- return value instanceof ElementRef ? value.nativeElement : value;
9029
- }
9392
+ const ELEMENT_CONTAINERS = 'e';
9393
+ const TEMPLATES = 't';
9394
+ const CONTAINERS = 'c';
9395
+ const MULTIPLIER = 'x';
9396
+ const NUM_ROOT_NODES = 'r';
9397
+ const TEMPLATE_ID = 'i'; // as it's also an "id"
9398
+ const NODES = 'n';
9399
+ const DISCONNECTED_NODES = 'd';
9030
9400
 
9031
9401
  /**
9032
- * Creates and initializes a custom renderer that implements the `Renderer2` base class.
9033
- *
9034
- * @publicApi
9402
+ * The name of the key used in the TransferState collection,
9403
+ * where hydration information is located.
9035
9404
  */
9036
- class RendererFactory2 {
9037
- }
9405
+ const TRANSFER_STATE_TOKEN_ID = '__ɵnghData__';
9038
9406
  /**
9039
- * Extend this base class to implement custom rendering. By default, Angular
9040
- * renders a template into DOM. You can use custom rendering to intercept
9041
- * rendering calls, or to render to something other than DOM.
9042
- *
9043
- * Create your custom renderer using `RendererFactory2`.
9044
- *
9045
- * Use a custom renderer to bypass Angular's templating and
9046
- * make custom UI changes that can't be expressed declaratively.
9047
- * For example if you need to set a property or an attribute whose name is
9048
- * not statically known, use the `setProperty()` or
9049
- * `setAttribute()` method.
9050
- *
9051
- * @publicApi
9407
+ * Lookup key used to reference DOM hydration data (ngh) in `TransferState`.
9052
9408
  */
9053
- class Renderer2 {
9054
- }
9409
+ const NGH_DATA_KEY = makeStateKey(TRANSFER_STATE_TOKEN_ID);
9055
9410
  /**
9056
- * @internal
9057
- * @nocollapse
9411
+ * The name of the attribute that would be added to host component
9412
+ * nodes and contain a reference to a particular slot in transferred
9413
+ * state that contains the necessary hydration info for this component.
9058
9414
  */
9059
- Renderer2.__NG_ELEMENT_ID__ = () => injectRenderer2();
9060
- /** Injects a Renderer2 for the current component. */
9061
- function injectRenderer2() {
9062
- // We need the Renderer to be based on the component that it's being injected into, however since
9063
- // DI happens before we've entered its view, `getLView` will return the parent view instead.
9064
- const lView = getLView();
9065
- const tNode = getCurrentTNode();
9066
- const nodeAtIndex = getComponentLViewByIndex(tNode.index, lView);
9067
- return (isLView(nodeAtIndex) ? nodeAtIndex : lView)[RENDERER];
9068
- }
9069
-
9415
+ const NGH_ATTR_NAME = 'ngh';
9070
9416
  /**
9071
- * Sanitizer is used by the views to sanitize potentially dangerous values.
9417
+ * Reference to a function that reads `ngh` attribute value from a given RNode
9418
+ * and retrieves hydration information from the TransferState using that value
9419
+ * as an index. Returns `null` by default, when hydration is not enabled.
9072
9420
  *
9073
- * @publicApi
9421
+ * @param rNode Component's host element.
9422
+ * @param injector Injector that this component has access to.
9074
9423
  */
9075
- class Sanitizer {
9424
+ let _retrieveHydrationInfoImpl = (rNode, injector) => null;
9425
+ function retrieveHydrationInfoImpl(rNode, injector) {
9426
+ const nghAttrValue = rNode.getAttribute(NGH_ATTR_NAME);
9427
+ if (nghAttrValue == null)
9428
+ return null;
9429
+ let data = {};
9430
+ // An element might have an empty `ngh` attribute value (e.g. `<comp ngh="" />`),
9431
+ // which means that no special annotations are required. Do not attempt to read
9432
+ // from the TransferState in this case.
9433
+ if (nghAttrValue !== '') {
9434
+ const transferState = injector.get(TransferState, null, { optional: true });
9435
+ if (transferState !== null) {
9436
+ const nghData = transferState.get(NGH_DATA_KEY, []);
9437
+ // The nghAttrValue is always a number referencing an index
9438
+ // in the hydration TransferState data.
9439
+ data = nghData[Number(nghAttrValue)];
9440
+ // If the `ngh` attribute exists and has a non-empty value,
9441
+ // the hydration info *must* be present in the TransferState.
9442
+ // If there is no data for some reasons, this is an error.
9443
+ ngDevMode && assertDefined(data, 'Unable to retrieve hydration info from the TransferState.');
9444
+ }
9445
+ }
9446
+ const dehydratedView = {
9447
+ data,
9448
+ firstChild: rNode.firstChild ?? null,
9449
+ };
9450
+ // The `ngh` attribute is cleared from the DOM node now
9451
+ // that the data has been retrieved.
9452
+ rNode.removeAttribute(NGH_ATTR_NAME);
9453
+ // Note: don't check whether this node was claimed for hydration,
9454
+ // because this node might've been previously claimed while processing
9455
+ // template instructions.
9456
+ ngDevMode && markRNodeAsClaimedByHydration(rNode, /* checkIfAlreadyClaimed */ false);
9457
+ ngDevMode && ngDevMode.hydratedComponents++;
9458
+ return dehydratedView;
9076
9459
  }
9077
- /** @nocollapse */
9078
- Sanitizer.ɵprov = ɵɵdefineInjectable({
9079
- token: Sanitizer,
9080
- providedIn: 'root',
9081
- factory: () => null,
9082
- });
9083
-
9084
9460
  /**
9085
- * @description Represents the version of Angular
9086
- *
9087
- * @publicApi
9461
+ * Sets the implementation for the `retrieveHydrationInfo` function.
9088
9462
  */
9089
- class Version {
9090
- constructor(full) {
9091
- this.full = full;
9092
- this.major = full.split('.')[0];
9093
- this.minor = full.split('.')[1];
9094
- this.patch = full.split('.').slice(2).join('.');
9095
- }
9463
+ function enableRetrieveHydrationInfoImpl() {
9464
+ _retrieveHydrationInfoImpl = retrieveHydrationInfoImpl;
9096
9465
  }
9097
9466
  /**
9098
- * @publicApi
9467
+ * Retrieves hydration info by reading the value from the `ngh` attribute
9468
+ * and accessing a corresponding slot in TransferState storage.
9099
9469
  */
9100
- const VERSION = new Version('16.0.0-next.2');
9101
-
9102
- // This default value is when checking the hierarchy for a token.
9103
- //
9104
- // It means both:
9105
- // - the token is not provided by the current injector,
9106
- // - only the element injectors should be checked (ie do not check module injectors
9107
- //
9108
- // mod1
9109
- // /
9110
- // el1 mod2
9111
- // \ /
9112
- // el2
9113
- //
9114
- // When requesting el2.injector.get(token), we should check in the following order and return the
9115
- // first found value:
9116
- // - el2.injector.get(token, default)
9117
- // - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module
9118
- // - mod2.injector.get(token, default)
9119
- const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
9120
-
9121
- const ERROR_ORIGINAL_ERROR = 'ngOriginalError';
9122
- function wrappedError(message, originalError) {
9123
- const msg = `${message} caused by: ${originalError instanceof Error ? originalError.message : originalError}`;
9124
- const error = Error(msg);
9125
- error[ERROR_ORIGINAL_ERROR] = originalError;
9126
- return error;
9470
+ function retrieveHydrationInfo(rNode, injector) {
9471
+ return _retrieveHydrationInfoImpl(rNode, injector);
9127
9472
  }
9128
- function getOriginalError(error) {
9129
- return error[ERROR_ORIGINAL_ERROR];
9473
+ /**
9474
+ * Retrieves an instance of a component LView from a given ViewRef.
9475
+ * Returns an instance of a component LView or `null` in case of an embedded view.
9476
+ */
9477
+ function getComponentLViewForHydration(viewRef) {
9478
+ // Reading an internal field from `ViewRef` instance.
9479
+ let lView = viewRef._lView;
9480
+ const tView = lView[TVIEW];
9481
+ // A registered ViewRef might represent an instance of an
9482
+ // embedded view, in which case we do not need to annotate it.
9483
+ if (tView.type === 2 /* TViewType.Embedded */) {
9484
+ return null;
9485
+ }
9486
+ // Check if it's a root view and if so, retrieve component's
9487
+ // LView from the first slot after the header.
9488
+ if (isRootView(lView)) {
9489
+ lView = lView[HEADER_OFFSET];
9490
+ }
9491
+ return lView;
9492
+ }
9493
+ function getTextNodeContent(node) {
9494
+ return node.textContent?.replace(/\s/gm, '');
9130
9495
  }
9131
-
9132
9496
  /**
9133
- * Provides a hook for centralized exception handling.
9497
+ * Restores text nodes and separators into the DOM that were lost during SSR
9498
+ * serialization. The hydration process replaces empty text nodes and text
9499
+ * nodes that are immediately adjacent to other text nodes with comment nodes
9500
+ * that this method filters on to restore those missing nodes that the
9501
+ * hydration process is expecting to be present.
9134
9502
  *
9135
- * The default implementation of `ErrorHandler` prints error messages to the `console`. To
9136
- * intercept error handling, write a custom exception handler that replaces this default as
9137
- * appropriate for your app.
9138
- *
9139
- * @usageNotes
9140
- * ### Example
9141
- *
9142
- * ```
9143
- * class MyErrorHandler implements ErrorHandler {
9144
- * handleError(error) {
9145
- * // do something with the exception
9146
- * }
9147
- * }
9148
- *
9149
- * @NgModule({
9150
- * providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
9151
- * })
9152
- * class MyModule {}
9153
- * ```
9154
- *
9155
- * @publicApi
9503
+ * @param node The app's root HTML Element
9156
9504
  */
9157
- class ErrorHandler {
9158
- constructor() {
9159
- /**
9160
- * @internal
9161
- */
9162
- this._console = console;
9163
- }
9164
- handleError(error) {
9165
- const originalError = this._findOriginalError(error);
9166
- this._console.error('ERROR', error);
9167
- if (originalError) {
9168
- this._console.error('ORIGINAL ERROR', originalError);
9505
+ function processTextNodeMarkersBeforeHydration(node) {
9506
+ const doc = getDocument();
9507
+ const commentNodesIterator = doc.createNodeIterator(node, NodeFilter.SHOW_COMMENT, {
9508
+ acceptNode(node) {
9509
+ const content = getTextNodeContent(node);
9510
+ const isTextNodeMarker = content === "ngetn" /* TextNodeMarker.EmptyNode */ || content === "ngtns" /* TextNodeMarker.Separator */;
9511
+ return isTextNodeMarker ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
9169
9512
  }
9170
- }
9171
- /** @internal */
9172
- _findOriginalError(error) {
9173
- let e = error && getOriginalError(error);
9174
- while (e && getOriginalError(e)) {
9175
- e = getOriginalError(e);
9513
+ });
9514
+ let currentNode;
9515
+ // We cannot modify the DOM while using the commentIterator,
9516
+ // because it throws off the iterator state.
9517
+ // So we collect all marker nodes first and then follow up with
9518
+ // applying the changes to the DOM: either inserting an empty node
9519
+ // or just removing the marker if it was used as a separator.
9520
+ const nodes = [];
9521
+ while (currentNode = commentNodesIterator.nextNode()) {
9522
+ nodes.push(currentNode);
9523
+ }
9524
+ for (const node of nodes) {
9525
+ if (node.textContent === "ngetn" /* TextNodeMarker.EmptyNode */) {
9526
+ node.replaceWith(doc.createTextNode(''));
9527
+ }
9528
+ else {
9529
+ node.remove();
9176
9530
  }
9177
- return e || null;
9178
9531
  }
9179
9532
  }
9180
-
9181
- const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
9182
9533
  /**
9183
- * Internal token that specifies whether hydration is enabled.
9534
+ * Marks a node as "claimed" by hydration process.
9535
+ * This is needed to make assessments in tests whether
9536
+ * the hydration process handled all nodes.
9184
9537
  */
9185
- const IS_HYDRATION_FEATURE_ENABLED = new InjectionToken(NG_DEV_MODE ? 'IS_HYDRATION_FEATURE_ENABLED' : '');
9186
- // By default (in client rendering mode), we remove all the contents
9187
- // of the host element and render an application after that.
9188
- const PRESERVE_HOST_CONTENT_DEFAULT = false;
9538
+ function markRNodeAsClaimedByHydration(node, checkIfAlreadyClaimed = true) {
9539
+ if (!ngDevMode) {
9540
+ throw new Error('Calling `markRNodeAsClaimedByHydration` in prod mode ' +
9541
+ 'is not supported and likely a mistake.');
9542
+ }
9543
+ if (checkIfAlreadyClaimed && isRNodeClaimedForHydration(node)) {
9544
+ throw new Error('Trying to claim a node, which was claimed already.');
9545
+ }
9546
+ node.__claimed = true;
9547
+ ngDevMode.hydratedNodes++;
9548
+ }
9549
+ function isRNodeClaimedForHydration(node) {
9550
+ return !!node.__claimed;
9551
+ }
9552
+ function setSegmentHead(hydrationInfo, index, node) {
9553
+ hydrationInfo.segmentHeads ?? (hydrationInfo.segmentHeads = {});
9554
+ hydrationInfo.segmentHeads[index] = node;
9555
+ }
9556
+ function getSegmentHead(hydrationInfo, index) {
9557
+ return hydrationInfo.segmentHeads?.[index] ?? null;
9558
+ }
9189
9559
  /**
9190
- * Internal token that indicates whether host element content should be
9191
- * retained during the bootstrap.
9560
+ * Returns the size of an <ng-container>, using either the information
9561
+ * serialized in `ELEMENT_CONTAINERS` (element container size) or by
9562
+ * computing the sum of root nodes in all dehydrated views in a given
9563
+ * container (in case this `<ng-container>` was also used as a view
9564
+ * container host node, e.g. <ng-container *ngIf>).
9192
9565
  */
9193
- const PRESERVE_HOST_CONTENT = new InjectionToken(NG_DEV_MODE ? 'PRESERVE_HOST_CONTENT' : '', {
9194
- providedIn: 'root',
9195
- factory: () => PRESERVE_HOST_CONTENT_DEFAULT,
9196
- });
9197
-
9198
- function normalizeDebugBindingName(name) {
9199
- // Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
9200
- name = camelCaseToDashCase(name.replace(/[$@]/g, '_'));
9201
- return `ng-reflect-${name}`;
9566
+ function getNgContainerSize(hydrationInfo, index) {
9567
+ const data = hydrationInfo.data;
9568
+ let size = data[ELEMENT_CONTAINERS]?.[index] ?? null;
9569
+ // If there is no serialized information available in the `ELEMENT_CONTAINERS` slot,
9570
+ // check if we have info about view containers at this location (e.g.
9571
+ // `<ng-container *ngIf>`) and use container size as a number of root nodes in this
9572
+ // element container.
9573
+ if (size === null && data[CONTAINERS]?.[index]) {
9574
+ size = calcSerializedContainerSize(hydrationInfo, index);
9575
+ }
9576
+ return size;
9202
9577
  }
9203
- const CAMEL_CASE_REGEXP = /([A-Z])/g;
9204
- function camelCaseToDashCase(input) {
9205
- return input.replace(CAMEL_CASE_REGEXP, (...m) => '-' + m[1].toLowerCase());
9578
+ function getSerializedContainerViews(hydrationInfo, index) {
9579
+ return hydrationInfo.data[CONTAINERS]?.[index] ?? null;
9206
9580
  }
9207
- function normalizeDebugBindingValue(value) {
9208
- try {
9209
- // Limit the size of the value as otherwise the DOM just gets polluted.
9210
- return value != null ? value.toString().slice(0, 30) : value;
9581
+ /**
9582
+ * Computes the size of a serialized container (the number of root nodes)
9583
+ * by calculating the sum of root nodes in all dehydrated views in this container.
9584
+ */
9585
+ function calcSerializedContainerSize(hydrationInfo, index) {
9586
+ const views = getSerializedContainerViews(hydrationInfo, index) ?? [];
9587
+ let numNodes = 0;
9588
+ for (let view of views) {
9589
+ numNodes += view[NUM_ROOT_NODES] * (view[MULTIPLIER] ?? 1);
9211
9590
  }
9212
- catch (e) {
9213
- return '[ERROR] Exception while trying to serialize the value';
9591
+ return numNodes;
9592
+ }
9593
+ /**
9594
+ * Checks whether a node is annotated as "disconnected", i.e. not present
9595
+ * in the DOM at serialization time. We should not attempt hydration for
9596
+ * such nodes and instead, use a regular "creation mode".
9597
+ */
9598
+ function isDisconnectedNode(hydrationInfo, index) {
9599
+ // Check if we are processing disconnected info for the first time.
9600
+ if (typeof hydrationInfo.disconnectedNodes === 'undefined') {
9601
+ const nodeIds = hydrationInfo.data[DISCONNECTED_NODES];
9602
+ hydrationInfo.disconnectedNodes = nodeIds ? (new Set(nodeIds)) : null;
9214
9603
  }
9604
+ return !!hydrationInfo.disconnectedNodes?.has(index);
9215
9605
  }
9216
9606
 
9217
- /** Verifies that a given type is a Standalone Component. */
9218
- function assertStandaloneComponentType(type) {
9219
- assertComponentDef(type);
9220
- const componentDef = getComponentDef$1(type);
9221
- if (!componentDef.standalone) {
9222
- throw new RuntimeError(907 /* RuntimeErrorCode.TYPE_IS_NOT_STANDALONE */, `The ${stringifyForError(type)} component is not marked as standalone, ` +
9223
- `but Angular expects to have a standalone component here. ` +
9224
- `Please make sure the ${stringifyForError(type)} component has ` +
9225
- `the \`standalone: true\` flag in the decorator.`);
9226
- }
9607
+ /**
9608
+ * Represents a component created by a `ComponentFactory`.
9609
+ * Provides access to the component instance and related objects,
9610
+ * and provides the means of destroying the instance.
9611
+ *
9612
+ * @publicApi
9613
+ */
9614
+ class ComponentRef$1 {
9227
9615
  }
9228
- /** Verifies whether a given type is a component */
9229
- function assertComponentDef(type) {
9230
- if (!getComponentDef$1(type)) {
9231
- throw new RuntimeError(906 /* RuntimeErrorCode.MISSING_GENERATED_DEF */, `The ${stringifyForError(type)} is not an Angular component, ` +
9232
- `make sure it has the \`@Component\` decorator.`);
9233
- }
9616
+ /**
9617
+ * Base class for a factory that can create a component dynamically.
9618
+ * Instantiate a factory for a given type of component with `resolveComponentFactory()`.
9619
+ * Use the resulting `ComponentFactory.create()` method to create a component of that type.
9620
+ *
9621
+ * @see [Dynamic Components](guide/dynamic-component-loader)
9622
+ *
9623
+ * @publicApi
9624
+ *
9625
+ * @deprecated Angular no longer requires Component factories. Please use other APIs where
9626
+ * Component class can be used directly.
9627
+ */
9628
+ class ComponentFactory$1 {
9234
9629
  }
9235
- /** Called when there are multiple component selectors that match a given node */
9236
- function throwMultipleComponentError(tNode, first, second) {
9237
- throw new RuntimeError(-300 /* RuntimeErrorCode.MULTIPLE_COMPONENTS_MATCH */, `Multiple components match node with tagname ${tNode.value}: ` +
9238
- `${stringifyForError(first)} and ` +
9239
- `${stringifyForError(second)}`);
9630
+
9631
+ function noComponentFactoryError(component) {
9632
+ const error = Error(`No component factory found for ${stringify(component)}. Did you add it to @NgModule.entryComponents?`);
9633
+ error[ERROR_COMPONENT] = component;
9634
+ return error;
9240
9635
  }
9241
- /** Throws an ExpressionChangedAfterChecked error if checkNoChanges mode is on. */
9242
- function throwErrorIfNoChangesMode(creationMode, oldValue, currValue, propName) {
9243
- const field = propName ? ` for '${propName}'` : '';
9244
- let msg = `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value${field}: '${oldValue}'. Current value: '${currValue}'.`;
9245
- if (creationMode) {
9246
- msg +=
9247
- ` It seems like the view has been created after its parent and its children have been dirty checked.` +
9248
- ` Has it been created in a change detection hook?`;
9249
- }
9250
- throw new RuntimeError(-100 /* RuntimeErrorCode.EXPRESSION_CHANGED_AFTER_CHECKED */, msg);
9636
+ const ERROR_COMPONENT = 'ngComponent';
9637
+ function getComponent$1(error) {
9638
+ return error[ERROR_COMPONENT];
9251
9639
  }
9252
- function constructDetailsForInterpolation(lView, rootIndex, expressionIndex, meta, changedValue) {
9253
- const [propName, prefix, ...chunks] = meta.split(INTERPOLATION_DELIMITER);
9254
- let oldValue = prefix, newValue = prefix;
9255
- for (let i = 0; i < chunks.length; i++) {
9256
- const slotIdx = rootIndex + i;
9257
- oldValue += `${lView[slotIdx]}${chunks[i]}`;
9258
- newValue += `${slotIdx === expressionIndex ? changedValue : lView[slotIdx]}${chunks[i]}`;
9640
+ class _NullComponentFactoryResolver {
9641
+ resolveComponentFactory(component) {
9642
+ throw noComponentFactoryError(component);
9259
9643
  }
9260
- return { propName, oldValue, newValue };
9261
9644
  }
9262
9645
  /**
9263
- * Constructs an object that contains details for the ExpressionChangedAfterItHasBeenCheckedError:
9264
- * - property name (for property bindings or interpolations)
9265
- * - old and new values, enriched using information from metadata
9646
+ * A simple registry that maps `Components` to generated `ComponentFactory` classes
9647
+ * that can be used to create instances of components.
9648
+ * Use to obtain the factory for a given component type,
9649
+ * then use the factory's `create()` method to create a component of that type.
9266
9650
  *
9267
- * More information on the metadata storage format can be found in `storePropertyBindingMetadata`
9268
- * function description.
9651
+ * Note: since v13, dynamic component creation via
9652
+ * [`ViewContainerRef.createComponent`](api/core/ViewContainerRef#createComponent)
9653
+ * does **not** require resolving component factory: component class can be used directly.
9654
+ *
9655
+ * @publicApi
9656
+ *
9657
+ * @deprecated Angular no longer requires Component factories. Please use other APIs where
9658
+ * Component class can be used directly.
9269
9659
  */
9270
- function getExpressionChangedErrorDetails(lView, bindingIndex, oldValue, newValue) {
9271
- const tData = lView[TVIEW].data;
9272
- const metadata = tData[bindingIndex];
9273
- if (typeof metadata === 'string') {
9274
- // metadata for property interpolation
9275
- if (metadata.indexOf(INTERPOLATION_DELIMITER) > -1) {
9276
- return constructDetailsForInterpolation(lView, bindingIndex, bindingIndex, metadata, newValue);
9277
- }
9278
- // metadata for property binding
9279
- return { propName: metadata, oldValue, newValue };
9280
- }
9281
- // metadata is not available for this expression, check if this expression is a part of the
9282
- // property interpolation by going from the current binding index left and look for a string that
9283
- // contains INTERPOLATION_DELIMITER, the layout in tView.data for this case will look like this:
9284
- // [..., 'id�Prefix � and � suffix', null, null, null, ...]
9285
- if (metadata === null) {
9286
- let idx = bindingIndex - 1;
9287
- while (typeof tData[idx] !== 'string' && tData[idx + 1] === null) {
9288
- idx--;
9289
- }
9290
- const meta = tData[idx];
9291
- if (typeof meta === 'string') {
9292
- const matches = meta.match(new RegExp(INTERPOLATION_DELIMITER, 'g'));
9293
- // first interpolation delimiter separates property name from interpolation parts (in case of
9294
- // property interpolations), so we subtract one from total number of found delimiters
9295
- if (matches && (matches.length - 1) > bindingIndex - idx) {
9296
- return constructDetailsForInterpolation(lView, idx, bindingIndex, meta, newValue);
9297
- }
9298
- }
9299
- }
9300
- return { propName: undefined, oldValue, newValue };
9660
+ class ComponentFactoryResolver$1 {
9301
9661
  }
9662
+ ComponentFactoryResolver$1.NULL = ( /* @__PURE__ */new _NullComponentFactoryResolver());
9302
9663
 
9303
9664
  /**
9304
- * Returns an index of `classToSearch` in `className` taking token boundaries into account.
9665
+ * Creates an ElementRef from the most recent node.
9305
9666
  *
9306
- * `classIndexOf('AB A', 'A', 0)` will be 3 (not 0 since `AB!==A`)
9667
+ * @returns The ElementRef instance to use
9668
+ */
9669
+ function injectElementRef() {
9670
+ return createElementRef(getCurrentTNode(), getLView());
9671
+ }
9672
+ /**
9673
+ * Creates an ElementRef given a node.
9307
9674
  *
9308
- * @param className A string containing classes (whitespace separated)
9309
- * @param classToSearch A class name to locate
9310
- * @param startingIndex Starting location of search
9311
- * @returns an index of the located class (or -1 if not found)
9675
+ * @param tNode The node for which you'd like an ElementRef
9676
+ * @param lView The view to which the node belongs
9677
+ * @returns The ElementRef instance to use
9312
9678
  */
9313
- function classIndexOf(className, classToSearch, startingIndex) {
9314
- ngDevMode && assertNotEqual(classToSearch, '', 'can not look for "" string.');
9315
- let end = className.length;
9316
- while (true) {
9317
- const foundIndex = className.indexOf(classToSearch, startingIndex);
9318
- if (foundIndex === -1)
9319
- return foundIndex;
9320
- if (foundIndex === 0 || className.charCodeAt(foundIndex - 1) <= 32 /* CharCode.SPACE */) {
9321
- // Ensure that it has leading whitespace
9322
- const length = classToSearch.length;
9323
- if (foundIndex + length === end ||
9324
- className.charCodeAt(foundIndex + length) <= 32 /* CharCode.SPACE */) {
9325
- // Ensure that it has trailing whitespace
9326
- return foundIndex;
9327
- }
9328
- }
9329
- // False positive, keep searching from where we left off.
9330
- startingIndex = foundIndex + 1;
9679
+ function createElementRef(tNode, lView) {
9680
+ return new ElementRef(getNativeByTNode(tNode, lView));
9681
+ }
9682
+ /**
9683
+ * A wrapper around a native element inside of a View.
9684
+ *
9685
+ * An `ElementRef` is backed by a render-specific element. In the browser, this is usually a DOM
9686
+ * element.
9687
+ *
9688
+ * @security Permitting direct access to the DOM can make your application more vulnerable to
9689
+ * XSS attacks. Carefully review any use of `ElementRef` in your code. For more detail, see the
9690
+ * [Security Guide](https://g.co/ng/security).
9691
+ *
9692
+ * @publicApi
9693
+ */
9694
+ // Note: We don't expose things like `Injector`, `ViewContainer`, ... here,
9695
+ // i.e. users have to ask for what they need. With that, we can build better analysis tools
9696
+ // and could do better codegen in the future.
9697
+ class ElementRef {
9698
+ constructor(nativeElement) {
9699
+ this.nativeElement = nativeElement;
9331
9700
  }
9332
9701
  }
9702
+ /**
9703
+ * @internal
9704
+ * @nocollapse
9705
+ */
9706
+ ElementRef.__NG_ELEMENT_ID__ = injectElementRef;
9707
+ /**
9708
+ * Unwraps `ElementRef` and return the `nativeElement`.
9709
+ *
9710
+ * @param value value to unwrap
9711
+ * @returns `nativeElement` if `ElementRef` otherwise returns value as is.
9712
+ */
9713
+ function unwrapElementRef(value) {
9714
+ return value instanceof ElementRef ? value.nativeElement : value;
9715
+ }
9333
9716
 
9334
- const NG_TEMPLATE_SELECTOR = 'ng-template';
9335
9717
  /**
9336
- * Search the `TAttributes` to see if it contains `cssClassToMatch` (case insensitive)
9718
+ * Creates and initializes a custom renderer that implements the `Renderer2` base class.
9337
9719
  *
9338
- * @param attrs `TAttributes` to search through.
9339
- * @param cssClassToMatch class to match (lowercase)
9340
- * @param isProjectionMode Whether or not class matching should look into the attribute `class` in
9341
- * addition to the `AttributeMarker.Classes`.
9720
+ * @publicApi
9342
9721
  */
9343
- function isCssClassMatching(attrs, cssClassToMatch, isProjectionMode) {
9344
- // TODO(misko): The fact that this function needs to know about `isProjectionMode` seems suspect.
9345
- // It is strange to me that sometimes the class information comes in form of `class` attribute
9346
- // and sometimes in form of `AttributeMarker.Classes`. Some investigation is needed to determine
9347
- // if that is the right behavior.
9348
- ngDevMode &&
9349
- assertEqual(cssClassToMatch, cssClassToMatch.toLowerCase(), 'Class name expected to be lowercase.');
9350
- let i = 0;
9351
- while (i < attrs.length) {
9352
- let item = attrs[i++];
9353
- if (isProjectionMode && item === 'class') {
9354
- item = attrs[i];
9355
- if (classIndexOf(item.toLowerCase(), cssClassToMatch, 0) !== -1) {
9356
- return true;
9357
- }
9358
- }
9359
- else if (item === 1 /* AttributeMarker.Classes */) {
9360
- // We found the classes section. Start searching for the class.
9361
- while (i < attrs.length && typeof (item = attrs[i++]) == 'string') {
9362
- // while we have strings
9363
- if (item.toLowerCase() === cssClassToMatch)
9364
- return true;
9365
- }
9366
- return false;
9367
- }
9368
- }
9369
- return false;
9722
+ class RendererFactory2 {
9370
9723
  }
9371
9724
  /**
9372
- * Checks whether the `tNode` represents an inline template (e.g. `*ngFor`).
9725
+ * Extend this base class to implement custom rendering. By default, Angular
9726
+ * renders a template into DOM. You can use custom rendering to intercept
9727
+ * rendering calls, or to render to something other than DOM.
9373
9728
  *
9374
- * @param tNode current TNode
9729
+ * Create your custom renderer using `RendererFactory2`.
9730
+ *
9731
+ * Use a custom renderer to bypass Angular's templating and
9732
+ * make custom UI changes that can't be expressed declaratively.
9733
+ * For example if you need to set a property or an attribute whose name is
9734
+ * not statically known, use the `setProperty()` or
9735
+ * `setAttribute()` method.
9736
+ *
9737
+ * @publicApi
9375
9738
  */
9376
- function isInlineTemplate(tNode) {
9377
- return tNode.type === 4 /* TNodeType.Container */ && tNode.value !== NG_TEMPLATE_SELECTOR;
9739
+ class Renderer2 {
9378
9740
  }
9379
9741
  /**
9380
- * Function that checks whether a given tNode matches tag-based selector and has a valid type.
9742
+ * @internal
9743
+ * @nocollapse
9744
+ */
9745
+ Renderer2.__NG_ELEMENT_ID__ = () => injectRenderer2();
9746
+ /** Injects a Renderer2 for the current component. */
9747
+ function injectRenderer2() {
9748
+ // We need the Renderer to be based on the component that it's being injected into, however since
9749
+ // DI happens before we've entered its view, `getLView` will return the parent view instead.
9750
+ const lView = getLView();
9751
+ const tNode = getCurrentTNode();
9752
+ const nodeAtIndex = getComponentLViewByIndex(tNode.index, lView);
9753
+ return (isLView(nodeAtIndex) ? nodeAtIndex : lView)[RENDERER];
9754
+ }
9755
+
9756
+ /**
9757
+ * Sanitizer is used by the views to sanitize potentially dangerous values.
9381
9758
  *
9382
- * Matching can be performed in 2 modes: projection mode (when we project nodes) and regular
9383
- * directive matching mode:
9384
- * - in the "directive matching" mode we do _not_ take TContainer's tagName into account if it is
9385
- * different from NG_TEMPLATE_SELECTOR (value different from NG_TEMPLATE_SELECTOR indicates that a
9386
- * tag name was extracted from * syntax so we would match the same directive twice);
9387
- * - in the "projection" mode, we use a tag name potentially extracted from the * syntax processing
9388
- * (applicable to TNodeType.Container only).
9759
+ * @publicApi
9389
9760
  */
9390
- function hasTagAndTypeMatch(tNode, currentSelector, isProjectionMode) {
9391
- const tagNameToCompare = tNode.type === 4 /* TNodeType.Container */ && !isProjectionMode ? NG_TEMPLATE_SELECTOR : tNode.value;
9392
- return currentSelector === tagNameToCompare;
9761
+ class Sanitizer {
9393
9762
  }
9763
+ /** @nocollapse */
9764
+ Sanitizer.ɵprov = ɵɵdefineInjectable({
9765
+ token: Sanitizer,
9766
+ providedIn: 'root',
9767
+ factory: () => null,
9768
+ });
9769
+
9394
9770
  /**
9395
- * A utility function to match an Ivy node static data against a simple CSS selector
9771
+ * @description Represents the version of Angular
9396
9772
  *
9397
- * @param node static data of the node to match
9398
- * @param selector The selector to try matching against the node.
9399
- * @param isProjectionMode if `true` we are matching for content projection, otherwise we are doing
9400
- * directive matching.
9401
- * @returns true if node matches the selector.
9773
+ * @publicApi
9402
9774
  */
9403
- function isNodeMatchingSelector(tNode, selector, isProjectionMode) {
9404
- ngDevMode && assertDefined(selector[0], 'Selector should have a tag name');
9405
- let mode = 4 /* SelectorFlags.ELEMENT */;
9406
- const nodeAttrs = tNode.attrs || [];
9407
- // Find the index of first attribute that has no value, only a name.
9408
- const nameOnlyMarkerIdx = getNameOnlyMarkerIndex(nodeAttrs);
9409
- // When processing ":not" selectors, we skip to the next ":not" if the
9410
- // current one doesn't match
9411
- let skipToNextSelector = false;
9412
- for (let i = 0; i < selector.length; i++) {
9413
- const current = selector[i];
9414
- if (typeof current === 'number') {
9415
- // If we finish processing a :not selector and it hasn't failed, return false
9416
- if (!skipToNextSelector && !isPositive(mode) && !isPositive(current)) {
9417
- return false;
9418
- }
9419
- // If we are skipping to the next :not() and this mode flag is positive,
9420
- // it's a part of the current :not() selector, and we should keep skipping
9421
- if (skipToNextSelector && isPositive(current))
9422
- continue;
9423
- skipToNextSelector = false;
9424
- mode = current | (mode & 1 /* SelectorFlags.NOT */);
9425
- continue;
9426
- }
9427
- if (skipToNextSelector)
9428
- continue;
9429
- if (mode & 4 /* SelectorFlags.ELEMENT */) {
9430
- mode = 2 /* SelectorFlags.ATTRIBUTE */ | mode & 1 /* SelectorFlags.NOT */;
9431
- if (current !== '' && !hasTagAndTypeMatch(tNode, current, isProjectionMode) ||
9432
- current === '' && selector.length === 1) {
9433
- if (isPositive(mode))
9434
- return false;
9435
- skipToNextSelector = true;
9436
- }
9437
- }
9438
- else {
9439
- const selectorAttrValue = mode & 8 /* SelectorFlags.CLASS */ ? current : selector[++i];
9440
- // special case for matching against classes when a tNode has been instantiated with
9441
- // class and style values as separate attribute values (e.g. ['title', CLASS, 'foo'])
9442
- if ((mode & 8 /* SelectorFlags.CLASS */) && tNode.attrs !== null) {
9443
- if (!isCssClassMatching(tNode.attrs, selectorAttrValue, isProjectionMode)) {
9444
- if (isPositive(mode))
9445
- return false;
9446
- skipToNextSelector = true;
9447
- }
9448
- continue;
9449
- }
9450
- const attrName = (mode & 8 /* SelectorFlags.CLASS */) ? 'class' : current;
9451
- const attrIndexInNode = findAttrIndexInNode(attrName, nodeAttrs, isInlineTemplate(tNode), isProjectionMode);
9452
- if (attrIndexInNode === -1) {
9453
- if (isPositive(mode))
9454
- return false;
9455
- skipToNextSelector = true;
9456
- continue;
9457
- }
9458
- if (selectorAttrValue !== '') {
9459
- let nodeAttrValue;
9460
- if (attrIndexInNode > nameOnlyMarkerIdx) {
9461
- nodeAttrValue = '';
9462
- }
9463
- else {
9464
- ngDevMode &&
9465
- assertNotEqual(nodeAttrs[attrIndexInNode], 0 /* AttributeMarker.NamespaceURI */, 'We do not match directives on namespaced attributes');
9466
- // we lowercase the attribute value to be able to match
9467
- // selectors without case-sensitivity
9468
- // (selectors are already in lowercase when generated)
9469
- nodeAttrValue = nodeAttrs[attrIndexInNode + 1].toLowerCase();
9470
- }
9471
- const compareAgainstClassName = mode & 8 /* SelectorFlags.CLASS */ ? nodeAttrValue : null;
9472
- if (compareAgainstClassName &&
9473
- classIndexOf(compareAgainstClassName, selectorAttrValue, 0) !== -1 ||
9474
- mode & 2 /* SelectorFlags.ATTRIBUTE */ && selectorAttrValue !== nodeAttrValue) {
9475
- if (isPositive(mode))
9476
- return false;
9477
- skipToNextSelector = true;
9478
- }
9479
- }
9480
- }
9775
+ class Version {
9776
+ constructor(full) {
9777
+ this.full = full;
9778
+ this.major = full.split('.')[0];
9779
+ this.minor = full.split('.')[1];
9780
+ this.patch = full.split('.').slice(2).join('.');
9481
9781
  }
9482
- return isPositive(mode) || skipToNextSelector;
9483
9782
  }
9484
- function isPositive(mode) {
9485
- return (mode & 1 /* SelectorFlags.NOT */) === 0;
9783
+ /**
9784
+ * @publicApi
9785
+ */
9786
+ const VERSION = new Version('16.0.0-next.4');
9787
+
9788
+ // This default value is when checking the hierarchy for a token.
9789
+ //
9790
+ // It means both:
9791
+ // - the token is not provided by the current injector,
9792
+ // - only the element injectors should be checked (ie do not check module injectors
9793
+ //
9794
+ // mod1
9795
+ // /
9796
+ // el1 mod2
9797
+ // \ /
9798
+ // el2
9799
+ //
9800
+ // When requesting el2.injector.get(token), we should check in the following order and return the
9801
+ // first found value:
9802
+ // - el2.injector.get(token, default)
9803
+ // - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module
9804
+ // - mod2.injector.get(token, default)
9805
+ const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
9806
+
9807
+ const ERROR_ORIGINAL_ERROR = 'ngOriginalError';
9808
+ function wrappedError(message, originalError) {
9809
+ const msg = `${message} caused by: ${originalError instanceof Error ? originalError.message : originalError}`;
9810
+ const error = Error(msg);
9811
+ error[ERROR_ORIGINAL_ERROR] = originalError;
9812
+ return error;
9813
+ }
9814
+ function getOriginalError(error) {
9815
+ return error[ERROR_ORIGINAL_ERROR];
9486
9816
  }
9817
+
9487
9818
  /**
9488
- * Examines the attribute's definition array for a node to find the index of the
9489
- * attribute that matches the given `name`.
9490
- *
9491
- * NOTE: This will not match namespaced attributes.
9819
+ * Provides a hook for centralized exception handling.
9492
9820
  *
9493
- * Attribute matching depends upon `isInlineTemplate` and `isProjectionMode`.
9494
- * The following table summarizes which types of attributes we attempt to match:
9821
+ * The default implementation of `ErrorHandler` prints error messages to the `console`. To
9822
+ * intercept error handling, write a custom exception handler that replaces this default as
9823
+ * appropriate for your app.
9495
9824
  *
9496
- * ===========================================================================================================
9497
- * Modes | Normal Attributes | Bindings Attributes | Template Attributes | I18n
9498
- * Attributes
9499
- * ===========================================================================================================
9500
- * Inline + Projection | YES | YES | NO | YES
9501
- * -----------------------------------------------------------------------------------------------------------
9502
- * Inline + Directive | NO | NO | YES | NO
9503
- * -----------------------------------------------------------------------------------------------------------
9504
- * Non-inline + Projection | YES | YES | NO | YES
9505
- * -----------------------------------------------------------------------------------------------------------
9506
- * Non-inline + Directive | YES | YES | NO | YES
9507
- * ===========================================================================================================
9825
+ * @usageNotes
9826
+ * ### Example
9508
9827
  *
9509
- * @param name the name of the attribute to find
9510
- * @param attrs the attribute array to examine
9511
- * @param isInlineTemplate true if the node being matched is an inline template (e.g. `*ngFor`)
9512
- * rather than a manually expanded template node (e.g `<ng-template>`).
9513
- * @param isProjectionMode true if we are matching against content projection otherwise we are
9514
- * matching against directives.
9515
- */
9516
- function findAttrIndexInNode(name, attrs, isInlineTemplate, isProjectionMode) {
9517
- if (attrs === null)
9518
- return -1;
9519
- let i = 0;
9520
- if (isProjectionMode || !isInlineTemplate) {
9521
- let bindingsMode = false;
9522
- while (i < attrs.length) {
9523
- const maybeAttrName = attrs[i];
9524
- if (maybeAttrName === name) {
9525
- return i;
9526
- }
9527
- else if (maybeAttrName === 3 /* AttributeMarker.Bindings */ || maybeAttrName === 6 /* AttributeMarker.I18n */) {
9528
- bindingsMode = true;
9529
- }
9530
- else if (maybeAttrName === 1 /* AttributeMarker.Classes */ || maybeAttrName === 2 /* AttributeMarker.Styles */) {
9531
- let value = attrs[++i];
9532
- // We should skip classes here because we have a separate mechanism for
9533
- // matching classes in projection mode.
9534
- while (typeof value === 'string') {
9535
- value = attrs[++i];
9536
- }
9537
- continue;
9538
- }
9539
- else if (maybeAttrName === 4 /* AttributeMarker.Template */) {
9540
- // We do not care about Template attributes in this scenario.
9541
- break;
9542
- }
9543
- else if (maybeAttrName === 0 /* AttributeMarker.NamespaceURI */) {
9544
- // Skip the whole namespaced attribute and value. This is by design.
9545
- i += 4;
9546
- continue;
9547
- }
9548
- // In binding mode there are only names, rather than name-value pairs.
9549
- i += bindingsMode ? 1 : 2;
9550
- }
9551
- // We did not match the attribute
9552
- return -1;
9553
- }
9554
- else {
9555
- return matchTemplateAttribute(attrs, name);
9828
+ * ```
9829
+ * class MyErrorHandler implements ErrorHandler {
9830
+ * handleError(error) {
9831
+ * // do something with the exception
9832
+ * }
9833
+ * }
9834
+ *
9835
+ * @NgModule({
9836
+ * providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
9837
+ * })
9838
+ * class MyModule {}
9839
+ * ```
9840
+ *
9841
+ * @publicApi
9842
+ */
9843
+ class ErrorHandler {
9844
+ constructor() {
9845
+ /**
9846
+ * @internal
9847
+ */
9848
+ this._console = console;
9556
9849
  }
9557
- }
9558
- function isNodeMatchingSelectorList(tNode, selector, isProjectionMode = false) {
9559
- for (let i = 0; i < selector.length; i++) {
9560
- if (isNodeMatchingSelector(tNode, selector[i], isProjectionMode)) {
9561
- return true;
9850
+ handleError(error) {
9851
+ const originalError = this._findOriginalError(error);
9852
+ this._console.error('ERROR', error);
9853
+ if (originalError) {
9854
+ this._console.error('ORIGINAL ERROR', originalError);
9562
9855
  }
9563
9856
  }
9564
- return false;
9565
- }
9566
- function getProjectAsAttrValue(tNode) {
9567
- const nodeAttrs = tNode.attrs;
9568
- if (nodeAttrs != null) {
9569
- const ngProjectAsAttrIdx = nodeAttrs.indexOf(5 /* AttributeMarker.ProjectAs */);
9570
- // only check for ngProjectAs in attribute names, don't accidentally match attribute's value
9571
- // (attribute names are stored at even indexes)
9572
- if ((ngProjectAsAttrIdx & 1) === 0) {
9573
- return nodeAttrs[ngProjectAsAttrIdx + 1];
9857
+ /** @internal */
9858
+ _findOriginalError(error) {
9859
+ let e = error && getOriginalError(error);
9860
+ while (e && getOriginalError(e)) {
9861
+ e = getOriginalError(e);
9574
9862
  }
9863
+ return e || null;
9575
9864
  }
9576
- return null;
9577
9865
  }
9578
- function getNameOnlyMarkerIndex(nodeAttrs) {
9579
- for (let i = 0; i < nodeAttrs.length; i++) {
9580
- const nodeAttr = nodeAttrs[i];
9581
- if (isNameOnlyAttributeMarker(nodeAttr)) {
9582
- return i;
9583
- }
9866
+
9867
+ const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
9868
+ /**
9869
+ * Internal token that specifies whether hydration is enabled.
9870
+ */
9871
+ const IS_HYDRATION_FEATURE_ENABLED = new InjectionToken(NG_DEV_MODE ? 'IS_HYDRATION_FEATURE_ENABLED' : '');
9872
+ // By default (in client rendering mode), we remove all the contents
9873
+ // of the host element and render an application after that.
9874
+ const PRESERVE_HOST_CONTENT_DEFAULT = false;
9875
+ /**
9876
+ * Internal token that indicates whether host element content should be
9877
+ * retained during the bootstrap.
9878
+ */
9879
+ const PRESERVE_HOST_CONTENT = new InjectionToken(NG_DEV_MODE ? 'PRESERVE_HOST_CONTENT' : '', {
9880
+ providedIn: 'root',
9881
+ factory: () => PRESERVE_HOST_CONTENT_DEFAULT,
9882
+ });
9883
+
9884
+ function normalizeDebugBindingName(name) {
9885
+ // Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
9886
+ name = camelCaseToDashCase(name.replace(/[$@]/g, '_'));
9887
+ return `ng-reflect-${name}`;
9888
+ }
9889
+ const CAMEL_CASE_REGEXP = /([A-Z])/g;
9890
+ function camelCaseToDashCase(input) {
9891
+ return input.replace(CAMEL_CASE_REGEXP, (...m) => '-' + m[1].toLowerCase());
9892
+ }
9893
+ function normalizeDebugBindingValue(value) {
9894
+ try {
9895
+ // Limit the size of the value as otherwise the DOM just gets polluted.
9896
+ return value != null ? value.toString().slice(0, 30) : value;
9897
+ }
9898
+ catch (e) {
9899
+ return '[ERROR] Exception while trying to serialize the value';
9584
9900
  }
9585
- return nodeAttrs.length;
9586
9901
  }
9587
- function matchTemplateAttribute(attrs, name) {
9588
- let i = attrs.indexOf(4 /* AttributeMarker.Template */);
9589
- if (i > -1) {
9590
- i++;
9591
- while (i < attrs.length) {
9592
- const attr = attrs[i];
9593
- // Return in case we checked all template attrs and are switching to the next section in the
9594
- // attrs array (that starts with a number that represents an attribute marker).
9595
- if (typeof attr === 'number')
9596
- return -1;
9597
- if (attr === name)
9598
- return i;
9599
- i++;
9600
- }
9902
+
9903
+ /** Verifies that a given type is a Standalone Component. */
9904
+ function assertStandaloneComponentType(type) {
9905
+ assertComponentDef(type);
9906
+ const componentDef = getComponentDef$1(type);
9907
+ if (!componentDef.standalone) {
9908
+ throw new RuntimeError(907 /* RuntimeErrorCode.TYPE_IS_NOT_STANDALONE */, `The ${stringifyForError(type)} component is not marked as standalone, ` +
9909
+ `but Angular expects to have a standalone component here. ` +
9910
+ `Please make sure the ${stringifyForError(type)} component has ` +
9911
+ `the \`standalone: true\` flag in the decorator.`);
9601
9912
  }
9602
- return -1;
9603
9913
  }
9604
- /**
9605
- * Checks whether a selector is inside a CssSelectorList
9606
- * @param selector Selector to be checked.
9607
- * @param list List in which to look for the selector.
9608
- */
9609
- function isSelectorInSelectorList(selector, list) {
9610
- selectorListLoop: for (let i = 0; i < list.length; i++) {
9611
- const currentSelectorInList = list[i];
9612
- if (selector.length !== currentSelectorInList.length) {
9613
- continue;
9614
- }
9615
- for (let j = 0; j < selector.length; j++) {
9616
- if (selector[j] !== currentSelectorInList[j]) {
9617
- continue selectorListLoop;
9618
- }
9619
- }
9620
- return true;
9914
+ /** Verifies whether a given type is a component */
9915
+ function assertComponentDef(type) {
9916
+ if (!getComponentDef$1(type)) {
9917
+ throw new RuntimeError(906 /* RuntimeErrorCode.MISSING_GENERATED_DEF */, `The ${stringifyForError(type)} is not an Angular component, ` +
9918
+ `make sure it has the \`@Component\` decorator.`);
9621
9919
  }
9622
- return false;
9623
9920
  }
9624
- function maybeWrapInNotSelector(isNegativeMode, chunk) {
9625
- return isNegativeMode ? ':not(' + chunk.trim() + ')' : chunk;
9921
+ /** Called when there are multiple component selectors that match a given node */
9922
+ function throwMultipleComponentError(tNode, first, second) {
9923
+ throw new RuntimeError(-300 /* RuntimeErrorCode.MULTIPLE_COMPONENTS_MATCH */, `Multiple components match node with tagname ${tNode.value}: ` +
9924
+ `${stringifyForError(first)} and ` +
9925
+ `${stringifyForError(second)}`);
9626
9926
  }
9627
- function stringifyCSSSelector(selector) {
9628
- let result = selector[0];
9629
- let i = 1;
9630
- let mode = 2 /* SelectorFlags.ATTRIBUTE */;
9631
- let currentChunk = '';
9632
- let isNegativeMode = false;
9633
- while (i < selector.length) {
9634
- let valueOrMarker = selector[i];
9635
- if (typeof valueOrMarker === 'string') {
9636
- if (mode & 2 /* SelectorFlags.ATTRIBUTE */) {
9637
- const attrValue = selector[++i];
9638
- currentChunk +=
9639
- '[' + valueOrMarker + (attrValue.length > 0 ? '="' + attrValue + '"' : '') + ']';
9640
- }
9641
- else if (mode & 8 /* SelectorFlags.CLASS */) {
9642
- currentChunk += '.' + valueOrMarker;
9643
- }
9644
- else if (mode & 4 /* SelectorFlags.ELEMENT */) {
9645
- currentChunk += ' ' + valueOrMarker;
9646
- }
9647
- }
9648
- else {
9649
- //
9650
- // Append current chunk to the final result in case we come across SelectorFlag, which
9651
- // indicates that the previous section of a selector is over. We need to accumulate content
9652
- // between flags to make sure we wrap the chunk later in :not() selector if needed, e.g.
9653
- // ```
9654
- // ['', Flags.CLASS, '.classA', Flags.CLASS | Flags.NOT, '.classB', '.classC']
9655
- // ```
9656
- // should be transformed to `.classA :not(.classB .classC)`.
9657
- //
9658
- // Note: for negative selector part, we accumulate content between flags until we find the
9659
- // next negative flag. This is needed to support a case where `:not()` rule contains more than
9660
- // one chunk, e.g. the following selector:
9661
- // ```
9662
- // ['', Flags.ELEMENT | Flags.NOT, 'p', Flags.CLASS, 'foo', Flags.CLASS | Flags.NOT, 'bar']
9663
- // ```
9664
- // should be stringified to `:not(p.foo) :not(.bar)`
9665
- //
9666
- if (currentChunk !== '' && !isPositive(valueOrMarker)) {
9667
- result += maybeWrapInNotSelector(isNegativeMode, currentChunk);
9668
- currentChunk = '';
9669
- }
9670
- mode = valueOrMarker;
9671
- // According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative
9672
- // mode is maintained for remaining chunks of a selector.
9673
- isNegativeMode = isNegativeMode || !isPositive(mode);
9674
- }
9675
- i++;
9676
- }
9677
- if (currentChunk !== '') {
9678
- result += maybeWrapInNotSelector(isNegativeMode, currentChunk);
9927
+ /** Throws an ExpressionChangedAfterChecked error if checkNoChanges mode is on. */
9928
+ function throwErrorIfNoChangesMode(creationMode, oldValue, currValue, propName) {
9929
+ const field = propName ? ` for '${propName}'` : '';
9930
+ let msg = `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value${field}: '${oldValue}'. Current value: '${currValue}'.`;
9931
+ if (creationMode) {
9932
+ msg +=
9933
+ ` It seems like the view has been created after its parent and its children have been dirty checked.` +
9934
+ ` Has it been created in a change detection hook?`;
9679
9935
  }
9680
- return result;
9936
+ throw new RuntimeError(-100 /* RuntimeErrorCode.EXPRESSION_CHANGED_AFTER_CHECKED */, msg);
9681
9937
  }
9682
- /**
9683
- * Generates string representation of CSS selector in parsed form.
9684
- *
9685
- * ComponentDef and DirectiveDef are generated with the selector in parsed form to avoid doing
9686
- * additional parsing at runtime (for example, for directive matching). However in some cases (for
9687
- * example, while bootstrapping a component), a string version of the selector is required to query
9688
- * for the host element on the page. This function takes the parsed form of a selector and returns
9689
- * its string representation.
9690
- *
9691
- * @param selectorList selector in parsed form
9692
- * @returns string representation of a given selector
9693
- */
9694
- function stringifyCSSSelectorList(selectorList) {
9695
- return selectorList.map(stringifyCSSSelector).join(',');
9938
+ function constructDetailsForInterpolation(lView, rootIndex, expressionIndex, meta, changedValue) {
9939
+ const [propName, prefix, ...chunks] = meta.split(INTERPOLATION_DELIMITER);
9940
+ let oldValue = prefix, newValue = prefix;
9941
+ for (let i = 0; i < chunks.length; i++) {
9942
+ const slotIdx = rootIndex + i;
9943
+ oldValue += `${lView[slotIdx]}${chunks[i]}`;
9944
+ newValue += `${slotIdx === expressionIndex ? changedValue : lView[slotIdx]}${chunks[i]}`;
9945
+ }
9946
+ return { propName, oldValue, newValue };
9696
9947
  }
9697
9948
  /**
9698
- * Extracts attributes and classes information from a given CSS selector.
9699
- *
9700
- * This function is used while creating a component dynamically. In this case, the host element
9701
- * (that is created dynamically) should contain attributes and classes specified in component's CSS
9702
- * selector.
9949
+ * Constructs an object that contains details for the ExpressionChangedAfterItHasBeenCheckedError:
9950
+ * - property name (for property bindings or interpolations)
9951
+ * - old and new values, enriched using information from metadata
9703
9952
  *
9704
- * @param selector CSS selector in parsed form (in a form of array)
9705
- * @returns object with `attrs` and `classes` fields that contain extracted information
9953
+ * More information on the metadata storage format can be found in `storePropertyBindingMetadata`
9954
+ * function description.
9706
9955
  */
9707
- function extractAttrsAndClassesFromSelector(selector) {
9708
- const attrs = [];
9709
- const classes = [];
9710
- let i = 1;
9711
- let mode = 2 /* SelectorFlags.ATTRIBUTE */;
9712
- while (i < selector.length) {
9713
- let valueOrMarker = selector[i];
9714
- if (typeof valueOrMarker === 'string') {
9715
- if (mode === 2 /* SelectorFlags.ATTRIBUTE */) {
9716
- if (valueOrMarker !== '') {
9717
- attrs.push(valueOrMarker, selector[++i]);
9718
- }
9719
- }
9720
- else if (mode === 8 /* SelectorFlags.CLASS */) {
9721
- classes.push(valueOrMarker);
9722
- }
9956
+ function getExpressionChangedErrorDetails(lView, bindingIndex, oldValue, newValue) {
9957
+ const tData = lView[TVIEW].data;
9958
+ const metadata = tData[bindingIndex];
9959
+ if (typeof metadata === 'string') {
9960
+ // metadata for property interpolation
9961
+ if (metadata.indexOf(INTERPOLATION_DELIMITER) > -1) {
9962
+ return constructDetailsForInterpolation(lView, bindingIndex, bindingIndex, metadata, newValue);
9723
9963
  }
9724
- else {
9725
- // According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative
9726
- // mode is maintained for remaining chunks of a selector. Since attributes and classes are
9727
- // extracted only for "positive" part of the selector, we can stop here.
9728
- if (!isPositive(mode))
9729
- break;
9730
- mode = valueOrMarker;
9964
+ // metadata for property binding
9965
+ return { propName: metadata, oldValue, newValue };
9966
+ }
9967
+ // metadata is not available for this expression, check if this expression is a part of the
9968
+ // property interpolation by going from the current binding index left and look for a string that
9969
+ // contains INTERPOLATION_DELIMITER, the layout in tView.data for this case will look like this:
9970
+ // [..., 'id�Prefix � and � suffix', null, null, null, ...]
9971
+ if (metadata === null) {
9972
+ let idx = bindingIndex - 1;
9973
+ while (typeof tData[idx] !== 'string' && tData[idx + 1] === null) {
9974
+ idx--;
9975
+ }
9976
+ const meta = tData[idx];
9977
+ if (typeof meta === 'string') {
9978
+ const matches = meta.match(new RegExp(INTERPOLATION_DELIMITER, 'g'));
9979
+ // first interpolation delimiter separates property name from interpolation parts (in case of
9980
+ // property interpolations), so we subtract one from total number of found delimiters
9981
+ if (matches && (matches.length - 1) > bindingIndex - idx) {
9982
+ return constructDetailsForInterpolation(lView, idx, bindingIndex, meta, newValue);
9983
+ }
9731
9984
  }
9732
- i++;
9733
9985
  }
9734
- return { attrs, classes };
9986
+ return { propName: undefined, oldValue, newValue };
9735
9987
  }
9736
9988
 
9737
9989
  /** A special value which designates that a value has not changed. */
@@ -9790,6 +10042,33 @@ function selectIndexInternal(tView, lView, index, checkNoChangesMode) {
9790
10042
  setSelectedIndex(index);
9791
10043
  }
9792
10044
 
10045
+ /**
10046
+ * Runs the given function in the context of the given `Injector`.
10047
+ *
10048
+ * Within the function's stack frame, `inject` can be used to inject dependencies from the given
10049
+ * `Injector`. Note that `inject` is only usable synchronously, and cannot be used in any
10050
+ * asynchronous callbacks or after any `await` points.
10051
+ *
10052
+ * @param injector the injector which will satisfy calls to `inject` while `fn` is executing
10053
+ * @param fn the closure to be run in the context of `injector`
10054
+ * @returns the return value of the function, if any
10055
+ * @publicApi
10056
+ */
10057
+ function runInInjectionContext(injector, fn) {
10058
+ if (injector instanceof R3Injector) {
10059
+ injector.assertNotDestroyed();
10060
+ }
10061
+ const prevInjector = setCurrentInjector(injector);
10062
+ const previousInjectImplementation = setInjectImplementation(undefined);
10063
+ try {
10064
+ return fn();
10065
+ }
10066
+ finally {
10067
+ setCurrentInjector(prevInjector);
10068
+ setInjectImplementation(previousInjectImplementation);
10069
+ }
10070
+ }
10071
+
9793
10072
  /**
9794
10073
  * A mapping of the @angular/core API surface used in generated expressions to the actual symbols.
9795
10074
  *
@@ -11228,7 +11507,7 @@ function getOrCreateComponentTView(def) {
11228
11507
  // Declaration node here is null since this function is called when we dynamically create a
11229
11508
  // component and hence there is no declaration.
11230
11509
  const declTNode = null;
11231
- return def.tView = createTView(1 /* TViewType.Component */, declTNode, def.template, def.decls, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery, def.schemas, def.consts);
11510
+ return def.tView = createTView(1 /* TViewType.Component */, declTNode, def.template, def.decls, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery, def.schemas, def.consts, def.id);
11232
11511
  }
11233
11512
  return tView;
11234
11513
  }
@@ -11245,7 +11524,7 @@ function getOrCreateComponentTView(def) {
11245
11524
  * @param schemas Schemas for this view
11246
11525
  * @param consts Constants for this view
11247
11526
  */
11248
- function createTView(type, declTNode, templateFn, decls, vars, directives, pipes, viewQuery, schemas, constsOrFactory) {
11527
+ function createTView(type, declTNode, templateFn, decls, vars, directives, pipes, viewQuery, schemas, constsOrFactory, ssrId) {
11249
11528
  ngDevMode && ngDevMode.tView++;
11250
11529
  const bindingStartIndex = HEADER_OFFSET + decls;
11251
11530
  // This length does not yet contain host bindings from child directives because at this point,
@@ -11284,7 +11563,8 @@ function createTView(type, declTNode, templateFn, decls, vars, directives, pipes
11284
11563
  firstChild: null,
11285
11564
  schemas: schemas,
11286
11565
  consts: consts,
11287
- incompleteFirstPass: false
11566
+ incompleteFirstPass: false,
11567
+ ssrId,
11288
11568
  };
11289
11569
  if (ngDevMode) {
11290
11570
  // For performance reasons it is important that the tView retains the same shape during runtime.
@@ -11304,23 +11584,58 @@ function createViewBlueprint(bindingStartIndex, initialViewLength) {
11304
11584
  /**
11305
11585
  * Locates the host native element, used for bootstrapping existing nodes into rendering pipeline.
11306
11586
  *
11307
- * @param rendererFactory Factory function to create renderer instance.
11587
+ * @param renderer the renderer used to locate the element.
11308
11588
  * @param elementOrSelector Render element or CSS selector to locate the element.
11309
11589
  * @param encapsulation View Encapsulation defined for component that requests host element.
11310
11590
  * @param injector Root view injector instance.
11311
11591
  */
11312
11592
  function locateHostElement(renderer, elementOrSelector, encapsulation, injector) {
11313
11593
  // Note: we use default value for the `PRESERVE_HOST_CONTENT` here even though it's a
11314
- // tree-shakable one (providedIn:'root'). This code path can be triggered during dynamic component
11315
- // creation (after calling ViewContainerRef.createComponent) when an injector instance can be
11316
- // provided. The injector instance might be disconnected from the main DI tree, thus the
11317
- // `PRESERVE_HOST_CONTENT` woild not be able to instantiate. In this case, the default value will
11318
- // be used.
11594
+ // tree-shakable one (providedIn:'root'). This code path can be triggered during dynamic
11595
+ // component creation (after calling ViewContainerRef.createComponent) when an injector
11596
+ // instance can be provided. The injector instance might be disconnected from the main DI
11597
+ // tree, thus the `PRESERVE_HOST_CONTENT` woild not be able to instantiate. In this case, the
11598
+ // default value will be used.
11319
11599
  const preserveHostContent = injector.get(PRESERVE_HOST_CONTENT, PRESERVE_HOST_CONTENT_DEFAULT);
11320
11600
  // When using native Shadow DOM, do not clear host element to allow native slot
11321
11601
  // projection.
11322
11602
  const preserveContent = preserveHostContent || encapsulation === ViewEncapsulation.ShadowDom;
11323
- return renderer.selectRootElement(elementOrSelector, preserveContent);
11603
+ const rootElement = renderer.selectRootElement(elementOrSelector, preserveContent);
11604
+ applyRootElementTransform(rootElement);
11605
+ return rootElement;
11606
+ }
11607
+ /**
11608
+ * Applies any root element transformations that are needed. If hydration is enabled,
11609
+ * this will process corrupted text nodes.
11610
+ *
11611
+ * @param rootElement the app root HTML Element
11612
+ */
11613
+ function applyRootElementTransform(rootElement) {
11614
+ _applyRootElementTransformImpl(rootElement);
11615
+ }
11616
+ /**
11617
+ * Reference to a function that applies transformations to the root HTML element
11618
+ * of an app. When hydration is enabled, this processes any corrupt text nodes
11619
+ * so they are properly hydratable on the client.
11620
+ *
11621
+ * @param rootElement the app root HTML Element
11622
+ */
11623
+ let _applyRootElementTransformImpl = (rootElement) => null;
11624
+ /**
11625
+ * Processes text node markers before hydration begins. This replaces any special comment
11626
+ * nodes that were added prior to serialization are swapped out to restore proper text
11627
+ * nodes before hydration.
11628
+ *
11629
+ * @param rootElement the app root HTML Element
11630
+ */
11631
+ function applyRootElementTransformImpl(rootElement) {
11632
+ processTextNodeMarkersBeforeHydration(rootElement);
11633
+ }
11634
+ /**
11635
+ * Sets the implementation for the `applyRootElementTransform` function.
11636
+ */
11637
+ function enableApplyRootElementTransformImpl() {
11638
+ _applyRootElementTransformImpl = applyRootElementTransformImpl;
11324
11639
  }
11325
11640
  /**
11326
11641
  * Saves context for this cleanup function in LView.cleanupInstances.
@@ -11332,9 +11647,9 @@ function locateHostElement(renderer, elementOrSelector, encapsulation, injector)
11332
11647
  function storeCleanupWithContext(tView, lView, context, cleanupFn) {
11333
11648
  const lCleanup = getOrCreateLViewCleanup(lView);
11334
11649
  // Historically the `storeCleanupWithContext` was used to register both framework-level and
11335
- // user-defined cleanup callbacks, but over time those two types of cleanups were separated. This
11336
- // dev mode checks assures that user-level cleanup callbacks are _not_ stored in data structures
11337
- // reserved for framework-specific hooks.
11650
+ // user-defined cleanup callbacks, but over time those two types of cleanups were separated.
11651
+ // This dev mode checks assures that user-level cleanup callbacks are _not_ stored in data
11652
+ // structures reserved for framework-specific hooks.
11338
11653
  ngDevMode &&
11339
11654
  assertDefined(context, 'Cleanup context is mandatory when registering framework-level destroy hooks');
11340
11655
  lCleanup.push(context);
@@ -12052,7 +12367,8 @@ function createLContainer(hostNative, currentView, native, tNode) {
12052
12367
  tNode,
12053
12368
  native,
12054
12369
  null,
12055
- null, // moved views
12370
+ null,
12371
+ null, // dehydrated views
12056
12372
  ];
12057
12373
  ngDevMode &&
12058
12374
  assertEqual(lContainer.length, CONTAINER_HEADER_OFFSET, 'Should allocate correct number of slots for LContainer header.');
@@ -12874,7 +13190,7 @@ class ComponentFactory extends ComponentFactory$1 {
12874
13190
  const rootFlags = this.componentDef.onPush ? 32 /* LViewFlags.Dirty */ | 256 /* LViewFlags.IsRoot */ :
12875
13191
  16 /* LViewFlags.CheckAlways */ | 256 /* LViewFlags.IsRoot */;
12876
13192
  // Create the root view. Uses empty TView and ContentTemplate.
12877
- const rootTView = createTView(0 /* TViewType.Root */, null, null, 1, 0, null, null, null, null, null);
13193
+ const rootTView = createTView(0 /* TViewType.Root */, null, null, 1, 0, null, null, null, null, null, null);
12878
13194
  const rootLView = createLView(null, rootTView, null, rootFlags, null, null, rendererFactory, hostRenderer, sanitizer, rootViewInjector, null, null);
12879
13195
  // rootView is the parent when bootstrapping
12880
13196
  // TODO(misko): it looks like we are entering view here but we don't really need to as
@@ -14130,44 +14446,737 @@ function ɵɵattributeInterpolate8(attrName, prefix, v0, i0, v1, i1, v2, i2, v3,
14130
14446
  * @returns itself, so that it may be chained.
14131
14447
  * @codeGenApi
14132
14448
  */
14133
- function ɵɵattributeInterpolateV(attrName, values, sanitizer, namespace) {
14134
- const lView = getLView();
14135
- const interpolated = interpolationV(lView, values);
14136
- if (interpolated !== NO_CHANGE) {
14137
- const tNode = getSelectedTNode();
14138
- elementAttributeInternal(tNode, lView, attrName, interpolated, sanitizer, namespace);
14139
- if (ngDevMode) {
14140
- const interpolationInBetween = [values[0]]; // prefix
14141
- for (let i = 2; i < values.length; i += 2) {
14142
- interpolationInBetween.push(values[i]);
14449
+ function ɵɵattributeInterpolateV(attrName, values, sanitizer, namespace) {
14450
+ const lView = getLView();
14451
+ const interpolated = interpolationV(lView, values);
14452
+ if (interpolated !== NO_CHANGE) {
14453
+ const tNode = getSelectedTNode();
14454
+ elementAttributeInternal(tNode, lView, attrName, interpolated, sanitizer, namespace);
14455
+ if (ngDevMode) {
14456
+ const interpolationInBetween = [values[0]]; // prefix
14457
+ for (let i = 2; i < values.length; i += 2) {
14458
+ interpolationInBetween.push(values[i]);
14459
+ }
14460
+ storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - interpolationInBetween.length + 1, ...interpolationInBetween);
14461
+ }
14462
+ }
14463
+ return ɵɵattributeInterpolateV;
14464
+ }
14465
+
14466
+ /**
14467
+ * Synchronously perform change detection on a component (and possibly its sub-components).
14468
+ *
14469
+ * This function triggers change detection in a synchronous way on a component.
14470
+ *
14471
+ * @param component The component which the change detection should be performed on.
14472
+ */
14473
+ function detectChanges(component) {
14474
+ const view = getComponentViewByInstance(component);
14475
+ detectChangesInternal(view[TVIEW], view, component);
14476
+ }
14477
+
14478
+ const AT_THIS_LOCATION = '<-- AT THIS LOCATION';
14479
+ /**
14480
+ * Retrieves a user friendly string for a given TNodeType for use in
14481
+ * friendly error messages
14482
+ *
14483
+ * @param tNodeType
14484
+ * @returns
14485
+ */
14486
+ function getFriendlyStringFromTNodeType(tNodeType) {
14487
+ switch (tNodeType) {
14488
+ case 4 /* TNodeType.Container */:
14489
+ return 'view container';
14490
+ case 2 /* TNodeType.Element */:
14491
+ return 'element';
14492
+ case 8 /* TNodeType.ElementContainer */:
14493
+ return 'ng-container';
14494
+ case 32 /* TNodeType.Icu */:
14495
+ return 'icu';
14496
+ case 64 /* TNodeType.Placeholder */:
14497
+ return 'i18n';
14498
+ case 16 /* TNodeType.Projection */:
14499
+ return 'projection';
14500
+ case 1 /* TNodeType.Text */:
14501
+ return 'text';
14502
+ default:
14503
+ // This should not happen as we cover all possible TNode types above.
14504
+ return '<unknown>';
14505
+ }
14506
+ }
14507
+ /**
14508
+ * Validates that provided nodes match during the hydration process.
14509
+ */
14510
+ function validateMatchingNode(node, nodeType, tagName, lView, tNode, isViewContainerAnchor = false) {
14511
+ if (!node ||
14512
+ (node.nodeType !== nodeType ||
14513
+ (node.nodeType === Node.ELEMENT_NODE &&
14514
+ node.tagName.toLowerCase() !== tagName?.toLowerCase()))) {
14515
+ const expectedNode = shortRNodeDescription(nodeType, tagName, null);
14516
+ let header = `During hydration Angular expected ${expectedNode} but `;
14517
+ const hostComponentDef = getDeclarationComponentDef(lView);
14518
+ const componentClassName = hostComponentDef?.type?.name;
14519
+ const expected = `Angular expected this DOM:\n\n${describeExpectedDom(lView, tNode, isViewContainerAnchor)}\n\n`;
14520
+ let actual = '';
14521
+ if (!node) {
14522
+ // No node found during hydration.
14523
+ header += `the node was not found.\n\n`;
14524
+ }
14525
+ else {
14526
+ const actualNode = shortRNodeDescription(node.nodeType, node.tagName ?? null, node.textContent ?? null);
14527
+ header += `found ${actualNode}.\n\n`;
14528
+ actual = `Actual DOM is:\n\n${describeDomFromNode(node)}\n\n`;
14529
+ }
14530
+ const footer = getHydrationErrorFooter(componentClassName);
14531
+ const message = header + expected + actual + getHydrationAttributeNote() + footer;
14532
+ throw new RuntimeError(500 /* RuntimeErrorCode.HYDRATION_NODE_MISMATCH */, message);
14533
+ }
14534
+ }
14535
+ /**
14536
+ * Validates that a given node has sibling nodes
14537
+ */
14538
+ function validateSiblingNodeExists(node) {
14539
+ validateNodeExists(node);
14540
+ if (!node.nextSibling) {
14541
+ const header = 'During hydration Angular expected more sibling nodes to be present.\n\n';
14542
+ const actual = `Actual DOM is:\n\n${describeDomFromNode(node)}\n\n`;
14543
+ const footer = getHydrationErrorFooter();
14544
+ const message = header + actual + footer;
14545
+ throw new RuntimeError(501 /* RuntimeErrorCode.HYDRATION_MISSING_SIBLINGS */, message);
14546
+ }
14547
+ }
14548
+ /**
14549
+ * Validates that a node exists or throws
14550
+ */
14551
+ function validateNodeExists(node) {
14552
+ if (!node) {
14553
+ throw new RuntimeError(502 /* RuntimeErrorCode.HYDRATION_MISSING_NODE */, `Hydration expected an element to be present at this location.`);
14554
+ }
14555
+ }
14556
+ /**
14557
+ * Builds the hydration error message when a node is not found
14558
+ *
14559
+ * @param lView the LView where the node exists
14560
+ * @param tNode the TNode
14561
+ */
14562
+ function nodeNotFoundError(lView, tNode) {
14563
+ const header = 'During serialization, Angular was unable to find an element in the DOM:\n\n';
14564
+ const expected = `${describeExpectedDom(lView, tNode, false)}\n\n`;
14565
+ const footer = getHydrationErrorFooter();
14566
+ throw new RuntimeError(502 /* RuntimeErrorCode.HYDRATION_MISSING_NODE */, header + expected + footer);
14567
+ }
14568
+ /**
14569
+ * Builds a hydration error message when a node is not found at a path location
14570
+ *
14571
+ * @param host the Host Node
14572
+ * @param path the path to the node
14573
+ */
14574
+ function nodeNotFoundAtPathError(host, path) {
14575
+ const header = `During hydration Angular was unable to locate a node ` +
14576
+ `using the "${path}" path, starting from the ${describeRNode(host)} node.\n\n`;
14577
+ const footer = getHydrationErrorFooter();
14578
+ throw new RuntimeError(502 /* RuntimeErrorCode.HYDRATION_MISSING_NODE */, header + footer);
14579
+ }
14580
+ /**
14581
+ * Builds the hydration error message in the case that dom nodes are created outside of
14582
+ * the Angular context and are being used as projected nodes
14583
+ *
14584
+ * @param lView the LView
14585
+ * @param tNode the TNode
14586
+ * @returns an error
14587
+ */
14588
+ function unsupportedProjectionOfDomNodes(rNode) {
14589
+ const header = 'During serialization, Angular detected DOM nodes ' +
14590
+ 'that were created outside of Angular context and provided as projectable nodes ' +
14591
+ '(likely via `ViewContainerRef.createComponent` or `createComponent` APIs). ' +
14592
+ 'Hydration is not supported for such cases, consider refactoring the code to avoid ' +
14593
+ 'this pattern or using `ngSkipHydration` on the host element of the component.\n\n';
14594
+ const actual = `${describeDomFromNode(rNode)}\n\n`;
14595
+ const message = header + actual + getHydrationAttributeNote();
14596
+ return new RuntimeError(503 /* RuntimeErrorCode.UNSUPPORTED_PROJECTION_DOM_NODES */, message);
14597
+ }
14598
+ /**
14599
+ * Builds the hydration error message in the case that ngSkipHydration was used on a
14600
+ * node that is not a component host element or host binding
14601
+ *
14602
+ * @param rNode the HTML Element
14603
+ * @returns an error
14604
+ */
14605
+ function invalidSkipHydrationHost(rNode) {
14606
+ const header = 'The `ngSkipHydration` flag is applied on a node ' +
14607
+ 'that doesn\'t act as a component host. Hydration can be ' +
14608
+ 'skipped only on per-component basis.\n\n';
14609
+ const actual = `${describeDomFromNode(rNode)}\n\n`;
14610
+ const footer = 'Please move the `ngSkipHydration` attribute to the component host element.';
14611
+ const message = header + actual + footer;
14612
+ return new RuntimeError(504 /* RuntimeErrorCode.INVALID_SKIP_HYDRATION_HOST */, message);
14613
+ }
14614
+ /**
14615
+ * Builds the hydration error message in the case that a user is attempting to enable
14616
+ * hydration on internationalized nodes, which is not yet supported.
14617
+ *
14618
+ * @param rNode the HTML Element
14619
+ * @returns an error
14620
+ */
14621
+ function notYetSupportedI18nBlockError(rNode) {
14622
+ const header = 'Hydration for nodes marked with `i18n` is not yet supported. ' +
14623
+ 'You can opt-out a component that uses `i18n` in a template using ' +
14624
+ 'the `ngSkipHydration` attribute or fall back to the previous ' +
14625
+ 'hydration logic (which re-creates the application structure).\n\n';
14626
+ const actual = `${describeDomFromNode(rNode)}\n\n`;
14627
+ const message = header + actual;
14628
+ return new RuntimeError(518 /* RuntimeErrorCode.HYDRATION_I18N_NOT_YET_SUPPORTED */, message);
14629
+ }
14630
+ // Stringification methods
14631
+ /**
14632
+ * Stringifies a given TNode's attributes
14633
+ *
14634
+ * @param tNode a provided TNode
14635
+ * @returns string
14636
+ */
14637
+ function stringifyTNodeAttrs(tNode) {
14638
+ const results = [];
14639
+ if (tNode.attrs) {
14640
+ for (let i = 0; i < tNode.attrs.length;) {
14641
+ const attrName = tNode.attrs[i++];
14642
+ // Once we reach the first flag, we know that the list of
14643
+ // attributes is over.
14644
+ if (typeof attrName == 'number') {
14645
+ break;
14646
+ }
14647
+ const attrValue = tNode.attrs[i++];
14648
+ results.push(`${attrName}="${shorten(attrValue)}"`);
14649
+ }
14650
+ }
14651
+ return results.join(' ');
14652
+ }
14653
+ /**
14654
+ * The list of internal attributes that should be filtered out while
14655
+ * producing an error message.
14656
+ */
14657
+ const internalAttrs = new Set(['ngh', 'ng-version', 'ng-server-context']);
14658
+ /**
14659
+ * Stringifies an HTML Element's attributes
14660
+ *
14661
+ * @param rNode an HTML Element
14662
+ * @returns string
14663
+ */
14664
+ function stringifyRNodeAttrs(rNode) {
14665
+ const results = [];
14666
+ for (let i = 0; i < rNode.attributes.length; i++) {
14667
+ const attr = rNode.attributes[i];
14668
+ if (internalAttrs.has(attr.name))
14669
+ continue;
14670
+ results.push(`${attr.name}="${shorten(attr.value)}"`);
14671
+ }
14672
+ return results.join(' ');
14673
+ }
14674
+ // Methods for Describing the DOM
14675
+ /**
14676
+ * Converts a tNode to a helpful readable string value for use in error messages
14677
+ *
14678
+ * @param tNode a given TNode
14679
+ * @param innerContent the content of the node
14680
+ * @returns string
14681
+ */
14682
+ function describeTNode(tNode, innerContent = '…') {
14683
+ switch (tNode.type) {
14684
+ case 1 /* TNodeType.Text */:
14685
+ const content = tNode.value ? `(${tNode.value})` : '';
14686
+ return `#text${content}`;
14687
+ case 2 /* TNodeType.Element */:
14688
+ const attrs = stringifyTNodeAttrs(tNode);
14689
+ const tag = tNode.value.toLowerCase();
14690
+ return `<${tag}${attrs ? ' ' + attrs : ''}>${innerContent}</${tag}>`;
14691
+ case 8 /* TNodeType.ElementContainer */:
14692
+ return '<!-- ng-container -->';
14693
+ case 4 /* TNodeType.Container */:
14694
+ return '<!-- container -->';
14695
+ default:
14696
+ const typeAsString = getFriendlyStringFromTNodeType(tNode.type);
14697
+ return `#node(${typeAsString})`;
14698
+ }
14699
+ }
14700
+ /**
14701
+ * Converts an RNode to a helpful readable string value for use in error messages
14702
+ *
14703
+ * @param rNode a given RNode
14704
+ * @param innerContent the content of the node
14705
+ * @returns string
14706
+ */
14707
+ function describeRNode(rNode, innerContent = '…') {
14708
+ const node = rNode;
14709
+ switch (node.nodeType) {
14710
+ case Node.ELEMENT_NODE:
14711
+ const tag = node.tagName.toLowerCase();
14712
+ const attrs = stringifyRNodeAttrs(node);
14713
+ return `<${tag}${attrs ? ' ' + attrs : ''}>${innerContent}</${tag}>`;
14714
+ case Node.TEXT_NODE:
14715
+ const content = node.textContent ? shorten(node.textContent) : '';
14716
+ return `#text${content ? `(${content})` : ''}`;
14717
+ case Node.COMMENT_NODE:
14718
+ return `<!-- ${shorten(node.textContent ?? '')} -->`;
14719
+ default:
14720
+ return `#node(${node.nodeType})`;
14721
+ }
14722
+ }
14723
+ /**
14724
+ * Builds the string containing the expected DOM present given the LView and TNode
14725
+ * values for a readable error message
14726
+ *
14727
+ * @param lView the lView containing the DOM
14728
+ * @param tNode the tNode
14729
+ * @param isViewContainerAnchor boolean
14730
+ * @returns string
14731
+ */
14732
+ function describeExpectedDom(lView, tNode, isViewContainerAnchor) {
14733
+ const spacer = ' ';
14734
+ let content = '';
14735
+ if (tNode.prev) {
14736
+ content += spacer + '…\n';
14737
+ content += spacer + describeTNode(tNode.prev) + '\n';
14738
+ }
14739
+ else if (tNode.type && tNode.type & 12 /* TNodeType.AnyContainer */) {
14740
+ content += spacer + '…\n';
14741
+ }
14742
+ if (isViewContainerAnchor) {
14743
+ content += spacer + describeTNode(tNode) + '\n';
14744
+ content += spacer + `<!-- container --> ${AT_THIS_LOCATION}\n`;
14745
+ }
14746
+ else {
14747
+ content += spacer + describeTNode(tNode) + ` ${AT_THIS_LOCATION}\n`;
14748
+ }
14749
+ content += spacer + '…\n';
14750
+ const parentRNode = tNode.type ? getParentRElement(lView[TVIEW], tNode, lView) : null;
14751
+ if (parentRNode) {
14752
+ content = describeRNode(parentRNode, '\n' + content);
14753
+ }
14754
+ return content;
14755
+ }
14756
+ /**
14757
+ * Builds the string containing the DOM present around a given RNode for a
14758
+ * readable error message
14759
+ *
14760
+ * @param node the RNode
14761
+ * @returns string
14762
+ */
14763
+ function describeDomFromNode(node) {
14764
+ const spacer = ' ';
14765
+ let content = '';
14766
+ const currentNode = node;
14767
+ if (currentNode.previousSibling) {
14768
+ content += spacer + '…\n';
14769
+ content += spacer + describeRNode(currentNode.previousSibling) + '\n';
14770
+ }
14771
+ content += spacer + describeRNode(currentNode) + ` ${AT_THIS_LOCATION}\n`;
14772
+ if (node.nextSibling) {
14773
+ content += spacer + '…\n';
14774
+ }
14775
+ if (node.parentNode) {
14776
+ content = describeRNode(currentNode.parentNode, '\n' + content);
14777
+ }
14778
+ return content;
14779
+ }
14780
+ /**
14781
+ * Shortens the description of a given RNode by its type for readability
14782
+ *
14783
+ * @param nodeType the type of node
14784
+ * @param tagName the node tag name
14785
+ * @param textContent the text content in the node
14786
+ * @returns string
14787
+ */
14788
+ function shortRNodeDescription(nodeType, tagName, textContent) {
14789
+ switch (nodeType) {
14790
+ case Node.ELEMENT_NODE:
14791
+ return `<${tagName.toLowerCase()}>`;
14792
+ case Node.TEXT_NODE:
14793
+ const content = textContent ? ` (with the "${shorten(textContent)}" content)` : '';
14794
+ return `a text node${content}`;
14795
+ case Node.COMMENT_NODE:
14796
+ return 'a comment node';
14797
+ default:
14798
+ return `#node(nodeType=${nodeType})`;
14799
+ }
14800
+ }
14801
+ /**
14802
+ * Builds the footer hydration error message
14803
+ *
14804
+ * @param componentClassName the name of the component class
14805
+ * @returns string
14806
+ */
14807
+ function getHydrationErrorFooter(componentClassName) {
14808
+ const componentInfo = componentClassName ? `the "${componentClassName}"` : 'corresponding';
14809
+ return `To fix this problem:\n` +
14810
+ ` * check ${componentInfo} component for hydration-related issues\n` +
14811
+ ` * or skip hydration by adding the \`ngSkipHydration\` attribute ` +
14812
+ `to its host node in a template`;
14813
+ }
14814
+ /**
14815
+ * An attribute related note for hydration errors
14816
+ */
14817
+ function getHydrationAttributeNote() {
14818
+ return 'Note: attributes are only displayed to better represent the DOM' +
14819
+ ' but have no effect on hydration mismatches.\n\n';
14820
+ }
14821
+ // Node string utility functions
14822
+ /**
14823
+ * Strips all newlines out of a given string
14824
+ *
14825
+ * @param input a string to be cleared of new line characters
14826
+ * @returns
14827
+ */
14828
+ function stripNewlines(input) {
14829
+ return input.replace(/\s+/gm, '');
14830
+ }
14831
+ /**
14832
+ * Reduces a string down to a maximum length of characters with ellipsis for readability
14833
+ *
14834
+ * @param input a string input
14835
+ * @param maxLength a maximum length in characters
14836
+ * @returns string
14837
+ */
14838
+ function shorten(input, maxLength = 50) {
14839
+ if (!input) {
14840
+ return '';
14841
+ }
14842
+ input = stripNewlines(input);
14843
+ return input.length > maxLength ? `${input.substring(0, maxLength - 1)}…` : input;
14844
+ }
14845
+
14846
+ /**
14847
+ * Regexp that extracts a reference node information from the compressed node location.
14848
+ * The reference node is represented as either:
14849
+ * - a number which points to an LView slot
14850
+ * - the `b` char which indicates that the lookup should start from the `document.body`
14851
+ * - the `h` char to start lookup from the component host node (`lView[HOST]`)
14852
+ */
14853
+ const REF_EXTRACTOR_REGEXP = new RegExp(`^(\\d+)*(${REFERENCE_NODE_BODY}|${REFERENCE_NODE_HOST})*(.*)`);
14854
+ /**
14855
+ * Helper function that takes a reference node location and a set of navigation steps
14856
+ * (from the reference node) to a target node and outputs a string that represents
14857
+ * a location.
14858
+ *
14859
+ * For example, given: referenceNode = 'b' (body) and path = ['firstChild', 'firstChild',
14860
+ * 'nextSibling'], the function returns: `bf2n`.
14861
+ */
14862
+ function compressNodeLocation(referenceNode, path) {
14863
+ const result = [referenceNode];
14864
+ for (const segment of path) {
14865
+ const lastIdx = result.length - 1;
14866
+ if (lastIdx > 0 && result[lastIdx - 1] === segment) {
14867
+ // An empty string in a count slot represents 1 occurrence of an instruction.
14868
+ const value = (result[lastIdx] || 1);
14869
+ result[lastIdx] = value + 1;
14870
+ }
14871
+ else {
14872
+ // Adding a new segment to the path.
14873
+ // Using an empty string in a counter field to avoid encoding `1`s
14874
+ // into the path, since they are implicit (e.g. `f1n1` vs `fn`), so
14875
+ // it's enough to have a single char in this case.
14876
+ result.push(segment, '');
14877
+ }
14878
+ }
14879
+ return result.join('');
14880
+ }
14881
+ /**
14882
+ * Helper function that reverts the `compressNodeLocation` and transforms a given
14883
+ * string into an array where at 0th position there is a reference node info and
14884
+ * after that it contains information (in pairs) about a navigation step and the
14885
+ * number of repetitions.
14886
+ *
14887
+ * For example, the path like 'bf2n' will be transformed to:
14888
+ * ['b', 'firstChild', 2, 'nextSibling', 1].
14889
+ *
14890
+ * This information is later consumed by the code that navigates the DOM to find
14891
+ * a given node by its location.
14892
+ */
14893
+ function decompressNodeLocation(path) {
14894
+ const matches = path.match(REF_EXTRACTOR_REGEXP);
14895
+ const [_, refNodeId, refNodeName, rest] = matches;
14896
+ // If a reference node is represented by an index, transform it to a number.
14897
+ const ref = refNodeId ? parseInt(refNodeId, 10) : refNodeName;
14898
+ const steps = [];
14899
+ // Match all segments in a path.
14900
+ for (const [_, step, count] of rest.matchAll(/(f|n)(\d*)/g)) {
14901
+ const repeat = parseInt(count, 10) || 1;
14902
+ steps.push(step, repeat);
14903
+ }
14904
+ return [ref, ...steps];
14905
+ }
14906
+
14907
+ /** Whether current TNode is a first node in an <ng-container>. */
14908
+ function isFirstElementInNgContainer(tNode) {
14909
+ return !tNode.prev && tNode.parent?.type === 8 /* TNodeType.ElementContainer */;
14910
+ }
14911
+ /** Returns an instruction index (subtracting HEADER_OFFSET). */
14912
+ function getNoOffsetIndex(tNode) {
14913
+ return tNode.index - HEADER_OFFSET;
14914
+ }
14915
+ /**
14916
+ * Locate a node in DOM tree that corresponds to a given TNode.
14917
+ *
14918
+ * @param hydrationInfo The hydration annotation data
14919
+ * @param tView the current tView
14920
+ * @param lView the current lView
14921
+ * @param tNode the current tNode
14922
+ * @returns an RNode that represents a given tNode
14923
+ */
14924
+ function locateNextRNode(hydrationInfo, tView, lView, tNode) {
14925
+ let native = null;
14926
+ const noOffsetIndex = getNoOffsetIndex(tNode);
14927
+ const nodes = hydrationInfo.data[NODES];
14928
+ if (nodes?.[noOffsetIndex]) {
14929
+ // We know the exact location of the node.
14930
+ native = locateRNodeByPath(nodes[noOffsetIndex], lView);
14931
+ }
14932
+ else if (tView.firstChild === tNode) {
14933
+ // We create a first node in this view, so we use a reference
14934
+ // to the first child in this DOM segment.
14935
+ native = hydrationInfo.firstChild;
14936
+ }
14937
+ else {
14938
+ // Locate a node based on a previous sibling or a parent node.
14939
+ const previousTNodeParent = tNode.prev === null;
14940
+ const previousTNode = (tNode.prev ?? tNode.parent);
14941
+ ngDevMode &&
14942
+ assertDefined(previousTNode, 'Unexpected state: current TNode does not have a connection ' +
14943
+ 'to the previous node or a parent node.');
14944
+ if (isFirstElementInNgContainer(tNode)) {
14945
+ const noOffsetParentIndex = getNoOffsetIndex(tNode.parent);
14946
+ native = getSegmentHead(hydrationInfo, noOffsetParentIndex);
14947
+ }
14948
+ else {
14949
+ let previousRElement = getNativeByTNode(previousTNode, lView);
14950
+ if (previousTNodeParent) {
14951
+ native = previousRElement.firstChild;
14952
+ }
14953
+ else {
14954
+ // If the previous node is an element, but it also has container info,
14955
+ // this means that we are processing a node like `<div #vcrTarget>`, which is
14956
+ // represented in the DOM as `<div></div>...<!--container-->`.
14957
+ // In this case, there are nodes *after* this element and we need to skip
14958
+ // all of them to reach an element that we are looking for.
14959
+ const noOffsetPrevSiblingIndex = getNoOffsetIndex(previousTNode);
14960
+ const segmentHead = getSegmentHead(hydrationInfo, noOffsetPrevSiblingIndex);
14961
+ if (previousTNode.type === 2 /* TNodeType.Element */ && segmentHead) {
14962
+ const numRootNodesToSkip = calcSerializedContainerSize(hydrationInfo, noOffsetPrevSiblingIndex);
14963
+ // `+1` stands for an anchor comment node after all the views in this container.
14964
+ const nodesToSkip = numRootNodesToSkip + 1;
14965
+ // First node after this segment.
14966
+ native = siblingAfter(nodesToSkip, segmentHead);
14967
+ }
14968
+ else {
14969
+ native = previousRElement.nextSibling;
14970
+ }
14971
+ }
14972
+ }
14973
+ }
14974
+ return native;
14975
+ }
14976
+ /**
14977
+ * Skips over a specified number of nodes and returns the next sibling node after that.
14978
+ */
14979
+ function siblingAfter(skip, from) {
14980
+ let currentNode = from;
14981
+ for (let i = 0; i < skip; i++) {
14982
+ ngDevMode && validateSiblingNodeExists(currentNode);
14983
+ currentNode = currentNode.nextSibling;
14984
+ }
14985
+ return currentNode;
14986
+ }
14987
+ /**
14988
+ * Helper function to produce a string representation of the navigation steps
14989
+ * (in terms of `nextSibling` and `firstChild` navigations). Used in error
14990
+ * messages in dev mode.
14991
+ */
14992
+ function stringifyNavigationInstructions(instructions) {
14993
+ const container = [];
14994
+ let i = 0;
14995
+ while (i < instructions.length) {
14996
+ const step = instructions[i++];
14997
+ const repeat = instructions[i++];
14998
+ for (let r = 0; r < repeat; r++) {
14999
+ container.push(step === NodeNavigationStep.FirstChild ? 'firstChild' : 'nextSibling');
15000
+ }
15001
+ }
15002
+ return container.join('.');
15003
+ }
15004
+ /**
15005
+ * Helper function that navigates from a starting point node (the `from` node)
15006
+ * using provided set of navigation instructions (within `path` argument).
15007
+ */
15008
+ function navigateToNode(from, instructions) {
15009
+ let node = from;
15010
+ let i = 0;
15011
+ while (i < instructions.length) {
15012
+ const step = instructions[i++];
15013
+ const repeat = instructions[i++];
15014
+ for (let r = 0; r < repeat; r++) {
15015
+ if (ngDevMode && !node) {
15016
+ throw nodeNotFoundAtPathError(from, stringifyNavigationInstructions(instructions));
15017
+ }
15018
+ switch (step) {
15019
+ case NodeNavigationStep.FirstChild:
15020
+ node = node.firstChild;
15021
+ break;
15022
+ case NodeNavigationStep.NextSibling:
15023
+ node = node.nextSibling;
15024
+ break;
14143
15025
  }
14144
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - interpolationInBetween.length + 1, ...interpolationInBetween);
14145
15026
  }
14146
15027
  }
14147
- return ɵɵattributeInterpolateV;
15028
+ if (ngDevMode && !node) {
15029
+ throw nodeNotFoundAtPathError(from, stringifyNavigationInstructions(instructions));
15030
+ }
15031
+ return node;
14148
15032
  }
14149
-
14150
15033
  /**
14151
- * Synchronously perform change detection on a component (and possibly its sub-components).
14152
- *
14153
- * This function triggers change detection in a synchronous way on a component.
15034
+ * Locates an RNode given a set of navigation instructions (which also contains
15035
+ * a starting point node info).
15036
+ */
15037
+ function locateRNodeByPath(path, lView) {
15038
+ const [referenceNode, ...navigationInstructions] = decompressNodeLocation(path);
15039
+ let ref;
15040
+ if (referenceNode === REFERENCE_NODE_HOST) {
15041
+ ref = lView[0];
15042
+ }
15043
+ else if (referenceNode === REFERENCE_NODE_BODY) {
15044
+ ref = ɵɵresolveBody(lView[0]);
15045
+ }
15046
+ else {
15047
+ const parentElementId = Number(referenceNode);
15048
+ ref = unwrapRNode(lView[parentElementId + HEADER_OFFSET]);
15049
+ }
15050
+ return navigateToNode(ref, navigationInstructions);
15051
+ }
15052
+ /**
15053
+ * Generate a list of DOM navigation operations to get from node `start` to node `finish`.
14154
15054
  *
14155
- * @param component The component which the change detection should be performed on.
15055
+ * Note: assumes that node `start` occurs before node `finish` in an in-order traversal of the DOM
15056
+ * tree. That is, we should be able to get from `start` to `finish` purely by using `.firstChild`
15057
+ * and `.nextSibling` operations.
14156
15058
  */
14157
- function detectChanges(component) {
14158
- const view = getComponentViewByInstance(component);
14159
- detectChangesInternal(view[TVIEW], view, component);
15059
+ function navigateBetween(start, finish) {
15060
+ if (start === finish) {
15061
+ return [];
15062
+ }
15063
+ else if (start.parentElement == null || finish.parentElement == null) {
15064
+ return null;
15065
+ }
15066
+ else if (start.parentElement === finish.parentElement) {
15067
+ return navigateBetweenSiblings(start, finish);
15068
+ }
15069
+ else {
15070
+ // `finish` is a child of its parent, so the parent will always have a child.
15071
+ const parent = finish.parentElement;
15072
+ const parentPath = navigateBetween(start, parent);
15073
+ const childPath = navigateBetween(parent.firstChild, finish);
15074
+ if (!parentPath || !childPath)
15075
+ return null;
15076
+ return [
15077
+ // First navigate to `finish`'s parent
15078
+ ...parentPath,
15079
+ // Then to its first child.
15080
+ NodeNavigationStep.FirstChild,
15081
+ // And finally from that node to `finish` (maybe a no-op if we're already there).
15082
+ ...childPath,
15083
+ ];
15084
+ }
15085
+ }
15086
+ /**
15087
+ * Calculates a path between 2 sibling nodes (generates a number of `NextSibling` navigations).
15088
+ */
15089
+ function navigateBetweenSiblings(start, finish) {
15090
+ const nav = [];
15091
+ let node = null;
15092
+ for (node = start; node != null && node !== finish; node = node.nextSibling) {
15093
+ nav.push(NodeNavigationStep.NextSibling);
15094
+ }
15095
+ return node === null ? [] : nav;
15096
+ }
15097
+ /**
15098
+ * Calculates a path between 2 nodes in terms of `nextSibling` and `firstChild`
15099
+ * navigations:
15100
+ * - the `from` node is a known node, used as an starting point for the lookup
15101
+ * (the `fromNodeName` argument is a string representation of the node).
15102
+ * - the `to` node is a node that the runtime logic would be looking up,
15103
+ * using the path generated by this function.
15104
+ */
15105
+ function calcPathBetween(from, to, fromNodeName) {
15106
+ const path = navigateBetween(from, to);
15107
+ return path === null ? null : compressNodeLocation(fromNodeName, path);
15108
+ }
15109
+ /**
15110
+ * Invoked at serialization time (on the server) when a set of navigation
15111
+ * instructions needs to be generated for a TNode.
15112
+ */
15113
+ function calcPathForNode(tNode, lView) {
15114
+ const parentTNode = tNode.parent;
15115
+ let parentIndex;
15116
+ let parentRNode;
15117
+ let referenceNodeName;
15118
+ if (parentTNode === null) {
15119
+ // No parent TNode - use host element as a reference node.
15120
+ parentIndex = referenceNodeName = REFERENCE_NODE_HOST;
15121
+ parentRNode = lView[HOST];
15122
+ }
15123
+ else {
15124
+ // Use parent TNode as a reference node.
15125
+ parentIndex = parentTNode.index;
15126
+ parentRNode = unwrapRNode(lView[parentIndex]);
15127
+ referenceNodeName = renderStringify(parentIndex - HEADER_OFFSET);
15128
+ }
15129
+ let rNode = unwrapRNode(lView[tNode.index]);
15130
+ if (tNode.type & 12 /* TNodeType.AnyContainer */) {
15131
+ // For <ng-container> nodes, instead of serializing a reference
15132
+ // to the anchor comment node, serialize a location of the first
15133
+ // DOM element. Paired with the container size (serialized as a part
15134
+ // of `ngh.containers`), it should give enough information for runtime
15135
+ // to hydrate nodes in this container.
15136
+ const firstRNode = getFirstNativeNode(lView, tNode);
15137
+ // If container is not empty, use a reference to the first element,
15138
+ // otherwise, rNode would point to an anchor comment node.
15139
+ if (firstRNode) {
15140
+ rNode = firstRNode;
15141
+ }
15142
+ }
15143
+ let path = calcPathBetween(parentRNode, rNode, referenceNodeName);
15144
+ if (path === null && parentRNode !== rNode) {
15145
+ // Searching for a path between elements within a host node failed.
15146
+ // Trying to find a path to an element starting from the `document.body` instead.
15147
+ //
15148
+ // Important note: this type of reference is relatively unstable, since Angular
15149
+ // may not be able to control parts of the page that the runtime logic navigates
15150
+ // through. This is mostly needed to cover "portals" use-case (like menus, dialog boxes,
15151
+ // etc), where nodes are content-projected (including direct DOM manipulations) outside
15152
+ // of the host node. The better solution is to provide APIs to work with "portals",
15153
+ // at which point this code path would not be needed.
15154
+ const body = parentRNode.ownerDocument.body;
15155
+ path = calcPathBetween(body, rNode, REFERENCE_NODE_BODY);
15156
+ if (path === null) {
15157
+ // If the path is still empty, it's likely that this node is detached and
15158
+ // won't be found during hydration.
15159
+ throw nodeNotFoundError(lView, tNode);
15160
+ }
15161
+ }
15162
+ return path;
14160
15163
  }
14161
15164
 
14162
15165
  function templateFirstCreatePass(index, tView, lView, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex) {
14163
15166
  ngDevMode && assertFirstCreatePass(tView);
14164
15167
  ngDevMode && ngDevMode.firstCreatePass++;
14165
15168
  const tViewConsts = tView.consts;
15169
+ let ssrId = null;
15170
+ const hydrationInfo = lView[HYDRATION];
15171
+ if (hydrationInfo) {
15172
+ const noOffsetIndex = index - HEADER_OFFSET;
15173
+ ssrId = (hydrationInfo && hydrationInfo.data[TEMPLATES]?.[noOffsetIndex]) ?? null;
15174
+ }
14166
15175
  // TODO(pk): refactor getOrCreateTNode to have the "create" only version
14167
15176
  const tNode = getOrCreateTNode(tView, index, 4 /* TNodeType.Container */, tagName || null, getConstant(tViewConsts, attrsIndex));
14168
15177
  resolveDirectives(tView, lView, tNode, getConstant(tViewConsts, localRefsIndex));
14169
15178
  registerPostOrderHooks(tView, tNode);
14170
- const embeddedTView = tNode.tView = createTView(2 /* TViewType.Embedded */, tNode, templateFn, decls, vars, tView.directiveRegistry, tView.pipeRegistry, null, tView.schemas, tViewConsts);
15179
+ const embeddedTView = tNode.tView = createTView(2 /* TViewType.Embedded */, tNode, templateFn, decls, vars, tView.directiveRegistry, tView.pipeRegistry, null, tView.schemas, tViewConsts, ssrId);
14171
15180
  if (tView.queries !== null) {
14172
15181
  tView.queries.template(tView, tNode);
14173
15182
  embeddedTView.queries = tView.queries.embeddedTView(tNode);
@@ -14200,8 +15209,10 @@ function ɵɵtemplate(index, templateFn, decls, vars, tagName, attrsIndex, local
14200
15209
  const tNode = tView.firstCreatePass ? templateFirstCreatePass(adjustedIndex, tView, lView, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex) :
14201
15210
  tView.data[adjustedIndex];
14202
15211
  setCurrentTNode(tNode, false);
14203
- const comment = lView[RENDERER].createComment(ngDevMode ? 'container' : '');
14204
- appendChild(tView, lView, comment, tNode);
15212
+ const comment = _locateOrCreateContainerAnchor(tView, lView, tNode, index);
15213
+ if (wasLastNodeCreated()) {
15214
+ appendChild(tView, lView, comment, tNode);
15215
+ }
14205
15216
  attachPatchData(comment, lView);
14206
15217
  addToViewTree(lView, lView[adjustedIndex] = createLContainer(comment, lView, comment, tNode));
14207
15218
  if (isDirectiveHost(tNode)) {
@@ -14211,6 +15222,42 @@ function ɵɵtemplate(index, templateFn, decls, vars, tagName, attrsIndex, local
14211
15222
  saveResolvedLocalsInData(lView, tNode, localRefExtractor);
14212
15223
  }
14213
15224
  }
15225
+ let _locateOrCreateContainerAnchor = createContainerAnchorImpl;
15226
+ /**
15227
+ * Regular creation mode for LContainers and their anchor (comment) nodes.
15228
+ */
15229
+ function createContainerAnchorImpl(tView, lView, tNode, index) {
15230
+ lastNodeWasCreated(true);
15231
+ return lView[RENDERER].createComment(ngDevMode ? 'container' : '');
15232
+ }
15233
+ /**
15234
+ * Enables hydration code path (to lookup existing elements in DOM)
15235
+ * in addition to the regular creation mode for LContainers and their
15236
+ * anchor (comment) nodes.
15237
+ */
15238
+ function locateOrCreateContainerAnchorImpl(tView, lView, tNode, index) {
15239
+ const hydrationInfo = lView[HYDRATION];
15240
+ const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode(hydrationInfo, index);
15241
+ lastNodeWasCreated(isNodeCreationMode);
15242
+ // Regular creation mode.
15243
+ if (isNodeCreationMode) {
15244
+ return createContainerAnchorImpl(tView, lView, tNode, index);
15245
+ }
15246
+ // Hydration mode, looking up existing elements in DOM.
15247
+ const currentRNode = locateNextRNode(hydrationInfo, tView, lView, tNode);
15248
+ ngDevMode && validateNodeExists(currentRNode);
15249
+ setSegmentHead(hydrationInfo, index, currentRNode);
15250
+ const viewContainerSize = calcSerializedContainerSize(hydrationInfo, index);
15251
+ const comment = siblingAfter(viewContainerSize, currentRNode);
15252
+ if (ngDevMode) {
15253
+ validateMatchingNode(comment, Node.COMMENT_NODE, null, lView, tNode);
15254
+ markRNodeAsClaimedByHydration(comment);
15255
+ }
15256
+ return comment;
15257
+ }
15258
+ function enableLocateOrCreateContainerAnchorImpl() {
15259
+ _locateOrCreateContainerAnchor = locateOrCreateContainerAnchorImpl;
15260
+ }
14214
15261
 
14215
15262
  /** Store a value in the `data` at a given `index`. */
14216
15263
  function store(tView, lView, index, value) {
@@ -14237,92 +15284,6 @@ function ɵɵreference(index) {
14237
15284
  return load(contextLView, HEADER_OFFSET + index);
14238
15285
  }
14239
15286
 
14240
- /**
14241
- * Verifies whether a given node matches an expected criteria,
14242
- * based on internal data structure state.
14243
- */
14244
- function validateMatchingNode(node, nodeType, tagName, lView, tNode) {
14245
- if (node.nodeType !== nodeType ||
14246
- (node.nodeType === Node.ELEMENT_NODE &&
14247
- node.tagName.toLowerCase() !== tagName?.toLowerCase())) {
14248
- // TODO: improve error message and use RuntimeError instead.
14249
- throw new Error(`Unexpected node found during hydration.`);
14250
- }
14251
- }
14252
- /**
14253
- * Verifies whether next sibling node exists.
14254
- */
14255
- function validateSiblingNodeExists(node) {
14256
- if (!node.nextSibling) {
14257
- // TODO: improve error message and use RuntimeError instead.
14258
- throw new Error(`Unexpected state: insufficient number of sibling nodes.`);
14259
- }
14260
- }
14261
-
14262
- /** Whether current TNode is a first node in an <ng-container>. */
14263
- function isFirstElementInNgContainer(tNode) {
14264
- return !tNode.prev && tNode.parent?.type === 8 /* TNodeType.ElementContainer */;
14265
- }
14266
- /** Returns first element from a DOM segment that corresponds to this <ng-container>. */
14267
- function getDehydratedNgContainer(hydrationInfo, tContainerNode) {
14268
- const noOffsetIndex = tContainerNode.index - HEADER_OFFSET;
14269
- const ngContainer = hydrationInfo.ngContainers?.[noOffsetIndex];
14270
- ngDevMode &&
14271
- assertDefined(ngContainer, 'Unexpected state: no hydration info available for a given TNode, ' +
14272
- 'which represents an element container.');
14273
- return ngContainer;
14274
- }
14275
- /**
14276
- * Locate a node in DOM tree that corresponds to a given TNode.
14277
- *
14278
- * @param hydrationInfo The hydration annotation data
14279
- * @param tView the current tView
14280
- * @param lView the current lView
14281
- * @param tNode the current tNode
14282
- * @returns an RNode that represents a given tNode
14283
- */
14284
- function locateNextRNode(hydrationInfo, tView, lView, tNode) {
14285
- let native = null;
14286
- if (tView.firstChild === tNode) {
14287
- // We create a first node in this view, so we use a reference
14288
- // to the first child in this DOM segment.
14289
- native = hydrationInfo.firstChild;
14290
- }
14291
- else {
14292
- // Locate a node based on a previous sibling or a parent node.
14293
- const previousTNodeParent = tNode.prev === null;
14294
- const previousTNode = (tNode.prev ?? tNode.parent);
14295
- ngDevMode &&
14296
- assertDefined(previousTNode, 'Unexpected state: current TNode does not have a connection ' +
14297
- 'to the previous node or a parent node.');
14298
- const previousRElement = getNativeByTNode(previousTNode, lView);
14299
- if (isFirstElementInNgContainer(tNode)) {
14300
- const ngContainer = getDehydratedNgContainer(hydrationInfo, tNode.parent);
14301
- native = ngContainer.firstChild ?? null;
14302
- }
14303
- else {
14304
- if (previousTNodeParent) {
14305
- native = previousRElement.firstChild;
14306
- }
14307
- else {
14308
- native = previousRElement.nextSibling;
14309
- }
14310
- }
14311
- }
14312
- return native;
14313
- }
14314
- /**
14315
- * Skips over a specified number of nodes and returns the next sibling node after that.
14316
- */
14317
- function siblingAfter(skip, from) {
14318
- let currentNode = from;
14319
- for (let i = 0; i < skip; i++) {
14320
- ngDevMode && validateSiblingNodeExists(currentNode);
14321
- currentNode = currentNode.nextSibling;
14322
- }
14323
- return currentNode;
14324
- }
14325
-
14326
15287
  /**
14327
15288
  * The name of an attribute that can be added to the hydration boundary node
14328
15289
  * (component host node) to disable hydration for the content within that boundary.
@@ -14349,6 +15310,21 @@ function hasNgSkipHydrationAttr(tNode) {
14349
15310
  }
14350
15311
  return false;
14351
15312
  }
15313
+ /**
15314
+ * Helper function that determines if a given node is within a skip hydration block
15315
+ * by navigating up the TNode tree to see if any parent nodes have skip hydration
15316
+ * attribute.
15317
+ */
15318
+ function isInSkipHydrationBlock(tNode) {
15319
+ let currentTNode = tNode.parent;
15320
+ while (currentTNode) {
15321
+ if (hasNgSkipHydrationAttr(currentTNode)) {
15322
+ return true;
15323
+ }
15324
+ currentTNode = currentTNode.parent;
15325
+ }
15326
+ return false;
15327
+ }
14352
15328
 
14353
15329
  /**
14354
15330
  * Update a property on a selected element.
@@ -14434,7 +15410,7 @@ function ɵɵelementStart(index, name, attrsIndex, localRefsIndex) {
14434
15410
  const tNode = tView.firstCreatePass ?
14435
15411
  elementStartFirstCreatePass(adjustedIndex, tView, lView, name, attrsIndex, localRefsIndex) :
14436
15412
  tView.data[adjustedIndex];
14437
- const native = _locateOrCreateElementNode(tView, lView, tNode, renderer, name);
15413
+ const native = _locateOrCreateElementNode(tView, lView, tNode, renderer, name, index);
14438
15414
  lView[adjustedIndex] = native;
14439
15415
  const hasDirectives = isDirectiveHost(tNode);
14440
15416
  if (ngDevMode && tView.firstCreatePass) {
@@ -14517,7 +15493,7 @@ function ɵɵelement(index, name, attrsIndex, localRefsIndex) {
14517
15493
  ɵɵelementEnd();
14518
15494
  return ɵɵelement;
14519
15495
  }
14520
- let _locateOrCreateElementNode = (tView, lView, tNode, renderer, name) => {
15496
+ let _locateOrCreateElementNode = (tView, lView, tNode, renderer, name, index) => {
14521
15497
  lastNodeWasCreated(true);
14522
15498
  return createElementNode(renderer, name, getNamespace$1());
14523
15499
  };
@@ -14525,9 +15501,9 @@ let _locateOrCreateElementNode = (tView, lView, tNode, renderer, name) => {
14525
15501
  * Enables hydration code path (to lookup existing elements in DOM)
14526
15502
  * in addition to the regular creation mode of element nodes.
14527
15503
  */
14528
- function locateOrCreateElementNodeImpl(tView, lView, tNode, renderer, name) {
15504
+ function locateOrCreateElementNodeImpl(tView, lView, tNode, renderer, name, index) {
14529
15505
  const hydrationInfo = lView[HYDRATION];
14530
- const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock();
15506
+ const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode(hydrationInfo, index);
14531
15507
  lastNodeWasCreated(isNodeCreationMode);
14532
15508
  // Regular creation mode.
14533
15509
  if (isNodeCreationMode) {
@@ -14535,16 +15511,33 @@ function locateOrCreateElementNodeImpl(tView, lView, tNode, renderer, name) {
14535
15511
  }
14536
15512
  // Hydration mode, looking up an existing element in DOM.
14537
15513
  const native = locateNextRNode(hydrationInfo, tView, lView, tNode);
14538
- ngDevMode &&
14539
- validateMatchingNode(native, Node.ELEMENT_NODE, name, lView, tNode);
15514
+ ngDevMode && validateMatchingNode(native, Node.ELEMENT_NODE, name, lView, tNode);
14540
15515
  ngDevMode && markRNodeAsClaimedByHydration(native);
15516
+ // This element might also be an anchor of a view container.
15517
+ if (getSerializedContainerViews(hydrationInfo, index)) {
15518
+ // Important note: this element acts as an anchor, but it's **not** a part
15519
+ // of the embedded view, so we start the segment **after** this element, taking
15520
+ // a reference to the next sibling. For example, the following template:
15521
+ // `<div #vcrTarget>` is represented in the DOM as `<div></div>...<!--container-->`,
15522
+ // so while processing a `<div>` instruction, point to the next sibling as a
15523
+ // start of a segment.
15524
+ ngDevMode && validateNodeExists(native.nextSibling);
15525
+ setSegmentHead(hydrationInfo, index, native.nextSibling);
15526
+ }
14541
15527
  // Checks if the skip hydration attribute is present during hydration so we know to
14542
15528
  // skip attempting to hydrate this block.
14543
15529
  if (hydrationInfo && hasNgSkipHydrationAttr(tNode)) {
14544
- enterSkipHydrationBlock(tNode);
14545
- // Since this isn't hydratable, we need to empty the node
14546
- // so there's no duplicate content after render
14547
- clearElementContents(renderer, native);
15530
+ if (isComponentHost(tNode)) {
15531
+ enterSkipHydrationBlock(tNode);
15532
+ // Since this isn't hydratable, we need to empty the node
15533
+ // so there's no duplicate content after render
15534
+ clearElementContents(renderer, native);
15535
+ }
15536
+ else if (ngDevMode) {
15537
+ // If this is not a component host, throw an error.
15538
+ // Hydration can be skipped on per-component basis only.
15539
+ throw invalidSkipHydrationHost(native);
15540
+ }
14548
15541
  }
14549
15542
  return native;
14550
15543
  }
@@ -14665,7 +15658,7 @@ let _locateOrCreateElementContainerNode = (tView, lView, tNode, index) => {
14665
15658
  function locateOrCreateElementContainerNode(tView, lView, tNode, index) {
14666
15659
  let comment;
14667
15660
  const hydrationInfo = lView[HYDRATION];
14668
- const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock();
15661
+ const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1();
14669
15662
  lastNodeWasCreated(isNodeCreationMode);
14670
15663
  // Regular creation mode.
14671
15664
  if (isNodeCreationMode) {
@@ -14673,21 +15666,17 @@ function locateOrCreateElementContainerNode(tView, lView, tNode, index) {
14673
15666
  }
14674
15667
  // Hydration mode, looking up existing elements in DOM.
14675
15668
  const currentRNode = locateNextRNode(hydrationInfo, tView, lView, tNode);
15669
+ ngDevMode && validateNodeExists(currentRNode);
14676
15670
  const ngContainerSize = getNgContainerSize(hydrationInfo, index);
14677
15671
  ngDevMode &&
14678
15672
  assertNumber(ngContainerSize, 'Unexpected state: hydrating an <ng-container>, ' +
14679
15673
  'but no hydration info is available.');
14680
- if (ngContainerSize > 0) {
14681
- storeNgContainerInfo(hydrationInfo, index, currentRNode);
14682
- comment = siblingAfter(ngContainerSize, currentRNode);
14683
- }
14684
- else {
14685
- // If <ng-container> has no nodes,
14686
- // the current node is an anchor (comment) node.
14687
- comment = currentRNode;
15674
+ setSegmentHead(hydrationInfo, index, currentRNode);
15675
+ comment = siblingAfter(ngContainerSize, currentRNode);
15676
+ if (ngDevMode) {
15677
+ validateMatchingNode(comment, Node.COMMENT_NODE, null, lView, tNode);
15678
+ markRNodeAsClaimedByHydration(comment);
14688
15679
  }
14689
- ngDevMode && validateMatchingNode(comment, Node.COMMENT_NODE, null, lView, tNode);
14690
- ngDevMode && markRNodeAsClaimedByHydration(comment);
14691
15680
  return comment;
14692
15681
  }
14693
15682
  function enableLocateOrCreateElementContainerNodeImpl() {
@@ -15064,7 +16053,10 @@ function ɵɵprojection(nodeIndex, selectorIndex = 0, attrs) {
15064
16053
  tProjectionNode.projection = selectorIndex;
15065
16054
  // `<ng-content>` has no content
15066
16055
  setCurrentTNodeAsNotParent();
15067
- if ((tProjectionNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */) {
16056
+ const hydrationInfo = lView[HYDRATION];
16057
+ const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1();
16058
+ if (isNodeCreationMode &&
16059
+ (tProjectionNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */) {
15068
16060
  // re-distribution of projectable nodes is stored on a component's view level
15069
16061
  applyProjection(tView, lView, tProjectionNode);
15070
16062
  }
@@ -17027,7 +18019,7 @@ function ɵɵtext(index, value = '') {
17027
18019
  const tNode = tView.firstCreatePass ?
17028
18020
  getOrCreateTNode(tView, adjustedIndex, 1 /* TNodeType.Text */, value, null) :
17029
18021
  tView.data[adjustedIndex];
17030
- const textNative = _locateOrCreateTextNode(tView, lView, tNode, value);
18022
+ const textNative = _locateOrCreateTextNode(tView, lView, tNode, value, index);
17031
18023
  lView[adjustedIndex] = textNative;
17032
18024
  if (wasLastNodeCreated()) {
17033
18025
  appendChild(tView, lView, textNative, tNode);
@@ -17035,7 +18027,7 @@ function ɵɵtext(index, value = '') {
17035
18027
  // Text nodes are self closing.
17036
18028
  setCurrentTNode(tNode, false);
17037
18029
  }
17038
- let _locateOrCreateTextNode = (tView, lView, tNode, value) => {
18030
+ let _locateOrCreateTextNode = (tView, lView, tNode, value, index) => {
17039
18031
  lastNodeWasCreated(true);
17040
18032
  return createTextNode(lView[RENDERER], value);
17041
18033
  };
@@ -17043,9 +18035,9 @@ let _locateOrCreateTextNode = (tView, lView, tNode, value) => {
17043
18035
  * Enables hydration code path (to lookup existing elements in DOM)
17044
18036
  * in addition to the regular creation mode of text nodes.
17045
18037
  */
17046
- function locateOrCreateTextNodeImpl(tView, lView, tNode, value) {
18038
+ function locateOrCreateTextNodeImpl(tView, lView, tNode, value, index) {
17047
18039
  const hydrationInfo = lView[HYDRATION];
17048
- const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock();
18040
+ const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode(hydrationInfo, index);
17049
18041
  lastNodeWasCreated(isNodeCreationMode);
17050
18042
  // Regular creation mode.
17051
18043
  if (isNodeCreationMode) {
@@ -20713,7 +21705,7 @@ class NgModuleFactory$1 {
20713
21705
  * @publicApi
20714
21706
  */
20715
21707
  function createNgModule(ngModule, parentInjector) {
20716
- return new NgModuleRef(ngModule, parentInjector ?? null);
21708
+ return new NgModuleRef(ngModule, parentInjector ?? null, []);
20717
21709
  }
20718
21710
  /**
20719
21711
  * The `createNgModule` function alias for backwards-compatibility.
@@ -20723,7 +21715,7 @@ function createNgModule(ngModule, parentInjector) {
20723
21715
  */
20724
21716
  const createNgModuleRef = createNgModule;
20725
21717
  class NgModuleRef extends NgModuleRef$1 {
20726
- constructor(ngModuleType, _parent) {
21718
+ constructor(ngModuleType, _parent, additionalProviders) {
20727
21719
  super();
20728
21720
  this._parent = _parent;
20729
21721
  // tslint:disable-next-line:require-internal-with-underscore
@@ -20744,7 +21736,8 @@ class NgModuleRef extends NgModuleRef$1 {
20744
21736
  { provide: NgModuleRef$1, useValue: this }, {
20745
21737
  provide: ComponentFactoryResolver$1,
20746
21738
  useValue: this.componentFactoryResolver
20747
- }
21739
+ },
21740
+ ...additionalProviders
20748
21741
  ], stringify(ngModuleType), new Set(['environment']));
20749
21742
  // We need to resolve the injector types separately from the injector creation, because
20750
21743
  // the module might be trying to use this ref in its constructor for DI which will cause a
@@ -20773,9 +21766,12 @@ class NgModuleFactory extends NgModuleFactory$1 {
20773
21766
  this.moduleType = moduleType;
20774
21767
  }
20775
21768
  create(parentInjector) {
20776
- return new NgModuleRef(this.moduleType, parentInjector);
21769
+ return new NgModuleRef(this.moduleType, parentInjector, []);
20777
21770
  }
20778
21771
  }
21772
+ function createNgModuleRefWithProviders(moduleType, parentInjector, additionalProviders) {
21773
+ return new NgModuleRef(moduleType, parentInjector, additionalProviders);
21774
+ }
20779
21775
  class EnvironmentNgModuleRefAdapter extends NgModuleRef$1 {
20780
21776
  constructor(providers, parent, source) {
20781
21777
  super();
@@ -21223,7 +22219,8 @@ function sortListeners(a, b) {
21223
22219
  * See call site for more info.
21224
22220
  */
21225
22221
  function isDirectiveDefHack(obj) {
21226
- return obj.type !== undefined && obj.template !== undefined && obj.declaredInputs !== undefined;
22222
+ return obj.type !== undefined && obj.declaredInputs !== undefined &&
22223
+ obj.findHostDirectiveDefs !== undefined;
21227
22224
  }
21228
22225
  /**
21229
22226
  * Retrieve the component `LView` from component/element.
@@ -22060,9 +23057,24 @@ const R3TemplateRef = class TemplateRef extends ViewEngineTemplateRef {
22060
23057
  this._declarationTContainer = _declarationTContainer;
22061
23058
  this.elementRef = elementRef;
22062
23059
  }
23060
+ /**
23061
+ * Returns an `ssrId` associated with a TView, which was used to
23062
+ * create this instance of the `TemplateRef`.
23063
+ *
23064
+ * @internal
23065
+ */
23066
+ get ssrId() {
23067
+ return this._declarationTContainer.tView?.ssrId || null;
23068
+ }
22063
23069
  createEmbeddedView(context, injector) {
23070
+ return this.createEmbeddedViewImpl(context, injector, null);
23071
+ }
23072
+ /**
23073
+ * @internal
23074
+ */
23075
+ createEmbeddedViewImpl(context, injector, hydrationInfo) {
22064
23076
  const embeddedTView = this._declarationTContainer.tView;
22065
- const embeddedLView = createLView(this._declarationLView, embeddedTView, context, 16 /* LViewFlags.CheckAlways */, null, embeddedTView.declTNode, null, null, null, null, injector || null, null);
23077
+ const embeddedLView = createLView(this._declarationLView, embeddedTView, context, 16 /* LViewFlags.CheckAlways */, null, embeddedTView.declTNode, null, null, null, null, injector || null, hydrationInfo || null);
22066
23078
  const declarationLContainer = this._declarationLView[this._declarationTContainer.index];
22067
23079
  ngDevMode && assertLContainer(declarationLContainer);
22068
23080
  embeddedLView[DECLARATION_LCONTAINER] = declarationLContainer;
@@ -22097,6 +23109,164 @@ function createTemplateRef(hostTNode, hostLView) {
22097
23109
  return null;
22098
23110
  }
22099
23111
 
23112
+ /**
23113
+ * Removes all dehydrated views from a given LContainer:
23114
+ * both in internal data structure, as well as removing
23115
+ * corresponding DOM nodes that belong to that dehydrated view.
23116
+ */
23117
+ function removeDehydratedViews(lContainer) {
23118
+ const views = lContainer[DEHYDRATED_VIEWS] ?? [];
23119
+ const parentLView = lContainer[PARENT];
23120
+ const renderer = parentLView[RENDERER];
23121
+ for (const view of views) {
23122
+ removeDehydratedView(view, renderer);
23123
+ ngDevMode && ngDevMode.dehydratedViewsRemoved++;
23124
+ }
23125
+ // Reset the value to an empty array to indicate that no
23126
+ // further processing of dehydrated views is needed for
23127
+ // this view container (i.e. do not trigger the lookup process
23128
+ // once again in case a `ViewContainerRef` is created later).
23129
+ lContainer[DEHYDRATED_VIEWS] = EMPTY_ARRAY;
23130
+ }
23131
+ /**
23132
+ * Helper function to remove all nodes from a dehydrated view.
23133
+ */
23134
+ function removeDehydratedView(dehydratedView, renderer) {
23135
+ let nodesRemoved = 0;
23136
+ let currentRNode = dehydratedView.firstChild;
23137
+ if (currentRNode) {
23138
+ const numNodes = dehydratedView.data[NUM_ROOT_NODES];
23139
+ while (nodesRemoved < numNodes) {
23140
+ ngDevMode && validateSiblingNodeExists(currentRNode);
23141
+ const nextSibling = currentRNode.nextSibling;
23142
+ nativeRemoveNode(renderer, currentRNode, false);
23143
+ currentRNode = nextSibling;
23144
+ nodesRemoved++;
23145
+ }
23146
+ }
23147
+ }
23148
+ /**
23149
+ * Walks over all views within this LContainer invokes dehydrated views
23150
+ * cleanup function for each one.
23151
+ */
23152
+ function cleanupLContainer(lContainer) {
23153
+ removeDehydratedViews(lContainer);
23154
+ for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
23155
+ cleanupLView(lContainer[i]);
23156
+ }
23157
+ }
23158
+ /**
23159
+ * Walks over `LContainer`s and components registered within
23160
+ * this LView and invokes dehydrated views cleanup function for each one.
23161
+ */
23162
+ function cleanupLView(lView) {
23163
+ const tView = lView[TVIEW];
23164
+ for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
23165
+ if (isLContainer(lView[i])) {
23166
+ const lContainer = lView[i];
23167
+ cleanupLContainer(lContainer);
23168
+ }
23169
+ else if (Array.isArray(lView[i])) {
23170
+ // This is a component, enter the `cleanupLView` recursively.
23171
+ cleanupLView(lView[i]);
23172
+ }
23173
+ }
23174
+ }
23175
+ /**
23176
+ * Walks over all views registered within the ApplicationRef and removes
23177
+ * all dehydrated views from all `LContainer`s along the way.
23178
+ */
23179
+ function cleanupDehydratedViews(appRef) {
23180
+ // Wait once an app becomes stable and cleanup all views that
23181
+ // were not claimed during the application bootstrap process.
23182
+ // The timing is similar to when we kick off serialization on the server.
23183
+ return appRef.isStable.pipe(first((isStable) => isStable)).toPromise().then(() => {
23184
+ const viewRefs = appRef._views;
23185
+ for (const viewRef of viewRefs) {
23186
+ const lView = getComponentLViewForHydration(viewRef);
23187
+ // An `lView` might be `null` if a `ViewRef` represents
23188
+ // an embedded view (not a component view).
23189
+ if (lView !== null && lView[HOST] !== null) {
23190
+ cleanupLView(lView);
23191
+ ngDevMode && ngDevMode.dehydratedViewsCleanupRuns++;
23192
+ }
23193
+ }
23194
+ });
23195
+ }
23196
+
23197
+ /**
23198
+ * Given a current DOM node and a serialized information about the views
23199
+ * in a container, walks over the DOM structure, collecting the list of
23200
+ * dehydrated views.
23201
+ */
23202
+ function locateDehydratedViewsInContainer(currentRNode, serializedViews) {
23203
+ const dehydratedViews = [];
23204
+ for (const serializedView of serializedViews) {
23205
+ // Repeats a view multiple times as needed, based on the serialized information
23206
+ // (for example, for *ngFor-produced views).
23207
+ for (let i = 0; i < (serializedView[MULTIPLIER] ?? 1); i++) {
23208
+ const view = {
23209
+ data: serializedView,
23210
+ firstChild: null,
23211
+ };
23212
+ if (serializedView[NUM_ROOT_NODES] > 0) {
23213
+ // Keep reference to the first node in this view,
23214
+ // so it can be accessed while invoking template instructions.
23215
+ view.firstChild = currentRNode;
23216
+ // Move over to the next node after this view, which can
23217
+ // either be a first node of the next view or an anchor comment
23218
+ // node after the last view in a container.
23219
+ currentRNode = siblingAfter(serializedView[NUM_ROOT_NODES], currentRNode);
23220
+ }
23221
+ dehydratedViews.push(view);
23222
+ }
23223
+ }
23224
+ return [currentRNode, dehydratedViews];
23225
+ }
23226
+ /**
23227
+ * Reference to a function that searches for a matching dehydrated views
23228
+ * stored on a given lContainer.
23229
+ * Returns `null` by default, when hydration is not enabled.
23230
+ */
23231
+ let _findMatchingDehydratedViewImpl = (lContainer, template) => null;
23232
+ /**
23233
+ * Retrieves the next dehydrated view from the LContainer and verifies that
23234
+ * it matches a given template id (from the TView that was used to create this
23235
+ * instance of a view). If the id doesn't match, that means that we are in an
23236
+ * unexpected state and can not complete the reconciliation process. Thus,
23237
+ * all dehydrated views from this LContainer are removed (including corresponding
23238
+ * DOM nodes) and the rendering is performed as if there were no dehydrated views
23239
+ * in this container.
23240
+ */
23241
+ function findMatchingDehydratedViewImpl(lContainer, template) {
23242
+ const views = lContainer[DEHYDRATED_VIEWS] ?? [];
23243
+ if (!template || views.length === 0) {
23244
+ return null;
23245
+ }
23246
+ const view = views[0];
23247
+ // Verify whether the first dehydrated view in the container matches
23248
+ // the template id passed to this function (that originated from a TView
23249
+ // that was used to create an instance of an embedded or component views.
23250
+ if (view.data[TEMPLATE_ID] === template) {
23251
+ // If the template id matches - extract the first view and return it.
23252
+ return views.shift();
23253
+ }
23254
+ else {
23255
+ // Otherwise, we are at the state when reconciliation can not be completed,
23256
+ // thus we remove all dehydrated views within this container (remove them
23257
+ // from internal data structures as well as delete associated elements from
23258
+ // the DOM tree).
23259
+ removeDehydratedViews(lContainer);
23260
+ return null;
23261
+ }
23262
+ }
23263
+ function enableFindMatchingDehydratedViewImpl() {
23264
+ _findMatchingDehydratedViewImpl = findMatchingDehydratedViewImpl;
23265
+ }
23266
+ function findMatchingDehydratedView(lContainer, template) {
23267
+ return _findMatchingDehydratedViewImpl(lContainer, template);
23268
+ }
23269
+
22100
23270
  /**
22101
23271
  * Represents a container where one or more views can be attached to a component.
22102
23272
  *
@@ -22181,8 +23351,9 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
22181
23351
  index = indexOrOptions.index;
22182
23352
  injector = indexOrOptions.injector;
22183
23353
  }
22184
- const viewRef = templateRef.createEmbeddedView(context || {}, injector);
22185
- this.insert(viewRef, index);
23354
+ const hydrationInfo = findMatchingDehydratedView(this._lContainer, templateRef.ssrId);
23355
+ const viewRef = templateRef.createEmbeddedViewImpl(context || {}, injector, hydrationInfo);
23356
+ this.insertImpl(viewRef, index, !!hydrationInfo);
22186
23357
  return viewRef;
22187
23358
  }
22188
23359
  createComponent(componentFactoryOrType, indexOrOptions, injector, projectableNodes, environmentInjector) {
@@ -22252,11 +23423,17 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
22252
23423
  environmentInjector = result;
22253
23424
  }
22254
23425
  }
22255
- const componentRef = componentFactory.create(contextInjector, projectableNodes, undefined, environmentInjector);
22256
- this.insert(componentRef.hostView, index);
23426
+ const componentDef = getComponentDef$1(componentFactory.componentType ?? {});
23427
+ const dehydratedView = findMatchingDehydratedView(this._lContainer, componentDef?.id ?? null);
23428
+ const rNode = dehydratedView?.firstChild ?? null;
23429
+ const componentRef = componentFactory.create(contextInjector, projectableNodes, rNode, environmentInjector);
23430
+ this.insertImpl(componentRef.hostView, index, !!dehydratedView);
22257
23431
  return componentRef;
22258
23432
  }
22259
23433
  insert(viewRef, index) {
23434
+ return this.insertImpl(viewRef, index, false);
23435
+ }
23436
+ insertImpl(viewRef, index, skipDomInsertion) {
22260
23437
  const lView = viewRef._lView;
22261
23438
  const tView = lView[TVIEW];
22262
23439
  if (ngDevMode && viewRef.destroyed) {
@@ -22287,11 +23464,13 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
22287
23464
  const lContainer = this._lContainer;
22288
23465
  insertView(tView, lView, lContainer, adjustedIdx);
22289
23466
  // Physical operation of adding the DOM nodes.
22290
- const beforeNode = getBeforeNodeForView(adjustedIdx, lContainer);
22291
- const renderer = lView[RENDERER];
22292
- const parentRNode = nativeParentNode(renderer, lContainer[NATIVE]);
22293
- if (parentRNode !== null) {
22294
- addViewToContainer(tView, lContainer[T_HOST], renderer, lView, parentRNode, beforeNode);
23467
+ if (!skipDomInsertion) {
23468
+ const beforeNode = getBeforeNodeForView(adjustedIdx, lContainer);
23469
+ const renderer = lView[RENDERER];
23470
+ const parentRNode = nativeParentNode(renderer, lContainer[NATIVE]);
23471
+ if (parentRNode !== null) {
23472
+ addViewToContainer(tView, lContainer[T_HOST], renderer, lView, parentRNode, beforeNode);
23473
+ }
22295
23474
  }
22296
23475
  viewRef.attachToViewContainerRef();
22297
23476
  addToArray(getOrCreateViewRefs(lContainer), adjustedIdx, viewRef);
@@ -22348,8 +23527,6 @@ function getOrCreateViewRefs(lContainer) {
22348
23527
  /**
22349
23528
  * Creates a ViewContainerRef and stores it on the injector.
22350
23529
  *
22351
- * @param ViewContainerRefToken The ViewContainerRef type
22352
- * @param ElementRefToken The ElementRef type
22353
23530
  * @param hostTNode The node that is requesting a ViewContainerRef
22354
23531
  * @param hostLView The view to which the node belongs
22355
23532
  * @returns The ViewContainerRef instance to use
@@ -22363,31 +23540,95 @@ function createContainerRef(hostTNode, hostLView) {
22363
23540
  lContainer = slotValue;
22364
23541
  }
22365
23542
  else {
22366
- let commentNode;
22367
- // If the host is an element container, the native host element is guaranteed to be a
22368
- // comment and we can reuse that comment as anchor element for the new LContainer.
22369
- // The comment node in question is already part of the DOM structure so we don't need to append
22370
- // it again.
22371
- if (hostTNode.type & 8 /* TNodeType.ElementContainer */) {
22372
- commentNode = unwrapRNode(slotValue);
22373
- }
22374
- else {
22375
- // If the host is a regular element, we have to insert a comment node manually which will
22376
- // be used as an anchor when inserting elements. In this specific case we use low-level DOM
22377
- // manipulation to insert it.
22378
- const renderer = hostLView[RENDERER];
22379
- ngDevMode && ngDevMode.rendererCreateComment++;
22380
- commentNode = renderer.createComment(ngDevMode ? 'container' : '');
22381
- const hostNative = getNativeByTNode(hostTNode, hostLView);
22382
- const parentOfHostNative = nativeParentNode(renderer, hostNative);
22383
- nativeInsertBefore(renderer, parentOfHostNative, commentNode, nativeNextSibling(renderer, hostNative), false);
22384
- }
22385
- hostLView[hostTNode.index] = lContainer =
22386
- createLContainer(slotValue, hostLView, commentNode, hostTNode);
23543
+ // An LContainer anchor can not be `null`, but we set it here temporarily
23544
+ // and update to the actual value later in this function (see
23545
+ // `_locateOrCreateAnchorNode`).
23546
+ lContainer = createLContainer(slotValue, hostLView, null, hostTNode);
23547
+ hostLView[hostTNode.index] = lContainer;
22387
23548
  addToViewTree(hostLView, lContainer);
22388
23549
  }
23550
+ _locateOrCreateAnchorNode(lContainer, hostLView, hostTNode, slotValue);
22389
23551
  return new R3ViewContainerRef(lContainer, hostTNode, hostLView);
22390
23552
  }
23553
+ /**
23554
+ * Creates and inserts a comment node that acts as an anchor for a view container.
23555
+ *
23556
+ * If the host is a regular element, we have to insert a comment node manually which will
23557
+ * be used as an anchor when inserting elements. In this specific case we use low-level DOM
23558
+ * manipulation to insert it.
23559
+ */
23560
+ function insertAnchorNode(hostLView, hostTNode) {
23561
+ const renderer = hostLView[RENDERER];
23562
+ ngDevMode && ngDevMode.rendererCreateComment++;
23563
+ const commentNode = renderer.createComment(ngDevMode ? 'container' : '');
23564
+ const hostNative = getNativeByTNode(hostTNode, hostLView);
23565
+ const parentOfHostNative = nativeParentNode(renderer, hostNative);
23566
+ nativeInsertBefore(renderer, parentOfHostNative, commentNode, nativeNextSibling(renderer, hostNative), false);
23567
+ return commentNode;
23568
+ }
23569
+ let _locateOrCreateAnchorNode = createAnchorNode;
23570
+ /**
23571
+ * Regular creation mode: an anchor is created and
23572
+ * assigned to the `lContainer[NATIVE]` slot.
23573
+ */
23574
+ function createAnchorNode(lContainer, hostLView, hostTNode, slotValue) {
23575
+ // We already have a native element (anchor) set, return.
23576
+ if (lContainer[NATIVE])
23577
+ return;
23578
+ let commentNode;
23579
+ // If the host is an element container, the native host element is guaranteed to be a
23580
+ // comment and we can reuse that comment as anchor element for the new LContainer.
23581
+ // The comment node in question is already part of the DOM structure so we don't need to append
23582
+ // it again.
23583
+ if (hostTNode.type & 8 /* TNodeType.ElementContainer */) {
23584
+ commentNode = unwrapRNode(slotValue);
23585
+ }
23586
+ else {
23587
+ commentNode = insertAnchorNode(hostLView, hostTNode);
23588
+ }
23589
+ lContainer[NATIVE] = commentNode;
23590
+ }
23591
+ /**
23592
+ * Hydration logic that looks up:
23593
+ * - an anchor node in the DOM and stores the node in `lContainer[NATIVE]`
23594
+ * - all dehydrated views in this container and puts them into `lContainer[DEHYDRATED_VIEWS]`
23595
+ */
23596
+ function locateOrCreateAnchorNode(lContainer, hostLView, hostTNode, slotValue) {
23597
+ // We already have a native element (anchor) set and the process
23598
+ // of finding dehydrated views happened (so the `lContainer[DEHYDRATED_VIEWS]`
23599
+ // is not null), exit early.
23600
+ if (lContainer[NATIVE] && lContainer[DEHYDRATED_VIEWS])
23601
+ return;
23602
+ const hydrationInfo = hostLView[HYDRATION];
23603
+ const noOffsetIndex = hostTNode.index - HEADER_OFFSET;
23604
+ const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock(hostTNode) ||
23605
+ isDisconnectedNode(hydrationInfo, noOffsetIndex);
23606
+ // Regular creation mode.
23607
+ if (isNodeCreationMode) {
23608
+ return createAnchorNode(lContainer, hostLView, hostTNode, slotValue);
23609
+ }
23610
+ // Hydration mode, looking up an anchor node and dehydrated views in DOM.
23611
+ const currentRNode = getSegmentHead(hydrationInfo, noOffsetIndex);
23612
+ const serializedViews = hydrationInfo.data[CONTAINERS]?.[noOffsetIndex];
23613
+ ngDevMode &&
23614
+ assertDefined(serializedViews, 'Unexpected state: no hydration info available for a given TNode, ' +
23615
+ 'which represents a view container.');
23616
+ const [commentNode, dehydratedViews] = locateDehydratedViewsInContainer(currentRNode, serializedViews);
23617
+ if (ngDevMode) {
23618
+ validateMatchingNode(commentNode, Node.COMMENT_NODE, null, hostLView, hostTNode, true);
23619
+ // Do not throw in case this node is already claimed (thus `false` as a second
23620
+ // argument). If this container is created based on an `<ng-template>`, the comment
23621
+ // node would be already claimed from the `template` instruction. If an element acts
23622
+ // as an anchor (e.g. <div #vcRef>), a separate comment node would be created/located,
23623
+ // so we need to claim it here.
23624
+ markRNodeAsClaimedByHydration(commentNode, false);
23625
+ }
23626
+ lContainer[NATIVE] = commentNode;
23627
+ lContainer[DEHYDRATED_VIEWS] = dehydratedViews;
23628
+ }
23629
+ function enableLocateOrCreateContainerRefImpl() {
23630
+ _locateOrCreateAnchorNode = locateOrCreateAnchorNode;
23631
+ }
22391
23632
 
22392
23633
  class LQuery_ {
22393
23634
  constructor(queryList) {
@@ -23365,6 +24606,7 @@ function resetCompiledComponents() {
23365
24606
  ownerNgModule = new WeakMap();
23366
24607
  verifiedNgModule = new WeakMap();
23367
24608
  moduleQueue.length = 0;
24609
+ GENERATED_COMP_IDS.clear();
23368
24610
  }
23369
24611
  /**
23370
24612
  * Computes the combined declarations of explicit declarations, as well as declarations inherited by
@@ -23952,7 +25194,7 @@ class TestBedCompiler {
23952
25194
  // every component.
23953
25195
  this.componentToModuleScope.clear();
23954
25196
  const parentInjector = this.platform.injector;
23955
- this.testModuleRef = new ɵRender3NgModuleRef(this.testModuleType, parentInjector);
25197
+ this.testModuleRef = new ɵRender3NgModuleRef(this.testModuleType, parentInjector, []);
23956
25198
  // ApplicationInitStatus.runInitializers() is marked @internal to core.
23957
25199
  // Cast it to any before accessing it.
23958
25200
  this.testModuleRef.injector.get(ApplicationInitStatus).runInitializers();
@@ -24211,10 +25453,11 @@ class TestBedCompiler {
24211
25453
  }
24212
25454
  }
24213
25455
  queueTypesFromModulesArray(arr) {
24214
- // Because we may encounter the same NgModule while processing the imports and exports of an
24215
- // NgModule tree, we cache them in this set so we can skip ones that have already been seen
24216
- // encountered. In some test setups, this caching resulted in 10X runtime improvement.
24217
- const processedNgModuleDefs = new Set();
25456
+ // Because we may encounter the same NgModule or a standalone Component while processing
25457
+ // the dependencies of an NgModule or a standalone Component, we cache them in this set so we
25458
+ // can skip ones that have already been seen encountered. In some test setups, this caching
25459
+ // resulted in 10X runtime improvement.
25460
+ const processedDefs = new Set();
24218
25461
  const queueTypesFromModulesArrayRecur = (arr) => {
24219
25462
  for (const value of arr) {
24220
25463
  if (Array.isArray(value)) {
@@ -24222,10 +25465,10 @@ class TestBedCompiler {
24222
25465
  }
24223
25466
  else if (hasNgModuleDef(value)) {
24224
25467
  const def = value.ɵmod;
24225
- if (processedNgModuleDefs.has(def)) {
25468
+ if (processedDefs.has(def)) {
24226
25469
  continue;
24227
25470
  }
24228
- processedNgModuleDefs.add(def);
25471
+ processedDefs.add(def);
24229
25472
  // Look through declarations, imports, and exports, and queue
24230
25473
  // everything found there.
24231
25474
  this.queueTypeArray(maybeUnwrapFn(def.declarations), value);
@@ -24238,6 +25481,10 @@ class TestBedCompiler {
24238
25481
  else if (isStandaloneComponent(value)) {
24239
25482
  this.queueType(value, null);
24240
25483
  const def = getComponentDef(value);
25484
+ if (processedDefs.has(def)) {
25485
+ continue;
25486
+ }
25487
+ processedDefs.add(def);
24241
25488
  const dependencies = maybeUnwrapFn(def.dependencies ?? []);
24242
25489
  dependencies.forEach((dependency) => {
24243
25490
  // Note: in AOT, the `dependencies` might also contain regular
@@ -24376,7 +25623,7 @@ class TestBedCompiler {
24376
25623
  });
24377
25624
  const ngZone = new NgZone({ enableLongStackTrace: true });
24378
25625
  const providers = [
24379
- { provide: NgZone, useValue: ngZone },
25626
+ ɵprovideNgZoneChangeDetection(ngZone),
24380
25627
  { provide: Compiler, useFactory: () => new R3TestCompiler(this) },
24381
25628
  ...this.providers,
24382
25629
  ...this.providerOverrides,