Scroll view
<scroll-view> wraps its content in an internal container the way React Native's own
ScrollView.js does, because native scroll views have no equivalent to contentContainerStyle of
their own. contentContainerStyle styles that inner container - padding and gaps on the
scrollable content go there, not on [style], which styles the scroll view's own frame.
import { ScrollView, RefreshControl } from '@ng-native/components';
<scroll-view><refresh-control> once they are in the component's imports<scroll-view #list [contentContainerStyle]="{ padding: 16, gap: 12 }">
@for (item of items(); track item.id) {
<text>{{ item.label }}</text>
}
</scroll-view>Horizontal scrolling
horizontal changes the scroll axis, and it changes two things to do it: the scroll view's own
flex direction and the content container's. Setting only one leaves the content stretched to the
scroll view's width with nothing to scroll to, which looks exactly like a horizontal scroll view
that simply does not have enough in it to scroll - horizontal handles both, so this only matters
if something is overriding the content container's own layout.
Keyboard taps
keyboardShouldPersistTaps controls what a tap elsewhere in the scroll view does while a
<text-input> has focus: 'never' (the default) dismisses the keyboard immediately and nothing
else happens with the tap, 'handled' lets whatever was tapped handle it first and only dismisses
if nothing did, and 'always' never dismisses at all.
Sticky headers
stickyHeaderIndices pins children of the content to the top while the rest scroll under them,
each until the next sticky child pushes it off. React Native does this in JavaScript rather than
natively, by translating the child against the scroll offset, and so does this; the one difference
is that RN's translation runs on the native animation driver, where this one is written from the
scroll event, so a pinned header can trail a fast fling by a frame.
Methods
scrollTo({ x, y, animated }), scrollToEnd({ animated }), flashScrollIndicators() and, on iOS,
zoomToRect(rect, animated) are available as methods through a template reference.
A scroll handler and change detection
A (scroll) handler runs change detection for its component on every scroll event, and Angular
checks every row an @for in that component's template has rendered, whatever the handler
changed. A header that fades as the page scrolls, over a thousand rows in the same template,
re-checks all thousand rows every frame. Put the list in a component of its own and the same event
checks one input instead: with a thousand rows, a scroll event costs about 0.05ms of JavaScript
rather than about 2ms.
<scroll-view (scroll)="fadeHeader($event)">
<view [style]="{ opacity: headerOpacity() }"><text>Inbox</text></view>
<app-message-rows [messages]="messages()" />
</scroll-view>scroll-viewcomponent@ng-native/componentsimport { ScrollView } from '@ng-native/components';and add ScrollView to the component's imports A scrolling container. Commits as RCTScrollView , with the children inside a content view.
Inputs
contentContainerStyleRecord<string, unknown>Styles for the view that holds the children, e.g. padding and gap.
horizontalunknownLay children out in a row and scroll sideways.
contentOffsetPointWhere the content starts, before the user scrolls.
maintainVisibleContentPosition{
readonly minIndexForVisible: number;
readonly autoscrollToTopThreshold?: number;
}Keep what is on screen still when content is added above it.
keyboardShouldPersistTapsKeyboardShouldPersistTaps = 'never' What a tap does with the keyboard up. See the class note. Defaults to never .
nestedScrollEnabledunknownAndroid: cooperate with a scrolling ancestor.
stickyHeaderIndicesreadonly number[] = []Children of the content, by position, that stick to the leading edge while it scrolls.
Outputs
contentSizeChangeSizeThe content view's size changed; the value is its new width and height.
Methods
scrollTo(options: { x?: number; y?: number; animated?: boolean }): voidScroll to a position.
flashScrollIndicators(): voidBriefly show the scroll indicators, to hint that there is more.
zoomToRect(rect: Rect, animated = true): voidiOS: zoom so that a rectangle of the content fills the viewport.
Refresh control
<refresh-control> is a pull-to-refresh spinner, used as a direct child of a <scroll-view> or
<virtual-list>. refreshing is a two-way model: native shows the spinner the instant the user
pulls, before the app has said anything, and the app is expected to set refreshing to true when
the (refresh) output fires and back to false once the work is done. If an app never clears it,
the spinner keeps spinning indefinitely - there is no timeout.
<scroll-view>
<refresh-control [(refreshing)]="loading" (refresh)="reload()" />
@for (item of items(); track item.id) {
<text>{{ item.label }}</text>
}
</scroll-view>iOS also reads tintColor, title and titleColor for a label under the spinner. Android reads
colors (the colours the spinner cycles through), progressBackgroundColor, size
('default' or 'large') and enabled (whether pulling does anything at all, default true).
progressViewOffset sets how far from the top the spinner sits, on both platforms.
On Android the native swipe layout has to be the scroll view's parent, not its child, or a pull
does nothing. It is still written as a child, as on iOS, and moved into place: the committed tree
has AndroidSwipeRefreshLayout where the scroll view was, with the scroll view inside it. The
layout half of the scroll view's inline style (size, margins, flex, position, transform) moves to
the swipe layout, the way React Native's ScrollView.js splits it, and the scroll view keeps the
rest. Layout that comes from a class stays on the scroll view.
refresh-controldirective@ng-native/componentsimport { RefreshControl } from '@ng-native/components';and add RefreshControl to the component's imports Pull to refresh, as a child of a scroll view. Commits as PullToRefreshView on iOS and AndroidSwipeRefreshLayout on Android.
Inputs
progressViewOffsetunknownHow far from the top the spinner sits.
tintColorstringiOS: the spinner's colour.
titlestringiOS: a label under the spinner.
titleColorstringiOS: the label's colour.
colorsreadonly string[]Android: the colours the spinner cycles through.
enabledunknownAndroid: whether pulling does anything. Defaults to true.
progressBackgroundColorstringAndroid: the spinner's background.
size'default' | 'large'Android: the spinner's size.
Two-way
refreshingboolean = false Whether the spinner shows. Two-way: [(refreshing)] .