Skip to content

Commit 7289101

Browse files
authored
Merge pull request #358 from dhilt/issue-357-Allow-force-virtualization-on-append-prepend
Allow force virtualization on append prepend
2 parents 355f6fa + bc490e6 commit 7289101

15 files changed

+623
-562
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ on:
1414

1515
jobs:
1616
build:
17-
runs-on: ubuntu-latest
18-
strategy:
19-
matrix:
20-
node-version: [16.x]
17+
runs-on: ubuntu-22.04
2118
steps:
2219
- name: Dispatched?
2320
if: ${{ github.event_name == 'workflow_dispatch' }}
@@ -26,12 +23,12 @@ jobs:
2623
echo "Build reason: ${{ github.event.inputs.cause }}"
2724
2825
- name: Checkout
29-
uses: actions/checkout@v3
26+
uses: actions/checkout@v4
3027

3128
- name: Use Node.js ${{ matrix.node-version }}
32-
uses: actions/setup-node@v3
29+
uses: actions/setup-node@v4
3330
with:
34-
node-version: ${{ matrix.node-version }}
31+
node-version: 20
3532

3633
- run: npm run ci:lib
3734
- run: npm run build:lib
@@ -40,14 +37,14 @@ jobs:
4037
- run: npm run ci:tests
4138
- run: npm test
4239

43-
- uses: actions/upload-artifact@master
40+
- uses: actions/upload-artifact@v4
4441
with:
4542
name: demo-dist
4643
path: dist/demo
4744

4845
deploy:
4946
needs: build
50-
runs-on: ubuntu-latest
47+
runs-on: ubuntu-22.04
5148
steps:
5249
- name: Set env BRANCH
5350
run: echo "BRANCH=$(echo $GITHUB_REF | cut -d'/' -f 3)" >> $GITHUB_ENV
@@ -66,11 +63,11 @@ jobs:
6663

6764
- name: Checkout
6865
if: env.NEED == 'true'
69-
uses: actions/checkout@v3
66+
uses: actions/checkout@v4
7067

7168
- name: Download dist
7269
if: env.NEED == 'true'
73-
uses: actions/download-artifact@master
70+
uses: actions/download-artifact@v4
7471
with:
7572
name: demo-dist
7673
path: dist-demo-app
@@ -80,4 +77,4 @@ jobs:
8077
uses: JamesIves/github-pages-deploy-action@v4
8178
with:
8279
branch: gh-pages
83-
folder: dist-demo-app
80+
folder: dist-demo-app

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2024 Denis Hilt (https://github.com/dhilt)
3+
Copyright (c) 2025 Denis Hilt (https://github.com/dhilt)
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ Below is the list of invocable methods of the Adapter API with description and l
220220
|[resume](https://dhilt.github.io/ngx-ui-scroll/#adapter#pause-resume)| |Resumes the Scroller if it was paused. |
221221
|[check](https://dhilt.github.io/ngx-ui-scroll/#adapter#check-size)| |Checks if any of current items changed it's size and runs a procedure to provide internal consistency and new items fetching if needed. |
222222
|[clip](https://dhilt.github.io/ngx-ui-scroll/#adapter#clip)|(options: {<br>&nbsp;&nbsp;forwardOnly?:&nbsp;boolean,<br>&nbsp;&nbsp;backwardOnly?:&nbsp;boolean<br>})|Removes out-of-viewport items on demand. The direction in which invisible items should be clipped can be specified by passing an options object. If no options is passed (or both properties are set to _true_), clipping will occur in both directions. |
223-
|[append](https://dhilt.github.io/ngx-ui-scroll/#adapter#append-prepend)|(options: {<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;eof?:&nbsp;boolean<br>&nbsp;&nbsp;decrease?:&nbsp;boolean<br>})|Adds _items_ to the end of the Scroller's buffer/dataset. If _eof_ flag is not set, items will be inserted right after the last buffered item and rendered immediately. If _eof_ is _true_, rendering will occur only if the right border of the buffer matches the right border of the dataset (end-of-file is reached); otherwise, items will be virtualized as appended to the end of the dataset. Indexes increase by default. If _decrease_ is set to true, indexes are decremented. See also [bof/eof](https://dhilt.github.io/ngx-ui-scroll/#adapter#bof-eof). |
224-
|[prepend](https://dhilt.github.io/ngx-ui-scroll/#adapter#append-prepend)|(options: {<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;bof?:&nbsp;boolean<br>&nbsp;&nbsp;increase?:&nbsp;boolean<br>})|Adds _items_ to the beginning of the Scroller's buffer/dataset. If _bof_ flag is not set, items will be inserted right before the first buffered item and rendered immediately. If _bof_ is _true_, rendering will occur only if the left border of the buffer matches the left border of the dataset (begin-of-file is reached); otherwise, items will be virtualized as prepended to the beginning of the dataset. Indexes decrease by default. If _increase_ is set to true, indexes are incremented. Note, by historical reasons, _items_ are being reversed during prepending, so if you need to have "initial" order, you may reverse the _items_ array before prepend, or use Adapter.insert API instead. See also [bof/eof](https://dhilt.github.io/ngx-ui-scroll/#adapter#bof-eof). |
223+
|[append](https://dhilt.github.io/ngx-ui-scroll/#adapter#append-prepend)|(options: {<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;eof?:&nbsp;boolean,<br>&nbsp;&nbsp;virtualize?:&nbsp;boolean<br>&nbsp;&nbsp;decrease?:&nbsp;boolean<br>})|Adds _items_ to the end of the Scroller's buffer. If neither _eof_ nor _virtualize_ flag is set, items are inserted and rendered after the last item in the DOM. If _eof_ is _true_, rendering occurs only when the right border of the buffer aligns with the right border of the dataset (end-of-file is reached). Otherwise, items are added to the dataset but not rendered (they are virtualized). Setting the _virtualize_ flag enables hard virtualization: new _items_ will be rendered even if EOF has been reached. Indexes increase by default. If _decrease_ is set to true, indexes are decremented. See also [bof/eof](https://dhilt.github.io/ngx-ui-scroll/#adapter#bof-eof). |
224+
|[prepend](https://dhilt.github.io/ngx-ui-scroll/#adapter#append-prepend)|(options: {<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;bof?:&nbsp;boolean,<br>&nbsp;&nbsp;virtualize?:&nbsp;boolean<br>&nbsp;&nbsp;increase?:&nbsp;boolean<br>})|Adds _items_ to the beginning of the Scroller's buffer. If neither _bof_ nor _virtualize_ flag is set, items are inserted and rendered before the first item in the DOM. If _bof_ is true, rendering occurs only when the left border of the buffer aligns with the left border of the dataset (begin-of-file is reached). Otherwise, items are added to the dataset but not rendered (they are virtualized). Setting the _virtualize_ flag enables hard virtualization: new items will be rendered even if BOF has been reached. Indexes decrease by default. If _increase_ is set to true, indexes are decremented. For historical reasons, items are reversed when performing the prepend operation. To maintain the original order when prepending items, you can either reverse the array before calling Adapter.prepend, or use the Adapter.insert method instead. See also [bof/eof](https://dhilt.github.io/ngx-ui-scroll/#adapter#bof-eof). |
225225
|[insert](https://dhilt.github.io/ngx-ui-scroll/#adapter#insert)|(options: {<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;before?:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;after?:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;beforeIndex?:&nbsp;number,<br>&nbsp;&nbsp;afterIndex?:&nbsp;number,<br>&nbsp;&nbsp;decrease?:&nbsp;boolean<br>})|Inserts _items_ into the buffer or virtually. Only one of the _before_, _after_, _beforeIndex_ and _afterIndex_ options is allowed. If _before_ or _after_ option is used, the Scroller will try to insert items before or after the item that presents in the buffer and satisfies the predicate condition. If _beforeIndex_ or _afterIndex_ option is used, the Scroller will try to insert items by index. If the index to insert is out of the buffer but still belongs to the known datasource boundaries, then the _items_ will be virtualized. Indexes increase by default. Decreasing strategy can be enabled via _decrease_ option. |
226226
|[remove](https://dhilt.github.io/ngx-ui-scroll/#adapter#remove)|(options: {<br>&nbsp;&nbsp;predicate?:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;indexes?:&nbsp;number[],<br>&nbsp;&nbsp;increase?:&nbsp;boolean<br>}) <br><br> type&nbsp;ItemsPredicate&nbsp;=<br>&nbsp;&nbsp;(item: ItemAdapter)&nbsp;=><br>&nbsp;&nbsp;&nbsp;&nbsp;boolean|Removes items form buffer and/or virtually. Predicate is a function to be applied to every item presently in the buffer. Predicate must return a boolean value. If predicate's return value is true, the item will be removed. Alternatively, if _indexes_ array is passed, the items whose indexes match the list will be removed. Only one of the _predicate_ and _indexes_ options is allowed. In case of _indexes_, the deletion is performed also virtually. By default, indexes of the items following the deleted ones are decremented. Instead, if _increase_ is set to _true_, the indexes of the items before the removed ones are incremented. |
227227
|[replace](https://dhilt.github.io/ngx-ui-scroll/#adapter#replace)|(options: {<br>&nbsp;&nbsp;predicate:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;fixRight?:&nbsp;boolean<br>})|Replaces items that continuously match the _predicate_ with an array of new _items_. Indexes are maintained on the assumption that the left border of the dataset is fixed. To release the left border and fix the right one the _fixRight_ option should be set to _true_. |
@@ -287,4 +287,4 @@ Any support and participation are welcome, so feel free to <a href="https://gith
287287

288288
__________
289289

290-
2024 &copy; dhilt
290+
2025 &copy; dhilt

demo/angular.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,8 @@
7474
}
7575
}
7676
}
77+
},
78+
"cli": {
79+
"analytics": false
7780
}
7881
}
Lines changed: 26 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,33 @@
11
<ng-template #itemTemplate let-item="item" let-index="index">
22
<span class="index">{{ index }})</span> {{ item.text }}
33
</ng-template>
4-
5-
<app-demo
6-
[datasource]="datasource"
7-
[context]="demoContext"
8-
[sources]="sources"
9-
[itemTemplate]="itemTemplate"
10-
>
4+
<app-demo [datasource]="datasource" [context]="demoContext" [sources]="sources" [itemTemplate]="itemTemplate">
115
<div actions>
126
<button (click)="doPrepend()">Prepend</button>&nbsp;
13-
<input
14-
[ngModel]="inputPrepend"
15-
(change)="onInputChanged(true, $any($event.target))"
16-
size="2"
17-
/>
7+
<input [ngModel]="inputPrepend" (change)="onInputChanged(true, $any($event.target))" size="2" />
188
&nbsp;items
19-
{{ increasePrepend ? 'increasingly' : 'decreasingly' }} &nbsp;<input
20-
type="checkbox"
21-
[(ngModel)]="increasePrepend"
22-
/>
9+
{{ increasePrepend ? 'increasingly' : 'decreasingly' }} &nbsp;<input type="checkbox" [(ngModel)]="increasePrepend" />
2310
<br />
2411
<button (click)="doAppend()">Append</button>&nbsp;
25-
<input
26-
[ngModel]="inputAppend"
27-
(change)="onInputChanged(false, $any($event.target))"
28-
size="2"
29-
/>
12+
<input [ngModel]="inputAppend" (change)="onInputChanged(false, $any($event.target))" size="2" />
3013
&nbsp;items
31-
{{ decreaseAppend ? 'decreasingly' : 'increasingly' }} &nbsp;<input
32-
type="checkbox"
33-
[(ngModel)]="decreaseAppend"
34-
/>
14+
{{ decreaseAppend ? 'decreasingly' : 'increasingly' }} &nbsp;<input type="checkbox" [(ngModel)]="decreaseAppend" />
3515
</div>
36-
3716
<div description>
3817
<p>
39-
Along with <em>items</em> parameter both append and prepend methods have
40-
<em>eof</em>/<em>bof</em> parameter which is optional and which prevents
41-
rendering of new items when the end of the dataset (if we are speaking of
42-
<em>append</em>) or beginning of the dataset (<em>prepend</em> case) is
43-
not reached. See also
44-
<a
45-
[routerLink]="['/', adapterScope.id]"
46-
fragment="{{ adapterPropsScope.map.bofEof.id }}"
47-
>bof/eof demo</a
48-
>.
18+
The <em>append</em> and <em>prepend</em> methods have parameters
19+
responsible for the virtualization of inserted items:
20+
<em>eof</em>/<em>bof</em> and <em>virtualize</em>.
21+
Only one of these parameters can be used at a time
22+
(setting both to <em>true</em> simultaneously will reset both values to <em>false</em>).
23+
</p>
24+
<p>
25+
If <em>eof</em>/<em>bof</em> is set to <em>true</em>,
26+
the items added via the <em>append</em>/<em>prepend</em> methods will be virtualized
27+
and will not appear in the DOM if, at the time of insertion,
28+
we are not at the beginning (in the case of prepend-bof)
29+
or at the end (in the case of append-eof) of the list.
30+
See also <a [routerLink]="['/', adapterScope.id]" fragment="{{ adapterPropsScope.map.bofEof.id }}">bof/eof demo</a>.
4931
</p>
5032
<p>
5133
For example, if we call <em>{{ prependCallSample }}</em> when the
@@ -57,13 +39,16 @@
5739
default item size. The same works for <em>{{ appendCallSample }}</em> call
5840
adjusted for forward direction and forward padding element.
5941
</p>
42+
<p>
43+
If <em>virtualize</em> is set to <em>true</em>, the added rows will be virtualized in any case,
44+
even if we are at the beginning or end of the list.
45+
</p>
6046
<p>
6147
Indexes increase by default when <em>Adapter.append</em> and decrease by
6248
default when <em>Adapter.prepend</em>. The indexing strategy can be
6349
changed by <em>decrease</em>/<em>increase</em> params. They are boolean
6450
and set to <em>false</em> by default. For example, if we call
65-
<em>{{ prependIncreaseCallSample }}</em
66-
>, the topmost index remains unchanged, prepended items start with the
51+
<em>{{ prependIncreaseCallSample }}</em>, the topmost index remains unchanged, prepended items start with the
6752
topmost index, the rest indexes are incremented.
6853
</p>
6954
<p>
@@ -77,18 +62,10 @@
7762
<em>Datasource.get</em> and doPrepend/doAppend methods to guarantee a
7863
stable data flow from the App component to the Scroller. Another examples
7964
of such synchronization could be found in
80-
<a
81-
[routerLink]="['/', adapterScope.id]"
82-
fragment="{{ adapterMethodsScope.map.remove.id }}"
83-
>remove</a
84-
>
65+
<a [routerLink]="['/', adapterScope.id]" fragment="{{ adapterMethodsScope.map.remove.id }}">remove</a>
8566
and
86-
<a
87-
[routerLink]="['/', adapterScope.id]"
88-
fragment="{{ adapterMethodsScope.map.insert.id }}"
89-
>insert</a
90-
>
67+
<a [routerLink]="['/', adapterScope.id]" fragment="{{ adapterMethodsScope.map.insert.id }}">insert</a>
9168
demos.
9269
</p>
9370
</div>
94-
</app-demo>
71+
</app-demo>

demo/app/samples/adapter/append-prepend.component.html

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,14 @@
22
<div actions style="display: flex">
33
<button (click)="doAppend()">Append</button> /
44
<button (click)="doPrepend()">Prepend</button>
5-
<input
6-
[ngModel]="inputValue"
7-
(change)="onInputChanged($any($event.target))"
8-
size="2"
9-
/>
5+
<input [ngModel]="inputValue" (change)="onInputChanged($any($event.target))" size="2" />
106
</div>
11-
127
<div description>
138
<p>
149
Adding items at the end and at the beginning of the Scroller's Buffer is
1510
possible with
1611
<em>Adapter.append</em> and <em>Adapter.prepend</em> methods respectively.
17-
They have the following object-arguments:
12+
These methods accept the following argument objects:
1813
</p>
1914
<div class="row">
2015
<div class="column">
@@ -24,46 +19,42 @@
2419
<pre>{{ prependArgumentsDescription }}</pre>
2520
</div>
2621
</div>
22+
<ul>
23+
<li><em>items</em> An array of items to add.</li>
24+
<li><em>eof</em>/<em>bof</em>/<em>virtualize</em> Virtualization params.</li>
25+
<li><em>decrease</em>/<em>increase</em> Influence the index management strategy.</li>
26+
</ul>
2727
<p>
28-
The <em>items</em> parameter is an array of items we want to add,
29-
<em>eof</em>/<em>bof</em> params provide virtualization,
30-
<em>decrease</em>/<em>increase</em> define index strategy. Here we deal
31-
with only <em>items</em> parameter, other params are considered in the
32-
<a
33-
[routerLink]="['/', adapterScope.id]"
34-
fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}"
35-
>next demo</a
36-
>. Both <em>append</em> and <em>prepend</em> methods act in the same way,
28+
In this section, we focus on the <em>items</em> parameter.
29+
The other options will be considered in the
30+
<a [routerLink]="['/', adapterScope.id]" fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}">next demo</a>.
31+
Both <em>append</em> and <em>prepend</em> methods work similarly,
3732
so let's discuss <em>prepend</em>.
3833
</p>
3934
<p>
40-
In this demo we have 20 items on start, 5 of them (95-99) are invisible on
41-
the backward direction. By pushing "Prepend" button we want to add 4
42-
(which is the input value) new items to the top of the list. After they
43-
are prepended, they become visible when scrolling up. The Scroller
44-
accurately injects prepended items into its Buffer consisting of 95-114
45-
rows initially, so 1-4 new items temporary take place of items with
46-
indexes 91-94.
35+
In this demo we have 20 items on start. The first 5 items (indices 95–99)
36+
are invisible because they are outside the visible part of the viewport.
37+
When you click the “Prepend” button, 4 new items (as specified in the input)
38+
are added to the top of the list. These prepended items become visible as you scroll upward.
39+
The Scroller seamlessly injects these new items into its buffer,
40+
so the buffer indices change from 95–114 to 91–114.
4741
</p>
4842
<p>
49-
But if we scroll away and make '90s items invisible and removed from the
50-
viewport, and then scroll back, we will realise that nothing changes:
51-
"new" items are gone and the old 91-94 items returned to initial position.
52-
The point is that the changes we provide via <em>Adapter</em> over the
53-
internal Scroller's Buffer don't affect the external
54-
<em>Datasource</em> we implemented on the Component level. This is the end
55-
app developer responsibility to take care of the data consistency during
56-
manual updates such as append, prepend, insert, remove, etc: the
57-
<em>Datasource.get</em> must provide correct data, while the Scroller
58-
maintains indexes.
59-
<a
60-
[routerLink]="['/', adapterScope.id]"
61-
fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}"
62-
>The next sample</a
63-
>
64-
provides one of the approach of consistent
65-
<em>Datasource</em> implementation with <em>Adapter.append</em> and
66-
<em>Adapter.prepend</em> methods usage.
43+
However, if you scroll away — causing the items with indices in the 90s
44+
to become invisible and be removed from the DOM — and then scroll back,
45+
you’ll notice that the “new” items are gone and the original items (91–94)
46+
have returned to their places. This behavior occurs because changes
47+
made via the <em>Adapter</em> only affect the Scroller’s internal buffer,
48+
not the external <em>Datasource</em> managed at the App Component level.
49+
</p>
50+
<p>
51+
It is the responsibility of the end app developer to maintain data consistency
52+
when performing manual updates (such as append, prepend, insert, remove operations).
53+
The <em>Datasource.get</em> method must always return the correct data,
54+
while the Scroller manages the indexes.
55+
The <a [routerLink]="['/', adapterScope.id]" fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}">next sample</a>
56+
demonstrates an approach for consistent <em>Datasource</em> implementation
57+
synchronized with the use of the <em>Adapter.append</em> and <em>Adapter.prepend</em> methods.
6758
</p>
6859
</div>
69-
</app-demo>
60+
</app-demo>

demo/app/samples/adapter/append-prepend.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,13 @@ async doAppend() {
123123
prependArgumentsDescription = ` AdapterPrependOptions {
124124
items: unknown[];
125125
bof?: boolean;
126+
virtualize?: boolean;
126127
increase?: boolean;
127128
}`;
128129
appendArgumentsDescription = ` AdapterAppendOptions {
129130
items: unknown[];
130131
eof?: boolean;
132+
virtualize?: boolean;
131133
decrease?: boolean;
132134
}`;
133135

0 commit comments

Comments
 (0)