When an effect is created in a component constructor, it might read signals
which are derived from component inputs. These signals may be unreliable or
(in the case of the proposed input signals) may throw if accessed before the
component is first change detected (which is what makes required inputs
available).
Depending on the scenario involved, the effect may or may not run before
this initialization takes place, which isn't a great developer experience.
In particular, effects created during CD (e.g. via control flow) work fine,
as do effects created in bootstrap thanks to the sync CD it performs. When
an effect is created through dynamic component creation outside of CD though
(such as on router navigations), it runs before the component is first CD'd,
causing the issue.
In fact, in the signal components RFC we described how effects would wait
until ngOnInit for their first execution for exactly this reason, but this
behavior was never implemented as it was thought our effect scheduling
design made it unnecessary. This is true of the regular execution of effects
but the above scenario shows that *creation* of the effect is still
vulnerable. Thus, this logic is needed.
This commit makes effects sensitive to their creation context, by injecting
`ChangeDetectorRef` optionally. An effect created with an injector that's
tied to a component will wait until that component is initialized before
initially being scheduled. TestBed effect flushing is also adjusted to
account for the additional interaction with change detection.
PR Close#52473
With the directive-based control flow users were able to conditionally project content using the `*` syntax. E.g. `<div *ngIf="expr" projectMe></div>` will be projected into `<ng-content select="[projectMe]"/>`, because the attributes and tag name from the `div` are copied to the template via the template creation instruction. With `@if` and `@for` that is not the case, because the conditional is placed *around* elements, rather than *on* them. The result is that content projection won't work in the same way if a user converts from `*ngIf` to `@if`.
These changes aim to cover the most common case by doing the same copying when a control flow node has *one and only one* root element or template node.
This approach comes with some caveats:
1. As soon as any other node is added to the root, the copying behavior won't work anymore. A diagnostic will be added to flag cases like this and to explain how to work around it.
2. If `preserveWhitespaces` is enabled, it's very likely that indentation will break this workaround, because it'll include an additional text node as the first child. We can work around it here, but in a discussion it was decided not to, because the user explicitly opted into preserving the whitespace and we would have to drop it from the generated code. The diagnostic mentioned point #1 will flag such cases to users.
Fixes#52277.
PR Close#52414
`RootViewRef<T>` extends `ViewRef<T>` and overrides 3 methods with behavior
that is identical to `ViewRef<T>`. This commit removes `RootViewRef<T>`
because it is not needed.
PR Close#52430
The `ViewRef<T>` interface extends `InternalViewRef` and is already not
part of the public API. There is no need for the extra `InternalViewRef`
interface. This confusing setup is likely leftover from the types
necessary to support both Ivy and ViewEngine.
PR Close#52430
This commit adds a global epoch to the reactive graph, which can optimize
non-live reads.
When a non-live read occurs, a computed must poll its dependencies to check
if they've changed, and this operation is transitive and not cacheable.
Since non-live computeds don't receive dirty notifications, they're forced
to assume potential dirtiness on each and every read.
Using a global epoch, we can add an important optimization: if *no* signals
have been set globally since the last time it polled its dependencies, then
we *can* assume a clean state. This significantly improves performance of
large unwatched graphs when repeatedly reading values.
PR Close#52420
This commit updates the reactive template and host binding consumers to
only mark their declaration components for refresh, but not parents/ancestors.
This also updates the `AfterViewChecked` hook to run when a component is
refreshed during change detection but its host is not. It is reasonable
to expect that the `ngAfterViewChecked` lifecycle hook will run when a
signal updates and the component is refreshed. The hooks are typically
run when the host is refreshed so without this change, the update to
not mark ancestors dirty would have caused `ngAfterViewChecked` to not
run.
resolves#14628resolves#22646resolves#34347 - this is not the direct request of the issue but
generally forcing change detection to run is necessary only because a
value was updated that needs to be synced to the DOM. Values that use
signals will mark the component for check automatically so accessing the
`ChangeDetectorRef` of a child is not necessary. The other part of this
request was to avoid the need to "mark all views for checking since
it wouldn't affect anything but itself". This is directly addressed by
this commit - updating a signal that's read in the view's template
will not cause ancestors/"all views" to be refreshed.
PR Close#52302
Issue #50320 shows that in some cases, updating a signal that's a dependency
of a template during change detection of that template can have several
adverse effects. This can happen, for example, if the signal is set during
the lifecycle hook of a directive within the same template that reads the
signal.
This can cause a few things to happen:
* Straightforwardly, it can cause `ExpressionChanged` errors.
* Surprisingly, it can cause an assertion within the `ReactiveLViewConsumer`
to fail.
* Very surprisingly, it can cause change detection for an `OnPush` component
to stop working.
The root cause of these later behaviors is subtle, and is ultimately a
desync between the reactive graph and the view tree's notion of "dirty" for
a given view. This will be fixed with further work planned for change
detection to handle such updates directly. Until then, this commit improves
the DX through two changes:
1. The mechanism of "committing" `ReactiveLViewConsumer`s to a view is
changed to use the `consumerOnSignalRead` hook from the reactive graph.
This prevents the situation which required the assertion in the first
place.
2. A `console.warn` warning is added when a view is marked dirty via a
signal while it's still executing.
The warning informs users that they're pushing data against the direction of
change detection, risking `ExpressionChanged` or other issues. It's a
warning and not an error because the check is overly broad and captures
situations where the application would not actually break as a result, such
as if a `computed` marked the template dirty but still returned the same
value.
PR Close#52234
Previously this case was missed by the default framework injector profiler. Now in ngDevMode this event emits correctly when a service is configured with `providedIn`. This includes the case where injection tokens are configured with a `providedIn`.
This commit also includes unit tests for this new case in the injector profiler.
PR Close#52365
Angular recently gained a local compilation mode (see commit
345dd6d81a). This is intended to be used
with the TypeScript compiler option isolatedModules, which bans imports
of const enums.
This changes all const enums tagged with @publicApi to regular enums.
Fixes#46240
PR Close#51670
This commit runs change detection in a loop while there are still dirty
views to be refreshed in the tree. At the moment, this only applies to
transplanted views but will also apply to views with changed signals.
fixes angular#49801
PR Close#51854
This commit updates the logic to ignore `after` and `minimum` conditions when `DeferBlockFixture.render` method is used in tests.
Resolves#52313.
PR Close#52314
This commit updates the code to report errors via `ErrorHandler` instance.
For dependency loading problems, errors are reported only when `@error` block is not provided.
PR Close#52320
This commit adds the logic to cleanup all triggers once defer block is triggered.
When a trigger is created, its cleanup function is stored alongside other defer block info. Prefetch and regular triggers are store in different slots, since we need to invoke them at different time.
PR Close#52291
The current way of computing a route's params and data recomputes
inherited data from the inheritance root every time. When the
inheritance strategy is "emptyOnly", this isn't necessarily the root of
the tree, but some point along the way (it stops once it reaches an
ancestor route with a component).
Instead, this commit updates parameter inheritance to only inherit data
directly from the parent route (again, instead of recomputing all
inherited data back to the inheritance root). The only requirement for
making this work is that the parent route data has already calculated
and updated its own inherited data. This was really already a
requirement -- parents need to be processed before children.
In addition, the update to the inheritance algorithm in this commit
requires more of an understanding that a resolver running higher up in
the tree has to propagate inherited data downwards. The previous
algorithm hid this knowledge because resolvers would recompute inherited
data from the root when run. However, routes that did not have resolvers
rerun or never had resolvers at all would not get the updated resolved data.
fixes#51934
PR Close#52167
A lot of our tests are wrapped in `{}` which serves no purpose, aside from increasing the nesting level and, in some cases, causing confusion. The braces appear to be a leftover from a time when all tests were wrapped in a `function main() {}`. The function declaration was removed in #21053, but the braces remained, presumably because it was easier to search&replace for `function main()`, but not to remove the braces at the same time.
PR Close#52239
Public afterRender phases have specific API guarantees which can be invalidated if the internal framework is implemented using them. Instead, the framework should use dedicated internal functions.
PR Close#52145
We have the `DEFER_BLOCK_DEPENDENCY_INTERCEPTOR` DI token that we use in tests to intercept the dependency loading function from deferred blocks, however we were referencing it in a way that caused it to be retained in production bundles as well.
These changes guard the call site with `ngDevMode` since the token is only used for testing.
PR Close#52199
This commit splits the `render3/instructions/defer.ts` file (that contained most of the runtime code) into smalle
r files that are easier to maintain.
There are no functional changes in this PR, just organizing code.
PR Close#52152
A runtime error will be thrown if a non-standalone component is being rendered without its NgModule loaded in the browser. This error is thrown only in dev mode and only if the Angular option `forbidOrphanComponents` is set to true. The error contains useful info to find the orphan component in the source code.
PR Close#52061
A new method `isOrphanComponent` is added to the deps tracker API to check if the NgModule declaring this component, if exists, is loaded into the browser.
PR Close#52061
The flag `forbidOrphanRendering` is only set for non-standalone components, and indicates that the dev mode runtime should through error if the component is rendered without its ngModule loaded in the browser. This runtime error can help with further debugging.
PR Close#52061
Using `afterRender` schedules long-living lifecycle hooks. Scheduling
such hooks inside reactive contexts could mean that many of the
same hooks would be scheduled, quickly piling up every time a
consumed signal changes. This is likely unintended and could degrade
application performance or result in unexpected behavior.
Additionally, scheduling `afterRender` inside a `computed` is considered
a side effect. Computed expressions are expected to be pure/ i.e. free
of side effects. We can avoid this caveat by detecting the reactive
context in development.
PR Close#52138
Using an `effect` inside a `computed` is a clear violation of
the conceptual idea of computed's being pure/ side-effect free.
Additionally, scheduling new effects from an existing actively
running effect is likely unintended as this could degrage application
performance or result in unintentional behaviors. Multiple long-living
effects would be scheduled every time the effect expressions runs.
For these reasons, we are explicitly preventing this pitfal, by
disallowing using `effect` inside reactive contexts.
PR Close#52138
Adds a check to the viewport cleanup function to prevent it from re-processing elements that have been fully cleaned up, because it can lead to the `IntersectionObserver` being destroyed even though there are still pending triggers. This can happen, because we have cleanup callbacks both for the block is loaded, but also when the placeholder view is destroyed.
Fixes#52113.
PR Close#52115
Fixes that the new block syntax was generating instructions in the wrong order which meant that pipes were being declared too early. This meant that if the block is first in the template, any pipes used in it won't be able to inject things like `ChangeDetectorRef`.
These changes update the compiler and add a bunch of tests to ensure that pipes work as expected.
Fixes#52102.
PR Close#52112
This commit updates `@defer` logic related to handling `after` and `minimum` parameters tree-shakable.
If `after` or `minimum` was used on a `@loading` or `@placeholder` blocks, compiler generates an extra argument for the `ɵɵdefer` instruction. This extra argument is a reference to a function that brings timer-related code.
PR Close#52042
The error message now contains the code location of the component. It now looks like: "Error: NG01001: Orphan component found! Trying to render the component Main (at $PROJECT_ROOT/src/main.ts:8) without first loading the NgModule ..."
PR Close#51919
The current error stringifier only includes the class name. In this change a new stringifier is added which returns a more helpful string which includes the file path and line number. Note that this is only the case with components, and for other class types (directive, pipes) it will fallback to the current stringifier. Subsequent changes can cover the case of directive and pipes as well.
PR Close#51919
A new field `debugInfo` is added to the component definition. Now the runtime ɵsetClassDebugInfo stores the debug info for components in this new field.
PR Close#51919
This commit updates the tracking of dirty child views to be a flag
rather than a counter. This is a much more simple method and less likely
to get into the same 'always-wrong' situation that could happen with the
counter (if it is off by 1 once, it's off by 1 forever and you either
get infinite change detection or your view is never refreshed).
PR Close#51515
Fixes that, depending on the matching and import order, in some cases we weren't throwing the error saying that a directive matched multiple times on the same element.
Fixes#52072.
PR Close#52073
This commit adds the code to mark newly created embedded views (that represent `@defer` block states) as dirty to indicate that the view sgould be checked during the next change detection cycle.
Resolves#52094.
PR Close#52095
This change removes the `mutate` method from the `WritableSignal` interface and
completely drops it from the public API surface.
The initial API proposal for Angular signals included the mutate method, allowing
in-place modification of JS objects, without changing their references (identity).
This was based on the reasoning that identity change on modification is not necessary
as we can send the “modified” notification through the signals graph.
Unfortunately the signal-specific change notification is lost as soon as we read
signal value outside of a reactive context (outside of a reactive graph).
In other words - any code outside of the Angular signals library can’t know
that an object is modified.
Secondly, to make the mutate method work, we’ve defaulted the signal value equality function
to the one that considers non-primitive values as always different.
This is unfortunate for people working with immutable data structures
(this is notably the case for the popular state management libraries)
as the default equality function de-optimizes memoization in computed,
making the application less performant.
Given the above reasons we prefer to remove the mutate method in the signals library -
at least for now. There are just too many sharp edges and tradeoffs that we don’t fully
understand yet.
BREAKING CHANGE:
The `mutate` method was removed from the `WritableSignal` interface and completely
dropped from the public API surface. As an alternative please use the update method and
make immutable changes to the object.
Example before:
```typescript
items.mutate(itemsArray => itemsArray.push(newItem));
```
Example after:
```typescript
items.update(itemsArray => [itemsArray, …newItem]);
```
PR Close#51986
This commit reorganizes the Angular code a bit, and moves signals into a
newly defined `@angular/core/primitives` location. This will be used inside
g3 to allow non-Angular targets to depend on the signals core without
incurring a dependency on the whole framework.
PR Close#51986
This commit refactors the signals API surface of Angular out of the
//packages/core/src/signals package. This is done in preparation of moving
the core signals package into a new 'primitives' package that's decoupled
from the public API.
PR Close#51986