Skip to content

fix(forms): use Object.hasOwn for stale-field diff in signal forms - #70233

Open
arshsmith1 wants to merge 2 commits into
angular:mainfrom
arshsmith1:signals-stale-field-hasown
Open

fix(forms): use Object.hasOwn for stale-field diff in signal forms#70233
arshsmith1 wants to merge 2 commits into
angular:mainfrom
arshsmith1:signals-stale-field-hasown

Conversation

@arshsmith1

Copy link
Copy Markdown
Contributor

PR Checklist

PR Type

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

maybeRemoveStaleObjectFields in packages/forms/signals/src/field/structure.ts diffs the previously materialized child keys against the current object model with value.hasOwnProperty(key). When a form model mirrors untrusted JSON that carries an own field literally named hasOwnProperty, that data value shadows the method, so the call throws TypeError: value.hasOwnProperty is not a function and the structural diff crashes inside the reactive children computation. The sibling maybeRemoveStaleArrayFields in the same file already avoids this by using Object.hasOwn, so only the object path is affected.

What is the new behavior?

The object path now uses Object.hasOwn(value, key), matching the array sibling. Models without a hasOwnProperty field diff exactly as before; a model that happens to carry that key no longer crashes the diff. Added a regression test in structure.spec.ts that materializes an object form, then updates it to a value carrying a hasOwnProperty key and asserts the recompute does not throw.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

maybeRemoveStaleObjectFields checks whether a previously materialized
child key still exists on the object model with value.hasOwnProperty(key).
When the model mirrors untrusted JSON that carries an own field named
hasOwnProperty, that data value shadows the method and the call throws a
TypeError, crashing the structural diff during the reactive children
computation. The sibling maybeRemoveStaleArrayFields already uses
Object.hasOwn, so switch the object path to Object.hasOwn(value, key) to
match. Valid models diff identically.
@pullapprove
pullapprove Bot requested a review from kirjs August 17, 2026 09:13
@ngbot ngbot Bot added this to the Backlog milestone Aug 17, 2026
});
});

describe('object models with shadowing keys', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind dropping that unecessary describe ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, dropped the nested describe and moved the test up into the parent block.

Comment on lines +138 to +139
const data = signal<Record<string, unknown>>({a: '', hasOwnProperty: ''});
const f = form(data, {injector: TestBed.inject(Injector)}) as any;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably improve the typings and drop that any.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Letting the signal infer the model type gives a real FieldTree, so the any is gone and f.a().value() types through cleanly.

Drop the extra describe block and rely on inferred typings instead of an
any cast in the stale-field diff regression test.

Signed-off-by: arshiya tabasum <arshi@bugqore.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants