@angular/core 16.2.3 → 16.2.5

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 (36) hide show
  1. package/esm2022/src/linker/view_container_ref.mjs +35 -1
  2. package/esm2022/src/render3/after_render_hooks.mjs +83 -49
  3. package/esm2022/src/render3/component.mjs +4 -3
  4. package/esm2022/src/render3/di.mjs +1 -1
  5. package/esm2022/src/render3/instructions/change_detection.mjs +4 -4
  6. package/esm2022/src/render3/instructions/shared.mjs +20 -14
  7. package/esm2022/src/render3/interfaces/injector.mjs +1 -1
  8. package/esm2022/src/render3/interfaces/styling.mjs +4 -7
  9. package/esm2022/src/render3/node_manipulation.mjs +4 -3
  10. package/esm2022/src/render3/reactive_lview_consumer.mjs +25 -45
  11. package/esm2022/src/render3/reactivity/effect.mjs +8 -8
  12. package/esm2022/src/render3/util/injector_utils.mjs +1 -1
  13. package/esm2022/src/signals/index.mjs +4 -4
  14. package/esm2022/src/signals/src/api.mjs +2 -11
  15. package/esm2022/src/signals/src/computed.mjs +43 -93
  16. package/esm2022/src/signals/src/graph.mjs +238 -162
  17. package/esm2022/src/signals/src/signal.mjs +59 -79
  18. package/esm2022/src/signals/src/watch.mjs +38 -52
  19. package/esm2022/src/signals/src/weak_ref.mjs +2 -29
  20. package/esm2022/src/util/security/trusted_type_defs.mjs +1 -1
  21. package/esm2022/src/util/security/trusted_types.mjs +1 -1
  22. package/esm2022/src/version.mjs +1 -1
  23. package/esm2022/src/zone/ng_zone.mjs +16 -1
  24. package/esm2022/testing/src/logger.mjs +3 -3
  25. package/fesm2022/core.mjs +616 -591
  26. package/fesm2022/core.mjs.map +1 -1
  27. package/fesm2022/rxjs-interop.mjs +373 -413
  28. package/fesm2022/rxjs-interop.mjs.map +1 -1
  29. package/fesm2022/testing.mjs +614 -590
  30. package/fesm2022/testing.mjs.map +1 -1
  31. package/index.d.ts +117 -121
  32. package/package.json +1 -1
  33. package/rxjs-interop/index.d.ts +1 -1
  34. package/schematics/ng-generate/standalone-migration/bundle.js +9 -9
  35. package/schematics/ng-generate/standalone-migration/bundle.js.map +1 -1
  36. package/testing/index.d.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Angular v16.2.3
2
+ * @license Angular v16.2.5
3
3
  * (c) 2010-2022 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
@@ -3565,15 +3565,6 @@ const SIGNAL = Symbol('SIGNAL');
3565
3565
  function isSignal(value) {
3566
3566
  return typeof value === 'function' && value[SIGNAL] !== undefined;
3567
3567
  }
3568
- /**
3569
- * Converts `fn` into a marked signal function (where `isSignal(fn)` will be `true`), and
3570
- * potentially add some set of extra properties (passed as an object record `extraApi`).
3571
- */
3572
- function createSignalFromFunction(node, fn, extraApi = {}) {
3573
- fn[SIGNAL] = node;
3574
- // Copy properties from `extraApi` to `fn` to complete the desired API of the `Signal`.
3575
- return Object.assign(fn, extraApi);
3576
- }
3577
3568
  /**
3578
3569
  * The default equality function used for `signal` and `computed`, which treats objects and arrays
3579
3570
  * as never equal, and all other primitive values using identity semantics.
@@ -3594,217 +3585,266 @@ function defaultEquals(a, b) {
3594
3585
 
3595
3586
  // Required as the signals library is in a separate package, so we need to explicitly ensure the
3596
3587
  /**
3597
- * A `WeakRef`-compatible reference that fakes the API with a strong reference
3598
- * internally.
3588
+ * The currently active consumer `ReactiveNode`, if running code in a reactive context.
3589
+ *
3590
+ * Change this via `setActiveConsumer`.
3599
3591
  */
3600
- class LeakyRef {
3601
- constructor(ref) {
3602
- this.ref = ref;
3592
+ let activeConsumer = null;
3593
+ let inNotificationPhase = false;
3594
+ function setActiveConsumer(consumer) {
3595
+ const prev = activeConsumer;
3596
+ activeConsumer = consumer;
3597
+ return prev;
3598
+ }
3599
+ const REACTIVE_NODE = {
3600
+ version: 0,
3601
+ dirty: false,
3602
+ producerNode: undefined,
3603
+ producerLastReadVersion: undefined,
3604
+ producerIndexOfThis: undefined,
3605
+ nextProducerIndex: 0,
3606
+ liveConsumerNode: undefined,
3607
+ liveConsumerIndexOfThis: undefined,
3608
+ consumerAllowSignalWrites: false,
3609
+ consumerIsAlwaysLive: false,
3610
+ producerMustRecompute: () => false,
3611
+ producerRecomputeValue: () => { },
3612
+ consumerMarkedDirty: () => { },
3613
+ };
3614
+ /**
3615
+ * Called by implementations when a producer's signal is read.
3616
+ */
3617
+ function producerAccessed(node) {
3618
+ if (inNotificationPhase) {
3619
+ throw new Error(typeof ngDevMode !== 'undefined' && ngDevMode ?
3620
+ `Assertion error: signal read during notification phase` :
3621
+ '');
3603
3622
  }
3604
- deref() {
3605
- return this.ref;
3623
+ if (activeConsumer === null) {
3624
+ // Accessed outside of a reactive context, so nothing to record.
3625
+ return;
3606
3626
  }
3607
- }
3608
- // `WeakRef` is not always defined in every TS environment where Angular is compiled. Instead,
3609
- // read it off of the global context if available.
3610
- // tslint:disable-next-line: no-toplevel-property-access
3611
- let WeakRefImpl = _global['WeakRef'] ?? LeakyRef;
3612
- function newWeakRef(value) {
3613
- if (typeof ngDevMode !== 'undefined' && ngDevMode && WeakRefImpl === undefined) {
3614
- throw new Error(`Angular requires a browser which supports the 'WeakRef' API`);
3627
+ // This producer is the `idx`th dependency of `activeConsumer`.
3628
+ const idx = activeConsumer.nextProducerIndex++;
3629
+ assertConsumerNode(activeConsumer);
3630
+ if (idx < activeConsumer.producerNode.length && activeConsumer.producerNode[idx] !== node) {
3631
+ // There's been a change in producers since the last execution of `activeConsumer`.
3632
+ // `activeConsumer.producerNode[idx]` holds a stale dependency which will be be removed and
3633
+ // replaced with `this`.
3634
+ //
3635
+ // If `activeConsumer` isn't live, then this is a no-op, since we can replace the producer in
3636
+ // `activeConsumer.producerNode` directly. However, if `activeConsumer` is live, then we need
3637
+ // to remove it from the stale producer's `liveConsumer`s.
3638
+ if (consumerIsLive(activeConsumer)) {
3639
+ const staleProducer = activeConsumer.producerNode[idx];
3640
+ producerRemoveLiveConsumerAtIndex(staleProducer, activeConsumer.producerIndexOfThis[idx]);
3641
+ // At this point, the only record of `staleProducer` is the reference at
3642
+ // `activeConsumer.producerNode[idx]` which will be overwritten below.
3643
+ }
3615
3644
  }
3616
- return new WeakRefImpl(value);
3617
- }
3618
- function setAlternateWeakRefImpl(impl) {
3619
- // no-op since the alternate impl is included by default by the framework. Remove once internal
3620
- // migration is complete.
3645
+ if (activeConsumer.producerNode[idx] !== node) {
3646
+ // We're a new dependency of the consumer (at `idx`).
3647
+ activeConsumer.producerNode[idx] = node;
3648
+ // If the active consumer is live, then add it as a live consumer. If not, then use 0 as a
3649
+ // placeholder value.
3650
+ activeConsumer.producerIndexOfThis[idx] =
3651
+ consumerIsLive(activeConsumer) ? producerAddLiveConsumer(node, activeConsumer, idx) : 0;
3652
+ }
3653
+ activeConsumer.producerLastReadVersion[idx] = node.version;
3621
3654
  }
3622
-
3623
- // Required as the signals library is in a separate package, so we need to explicitly ensure the
3624
3655
  /**
3625
- * Counter tracking the next `ProducerId` or `ConsumerId`.
3656
+ * Ensure this producer's `version` is up-to-date.
3626
3657
  */
3627
- let _nextReactiveId = 0;
3658
+ function producerUpdateValueVersion(node) {
3659
+ if (consumerIsLive(node) && !node.dirty) {
3660
+ // A live consumer will be marked dirty by producers, so a clean state means that its version
3661
+ // is guaranteed to be up-to-date.
3662
+ return;
3663
+ }
3664
+ if (!node.producerMustRecompute(node) && !consumerPollProducersForChange(node)) {
3665
+ // None of our producers report a change since the last time they were read, so no
3666
+ // recomputation of our value is necessary, and we can consider ourselves clean.
3667
+ node.dirty = false;
3668
+ return;
3669
+ }
3670
+ node.producerRecomputeValue(node);
3671
+ // After recomputing the value, we're no longer dirty.
3672
+ node.dirty = false;
3673
+ }
3628
3674
  /**
3629
- * Tracks the currently active reactive consumer (or `null` if there is no active
3630
- * consumer).
3675
+ * Propagate a dirty notification to live consumers of this producer.
3631
3676
  */
3632
- let activeConsumer = null;
3677
+ function producerNotifyConsumers(node) {
3678
+ if (node.liveConsumerNode === undefined) {
3679
+ return;
3680
+ }
3681
+ // Prevent signal reads when we're updating the graph
3682
+ const prev = inNotificationPhase;
3683
+ inNotificationPhase = true;
3684
+ try {
3685
+ for (const consumer of node.liveConsumerNode) {
3686
+ if (!consumer.dirty) {
3687
+ consumerMarkDirty(consumer);
3688
+ }
3689
+ }
3690
+ }
3691
+ finally {
3692
+ inNotificationPhase = prev;
3693
+ }
3694
+ }
3633
3695
  /**
3634
- * Whether the graph is currently propagating change notifications.
3696
+ * Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,
3697
+ * based on the current consumer context.
3635
3698
  */
3636
- let inNotificationPhase = false;
3637
- function setActiveConsumer(consumer) {
3638
- const prev = activeConsumer;
3639
- activeConsumer = consumer;
3640
- return prev;
3699
+ function producerUpdatesAllowed() {
3700
+ return activeConsumer?.consumerAllowSignalWrites !== false;
3701
+ }
3702
+ function consumerMarkDirty(node) {
3703
+ node.dirty = true;
3704
+ producerNotifyConsumers(node);
3705
+ node.consumerMarkedDirty?.(node);
3641
3706
  }
3642
3707
  /**
3643
- * A node in the reactive graph.
3644
- *
3645
- * Nodes can be producers of reactive values, consumers of other reactive values, or both.
3646
- *
3647
- * Producers are nodes that produce values, and can be depended upon by consumer nodes.
3648
- *
3649
- * Producers expose a monotonic `valueVersion` counter, and are responsible for incrementing this
3650
- * version when their value semantically changes. Some producers may produce their values lazily and
3651
- * thus at times need to be polled for potential updates to their value (and by extension their
3652
- * `valueVersion`). This is accomplished via the `onProducerUpdateValueVersion` method for
3653
- * implemented by producers, which should perform whatever calculations are necessary to ensure
3654
- * `valueVersion` is up to date.
3655
- *
3656
- * Consumers are nodes that depend on the values of producers and are notified when those values
3657
- * might have changed.
3658
- *
3659
- * Consumers do not wrap the reads they consume themselves, but rather can be set as the active
3660
- * reader via `setActiveConsumer`. Reads of producers that happen while a consumer is active will
3661
- * result in those producers being added as dependencies of that consumer node.
3708
+ * Prepare this consumer to run a computation in its reactive context.
3662
3709
  *
3663
- * The set of dependencies of a consumer is dynamic. Implementers expose a monotonically increasing
3664
- * `trackingVersion` counter, which increments whenever the consumer is about to re-run any reactive
3665
- * reads it needs and establish a new set of dependencies as a result.
3710
+ * Must be called by subclasses which represent reactive computations, before those computations
3711
+ * begin.
3712
+ */
3713
+ function consumerBeforeComputation(node) {
3714
+ node && (node.nextProducerIndex = 0);
3715
+ return setActiveConsumer(node);
3716
+ }
3717
+ /**
3718
+ * Finalize this consumer's state after a reactive computation has run.
3666
3719
  *
3667
- * Producers store the last `trackingVersion` they've seen from `Consumer`s which have read them.
3668
- * This allows a producer to identify whether its record of the dependency is current or stale, by
3669
- * comparing the consumer's `trackingVersion` to the version at which the dependency was
3670
- * last observed.
3720
+ * Must be called by subclasses which represent reactive computations, after those computations
3721
+ * have finished.
3671
3722
  */
3672
- class ReactiveNode {
3673
- constructor() {
3674
- this.id = _nextReactiveId++;
3675
- /**
3676
- * A cached weak reference to this node, which will be used in `ReactiveEdge`s.
3677
- */
3678
- this.ref = newWeakRef(this);
3679
- /**
3680
- * Edges to producers on which this node depends (in its consumer capacity).
3681
- */
3682
- this.producers = new Map();
3683
- /**
3684
- * Edges to consumers on which this node depends (in its producer capacity).
3685
- */
3686
- this.consumers = new Map();
3687
- /**
3688
- * Monotonically increasing counter representing a version of this `Consumer`'s
3689
- * dependencies.
3690
- */
3691
- this.trackingVersion = 0;
3692
- /**
3693
- * Monotonically increasing counter which increases when the value of this `Producer`
3694
- * semantically changes.
3695
- */
3696
- this.valueVersion = 0;
3723
+ function consumerAfterComputation(node, prevConsumer) {
3724
+ setActiveConsumer(prevConsumer);
3725
+ if (!node || node.producerNode === undefined || node.producerIndexOfThis === undefined ||
3726
+ node.producerLastReadVersion === undefined) {
3727
+ return;
3697
3728
  }
3698
- /**
3699
- * Polls dependencies of a consumer to determine if they have actually changed.
3700
- *
3701
- * If this returns `false`, then even though the consumer may have previously been notified of a
3702
- * change, the values of its dependencies have not actually changed and the consumer should not
3703
- * rerun any reactions.
3704
- */
3705
- consumerPollProducersForChange() {
3706
- for (const [producerId, edge] of this.producers) {
3707
- const producer = edge.producerNode.deref();
3708
- // On Safari < 16.1 deref can return null, we need to check for null also.
3709
- // See https://github.com/WebKit/WebKit/commit/44c15ba58912faab38b534fef909dd9e13e095e0
3710
- if (producer == null || edge.atTrackingVersion !== this.trackingVersion) {
3711
- // This dependency edge is stale, so remove it.
3712
- this.producers.delete(producerId);
3713
- producer?.consumers.delete(this.id);
3714
- continue;
3715
- }
3716
- if (producer.producerPollStatus(edge.seenValueVersion)) {
3717
- // One of the dependencies reports a real value change.
3718
- return true;
3719
- }
3729
+ if (consumerIsLive(node)) {
3730
+ // For live consumers, we need to remove the producer -> consumer edge for any stale producers
3731
+ // which weren't dependencies after the recomputation.
3732
+ for (let i = node.nextProducerIndex; i < node.producerNode.length; i++) {
3733
+ producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
3720
3734
  }
3721
- // No dependency reported a real value change, so the `Consumer` has also not been
3722
- // impacted.
3723
- return false;
3724
3735
  }
3725
- /**
3726
- * Notify all consumers of this producer that its value may have changed.
3727
- */
3728
- producerMayHaveChanged() {
3729
- // Prevent signal reads when we're updating the graph
3730
- const prev = inNotificationPhase;
3731
- inNotificationPhase = true;
3732
- try {
3733
- for (const [consumerId, edge] of this.consumers) {
3734
- const consumer = edge.consumerNode.deref();
3735
- // On Safari < 16.1 deref can return null, we need to check for null also.
3736
- // See https://github.com/WebKit/WebKit/commit/44c15ba58912faab38b534fef909dd9e13e095e0
3737
- if (consumer == null || consumer.trackingVersion !== edge.atTrackingVersion) {
3738
- this.consumers.delete(consumerId);
3739
- consumer?.producers.delete(this.id);
3740
- continue;
3741
- }
3742
- consumer.onConsumerDependencyMayHaveChanged();
3743
- }
3744
- }
3745
- finally {
3746
- inNotificationPhase = prev;
3747
- }
3736
+ // Truncate the producer tracking arrays.
3737
+ for (let i = node.nextProducerIndex; i < node.producerNode.length; i++) {
3738
+ node.producerNode.pop();
3739
+ node.producerLastReadVersion.pop();
3740
+ node.producerIndexOfThis.pop();
3748
3741
  }
3749
- /**
3750
- * Mark that this producer node has been accessed in the current reactive context.
3751
- */
3752
- producerAccessed() {
3753
- if (inNotificationPhase) {
3754
- throw new Error(typeof ngDevMode !== 'undefined' && ngDevMode ?
3755
- `Assertion error: signal read during notification phase` :
3756
- '');
3757
- }
3758
- if (activeConsumer === null) {
3759
- return;
3742
+ }
3743
+ /**
3744
+ * Determine whether this consumer has any dependencies which have changed since the last time
3745
+ * they were read.
3746
+ */
3747
+ function consumerPollProducersForChange(node) {
3748
+ assertConsumerNode(node);
3749
+ // Poll producers for change.
3750
+ for (let i = 0; i < node.producerNode.length; i++) {
3751
+ const producer = node.producerNode[i];
3752
+ const seenVersion = node.producerLastReadVersion[i];
3753
+ // First check the versions. A mismatch means that the producer's value is known to have
3754
+ // changed since the last time we read it.
3755
+ if (seenVersion !== producer.version) {
3756
+ return true;
3760
3757
  }
3761
- // Either create or update the dependency `Edge` in both directions.
3762
- let edge = activeConsumer.producers.get(this.id);
3763
- if (edge === undefined) {
3764
- edge = {
3765
- consumerNode: activeConsumer.ref,
3766
- producerNode: this.ref,
3767
- seenValueVersion: this.valueVersion,
3768
- atTrackingVersion: activeConsumer.trackingVersion,
3769
- };
3770
- activeConsumer.producers.set(this.id, edge);
3771
- this.consumers.set(activeConsumer.id, edge);
3758
+ // The producer's version is the same as the last time we read it, but it might itself be
3759
+ // stale. Force the producer to recompute its version (calculating a new value if necessary).
3760
+ producerUpdateValueVersion(producer);
3761
+ // Now when we do this check, `producer.version` is guaranteed to be up to date, so if the
3762
+ // versions still match then it has not changed since the last time we read it.
3763
+ if (seenVersion !== producer.version) {
3764
+ return true;
3772
3765
  }
3773
- else {
3774
- edge.seenValueVersion = this.valueVersion;
3775
- edge.atTrackingVersion = activeConsumer.trackingVersion;
3766
+ }
3767
+ return false;
3768
+ }
3769
+ /**
3770
+ * Disconnect this consumer from the graph.
3771
+ */
3772
+ function consumerDestroy(node) {
3773
+ assertConsumerNode(node);
3774
+ if (consumerIsLive(node)) {
3775
+ // Drop all connections from the graph to this node.
3776
+ for (let i = 0; i < node.producerNode.length; i++) {
3777
+ producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
3776
3778
  }
3777
3779
  }
3778
- /**
3779
- * Whether this consumer currently has any producers registered.
3780
- */
3781
- get hasProducers() {
3782
- return this.producers.size > 0;
3780
+ // Truncate all the arrays to drop all connection from this node to the graph.
3781
+ node.producerNode.length = node.producerLastReadVersion.length = node.producerIndexOfThis.length =
3782
+ 0;
3783
+ if (node.liveConsumerNode) {
3784
+ node.liveConsumerNode.length = node.liveConsumerIndexOfThis.length = 0;
3783
3785
  }
3784
- /**
3785
- * Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,
3786
- * based on the current consumer context.
3787
- */
3788
- get producerUpdatesAllowed() {
3789
- return activeConsumer?.consumerAllowSignalWrites !== false;
3786
+ }
3787
+ /**
3788
+ * Add `consumer` as a live consumer of this node.
3789
+ *
3790
+ * Note that this operation is potentially transitive. If this node becomes live, then it becomes
3791
+ * a live consumer of all of its current producers.
3792
+ */
3793
+ function producerAddLiveConsumer(node, consumer, indexOfThis) {
3794
+ assertProducerNode(node);
3795
+ assertConsumerNode(node);
3796
+ if (node.liveConsumerNode.length === 0) {
3797
+ // When going from 0 to 1 live consumers, we become a live consumer to our producers.
3798
+ for (let i = 0; i < node.producerNode.length; i++) {
3799
+ node.producerIndexOfThis[i] = producerAddLiveConsumer(node.producerNode[i], node, i);
3800
+ }
3790
3801
  }
3791
- /**
3792
- * Checks if a `Producer` has a current value which is different than the value
3793
- * last seen at a specific version by a `Consumer` which recorded a dependency on
3794
- * this `Producer`.
3795
- */
3796
- producerPollStatus(lastSeenValueVersion) {
3797
- // `producer.valueVersion` may be stale, but a mismatch still means that the value
3798
- // last seen by the `Consumer` is also stale.
3799
- if (this.valueVersion !== lastSeenValueVersion) {
3800
- return true;
3802
+ node.liveConsumerIndexOfThis.push(indexOfThis);
3803
+ return node.liveConsumerNode.push(consumer) - 1;
3804
+ }
3805
+ /**
3806
+ * Remove the live consumer at `idx`.
3807
+ */
3808
+ function producerRemoveLiveConsumerAtIndex(node, idx) {
3809
+ assertProducerNode(node);
3810
+ assertConsumerNode(node);
3811
+ if (node.liveConsumerNode.length === 1) {
3812
+ // When removing the last live consumer, we will no longer be live. We need to remove
3813
+ // ourselves from our producers' tracking (which may cause consumer-producers to lose
3814
+ // liveness as well).
3815
+ for (let i = 0; i < node.producerNode.length; i++) {
3816
+ producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
3801
3817
  }
3802
- // Trigger the `Producer` to update its `valueVersion` if necessary.
3803
- this.onProducerUpdateValueVersion();
3804
- // At this point, we can trust `producer.valueVersion`.
3805
- return this.valueVersion !== lastSeenValueVersion;
3818
+ }
3819
+ // Move the last value of `liveConsumers` into `idx`. Note that if there's only a single
3820
+ // live consumer, this is a no-op.
3821
+ const lastIdx = node.liveConsumerNode.length - 1;
3822
+ node.liveConsumerNode[idx] = node.liveConsumerNode[lastIdx];
3823
+ node.liveConsumerIndexOfThis[idx] = node.liveConsumerIndexOfThis[lastIdx];
3824
+ // Truncate the array.
3825
+ node.liveConsumerNode.length--;
3826
+ node.liveConsumerIndexOfThis.length--;
3827
+ // If the index is still valid, then we need to fix the index pointer from the producer to this
3828
+ // consumer, and update it from `lastIdx` to `idx` (accounting for the move above).
3829
+ if (idx < node.liveConsumerNode.length) {
3830
+ const idxProducer = node.liveConsumerIndexOfThis[idx];
3831
+ const consumer = node.liveConsumerNode[idx];
3832
+ assertConsumerNode(consumer);
3833
+ consumer.producerIndexOfThis[idxProducer] = idx;
3806
3834
  }
3807
3835
  }
3836
+ function consumerIsLive(node) {
3837
+ return node.consumerIsAlwaysLive || (node?.liveConsumerNode?.length ?? 0) > 0;
3838
+ }
3839
+ function assertConsumerNode(node) {
3840
+ node.producerNode ??= [];
3841
+ node.producerIndexOfThis ??= [];
3842
+ node.producerLastReadVersion ??= [];
3843
+ }
3844
+ function assertProducerNode(node) {
3845
+ node.liveConsumerNode ??= [];
3846
+ node.liveConsumerIndexOfThis ??= [];
3847
+ }
3808
3848
 
3809
3849
  /**
3810
3850
  * Create a computed `Signal` which derives a reactive value from an expression.
@@ -3812,10 +3852,21 @@ class ReactiveNode {
3812
3852
  * @developerPreview
3813
3853
  */
3814
3854
  function computed(computation, options) {
3815
- const node = new ComputedImpl(computation, options?.equal ?? defaultEquals);
3816
- // Casting here is required for g3, as TS inference behavior is slightly different between our
3817
- // version/options and g3's.
3818
- return createSignalFromFunction(node, node.signal.bind(node));
3855
+ const node = Object.create(COMPUTED_NODE);
3856
+ node.computation = computation;
3857
+ options?.equal && (node.equal = options.equal);
3858
+ const computed = () => {
3859
+ // Check if the value needs updating before returning it.
3860
+ producerUpdateValueVersion(node);
3861
+ // Record that someone looked at this signal.
3862
+ producerAccessed(node);
3863
+ if (node.value === ERRORED) {
3864
+ throw node.error;
3865
+ }
3866
+ return node.value;
3867
+ };
3868
+ computed[SIGNAL] = node;
3869
+ return computed;
3819
3870
  }
3820
3871
  /**
3821
3872
  * A dedicated symbol used before a computed value has been calculated for the first time.
@@ -3834,108 +3885,47 @@ const COMPUTING = Symbol('COMPUTING');
3834
3885
  * Explicitly typed as `any` so we can use it as signal's value.
3835
3886
  */
3836
3887
  const ERRORED = Symbol('ERRORED');
3837
- /**
3838
- * A computation, which derives a value from a declarative reactive expression.
3839
- *
3840
- * `Computed`s are both producers and consumers of reactivity.
3841
- */
3842
- class ComputedImpl extends ReactiveNode {
3843
- constructor(computation, equal) {
3844
- super();
3845
- this.computation = computation;
3846
- this.equal = equal;
3847
- /**
3848
- * Current value of the computation.
3849
- *
3850
- * This can also be one of the special values `UNSET`, `COMPUTING`, or `ERRORED`.
3851
- */
3852
- this.value = UNSET;
3853
- /**
3854
- * If `value` is `ERRORED`, the error caught from the last computation attempt which will
3855
- * be re-thrown.
3856
- */
3857
- this.error = null;
3858
- /**
3859
- * Flag indicating that the computation is currently stale, meaning that one of the
3860
- * dependencies has notified of a potential change.
3861
- *
3862
- * It's possible that no dependency has _actually_ changed, in which case the `stale`
3863
- * state can be resolved without recomputing the value.
3864
- */
3865
- this.stale = true;
3866
- this.consumerAllowSignalWrites = false;
3867
- }
3868
- onConsumerDependencyMayHaveChanged() {
3869
- if (this.stale) {
3870
- // We've already notified consumers that this value has potentially changed.
3871
- return;
3872
- }
3873
- // Record that the currently cached value may be stale.
3874
- this.stale = true;
3875
- // Notify any consumers about the potential change.
3876
- this.producerMayHaveChanged();
3877
- }
3878
- onProducerUpdateValueVersion() {
3879
- if (!this.stale) {
3880
- // The current value and its version are already up to date.
3881
- return;
3882
- }
3883
- // The current value is stale. Check whether we need to produce a new one.
3884
- if (this.value !== UNSET && this.value !== COMPUTING &&
3885
- !this.consumerPollProducersForChange()) {
3886
- // Even though we were previously notified of a potential dependency update, all of
3887
- // our dependencies report that they have not actually changed in value, so we can
3888
- // resolve the stale state without needing to recompute the current value.
3889
- this.stale = false;
3890
- return;
3891
- }
3892
- // The current value is stale, and needs to be recomputed. It still may not change -
3893
- // that depends on whether the newly computed value is equal to the old.
3894
- this.recomputeValue();
3895
- }
3896
- recomputeValue() {
3897
- if (this.value === COMPUTING) {
3888
+ const COMPUTED_NODE = {
3889
+ ...REACTIVE_NODE,
3890
+ value: UNSET,
3891
+ dirty: true,
3892
+ error: null,
3893
+ equal: defaultEquals,
3894
+ producerMustRecompute(node) {
3895
+ // Force a recomputation if there's no current value, or if the current value is in the process
3896
+ // of being calculated (which should throw an error).
3897
+ return node.value === UNSET || node.value === COMPUTING;
3898
+ },
3899
+ producerRecomputeValue(node) {
3900
+ if (node.value === COMPUTING) {
3898
3901
  // Our computation somehow led to a cyclic read of itself.
3899
3902
  throw new Error('Detected cycle in computations.');
3900
3903
  }
3901
- const oldValue = this.value;
3902
- this.value = COMPUTING;
3903
- // As we're re-running the computation, update our dependent tracking version number.
3904
- this.trackingVersion++;
3905
- const prevConsumer = setActiveConsumer(this);
3904
+ const oldValue = node.value;
3905
+ node.value = COMPUTING;
3906
+ const prevConsumer = consumerBeforeComputation(node);
3906
3907
  let newValue;
3907
3908
  try {
3908
- newValue = this.computation();
3909
+ newValue = node.computation();
3909
3910
  }
3910
3911
  catch (err) {
3911
3912
  newValue = ERRORED;
3912
- this.error = err;
3913
+ node.error = err;
3913
3914
  }
3914
3915
  finally {
3915
- setActiveConsumer(prevConsumer);
3916
+ consumerAfterComputation(node, prevConsumer);
3916
3917
  }
3917
- this.stale = false;
3918
3918
  if (oldValue !== UNSET && oldValue !== ERRORED && newValue !== ERRORED &&
3919
- this.equal(oldValue, newValue)) {
3919
+ node.equal(oldValue, newValue)) {
3920
3920
  // No change to `valueVersion` - old and new values are
3921
3921
  // semantically equivalent.
3922
- this.value = oldValue;
3922
+ node.value = oldValue;
3923
3923
  return;
3924
3924
  }
3925
- this.value = newValue;
3926
- this.valueVersion++;
3927
- }
3928
- signal() {
3929
- // Check if the value needs updating before returning it.
3930
- this.onProducerUpdateValueVersion();
3931
- // Record that someone looked at this signal.
3932
- this.producerAccessed();
3933
- if (this.value === ERRORED) {
3934
- throw this.error;
3935
- }
3936
- return this.value;
3937
- }
3938
- }
3925
+ node.value = newValue;
3926
+ node.version++;
3927
+ },
3928
+ };
3939
3929
 
3940
3930
  function defaultThrowError() {
3941
3931
  throw new Error();
@@ -3955,88 +3945,24 @@ function setThrowInvalidWriteToSignalError(fn) {
3955
3945
  * of setting a signal.
3956
3946
  */
3957
3947
  let postSignalSetFn = null;
3958
- class WritableSignalImpl extends ReactiveNode {
3959
- constructor(value, equal) {
3960
- super();
3961
- this.value = value;
3962
- this.equal = equal;
3963
- this.consumerAllowSignalWrites = false;
3964
- }
3965
- onConsumerDependencyMayHaveChanged() {
3966
- // This never happens for writable signals as they're not consumers.
3967
- }
3968
- onProducerUpdateValueVersion() {
3969
- // Writable signal value versions are always up to date.
3970
- }
3971
- /**
3972
- * Directly update the value of the signal to a new value, which may or may not be
3973
- * equal to the previous.
3974
- *
3975
- * In the event that `newValue` is semantically equal to the current value, `set` is
3976
- * a no-op.
3977
- */
3978
- set(newValue) {
3979
- if (!this.producerUpdatesAllowed) {
3980
- throwInvalidWriteToSignalError();
3981
- }
3982
- if (!this.equal(this.value, newValue)) {
3983
- this.value = newValue;
3984
- this.valueVersion++;
3985
- this.producerMayHaveChanged();
3986
- postSignalSetFn?.();
3987
- }
3988
- }
3989
- /**
3990
- * Derive a new value for the signal from its current value using the `updater` function.
3991
- *
3992
- * This is equivalent to calling `set` on the result of running `updater` on the current
3993
- * value.
3994
- */
3995
- update(updater) {
3996
- if (!this.producerUpdatesAllowed) {
3997
- throwInvalidWriteToSignalError();
3998
- }
3999
- this.set(updater(this.value));
4000
- }
4001
- /**
4002
- * Calls `mutator` on the current value and assumes that it has been mutated.
4003
- */
4004
- mutate(mutator) {
4005
- if (!this.producerUpdatesAllowed) {
4006
- throwInvalidWriteToSignalError();
4007
- }
4008
- // Mutate bypasses equality checks as it's by definition changing the value.
4009
- mutator(this.value);
4010
- this.valueVersion++;
4011
- this.producerMayHaveChanged();
4012
- postSignalSetFn?.();
4013
- }
4014
- asReadonly() {
4015
- if (this.readonlySignal === undefined) {
4016
- this.readonlySignal = createSignalFromFunction(this, () => this.signal());
4017
- }
4018
- return this.readonlySignal;
4019
- }
4020
- signal() {
4021
- this.producerAccessed();
4022
- return this.value;
4023
- }
4024
- }
4025
3948
  /**
4026
3949
  * Create a `Signal` that can be set or updated directly.
4027
3950
  *
4028
3951
  * @developerPreview
4029
3952
  */
4030
3953
  function signal(initialValue, options) {
4031
- const signalNode = new WritableSignalImpl(initialValue, options?.equal ?? defaultEquals);
4032
- // Casting here is required for g3, as TS inference behavior is slightly different between our
4033
- // version/options and g3's.
4034
- const signalFn = createSignalFromFunction(signalNode, signalNode.signal.bind(signalNode), {
4035
- set: signalNode.set.bind(signalNode),
4036
- update: signalNode.update.bind(signalNode),
4037
- mutate: signalNode.mutate.bind(signalNode),
4038
- asReadonly: signalNode.asReadonly.bind(signalNode)
4039
- });
3954
+ const node = Object.create(SIGNAL_NODE);
3955
+ node.value = initialValue;
3956
+ options?.equal && (node.equal = options.equal);
3957
+ function signalFn() {
3958
+ producerAccessed(node);
3959
+ return node.value;
3960
+ }
3961
+ signalFn.set = signalSetFn;
3962
+ signalFn.update = signalUpdateFn;
3963
+ signalFn.mutate = signalMutateFn;
3964
+ signalFn.asReadonly = signalAsReadonlyFn;
3965
+ signalFn[SIGNAL] = node;
4040
3966
  return signalFn;
4041
3967
  }
4042
3968
  function setPostSignalSetFn(fn) {
@@ -4044,6 +3970,50 @@ function setPostSignalSetFn(fn) {
4044
3970
  postSignalSetFn = fn;
4045
3971
  return prev;
4046
3972
  }
3973
+ const SIGNAL_NODE = {
3974
+ ...REACTIVE_NODE,
3975
+ equal: defaultEquals,
3976
+ readonlyFn: undefined,
3977
+ };
3978
+ function signalValueChanged(node) {
3979
+ node.version++;
3980
+ producerNotifyConsumers(node);
3981
+ postSignalSetFn?.();
3982
+ }
3983
+ function signalSetFn(newValue) {
3984
+ const node = this[SIGNAL];
3985
+ if (!producerUpdatesAllowed()) {
3986
+ throwInvalidWriteToSignalError();
3987
+ }
3988
+ if (!node.equal(node.value, newValue)) {
3989
+ node.value = newValue;
3990
+ signalValueChanged(node);
3991
+ }
3992
+ }
3993
+ function signalUpdateFn(updater) {
3994
+ if (!producerUpdatesAllowed()) {
3995
+ throwInvalidWriteToSignalError();
3996
+ }
3997
+ signalSetFn.call(this, updater(this[SIGNAL].value));
3998
+ }
3999
+ function signalMutateFn(mutator) {
4000
+ const node = this[SIGNAL];
4001
+ if (!producerUpdatesAllowed()) {
4002
+ throwInvalidWriteToSignalError();
4003
+ }
4004
+ // Mutate bypasses equality checks as it's by definition changing the value.
4005
+ mutator(node.value);
4006
+ signalValueChanged(node);
4007
+ }
4008
+ function signalAsReadonlyFn() {
4009
+ const node = this[SIGNAL];
4010
+ if (node.readonlyFn === undefined) {
4011
+ const readonlyFn = () => this();
4012
+ readonlyFn[SIGNAL] = node;
4013
+ node.readonlyFn = readonlyFn;
4014
+ }
4015
+ return node.readonlyFn;
4016
+ }
4047
4017
 
4048
4018
  /**
4049
4019
  * Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function
@@ -4063,63 +4033,53 @@ function untracked(nonReactiveReadsFn) {
4063
4033
  }
4064
4034
  }
4065
4035
 
4066
- const NOOP_CLEANUP_FN = () => { };
4067
- /**
4068
- * Watches a reactive expression and allows it to be scheduled to re-run
4069
- * when any dependencies notify of a change.
4070
- *
4071
- * `Watch` doesn't run reactive expressions itself, but relies on a consumer-
4072
- * provided scheduling operation to coordinate calling `Watch.run()`.
4073
- */
4074
- class Watch extends ReactiveNode {
4075
- constructor(watch, schedule, allowSignalWrites) {
4076
- super();
4077
- this.watch = watch;
4078
- this.schedule = schedule;
4079
- this.dirty = false;
4080
- this.cleanupFn = NOOP_CLEANUP_FN;
4081
- this.registerOnCleanup = (cleanupFn) => {
4082
- this.cleanupFn = cleanupFn;
4083
- };
4084
- this.consumerAllowSignalWrites = allowSignalWrites;
4085
- }
4086
- notify() {
4087
- if (!this.dirty) {
4088
- this.schedule(this);
4089
- }
4090
- this.dirty = true;
4091
- }
4092
- onConsumerDependencyMayHaveChanged() {
4093
- this.notify();
4094
- }
4095
- onProducerUpdateValueVersion() {
4096
- // Watches are not producers.
4097
- }
4098
- /**
4099
- * Execute the reactive expression in the context of this `Watch` consumer.
4100
- *
4101
- * Should be called by the user scheduling algorithm when the provided
4102
- * `schedule` hook is called by `Watch`.
4103
- */
4104
- run() {
4105
- this.dirty = false;
4106
- if (this.trackingVersion !== 0 && !this.consumerPollProducersForChange()) {
4036
+ function watch(fn, schedule, allowSignalWrites) {
4037
+ const node = Object.create(WATCH_NODE);
4038
+ if (allowSignalWrites) {
4039
+ node.consumerAllowSignalWrites = true;
4040
+ }
4041
+ node.fn = fn;
4042
+ node.schedule = schedule;
4043
+ const registerOnCleanup = (cleanupFn) => {
4044
+ node.cleanupFn = cleanupFn;
4045
+ };
4046
+ const run = () => {
4047
+ node.dirty = false;
4048
+ if (node.hasRun && !consumerPollProducersForChange(node)) {
4107
4049
  return;
4108
4050
  }
4109
- const prevConsumer = setActiveConsumer(this);
4110
- this.trackingVersion++;
4051
+ node.hasRun = true;
4052
+ const prevConsumer = consumerBeforeComputation(node);
4111
4053
  try {
4112
- this.cleanupFn();
4113
- this.cleanupFn = NOOP_CLEANUP_FN;
4114
- this.watch(this.registerOnCleanup);
4054
+ node.cleanupFn();
4055
+ node.cleanupFn = NOOP_CLEANUP_FN;
4056
+ node.fn(registerOnCleanup);
4115
4057
  }
4116
4058
  finally {
4117
- setActiveConsumer(prevConsumer);
4059
+ consumerAfterComputation(node, prevConsumer);
4118
4060
  }
4119
- }
4120
- cleanup() {
4121
- this.cleanupFn();
4122
- }
4061
+ };
4062
+ node.ref = {
4063
+ notify: () => consumerMarkDirty(node),
4064
+ run,
4065
+ cleanup: () => node.cleanupFn(),
4066
+ };
4067
+ return node.ref;
4068
+ }
4069
+ const NOOP_CLEANUP_FN = () => { };
4070
+ const WATCH_NODE = {
4071
+ ...REACTIVE_NODE,
4072
+ consumerIsAlwaysLive: true,
4073
+ consumerAllowSignalWrites: false,
4074
+ consumerMarkedDirty: (node) => {
4075
+ node.schedule(node.ref);
4076
+ },
4077
+ hasRun: false,
4078
+ cleanupFn: NOOP_CLEANUP_FN,
4079
+ };
4080
+
4081
+ function setAlternateWeakRefImpl(impl) {
4082
+ // TODO: remove this function
4123
4083
  }
4124
4084
 
4125
4085
  /**
@@ -8782,8 +8742,8 @@ function detachView(lContainer, removeIndex) {
8782
8742
  function destroyLView(tView, lView) {
8783
8743
  if (!(lView[FLAGS] & 256 /* LViewFlags.Destroyed */)) {
8784
8744
  const renderer = lView[RENDERER];
8785
- lView[REACTIVE_TEMPLATE_CONSUMER]?.destroy();
8786
- lView[REACTIVE_HOST_BINDING_CONSUMER]?.destroy();
8745
+ lView[REACTIVE_TEMPLATE_CONSUMER] && consumerDestroy(lView[REACTIVE_TEMPLATE_CONSUMER]);
8746
+ lView[REACTIVE_HOST_BINDING_CONSUMER] && consumerDestroy(lView[REACTIVE_HOST_BINDING_CONSUMER]);
8787
8747
  if (renderer.destroyNode) {
8788
8748
  applyView(tView, lView, renderer, 3 /* WalkTNodeTreeAction.Destroy */, null, null);
8789
8749
  }
@@ -10945,7 +10905,7 @@ class Version {
10945
10905
  /**
10946
10906
  * @publicApi
10947
10907
  */
10948
- const VERSION = new Version('16.2.3');
10908
+ const VERSION = new Version('16.2.5');
10949
10909
 
10950
10910
  // This default value is when checking the hierarchy for a token.
10951
10911
  //
@@ -10966,6 +10926,66 @@ const VERSION = new Version('16.2.3');
10966
10926
  // - mod2.injector.get(token, default)
10967
10927
  const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
10968
10928
 
10929
+ const ERROR_ORIGINAL_ERROR = 'ngOriginalError';
10930
+ function wrappedError(message, originalError) {
10931
+ const msg = `${message} caused by: ${originalError instanceof Error ? originalError.message : originalError}`;
10932
+ const error = Error(msg);
10933
+ error[ERROR_ORIGINAL_ERROR] = originalError;
10934
+ return error;
10935
+ }
10936
+ function getOriginalError(error) {
10937
+ return error[ERROR_ORIGINAL_ERROR];
10938
+ }
10939
+
10940
+ /**
10941
+ * Provides a hook for centralized exception handling.
10942
+ *
10943
+ * The default implementation of `ErrorHandler` prints error messages to the `console`. To
10944
+ * intercept error handling, write a custom exception handler that replaces this default as
10945
+ * appropriate for your app.
10946
+ *
10947
+ * @usageNotes
10948
+ * ### Example
10949
+ *
10950
+ * ```
10951
+ * class MyErrorHandler implements ErrorHandler {
10952
+ * handleError(error) {
10953
+ * // do something with the exception
10954
+ * }
10955
+ * }
10956
+ *
10957
+ * @NgModule({
10958
+ * providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
10959
+ * })
10960
+ * class MyModule {}
10961
+ * ```
10962
+ *
10963
+ * @publicApi
10964
+ */
10965
+ class ErrorHandler {
10966
+ constructor() {
10967
+ /**
10968
+ * @internal
10969
+ */
10970
+ this._console = console;
10971
+ }
10972
+ handleError(error) {
10973
+ const originalError = this._findOriginalError(error);
10974
+ this._console.error('ERROR', error);
10975
+ if (originalError) {
10976
+ this._console.error('ORIGINAL ERROR', originalError);
10977
+ }
10978
+ }
10979
+ /** @internal */
10980
+ _findOriginalError(error) {
10981
+ let e = error && getOriginalError(error);
10982
+ while (e && getOriginalError(e)) {
10983
+ e = getOriginalError(e);
10984
+ }
10985
+ return e || null;
10986
+ }
10987
+ }
10988
+
10969
10989
  /**
10970
10990
  * `DestroyRef` lets you set callbacks to run for any cleanup or destruction behavior.
10971
10991
  * The scope of this destruction depends on where `DestroyRef` is injected. If `DestroyRef`
@@ -11402,6 +11422,9 @@ function forkInnerZoneWithAngularBehavior(zone) {
11402
11422
  name: 'angular',
11403
11423
  properties: { 'isAngularZone': true },
11404
11424
  onInvokeTask: (delegate, current, target, task, applyThis, applyArgs) => {
11425
+ if (shouldBeIgnoredByZone(applyArgs)) {
11426
+ return delegate.invokeTask(target, task, applyThis, applyArgs);
11427
+ }
11405
11428
  try {
11406
11429
  onEnter(zone);
11407
11430
  return delegate.invokeTask(target, task, applyThis, applyArgs);
@@ -11553,6 +11576,18 @@ function isStableFactory() {
11553
11576
  });
11554
11577
  return merge$1(isCurrentlyStable, isStable.pipe(share()));
11555
11578
  }
11579
+ function shouldBeIgnoredByZone(applyArgs) {
11580
+ if (!Array.isArray(applyArgs)) {
11581
+ return false;
11582
+ }
11583
+ // We should only ever get 1 arg passed through to invokeTask.
11584
+ // Short circuit here incase that behavior changes.
11585
+ if (applyArgs.length !== 1) {
11586
+ return false;
11587
+ }
11588
+ // Prevent triggering change detection when the __ignore_ng_zone__ flag is detected.
11589
+ return applyArgs[0].data?.['__ignore_ng_zone__'] === true;
11590
+ }
11556
11591
 
11557
11592
  // Public API for Zone
11558
11593
 
@@ -11605,14 +11640,18 @@ function afterRender(callback, options) {
11605
11640
  }
11606
11641
  let destroy;
11607
11642
  const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
11608
- const manager = injector.get(AfterRenderEventManager);
11643
+ const afterRenderEventManager = injector.get(AfterRenderEventManager);
11644
+ // Lazily initialize the handler implementation, if necessary. This is so that it can be
11645
+ // tree-shaken if `afterRender` and `afterNextRender` aren't used.
11646
+ const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl();
11609
11647
  const ngZone = injector.get(NgZone);
11610
- const instance = new AfterRenderCallback(() => ngZone.runOutsideAngular(callback));
11648
+ const errorHandler = injector.get(ErrorHandler, null, { optional: true });
11649
+ const instance = new AfterRenderCallback(ngZone, errorHandler, callback);
11611
11650
  destroy = () => {
11612
- manager.unregister(instance);
11651
+ callbackHandler.unregister(instance);
11613
11652
  unregisterFn();
11614
11653
  };
11615
- manager.register(instance);
11654
+ callbackHandler.register(instance);
11616
11655
  return { destroy };
11617
11656
  }
11618
11657
  /**
@@ -11665,89 +11704,117 @@ function afterNextRender(callback, options) {
11665
11704
  }
11666
11705
  let destroy;
11667
11706
  const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
11668
- const manager = injector.get(AfterRenderEventManager);
11707
+ const afterRenderEventManager = injector.get(AfterRenderEventManager);
11708
+ // Lazily initialize the handler implementation, if necessary. This is so that it can be
11709
+ // tree-shaken if `afterRender` and `afterNextRender` aren't used.
11710
+ const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl();
11669
11711
  const ngZone = injector.get(NgZone);
11670
- const instance = new AfterRenderCallback(() => {
11712
+ const errorHandler = injector.get(ErrorHandler, null, { optional: true });
11713
+ const instance = new AfterRenderCallback(ngZone, errorHandler, () => {
11671
11714
  destroy?.();
11672
- ngZone.runOutsideAngular(callback);
11715
+ callback();
11673
11716
  });
11674
11717
  destroy = () => {
11675
- manager.unregister(instance);
11718
+ callbackHandler.unregister(instance);
11676
11719
  unregisterFn();
11677
11720
  };
11678
- manager.register(instance);
11721
+ callbackHandler.register(instance);
11679
11722
  return { destroy };
11680
11723
  }
11681
11724
  /**
11682
11725
  * A wrapper around a function to be used as an after render callback.
11683
- * @private
11684
11726
  */
11685
11727
  class AfterRenderCallback {
11686
- constructor(callback) {
11687
- this.callback = callback;
11728
+ constructor(zone, errorHandler, callbackFn) {
11729
+ this.zone = zone;
11730
+ this.errorHandler = errorHandler;
11731
+ this.callbackFn = callbackFn;
11688
11732
  }
11689
11733
  invoke() {
11690
- this.callback();
11734
+ try {
11735
+ this.zone.runOutsideAngular(this.callbackFn);
11736
+ }
11737
+ catch (err) {
11738
+ this.errorHandler?.handleError(err);
11739
+ }
11691
11740
  }
11692
11741
  }
11693
11742
  /**
11694
- * Implements `afterRender` and `afterNextRender` callback manager logic.
11743
+ * Core functionality for `afterRender` and `afterNextRender`. Kept separate from
11744
+ * `AfterRenderEventManager` for tree-shaking.
11695
11745
  */
11696
- class AfterRenderEventManager {
11746
+ class AfterRenderCallbackHandlerImpl {
11697
11747
  constructor() {
11748
+ this.executingCallbacks = false;
11698
11749
  this.callbacks = new Set();
11699
11750
  this.deferredCallbacks = new Set();
11700
- this.renderDepth = 0;
11701
- this.runningCallbacks = false;
11702
11751
  }
11703
- /**
11704
- * Mark the beginning of a render operation (i.e. CD cycle).
11705
- * Throws if called from an `afterRender` callback.
11706
- */
11707
- begin() {
11708
- if (this.runningCallbacks) {
11752
+ validateBegin() {
11753
+ if (this.executingCallbacks) {
11709
11754
  throw new RuntimeError(102 /* RuntimeErrorCode.RECURSIVE_APPLICATION_RENDER */, ngDevMode &&
11710
11755
  'A new render operation began before the previous operation ended. ' +
11711
11756
  'Did you trigger change detection from afterRender or afterNextRender?');
11712
11757
  }
11713
- this.renderDepth++;
11714
- }
11715
- /**
11716
- * Mark the end of a render operation. Registered callbacks
11717
- * are invoked if there are no more pending operations.
11718
- */
11719
- end() {
11720
- this.renderDepth--;
11721
- if (this.renderDepth === 0) {
11722
- try {
11723
- this.runningCallbacks = true;
11724
- for (const callback of this.callbacks) {
11725
- callback.invoke();
11726
- }
11727
- }
11728
- finally {
11729
- this.runningCallbacks = false;
11730
- for (const callback of this.deferredCallbacks) {
11731
- this.callbacks.add(callback);
11732
- }
11733
- this.deferredCallbacks.clear();
11734
- }
11735
- }
11736
11758
  }
11737
11759
  register(callback) {
11738
11760
  // If we're currently running callbacks, new callbacks should be deferred
11739
11761
  // until the next render operation.
11740
- const target = this.runningCallbacks ? this.deferredCallbacks : this.callbacks;
11762
+ const target = this.executingCallbacks ? this.deferredCallbacks : this.callbacks;
11741
11763
  target.add(callback);
11742
11764
  }
11743
11765
  unregister(callback) {
11744
11766
  this.callbacks.delete(callback);
11745
11767
  this.deferredCallbacks.delete(callback);
11746
11768
  }
11747
- ngOnDestroy() {
11769
+ execute() {
11770
+ this.executingCallbacks = true;
11771
+ for (const callback of this.callbacks) {
11772
+ callback.invoke();
11773
+ }
11774
+ this.executingCallbacks = false;
11775
+ for (const callback of this.deferredCallbacks) {
11776
+ this.callbacks.add(callback);
11777
+ }
11778
+ this.deferredCallbacks.clear();
11779
+ }
11780
+ destroy() {
11748
11781
  this.callbacks.clear();
11749
11782
  this.deferredCallbacks.clear();
11750
11783
  }
11784
+ }
11785
+ /**
11786
+ * Implements core timing for `afterRender` and `afterNextRender` events.
11787
+ * Delegates to an optional `AfterRenderCallbackHandler` for implementation.
11788
+ */
11789
+ class AfterRenderEventManager {
11790
+ constructor() {
11791
+ this.renderDepth = 0;
11792
+ /* @internal */
11793
+ this.handler = null;
11794
+ }
11795
+ /**
11796
+ * Mark the beginning of a render operation (i.e. CD cycle).
11797
+ * Throws if called while executing callbacks.
11798
+ */
11799
+ begin() {
11800
+ this.handler?.validateBegin();
11801
+ this.renderDepth++;
11802
+ }
11803
+ /**
11804
+ * Mark the end of a render operation. Callbacks will be
11805
+ * executed if there are no more pending operations.
11806
+ */
11807
+ end() {
11808
+ ngDevMode && assertGreaterThan(this.renderDepth, 0, 'renderDepth must be greater than 0');
11809
+ this.renderDepth--;
11810
+ if (this.renderDepth === 0) {
11811
+ this.handler?.execute();
11812
+ }
11813
+ }
11814
+ ngOnDestroy() {
11815
+ this.handler?.destroy();
11816
+ this.handler = null;
11817
+ }
11751
11818
  /** @nocollapse */
11752
11819
  static { this.ɵprov = ɵɵdefineInjectable({
11753
11820
  token: AfterRenderEventManager,
@@ -11781,66 +11848,6 @@ function markViewDirty(lView) {
11781
11848
  return null;
11782
11849
  }
11783
11850
 
11784
- const ERROR_ORIGINAL_ERROR = 'ngOriginalError';
11785
- function wrappedError(message, originalError) {
11786
- const msg = `${message} caused by: ${originalError instanceof Error ? originalError.message : originalError}`;
11787
- const error = Error(msg);
11788
- error[ERROR_ORIGINAL_ERROR] = originalError;
11789
- return error;
11790
- }
11791
- function getOriginalError(error) {
11792
- return error[ERROR_ORIGINAL_ERROR];
11793
- }
11794
-
11795
- /**
11796
- * Provides a hook for centralized exception handling.
11797
- *
11798
- * The default implementation of `ErrorHandler` prints error messages to the `console`. To
11799
- * intercept error handling, write a custom exception handler that replaces this default as
11800
- * appropriate for your app.
11801
- *
11802
- * @usageNotes
11803
- * ### Example
11804
- *
11805
- * ```
11806
- * class MyErrorHandler implements ErrorHandler {
11807
- * handleError(error) {
11808
- * // do something with the exception
11809
- * }
11810
- * }
11811
- *
11812
- * @NgModule({
11813
- * providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
11814
- * })
11815
- * class MyModule {}
11816
- * ```
11817
- *
11818
- * @publicApi
11819
- */
11820
- class ErrorHandler {
11821
- constructor() {
11822
- /**
11823
- * @internal
11824
- */
11825
- this._console = console;
11826
- }
11827
- handleError(error) {
11828
- const originalError = this._findOriginalError(error);
11829
- this._console.error('ERROR', error);
11830
- if (originalError) {
11831
- this._console.error('ORIGINAL ERROR', originalError);
11832
- }
11833
- }
11834
- /** @internal */
11835
- _findOriginalError(error) {
11836
- let e = error && getOriginalError(error);
11837
- while (e && getOriginalError(e)) {
11838
- e = getOriginalError(e);
11839
- }
11840
- return e || null;
11841
- }
11842
- }
11843
-
11844
11851
  /**
11845
11852
  * Internal token that specifies whether DOM reuse logic
11846
11853
  * during hydration is enabled.
@@ -11983,48 +11990,11 @@ function getExpressionChangedErrorDetails(lView, bindingIndex, oldValue, newValu
11983
11990
  return { propName: undefined, oldValue, newValue };
11984
11991
  }
11985
11992
 
11986
- class ReactiveLViewConsumer extends ReactiveNode {
11987
- constructor() {
11988
- super(...arguments);
11989
- this.consumerAllowSignalWrites = false;
11990
- this._lView = null;
11991
- }
11992
- set lView(lView) {
11993
- (typeof ngDevMode === 'undefined' || ngDevMode) &&
11994
- assertEqual(this._lView, null, 'Consumer already associated with a view.');
11995
- this._lView = lView;
11996
- }
11997
- onConsumerDependencyMayHaveChanged() {
11998
- (typeof ngDevMode === 'undefined' || ngDevMode) &&
11999
- assertDefined(this._lView, 'Updating a signal during template or host binding execution is not allowed.');
12000
- markViewDirty(this._lView);
12001
- }
12002
- onProducerUpdateValueVersion() {
12003
- // This type doesn't implement the producer side of a `ReactiveNode`.
12004
- }
12005
- get hasReadASignal() {
12006
- return this.hasProducers;
12007
- }
12008
- runInContext(fn, rf, ctx) {
12009
- const prevConsumer = setActiveConsumer(this);
12010
- this.trackingVersion++;
12011
- try {
12012
- fn(rf, ctx);
12013
- }
12014
- finally {
12015
- setActiveConsumer(prevConsumer);
12016
- }
12017
- }
12018
- destroy() {
12019
- // Incrementing the version means that every producer which tries to update this consumer will
12020
- // consider its record stale, and not notify.
12021
- this.trackingVersion++;
12022
- }
12023
- }
12024
11993
  let currentConsumer = null;
12025
- function getOrCreateCurrentLViewConsumer() {
12026
- currentConsumer ??= new ReactiveLViewConsumer();
12027
- return currentConsumer;
11994
+ function setLViewForConsumer(node, lView) {
11995
+ (typeof ngDevMode === 'undefined' || ngDevMode) &&
11996
+ assertEqual(node.lView, null, 'Consumer already associated with a view.');
11997
+ node.lView = lView;
12028
11998
  }
12029
11999
  /**
12030
12000
  * Create a new template consumer pointing at the specified LView.
@@ -12046,12 +12016,29 @@ function getReactiveLViewConsumer(lView, slot) {
12046
12016
  */
12047
12017
  function commitLViewConsumerIfHasProducers(lView, slot) {
12048
12018
  const consumer = getOrCreateCurrentLViewConsumer();
12049
- if (!consumer.hasReadASignal) {
12019
+ if (!consumer.producerNode?.length) {
12050
12020
  return;
12051
12021
  }
12052
12022
  lView[slot] = currentConsumer;
12053
12023
  consumer.lView = lView;
12054
- currentConsumer = new ReactiveLViewConsumer();
12024
+ currentConsumer = createLViewConsumer();
12025
+ }
12026
+ const REACTIVE_LVIEW_CONSUMER_NODE = {
12027
+ ...REACTIVE_NODE,
12028
+ consumerIsAlwaysLive: true,
12029
+ consumerMarkedDirty: (node) => {
12030
+ (typeof ngDevMode === 'undefined' || ngDevMode) &&
12031
+ assertDefined(node.lView, 'Updating a signal during template or host binding execution is not allowed.');
12032
+ markViewDirty(node.lView);
12033
+ },
12034
+ lView: null,
12035
+ };
12036
+ function createLViewConsumer() {
12037
+ return Object.create(REACTIVE_LVIEW_CONSUMER_NODE);
12038
+ }
12039
+ function getOrCreateCurrentLViewConsumer() {
12040
+ currentConsumer ??= createLViewConsumer();
12041
+ return currentConsumer;
12055
12042
  }
12056
12043
 
12057
12044
  /** A special value which designates that a value has not changed. */
@@ -12168,8 +12155,15 @@ function processHostBindingOpCodes(tView, lView) {
12168
12155
  const bindingRootIndx = hostBindingOpCodes[++i];
12169
12156
  const hostBindingFn = hostBindingOpCodes[++i];
12170
12157
  setBindingRootForHostBindings(bindingRootIndx, directiveIdx);
12171
- const context = lView[directiveIdx];
12172
- consumer.runInContext(hostBindingFn, 2 /* RenderFlags.Update */, context);
12158
+ consumer.dirty = false;
12159
+ const prevConsumer = consumerBeforeComputation(consumer);
12160
+ try {
12161
+ const context = lView[directiveIdx];
12162
+ hostBindingFn(2 /* RenderFlags.Update */, context);
12163
+ }
12164
+ finally {
12165
+ consumerAfterComputation(consumer, prevConsumer);
12166
+ }
12173
12167
  }
12174
12168
  }
12175
12169
  }
@@ -12309,17 +12303,16 @@ function executeTemplate(tView, lView, templateFn, rf, context) {
12309
12303
  }
12310
12304
  const preHookType = isUpdatePhase ? 2 /* ProfilerEvent.TemplateUpdateStart */ : 0 /* ProfilerEvent.TemplateCreateStart */;
12311
12305
  profiler(preHookType, context);
12312
- if (isUpdatePhase) {
12313
- consumer.runInContext(templateFn, rf, context);
12314
- }
12315
- else {
12316
- const prevConsumer = setActiveConsumer(null);
12317
- try {
12318
- templateFn(rf, context);
12319
- }
12320
- finally {
12321
- setActiveConsumer(prevConsumer);
12306
+ const effectiveConsumer = isUpdatePhase ? consumer : null;
12307
+ const prevConsumer = consumerBeforeComputation(effectiveConsumer);
12308
+ try {
12309
+ if (effectiveConsumer !== null) {
12310
+ effectiveConsumer.dirty = false;
12322
12311
  }
12312
+ templateFn(rf, context);
12313
+ }
12314
+ finally {
12315
+ consumerAfterComputation(effectiveConsumer, prevConsumer);
12323
12316
  }
12324
12317
  }
12325
12318
  finally {
@@ -13573,21 +13566,21 @@ class EffectManager {
13573
13566
  }
13574
13567
  create(effectFn, destroyRef, allowSignalWrites) {
13575
13568
  const zone = (typeof Zone === 'undefined') ? null : Zone.current;
13576
- const watch = new Watch(effectFn, (watch) => {
13569
+ const w = watch(effectFn, (watch) => {
13577
13570
  if (!this.all.has(watch)) {
13578
13571
  return;
13579
13572
  }
13580
13573
  this.queue.set(watch, zone);
13581
13574
  }, allowSignalWrites);
13582
- this.all.add(watch);
13575
+ this.all.add(w);
13583
13576
  // Effects start dirty.
13584
- watch.notify();
13577
+ w.notify();
13585
13578
  let unregisterOnDestroy;
13586
13579
  const destroy = () => {
13587
- watch.cleanup();
13580
+ w.cleanup();
13588
13581
  unregisterOnDestroy?.();
13589
- this.all.delete(watch);
13590
- this.queue.delete(watch);
13582
+ this.all.delete(w);
13583
+ this.queue.delete(w);
13591
13584
  };
13592
13585
  unregisterOnDestroy = destroyRef?.onDestroy(destroy);
13593
13586
  return {
@@ -13971,15 +13964,15 @@ function detectChangesInView(lView, mode) {
13971
13964
  return;
13972
13965
  }
13973
13966
  const tView = lView[TVIEW];
13974
- if ((lView[FLAGS] & (16 /* LViewFlags.CheckAlways */ | 64 /* LViewFlags.Dirty */) &&
13967
+ const flags = lView[FLAGS];
13968
+ if ((flags & (16 /* LViewFlags.CheckAlways */ | 64 /* LViewFlags.Dirty */) &&
13975
13969
  mode === 0 /* ChangeDetectionMode.Global */) ||
13976
- lView[FLAGS] & 1024 /* LViewFlags.RefreshView */ ||
13970
+ flags & 1024 /* LViewFlags.RefreshView */ ||
13977
13971
  mode === 2 /* ChangeDetectionMode.BugToForceRefreshAndIgnoreViewFlags */) {
13978
13972
  refreshView(tView, lView, tView.template, lView[CONTEXT]);
13979
13973
  }
13980
13974
  else if (lView[DESCENDANT_VIEWS_TO_REFRESH] > 0) {
13981
13975
  detectChangesInEmbeddedViews(lView, 1 /* ChangeDetectionMode.Targeted */);
13982
- const tView = lView[TVIEW];
13983
13976
  const components = tView.components;
13984
13977
  if (components !== null) {
13985
13978
  detectChangesInChildComponents(lView, components, 1 /* ChangeDetectionMode.Targeted */);
@@ -17755,14 +17748,12 @@ function getTStylingRangePrev(tStylingRange) {
17755
17748
  }
17756
17749
  function getTStylingRangePrevDuplicate(tStylingRange) {
17757
17750
  ngDevMode && assertNumber(tStylingRange, 'expected number');
17758
- return (tStylingRange & 2 /* StylingRange.PREV_DUPLICATE */) ==
17759
- 2 /* StylingRange.PREV_DUPLICATE */;
17751
+ return (tStylingRange & 2 /* StylingRange.PREV_DUPLICATE */) == 2 /* StylingRange.PREV_DUPLICATE */;
17760
17752
  }
17761
17753
  function setTStylingRangePrev(tStylingRange, previous) {
17762
17754
  ngDevMode && assertNumber(tStylingRange, 'expected number');
17763
17755
  ngDevMode && assertNumberInRange(previous, 0, 32767 /* StylingRange.UNSIGNED_MASK */);
17764
- return ((tStylingRange & ~4294836224 /* StylingRange.PREV_MASK */) |
17765
- (previous << 17 /* StylingRange.PREV_SHIFT */));
17756
+ return ((tStylingRange & ~4294836224 /* StylingRange.PREV_MASK */) | (previous << 17 /* StylingRange.PREV_SHIFT */));
17766
17757
  }
17767
17758
  function setTStylingRangePrevDuplicate(tStylingRange) {
17768
17759
  ngDevMode && assertNumber(tStylingRange, 'expected number');
@@ -17780,8 +17771,7 @@ function setTStylingRangeNext(tStylingRange, next) {
17780
17771
  }
17781
17772
  function getTStylingRangeNextDuplicate(tStylingRange) {
17782
17773
  ngDevMode && assertNumber(tStylingRange, 'expected number');
17783
- return (tStylingRange & 1 /* StylingRange.NEXT_DUPLICATE */) ===
17784
- 1 /* StylingRange.NEXT_DUPLICATE */;
17774
+ return ((tStylingRange) & 1 /* StylingRange.NEXT_DUPLICATE */) === 1 /* StylingRange.NEXT_DUPLICATE */;
17785
17775
  }
17786
17776
  function setTStylingRangeNextDuplicate(tStylingRange) {
17787
17777
  ngDevMode && assertNumber(tStylingRange, 'expected number');
@@ -24579,6 +24569,40 @@ function findMatchingDehydratedView(lContainer, template) {
24579
24569
  * A view container instance can contain other view containers,
24580
24570
  * creating a [view hierarchy](guide/glossary#view-hierarchy).
24581
24571
  *
24572
+ * @usageNotes
24573
+ *
24574
+ * The example below demonstrates how the `createComponent` function can be used
24575
+ * to create an instance of a ComponentRef dynamically and attach it to an ApplicationRef,
24576
+ * so that it gets included into change detection cycles.
24577
+ *
24578
+ * Note: the example uses standalone components, but the function can also be used for
24579
+ * non-standalone components (declared in an NgModule) as well.
24580
+ *
24581
+ * ```typescript
24582
+ * @Component({
24583
+ * standalone: true,
24584
+ * selector: 'dynamic',
24585
+ * template: `<span>This is a content of a dynamic component.</span>`,
24586
+ * })
24587
+ * class DynamicComponent {
24588
+ * vcr = inject(ViewContainerRef);
24589
+ * }
24590
+ *
24591
+ * @Component({
24592
+ * standalone: true,
24593
+ * selector: 'app',
24594
+ * template: `<main>Hi! This is the main content.</main>`,
24595
+ * })
24596
+ * class AppComponent {
24597
+ * vcr = inject(ViewContainerRef);
24598
+ *
24599
+ * ngAfterViewInit() {
24600
+ * const compRef = this.vcr.createComponent(DynamicComponent);
24601
+ * compRef.changeDetectorRef.detectChanges();
24602
+ * }
24603
+ * }
24604
+ * ```
24605
+ *
24582
24606
  * @see {@link ComponentRef}
24583
24607
  * @see {@link EmbeddedViewRef}
24584
24608
  *