Commit graph

755 commits

Author SHA1 Message Date
Kristiyan Kostadinov
c0788200e2 fix(compiler): capture data bindings for content projection purposes in blocks (#54876)
Fixes a regression in the template pipeline where data bindings weren't being captured for content projection purposes.

Fixes #54872.

PR Close #54876
2024-03-15 15:11:19 -07:00
JoostK
243ccce624 fix(core): exclude class attribute intended for projection matching from directive matching (#54800)
This commit resolves a regression that was introduced when the compiler switched from
`TemplateDefinitionBuilder` (TDB) to the template pipeline (TP) compiler. The TP compiler
has changed the output of

```html
if (false) { <div class="test"></div> }
```

from

```ts
defineComponent({
  consts: [['class', 'test'], [AttributeMarker.Classes, 'test']],
  template: function(rf) {
    if (rf & 1) {
      ɵɵtemplate(0, App_Conditional_0_Template, 2, 0, "div", 0)
    }
  }
});
```

to

```ts
defineComponent({
  consts: [[AttributeMarker.Classes, 'test']],
  template: function(rf) {
    if (rf & 1) {
      ɵɵtemplate(0, App_Conditional_0_Template, 2, 0, "div", 0)
    }
  }
});
```

The last argument to the `ɵɵtemplate` instruction (0 in both compilation outputs) corresponds with
the index in `consts` of the element's attribute's, and we observe how TP has allocated only a single
attribute array for the `div`, where there used to be two `consts` entries with TDB. Consequently,
the `ɵɵtemplate` instruction is now effectively referencing a different attributes array, where the
distinction between the `"class"` attribute vs. the `AttributeMarker.Classes` distinction affects
the behavior: TP's emit causes the runtime to incorrectly match a directive with `selector: '.foo'` to
be instantiated on the `ɵɵtemplate` instruction as if it corresponds with a structural directive!

Instead of changing TP to align with TDB's emit, this commit updates the runtime instead. This uncovered
an inconsistency in selector matching for class names, where there used to be two paths dealing with
class matching:

1. The first check was commented to be a special-case for class matching, implemented in `isCssClassMatching`.
2. The second path was part of the main selector matching algorithm, where `findAttrIndexInNode` was being used
   to find the start position in `tNode.attrs` to match the selector's value against.

The second path only considers `AttributeMarker.Classes` values if matching for content projection, OR of the
`TNode` is not an inline template. The special-case in path 1 however does not make that distinction, so it
would consider the `AttributeMarker.Classes` binding as a selector match, incorrectly causing a directive to
match on the `ɵɵtemplate` itself.

The second path was also buggy for class bindings, as the return value of `classIndexOf` was incorrectly
negated: it considered a matching class attribute as non-matching and vice-versa. This bug was not observable
because of another issue, where the class-handling in part 2 was never relevant because of the special-case
in part 1.

This commit separates path 1 entirely from path 2 and removes the buggy class-matching logic in part 2, as
that is entirely handled by path 1 anyway. `isCssClassMatching` is updated to exclude class bindings from
being matched for inline templates.

Fixes #54798

PR Close #54800
2024-03-12 14:05:18 -07:00
Paul Gschwendtner
7df0a8a278 refactor(core): report subscription errors for OutputEmitterRef to ErrorHandler (#54821)
Currently if an `(output)` listener fails, it will be handled gracefully
by Angular and reported to the `ErrorHandler`.

For programmatic subscriptions with `OutputEmitterRef`, this is not the case.
Instead, as soon as any subscription is failing, all other subsequent
subscription callbacks are not firing anymore.

This commit intends to make this more consistent by gracefully
reporting errors from `OutputEmitterRef#emit` to `ErrorHandler`,
allowing for listener execution to continue.

PR Close #54821
2024-03-12 10:21:50 -07:00
Kristiyan Kostadinov
39a50f9a8d fix(core): ensure all initializer functions run in an injection context (#54761)
Ensures that all of the functions intended to be run in initializers are in an injection context. This is a stop-gap until we have a compiler diagnostic for it.

PR Close #54761
2024-03-12 09:08:08 -07:00
Kristiyan Kostadinov
f386a04c9d fix(compiler): handle two-way bindings to signal-based template variables in instruction generation (#54714)
Updates the instruction generation for two-way bindings to only emit the `twoWayBindingSet` call when writing to template variables. Since template variables are constants, it's only allowed to write to them when they're signals. Non-signal values are flagged during template type checking.

Fixes #54670.

PR Close #54714
2024-03-11 11:01:43 -07:00
Kristiyan Kostadinov
962934bc4f build: update to TypeScript 5.4 stable (#54743)
Updates the repo to the stable version of TypeScript 5.4.

PR Close #54743
2024-03-11 09:16:55 -07:00
Paul Gschwendtner
db7962adb2 test: add runtime tests for output() function API (#54650)
Adds runtime acceptance tests for `output()` and
`outputFromObservable()`.

PR Close #54650
2024-03-06 12:34:39 +01:00
Paul Gschwendtner
30355f6719 refactor(core): model() implements OutputRef (#54650)
A model signal is technically an output, at runtime and conceptually.

This commit re-uses the shared output ref logic and ensures the
interfaces match.

PR Close #54650
2024-03-06 12:34:38 +01:00
Kristiyan Kostadinov
331b16efd2 feat(core): add API to inject attributes on the host node (#54604)
Angular has the `@Attribute` decorator that allows for attributes to be injected from the host node, but we don't have an equivalent for the `inject` function. These changes introduce the new `HostAttributeToken` class that can be used to inject attributes similarly to `@Attribute`. It can be used as follows:

```typescript
import {HostAttributeToken, inject} from '@angular/core';

class MyDir {
  someAttr = inject(new HostAttributeToken('some-attr'));
}
```

The new API works similarly to `@Attribute` with one key exception: it will throw a DI error when the attribute doesn't exist, instead of returning `null` like `@Attribute`. We made this change to align its behavior closer to other injection tokens.

PR Close #54604
2024-02-27 15:18:41 -08:00
Andrew Scott
53820dd896 refactor(core): Internal render hooks trigger view refresh before executing user hooks (#54224)
This commit ensures that any internal render hooks that cause views to
become dirty again first refresh those dirty views before running user
render hooks. This ensures that user render hooks have the most complete
render state possible and stops them from needlessly executing multiple
times. This is important to maintain the goal of the public render
hooks, which is to provide the safest place to place code that depends
on DOM state, especially in ways that may force a browser paint.

PR Close #54224
2024-02-26 18:31:13 -08:00
Kristiyan Kostadinov
f5c566c079 fix(compiler-cli): identify aliased initializer functions (#54609)
Fixes that initializer functions weren't being recognized if they are aliased (e.g. `import {model as alias} from '@angular/core';`).

To do this efficiently, I had to introduce the `ImportedSymbolsTracker` which scans the top-level imports of a file and allows them to be checked quickly, without having to go through the type checker. It will be useful in the future when verifying that that initializer APIs aren't used in unexpected places.

I've also introduced tests specifically for the `tryParseInitializerApiMember` function so that we can test it in isolation instead of going through the various functions that call into it.

PR Close #54609
2024-02-26 18:27:15 -08:00
Dylan Hunn
d4343b53de Revert "fix(compiler-cli): identify aliased initializer functions (#54480)" (#54595)
This reverts commit f04ecc0cda.

PR Close #54595
2024-02-26 08:36:49 -08:00
Andrew Kushnir
dcb9deb363 fix(core): collect providers from NgModules while rendering @defer block (#52881)
Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves #52876.

PR Close #52881
2024-02-23 12:30:05 -08:00
Kristiyan Kostadinov
f04ecc0cda fix(compiler-cli): identify aliased initializer functions (#54480)
Fixes that initializer functions weren't being recognized if they are aliased (e.g. `import {model as alias} from '@angular/core';`).

To do this efficiently, I had to introduce the `ImportedSymbolsTracker` which scans the top-level imports of a file and allows them to be checked quickly, without having to go through the type checker. It will be useful in the future when verifying that that initializer APIs aren't used in unexpected places.

I've also introduced tests specifically for the `tryParseInitializerApiMember` function so that we can test it in isolation instead of going through the various functions that call into it.

PR Close #54480
2024-02-23 11:44:36 -08:00
Pawel Kozlowski
69daa37b65 test(core): more tests for queries as signals (#54508)
A couple of tests that illustrate combination of signal
and decorator queries in once component.

PR Close #54508
2024-02-20 09:49:19 -08:00
Pawel Kozlowski
d9a1a7dd07 fix(core): properly execute content queries for root components (#54457)
Prior to this fix an incorrect view instance (a dynamically created component
one instead of the root view) was passed to the content query function. Having
incorrect view instance meant that a component instance could not be found.

This is a pre-existing bug, introduction of signal-based queries just surfaced it.

Fixes #54450

PR Close #54457
2024-02-15 12:21:29 -08:00
Kristiyan Kostadinov
4a7ca50328 refactor(core): avoid wrapper around subscribe return value (#54387)
Reworks `ModelSignal.subscribe` so it doesn't have to wrap its value to look like a subscription.

PR Close #54387
2024-02-12 11:01:52 -08:00
Pawel Kozlowski
ff62244c86 fix(core): return the same children query results if there are no changes (#54392)
Assure that the same readonly array corresponding to the children query results
is returned for cases where a query is marked as dirty but there were no actual
changes to the content of the results array (this can happen if a view is added
and removed thus marking queries as dirty but not influencing final results).

Fixes #54376

PR Close #54392
2024-02-12 08:48:29 -08:00
JoostK
abf637165b fix(core): do not crash for signal query that does not have any matches (#54353)
The newly introduced signal queries would error if no match exists, due to an
invalid read within the query internals.

This commit addresses the crash by allowing there to be no matches.

PR Close #54353
2024-02-09 14:59:51 +00:00
Andrew Scott
898a532aef fix(core): Fix possible infinite loop with markForCheck by partially reverting #54074 (#54329)
In some situations, calling `markForCheck` can result in an infinite
loop in seemingly valid scenarios. When a transplanted view is inserted
before its declaration, it gets refreshed in the retry loop of
`detectChanges`. At this point, the `Dirty` flag has been cleared from
all parents. Calling `markForCheck` marks the insertion tree up to the
root `Dirty`. If the declaration is checked again as a result (i.e.
because it has default change detection) and is reachable because its
parent was marked `Dirty`, this can cause an infinite loop. The
declaration is refreshed again, so the insertion is marked for refresh
(again). We enter an infinite loop if the insertion tree always calls
`markForCheck` for some reason (i.e. `{{createReplayObservable() | async}}`).

While the case above does fall into an infinite loop, it also truly is a
problem in the application. While it's not an infinite synchronous loop,
the declaration and insertion are infinitely dirty and will be refreshed
on every change detection round.

Usually `markForCheck` does not have this problem because the `Dirty`
flag is not cleared until the very end of change detection. However, if
the view did not already have the `Dirty` flag set, it is never cleared
because we never entered view refresh. One solution to this problem
could be to clear the `Dirty` flag even after skipping view refresh but
traversing to children.

PR Close #54329
2024-02-08 16:45:20 +00:00
Kristiyan Kostadinov
a4a76c3a38 refactor(core): throw if required model is changed via update too early (#54252)
Adds an assertion that will throw if `ModelSignal.update` is accessed before an initial value is set.

PR Close #54252
2024-02-07 16:36:14 +00:00
Kristiyan Kostadinov
551c5791f8 refactor(core): address PR feedback (#54252)
Addresses the feedback from #54252.

PR Close #54252
2024-02-07 16:36:12 +00:00
Kristiyan Kostadinov
8aac3c45b9 test(core): add runtime acceptance tests for model inputs (#54252)
Sets up the runtime tests for model inputs.

PR Close #54252
2024-02-07 16:36:06 +00:00
Pawel Kozlowski
43de097704 test(core): enable signal queries tests with authoring (#54283)
Use signal queries tests with the authoring functions and without
decorators.

PR Close #54283
2024-02-06 19:31:58 +00:00
Pawel Kozlowski
e95ef2cbc6 feat(core): expose queries as signals (#54283)
This commit exposes authoring functions for queries as signals
thus making those generally available.

PR Close #54283
2024-02-06 19:31:58 +00:00
Paul Gschwendtner
902481b6ec refactor(compiler-cli): add JIT transform for signal-based queries (#54257)
This commit adds a JIT transform for signal-based queries, so that
queries are working as expected in JIT environments like `ng test` where
decorator metadata is needed as a prerequisite for the component
definition creation.

This is similar to the JIT transforms for signal inputs etc.

PR Close #54257
2024-02-06 16:04:10 +00:00
Pawel Kozlowski
3a2ce9e0a2 refactor(core): add error code for required query results (#54103)
This commit introduces a dedicated error code for queries that require
results but none are available.

PR Close #54103
2024-02-06 15:04:36 +00:00
Pawel Kozlowski
20008a6b56 refactor(core): rework runtime implementation to simplify and fix issues (#54103)
This commit changes the approach to the reactive node representing query
results: instead of creating a custom node type we can use a computed -
the main change to get there is representing dirty change notification as
a signal (a counter updated every time a query changes its dirty status).

This change is dictated by simplification (we can avoid creation of a custom
signal type) as well as fixes to the multiple issues not covered by the
initial implementation:
- assuring referential stability of results (ex.: the same array instance
  returned from child queries until results change);
- per-view results collection to avoid a situation where accessing query
  results during view creation would return partial / inconsistent results;
- proper refresh of query results for both live and non-connected consumers.

All the above cases are covered by the additional tests in this commit.

PR Close #54103
2024-02-06 15:04:36 +00:00
Pawel Kozlowski
1aa63c158b refactor(core): enable content query as signal tests (#54103)
This commit adds tests for content queries and fixes the arguments
order in the contentQuerySignal instruction, thus fixing a bug
discovered while adding tests.

PR Close #54103
2024-02-06 15:04:36 +00:00
Pawel Kozlowski
26b11b5b0e test(core): convert query-as-signals test to the compiled ones (#54103)
This commits converts the hand-written tests into their usual,
compiled form. We can perform this change now since the compiler
bits of the queries-as-signal story landed.

PR Close #54103
2024-02-06 15:04:36 +00:00
Paul Gschwendtner
8ac4f7d4ef refactor: support multiple acceptance spec files with JIT transforms (#54253)
Updates the acceptance authoring test compiler targets to support
multiple spec files. This will be useful for output, model, inputs
and queries.

PR Close #54253
2024-02-05 15:09:00 +00:00
Andrew Scott
74aa8a3888 test(core): ExpressionChanged... error does not happen with signals (#54206)
This test ensures that the `ExpressionChanged...` error does not happen
when signals are updated in a view that is attached to `ApplicationRef`
but was already checked. This was fixed in 432afd1ef4
which actually consequently fixes it for regular `markForCheck` as well.

PR Close #54206
2024-02-02 14:51:46 +00:00
Kristiyan Kostadinov
fc5fe3e8e9 build: rework signals acceptance tests build setup (#54213)
* Renames the `input_signals` directory to `signals` so it can be reused for other tests.
* Reworks the build file to allow multiple test files.

PR Close #54213
2024-02-02 14:51:18 +00:00
Paul Gschwendtner
d74ee6e343 refactor(compiler-cli): group initializer-API based transforms into single transform (#54200)
Instead of maintaining individual transforms for `input`, `output`,
`model` etc. we are grouping them directly and the first one matching,
will execute.

This reduces needed traversal through AST and also makes it a little
more clean to write new initializer API metadata transforms.

Note: The Angular JIT transform is now also moving from `tooling.ts`
directly into `/transformers` for more local placement of transformer
logic.

PR Close #54200
2024-02-01 15:58:50 +00:00
Andrew Scott
432afd1ef4 fix(core): afterRender hooks should allow updating state (#54074)
It was always the intent to have `afterRender` hooks allow updating
state, as long as those state updates specifically mark views for
(re)check. This commit delivers that behavior. Developers can now use
`afterRender` hooks to perform further updates within the same change
detection cycle rather than needing to defer this work to another round
(i.e. `queueMicrotask(() => <<updateState>>)`). This is an important
change to support migrating from the `queueMicrotask`-style deferred
updates, which are not entirely compatible with zoneless or event `NgZone` run
coalescing.

When using a microtask to update state after a render, it
doesn't work with coalescing because the render may not have happened
yet (coalescing and zoneless use `requestAnimationFrame` while the
default for `NgZone` is effectively a microtask-based change detection
scheduler). Second, when using a `microtask` _during_ change detection to defer
updates to the next cycle, this can cause updates to be split across
multiple frames with run coalescing and with zoneless (again, because of
`requestAnimationFrame`/`macrotask` change detection scheduling).

PR Close #54074
2024-01-31 20:19:06 +00:00
cexbrayat
037b79b72e fix(core): change defer block fixture default behavior to playthrough (#54088)
This is a followup to #53956

The default behavior needs to be changed in `TestBedCompiler` as well to have an effect.

PR Close #54088
2024-01-26 15:44:40 +00:00
Pawel Kozlowski
c04312860b refactor(core): introduce instructions for view queries as signals (#54017)
This commit adds new instructions to support view queries as signals.

PR Close #54017
2024-01-24 18:39:51 -05:00
Paul Gschwendtner
77516450c8 refactor(compiler): support JIT for signal-based queries (#54019)
Similar to signal-based inputs, we support signal-based queries in JIT
by expecting a decorator to be added. This is a consequence of the
design, given that JIT requires query declaration information before
the class is initialized- but ironically there is no way to collect this
information without instantiating the class.

A JIT transform in the Angular CLI will automatically generate these
decorators for testing.

PR Close #54019
2024-01-24 16:13:31 +01:00
Andrew Scott
5ae85e4849 refactor(core): node removal notifies scheduler only when animations are enabled (#53857)
Node removal is immediate and does not require change detection to run
when animations are not provided. This refactor makes the animation
engine notify the scheduler rather than doing it on all node removals.

PR Close #53857
2024-01-19 10:28:24 +01:00
Dylan Hunn
d0ce0110e8 Revert "refactor(core): improve forwardRef typings (#53880)" (#53961)
This reverts commit af6f6e6448.

PR Close #53961
2024-01-17 13:56:07 -08:00
Pawel Kozlowski
af6f6e6448 refactor(core): improve forwardRef typings (#53880)
This commit improves the forwardRef typings for better
type safety and inference.

PR Close #53880
2024-01-12 10:26:01 -08:00
Paul Gschwendtner
863be4b698 feat(core): expose new input API for signal-based inputs (#53872)
Enables signal inputs for existing Zone based components.
This is a next step we are taking to bring signal inputs earlier to the Angular community.

The goal is to enable early access for the ecosystem to signal inputs, while we are continuing
development of full signal components as outlined in the RFC. This will allow the ecosystem
to start integrating signals more deeply, prepare for future migrations, and improves code quality
and DX for existing components (especially for OnPush).

Based on our work on full signal components, we've gathered more information and learned
new things. We've improved the API by introducing a way to intuitively declare required inputs,
as well as improved the API around initial values. We even support non-primitive initial values
as the first argument to the `input` function now.

```ts
@Directive({..})
export class MyDir {
  firstName = input<string>();            // string|undefined
  lastName = input.required<string>();    // string
  age = input(0);                         // number
```

PR Close #53872
2024-01-10 12:33:31 -08:00
Paul Gschwendtner
b2066d4922 refactor(compiler-cli): detect input functions without partial evaluation (#53872)
This allows us to ensure signal inputs and a potential JIT transform
remain single file compilation compatible. The consequences are that
options need to be statically analyzable more strictly, compared to
loosened restrictions with static interpretation where e.g. `alias`
can be defined through a shared variable.

PR Close #53872
2024-01-10 12:33:31 -08:00
Paul Gschwendtner
7862686a60 test(core): add acceptance unit tests for signal inputs (#53808)
Adds AOT and JIT unit tests for signal inputs that verify integration of
signal inputs for our users.

PR Close #53808
2024-01-10 12:21:06 +00:00
Paul Gschwendtner
74099a3d4a test: add infrastructure to run signal acceptance tests with JIT and AOT (#53808)
Adds infrastructure to run signal input tests with JIT (using the
transform) and AOT. Acceptance tests for signal inputs will run with
both variants. In the future we can consider expanding this
infrastructure for all of our acceptance tests, but that's a different
story.

PR Close #53808
2024-01-10 12:21:06 +00:00
Andrew Scott
dfcf0d5882 fix(core): afterRender hooks now only run on ApplicationRef.tick (#52455)
The `afterRender` hooks currently run after `ApplicationRef.tick` but
also run after any call to `ChangeDetectorRef.detectChanges`. This is
problematic because code which uses `afterRender` cannot expect the
component it's registered from to be rendered when the callback
executes. If there is a call to `ChangeDetectorRef.detectChanges` before
the global change detection, that will cause the hooks to run earlier
than expected.

This behavior is somewhat of a blocker for the zoneless project. There
is plenty of application code that do things like `setTimeout(() =>
doSomethingThatExpectsComponentToBeRendered())`, `NgZone.onStable(() =>
...)` or `ApplicationRef.onStable...`. `ApplicationRef.onStable` is a
should likely work similarly, but all of these are really wanting an API
that is `afterRender` with the requirement that the hook runs after the
global render, not an individual CDRef instance.

This change updates the `afterRender` hooks to only run when
`ApplicationRef.tick` happens.

fixes #52429
fixes #53232

PR Close #52455
2024-01-08 11:30:27 -08:00
Paul Gschwendtner
32f908ab70 fix(core): do not accidentally inherit input transforms when overridden (#53571)
Currently when a base class defines an input with a transform, derived
classes re-defining the input via `@Input`, or `inputs: [<..>]`, end up
inherting the transform due to a bug in the inherit definitions feature.

This commit fixes this. We verified in the Google codebase that this is
an unlikely occurrence and it's trivial to fix on user side by removing
the re-declaration/override, or explictly adding the necessary
transform.

Conceptually, the behavior was quite inconsistent as everything else of
inputs was overridden as expected. i.e. alias, required state etc. The
exception were input transforms. This commit fixes this.

PR Close #53571
2024-01-04 12:07:13 -08:00
Kristiyan Kostadinov
df8a825910 fix(compiler): project empty block root node (#53620)
Expands the workaround from #52414 to allow for the root nodes of `@empty` blocks to be content projected.

Fixes #53570.

PR Close #53620
2023-12-19 08:37:33 -08:00
Andrew Scott
6c8faaa769 refactor(core): Rename InitialRenderPendingTasks and restructure isStable observable (#53534)
The InitialRenderPendingTasks currently attempts to only contribute to
ApplicationRef stableness one time to support SSR. This isn't actually
how the switchMap works in reality. This commit updates
the isStable observable to be more clear that it's always a combination
of the zone stableness and pending tasks.

In addition, this commit renames the service to just be PendingTasks
because it doesn't directly relate to rendering. While the purpose is
to track things that might cause rendering to happen, we don't know if the
tasks will affect rendering at all.

PR Close #53534
2023-12-19 08:36:28 -08:00
Charles Lyding
e149ebf228 build: update rxjs build version to v7 (#53500)
The version of rxjs used to build the repository has been updated to v7.
This required only minimal changes to the code. Most of which were type
related only due to more strict types in v7. The behavior in those cases
was left intact. The most common type related change was to handle the
possibility of `undefined` with `toPromise` which was always possible with
v6 but the types did not reflect the runtime behavior. The one change that
was not type related was to provide a parameter value to the `defaultIfEmpty`
operator. It no longer defaults to a value of `null` if no default is provided.
To provide the same behavior the value of `null` is now passed to the operator.

PR Close #53500
2023-12-18 16:25:37 +00:00