Skip to content

fix(core): detach SegmentedBar/TabView items from a previous parent before adopting them - #11353

Merged
NathanWalker merged 2 commits into
mainfrom
fix/segmented-bar-tab-view-reparent-items
Aug 17, 2026
Merged

fix(core): detach SegmentedBar/TabView items from a previous parent before adopting them#11353
NathanWalker merged 2 commits into
mainfrom
fix/segmented-bar-tab-view-reparent-items

Conversation

@edusperoni

Copy link
Copy Markdown
Contributor

PR Checklist

What is the current behavior?

SegmentedBarBase.onItemsChanged and TabViewBase.onItemsChanged adopt each new item by calling this._addView(item), which throws when the item already has a parent:

View already has a parent. View: SegmentedBarItem(...) Parent: SegmentedBar(...)

Nothing ever un-parents those items. parent is only cleared by _removeView, and neither widget touches its items when it is destroyed — SegmentedBar.disposeNativeView only clears the tab listener / selection handler. So once a SegmentedBar is torn down, its items keep pointing at the dead bar forever.

The practical consequence: a component that holds a SegmentedBarItem[] field and binds it to a SegmentedBar inside a ListView item template crashes. ListView creates a fresh SegmentedBar per cell, all bound to the same array, so the second cell throws.

Originally reported as NativeScript/nativescript-angular#900.

@nativescript/angular currently works around this in its renderer by reaching in and assigning item.parent = undefined on every array-valued binding. That bypasses _removeView entirely, so _tearDownUI / _parentChanged never run and _context stays set — which makes _setupUI hit its this._context === context early return, and the item is never added to the new parent's native visual tree. Fixing it here lets that workaround go away.

What is the new behavior?

onItemsChanged now detaches an item from its previous parent through the public API before adopting it:

if (item.parent && item.parent !== this) {
    item.parent._removeView(item);
}

this._addView(item);

Applied identically in segmented-bar-common.ts and tab-view-common.ts (the latter keeps its existing "TabViewItem must have a view" check ahead of the detach). Because the detach goes through _removeView, the item is unloaded and torn down properly, _context is cleared, and _addView on the new parent can add it to the native visual tree.

Re-binding a shared item array no longer throws, and the items end up parented to the widget they were last assigned to.

Known and accepted limitation

If the previous parent is still alive, this steals the item from it, and that parent's items array will still list the now-detached item. This is inherent: a View has exactly one parent, so sharing a single item array between two live hosts cannot work. The goal here is "don't crash, last binding wins", not multi-parent support.

Tests

Added to apps/automated/src/ui/segmented-bar/segmented-bar-tests.ts and apps/automated/src/ui/tab-view/tab-view-tests.ts:

  • assigning one items array to a second widget while the first is still alive does not throw, and each item's parent becomes the second widget;
  • assigning the items of an already torn-down widget to a new one does not throw, and the native items/tabs are actually created for the new host (this is what the item.parent = undefined workaround got wrong).

All four new tests pass. The full apps/automated suite was run on an iOS 26 simulator against both main and this branch: 1800 → 1804 passing, with an identical set of 12 pre-existing SAFEAREALAYOUT *_tab_bar_flat inset failures on both, so this change introduces no regressions.

…ng them

`onItemsChanged` adopted every new item with `_addView`, which throws
"View already has a parent" when the item is still parented elsewhere.
Nothing ever un-parents items: `disposeNativeView` leaves `items`
untouched, so items keep pointing at a SegmentedBar even after it is
destroyed.

Binding one `SegmentedBarItem[]` to a SegmentedBar inside a ListView
item template therefore crashed on the second cell, since every cell got
a fresh bar bound to the same array.

Items are now detached through `_removeView` before being adopted, so
the item is properly unloaded and torn down and can be re-attached to
the new parent's native visual tree.
`onItemsChanged` adopted every new item with `_addView`, which throws
"View already has a parent" when the item is still parented elsewhere,
and nothing ever un-parents items when a TabView is destroyed.

Binding one `TabViewItem[]` array to more than one TabView therefore
crashed. Items are now detached through `_removeView` before being
adopted, so they are unloaded and torn down properly and can be
re-attached to the new parent's native visual tree.
@nx-cloud

nx-cloud Bot commented Aug 17, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 682aeba

Command Status Duration Result
nx test apps-automated -c=android ✅ Succeeded 5m 41s View ↗
nx run-many --target=test --configuration=ci --... ✅ Succeeded <1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-08-17 18:54:45 UTC

@NathanWalker
NathanWalker merged commit c99f310 into main Aug 17, 2026
8 checks passed
@NathanWalker
NathanWalker deleted the fix/segmented-bar-tab-view-reparent-items branch August 17, 2026 22:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants