Skip to content

refactor(forms): avoid unnecessary Set allocation in maybeRemoveStaleArrayFields#69338

Open
arturovt wants to merge 1 commit into
angular:mainfrom
arturovt:refactor/forms_maybeRemoveStaleArrayFields
Open

refactor(forms): avoid unnecessary Set allocation in maybeRemoveStaleArrayFields#69338
arturovt wants to merge 1 commit into
angular:mainfrom
arturovt:refactor/forms_maybeRemoveStaleArrayFields

Conversation

@arturovt

@arturovt arturovt commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Three small, behavior-neutral cleanups to maybeRemoveStaleArrayFields:

  1. Avoid allocating an empty Set when prevData.byTrackingKey is
    undefined. new Set(undefined) previously created an unused empty
    Set on every call for parents with no tracking keys.

  2. Guard the per-element tracking-key check on oldTracking being
    defined, skipping the isObject/hasOwn check entirely when there's
    nothing to track.

  3. Replace childValue.hasOwnProperty(identitySymbol) with
    Object.hasOwn(childValue, identitySymbol). hasOwnProperty throws
    on null-prototype array elements (Object.create(null)), which
    would crash computeChildrenMap. Object.hasOwn is null-prototype-safe
    and preserves "own property" semantics (does not match inherited
    identitySymbol values).

  4. Replace data.byTrackingKey?.delete(id) with
    data.byTrackingKey!.delete(id). The optional chaining was dead:
    if oldTracking.size > 0, prevData.byTrackingKey (and therefore
    data.byTrackingKey, same Map reference via the spread) is always
    defined. The ?. masked this invariant; ! documents it and
    would surface a runtime error instead of a silent no-op if the
    invariant is ever violated.

Verified via performance.mark/measure instrumented directly inside
the function, tested on a mobile device. count=1 call for a single-
field edit in both cases; total duration dropped from ~0.7ms to
~0.1ms, consistent with the avoided Set allocation in (1) and (2).

This is a per-call cost: a single array-valued field triggers one
call. A form with ~100 such fields would see this cost ~100x per
edit. At the unoptimized ~0.7ms/call, that's ~70ms for 100 fields —
over 4 frames at 60Hz (16.6ms budget), tighter still on 90/120Hz
mobile displays (11.1ms/8.3ms). After this change (~0.1ms/call,
~10ms for 100 fields), the same edit fits within a single 60Hz
frame budget.

@pullapprove pullapprove Bot requested a review from atscott June 12, 2026 22:52
…ArrayFields

Three small, behavior-neutral cleanups to maybeRemoveStaleArrayFields:

1. Avoid allocating an empty Set when prevData.byTrackingKey is
   undefined. new Set(undefined) previously created an unused empty
   Set on every call for parents with no tracking keys.

2. Guard the per-element tracking-key check on `oldTracking` being
   defined, skipping the isObject/hasOwn check entirely when there's
   nothing to track.

3. Replace childValue.hasOwnProperty(identitySymbol) with
   Object.hasOwn(childValue, identitySymbol). hasOwnProperty throws
   on null-prototype array elements (Object.create(null)), which
   would crash computeChildrenMap. Object.hasOwn is null-prototype-safe
   and preserves "own property" semantics (does not match inherited
   identitySymbol values).

4. Replace `data.byTrackingKey?.delete(id)` with
   `data.byTrackingKey!.delete(id)`. The optional chaining was dead:
   if oldTracking.size > 0, prevData.byTrackingKey (and therefore
   data.byTrackingKey, same Map reference via the spread) is always
   defined. The `?.` masked this invariant; `!` documents it and
   would surface a runtime error instead of a silent no-op if the
   invariant is ever violated.

Verified via performance.mark/measure instrumented directly inside
the function (count=1 call for a single-field edit in both cases).
Total duration dropped from ~0.7ms to ~0.1ms, consistent with the
avoided Set allocation in (1) and (2).
@arturovt arturovt force-pushed the refactor/forms_maybeRemoveStaleArrayFields branch from 9d4c2b0 to a7fd658 Compare June 12, 2026 22:52
@ngbot ngbot Bot added this to the Backlog milestone Jun 12, 2026
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