ComponentStandalone

Transfer

Dual listbox — two panes and the buttons that move rows between them. The RIGHT pane is the value.

Installation

import { WrTransfer } from 'ngwr/transfer';

@Component({ imports: [WrTransfer] })
export class MyComponent {}

Basic usage

[items] is the full set; [(value)] holds the values that ended up on the right, and everything else stays on the left. Ticking a row only stages it — the move commits when you press an arrow, so the value never contains a half-made choice. A disabled item can be neither ticked nor swept up by the header checkbox.

<wr-transfer [items]="permissions" [(value)]="granted" />
Available0 / 6
Selected0 / 1

Value: [ "read" ]

Search and titles

searchable puts a filter above each pane; it narrows what you see, what the header checkbox sweeps and what an arrow commits, never what is already chosen. A row the filter hides stops counting as staged, so a move can only carry rows the pane is actually showing. sourceTitle / targetTitle name the panes — both fall back to the ngwr/i18n catalog, so they are localized without being set.

<wr-transfer
  searchable
  sourceTitle="All permissions"
  targetTitle="Granted"
  [items]="permissions"
  [(value)]="granted"
/>
All permissions0 / 7
Granted0 / 0
  • Nothing here

In a form

A Signal Forms native control — [formField] binds straight to value, with no ControlValueAccessor in between. [(ngModel)] and reactive forms keep working through Angular's bridge, and (touch) fires on every committed move so a bound field marks itself touched.

<!-- Signal Forms — the value is the right pane -->
<wr-transfer [items]="permissions" [formField]="form.granted" />

<!-- Classic reactive forms keep working through Angular's bridge -->
<wr-transfer [items]="permissions" [formControl]="granted" />

API

NameDescriptionTypeDefault
itemsEvery row, in either pane. Membership of the right pane is value.readonly WrTransferItem[][]
valueValues currently in the RIGHT pane. Two-way bindable; bound automatically by [formField] / [(ngModel)].readonly unknown[][]
(touch)Emitted on blur / commit so a bound field marks itself touched.void
disabledDisable the whole control.booleanfalse
readonlyRefuse changes to the value while both panes stay focusable and readable. Bound automatically from the field's readonly state when used with [formField]. The move buttons go inert and the row checkboxes go read-only; SEARCH keeps working, because filtering a pane changes what is shown and not what is chosen. No aria-readonly on the host: role group does not support the state, so each row's checkbox mirrors its own instead.booleanfalse
searchableShow a filter box above each pane.booleanfalse
sourceTitleHeading above the left pane. Falls back to transfer.source.string | nullnull
targetTitleHeading above the right pane. Falls back to transfer.target.string | nullnull
searchPlaceholderPlaceholder in both filter boxes. Falls back to transfer.search.string | nullnull
emptyTextShown in a pane with no rows. Falls back to transfer.empty.string | nullnull
selectAllLabelAccessible name of each pane's select-all checkbox, composed with the pane heading. Falls back to transfer.selectAll.string | nullnull
toTargetLabelAccessible name of the move-right button. Falls back to transfer.toTarget.string | nullnull
toSourceLabelAccessible name of the move-left button. Falls back to transfer.toSource.string | nullnull

CSS variables

Custom properties ngwr/transfer publishes. Each default below is declared on the component's own selector, so a :root override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.

VariableDefaultDeclared on
--wr-transfer-pane-height15rem.wr-transfer
--wr-transfer-pane-width14rem.wr-transfer