Signal Forms: allow custom identity tracking for array items
Which @angular/* package(s) are relevant/related to the feature request?
forms
Description
I'd like to propose an API for explicitly defining how Signal Forms should identify items in an array when reconciling its FieldTree.
Currently, when an array in the form model is replaced with a new array containing new object references, the corresponding fields may no longer be associated correctly. In particular, using an item's logical ID with template @for tracking can result in orphan-field errors.
This can be problematic when the model is intentionally treated as the source of truth and is replaced with fresh objects—for example, after receiving updated data from a server.
Consider a model like:
[
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
An update might replace it with:
[
{ id: 1, name: 'Alice Updated' },
{ id: 2, name: 'Bob' }
]
Even though these represent the same logical entities, the object references are different.
Angular's @for already has a way to express this distinction:
@for (item of items(); track item) {
...
}
However, this controls DOM/view reconciliation. I think Signal Forms could benefit from a similar mechanism for reconciling the FieldTree itself.
Why this seems useful
There is already a distinction between object identity and logical entity identity in Angular applications.
For example, an application may routinely receive:
const updated = await repository.getItems();
model.set(updated);
The returned objects may be new instances even though their IDs represent the same entities.
In that situation, I'd expect the form model to remain the source of truth while the form infrastructure determines which existing fields correspond to which logical entities.
I'm not suggesting that @for's track should be changed. Rather, this would provide an equivalent identity mechanism at the Signal Forms FieldTree level.
There are already related issues around array tracking and orphan fields, including #66711 and #66796. Those discussions establish that FieldTree identity is currently the intended template tracking mechanism. This proposal is specifically about whether Signal Forms itself could support a separate, explicit logical identity for reconciling array item FieldTrees when the source model is replaced with fresh object instances.
Proposed solution
Something along the lines of:
form(model, (path) => {
trackBy(path.items, item => item.id));
applyEach(path.items, item => {
// field schema...
});
});
The exact API is just an example; the important part is the ability to provide a stable logical identity for array items.
The expected semantics would be that when the array changes:
- An item with the same key is considered the same logical form item.
- Its existing FieldTree and associated state can be preserved.
- New keys create new fields.
- Removed keys remove their corresponding fields.
This would allow applications to use immutable model updates while preserving the logical identity of Signal Forms fields.
Alternatives considered
Rely on object identity
The current behavior can associate array items based on their object references. This works when existing objects are mutated or reused, but it does not work well when the model is replaced with fresh objects from a server or repository. It also makes immutable updates more difficult to use without losing field identity and state.
Use @for tracking
Angular's @for supports logical tracking:
@for (item of items(); track item.id)) {
...
}
However, this only controls DOM and view reconciliation. It does not provide an equivalent identity mechanism for reconciling the Signal Forms FieldTree, so it cannot by itself prevent orphan-field errors when the source model contains new object instances.
Manually preserve object references
Applications could attempt to merge incoming data into existing objects so that references remain stable. This makes the form infrastructure dependent on application-level reference management and conflicts with treating the model as the source of truth. It also adds complexity when data is refreshed or normalized.
Rebuild fields whenever the array changes
Another option is to discard and recreate the affected FieldTree entries whenever the array is replaced. This avoids stale associations but loses field state, including values, validation state, interaction state, and other metadata for logically unchanged items.
Source: angular/angular