Skip to content

Commit c1f48b6

Browse files
authored
Merge pull request #226 from dhilt/issue-225-Adapter-remove-virtualization
Adapter.remove increase
2 parents 4eb0e78 + a5623c2 commit c1f48b6

File tree

19 files changed

+348
-139
lines changed

19 files changed

+348
-139
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ Below is the list of invocable methods of the Adapter API with description and l
181181
|[append](https://dhilt.github.io/ngx-ui-scroll/#/adapter#append-prepend)|(options: {<br>&nbsp;&nbsp;items:&nbsp;any[],<br>&nbsp;&nbsp;eof?:&nbsp;boolean<br>}) <br><br> (items:&nbsp;any&nbsp;&vert;&nbsp;any[], eof?:&nbsp;boolean) &#42;<br><sub>&#42; old signature, deprecated</sub>|Adds items to the end of the uiScroll dataset. If eof parameter is not set, items will be added and rendered immediately, they will be placed right after the last item in the uiScroll buffer. If eof parameter is set to true, items will be added and rendered only if the end of the dataset is reached; otherwise, these items will be virtualized. See also [bof/eof](https://dhilt.github.io/ngx-ui-scroll/#/adapter#bof-eof) demo. |
182182
|[prepend](https://dhilt.github.io/ngx-ui-scroll/#/adapter#append-prepend)|(options: {<br>&nbsp;&nbsp;items:&nbsp;any[],<br>&nbsp;&nbsp;bof?:&nbsp;boolean<br>}) <br><br> (items:&nbsp;any&nbsp;&vert;&nbsp;any[], bof?:&nbsp;boolean) &#42;<br><sub>&#42; old signature, deprecated</sub>|Adds items to the beginning of the uiScroll dataset. If bof parameter is not set, items will be added and rendered immediately, they will be placed right before the first item in the uiScroll buffer. If bof parameter is set to true, items will be added and rendered only if the beginning of the dataset is reached; otherwise, these items will be virtualized. See also [bof/eof](https://dhilt.github.io/ngx-ui-scroll/#/adapter#bof-eof) demo. |
183183
|[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. |
184-
|[remove](https://dhilt.github.io/ngx-ui-scroll/#/adapter#remove)|(predicate:&nbsp;ItemsPredicate)<br><br>type&nbsp;ItemsPredicate&nbsp;=<br>&nbsp;&nbsp;(item: ItemAdapter)&nbsp;=><br>&nbsp;&nbsp;&nbsp;&nbsp;boolean|Removes items from current buffer. Predicate is a function to be applied to every item presently in the buffer. Predicate must return boolean value. If predicate's return value is true, the item will be removed. _Note!_ Current implementation allows to remove only a continuous series of items per call. If you want to remove, say, 5 and 7 items, you should call the remove method twice. Removing a series of items from 5 to 7 could be done in a single call. |
184+
|[remove](https://dhilt.github.io/ngx-ui-scroll/#/adapter#remove)|(options: {<br>&nbsp;&nbsp;predicate:&nbsp;ItemsPredicate,<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 from current buffer. Predicate is a function to be applied to every item presently in the buffer. Predicate must return boolean value. If predicate's return value is true, the item will be removed. By default, indexes of the items following the deleted ones are decremented. Instead, if _increase_ is set to _true_, indexes of the items before the removed ones will be increased. <sub>_Note!_ Current implementation allows to remove only a continuous series of items per call. If you want to remove, say, 5 and 7 items, you should call the remove method twice. Removing a series of items from 5 to 7 could be done in a single call.</sub> |
185185
|[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. |
186186
|[insert](https://dhilt.github.io/ngx-ui-scroll/#/adapter#insert)|(options: {<br>&nbsp;&nbsp;items:&nbsp;any[],<br>&nbsp;&nbsp;before?:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;after?:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;decrease?:&nbsp;boolean<br>})|Inserts items _before_ or _after_ the one that satisfies the predicate condition. Only one of _before_ and _after_ options is allowed. Indexes increase by default. Decreasing strategy can be enabled via _decrease_ option. |
187187

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<ng-template #itemTemplate let-item="item">
22
{{item.text}}
3-
<span class="remove" (click)="doRemove(item.id)">[remove]</span>
3+
<span class="remove" (click)="removeById(item.id)">[remove]</span>
44
</ng-template>
55

66
<app-demo
@@ -9,28 +9,49 @@
99
[sources]="sources"
1010
[itemTemplate]="itemTemplate"
1111
>
12+
<div actions>
13+
<input [(ngModel)]="inputValue" (change)="onInputChanged($event.target)" size="3">
14+
<button (click)="removeByIndex(inputValue)">Remove by index</button>
15+
</div>
1216
<div description>
1317
<p>
14-
<em>Adapter.remove</em> method allows to remove items
15-
from current <em>uiScroll</em> buffer. This method accepts 1 argument
16-
which is a predicate function applying to each item in the current buffer.
18+
<em>Adapter.remove</em> method allows to remove items from current buffer.
19+
The argument of this method is an object of the following type:
20+
</p>
21+
<pre>{{argumentsDescription}}</pre>
22+
<p>
23+
The <em>predicate</em> option is a function applying to each item in the buffer.
1724
If the return value is true, the item will be removed.
18-
In this demo we have pretty simple predicate:
25+
Only a continuous series of items can be removed at a time.
26+
So, if you need to remove items 1, 2, 3 and 5,
27+
you need to call this method twice: once for 1, 2, 3 and then for 5.
28+
</p>
29+
<p>
30+
The argument of the predicate has <em>ItemAdapter</em> type, and
31+
in this demo we have two versions of <em>predicate</em> using for
32+
removing by id and by index:
1933
</p>
2034
<pre>{{predicateDescription}}</pre>
2135
<p>
22-
Since the argument of the predicate has <em>ItemAdapter</em> type,
23-
the <em>data</em> contains id-text objects defined at the component level.
24-
The argument of <em>doRemove</em> method comes from the template
25-
as the <em>id</em> of the item the user clicked on.
36+
Indexes are adjusted each time we perform a removing from the buffer.
37+
It is impossible to remove item with id = 5 twice,
38+
because there is only one item with id = 5.
39+
But we may remove item with index = 5 as many times
40+
as many items we have after this index. Indexes decrease by default.
41+
</p>
42+
<p>
43+
The second option is <em>increase</em>.
44+
It is for changing the default indexes adjustment strategy.
45+
By setting it to <em>true</em>, we tell the <em>uiScroll</em>
46+
that we want to increase indexes of the items before the removed one(s).
2647
</p>
2748
<p>
2849
The very important thing is to synchronize our datasource with the changes
2950
we are making over the <em>uiScroll</em> buffer via <em>Adapter.remove</em>.
3051
Generally this is the App component responsibility,
31-
and in this demo it happens via <em>doRemoveDatasource(id)</em> method.
32-
It removes item with given <em>id</em> from the initial dataset,
33-
and decrements <em>id</em> of all items whose ids are greater than the given one.
52+
and in this demo it is done by the <em>removeFromDatasource</em> method.
53+
It removes 1 item from the initial dataset,
54+
and decrements the value of the right border.
3455
</p>
3556
</div>
3657
</app-demo>

demo/app/samples/adapter/remove.component.ts

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class DemoRemoveComponent {
2424
MIN = -50;
2525
MAX = 50;
2626
data: any[];
27+
inputValue = 5;
2728

2829
constructor() {
2930
this.data = [];
@@ -34,12 +35,12 @@ export class DemoRemoveComponent {
3435

3536
datasource = new Datasource({
3637
get: (index: number, count: number, success: Function) => {
37-
const data = [];
38-
for (let i = index; i < index + count; i++) {
39-
const found = this.data.find(item => item.id === i);
40-
if (found) {
41-
data.push(found);
42-
}
38+
let data = [];
39+
const shift = -Math.min(this.MIN, 0);
40+
const start = Math.max(index + shift, 0);
41+
const end = Math.min(index + count - 1, this.MAX) + shift;
42+
if (start <= end) {
43+
data = this.data.slice(start, end + 1);
4344
}
4445
doLog(this.demoContext, index, count, data.length);
4546
success(data);
@@ -51,6 +52,7 @@ export class DemoRemoveComponent {
5152
text: `MIN = -50;
5253
MAX = 50;
5354
data: any[];
55+
inputValue = 5;
5456
5557
constructor() {
5658
this.data = [];
@@ -60,43 +62,55 @@ constructor() {
6062
}
6163
6264
datasource = new Datasource({
63-
get: (index, count, success) => {
64-
const data = [];
65-
for (let i = index; i < index + count; i++) {
66-
const found = this.data.find(item => item.id === i);
67-
if (found) {
68-
data.push(found);
69-
}
70-
}
71-
success(data);
65+
get: (index: number, count: number, success: Function) => {
66+
const shift = -Math.min(this.MIN, 0);
67+
const start = Math.max(index + shift, 0);
68+
const end = Math.min(index + count - 1, this.MAX) + shift;
69+
success(start <= end
70+
? this.data.slice(start, end + 1)
71+
: []
72+
);
7273
}
7374
});
7475
75-
doRemoveDatasource(id: number) {
76-
this.data = this.data.reduce((acc, item) => {
77-
if (item.id !== id) {
78-
if (item.id > id) {
79-
item.id--;
80-
}
81-
acc.push(item);
82-
}
83-
return acc;
84-
}, []);
85-
this.MAX = this.data[this.data.length - 1].id;
76+
removeFromDatasource(toRemove: number, byIndex = false) {
77+
const indexToRemove = byIndex
78+
? toRemove - Math.min(this.MIN, 0) // shift!
79+
: this.data.findIndex(({ id }) => id === toRemove);
80+
if (indexToRemove >= 0) {
81+
this.data.splice(indexToRemove, 1);
82+
}
83+
this.MAX--;
84+
}
85+
86+
async removeById(id: number) {
87+
this.removeFromDatasource(id);
88+
await this.datasource.adapter.remove({
89+
predicate: ({ data }) => data.id === id
90+
});
8691
}
8792
88-
async doRemove(id: number) {
89-
await this.datasource.adapter.remove(({ data }) => data.id === id);
90-
this.doRemoveDatasource(id);
93+
async removeByIndex(index: number) {
94+
this.removeFromDatasource(index, true);
95+
await this.datasource.adapter.remove({
96+
predicate: ({ $index }) => $index === index
97+
});
9198
}`
9299
}, {
93100
active: true,
94101
name: DemoSourceType.Template,
95-
text: `<div class="viewport">
102+
text: `<input [(ngModel)]="inputValue" size="3">
103+
<button (click)="removeByIndex(inputValue)">
104+
Remove by index
105+
</button>
106+
107+
<div class="viewport">
96108
<div *uiScroll="let item of datasource">
97109
<div class="item">
98110
{{item.text}}
99-
<span class="remove" (click)="doRemove(item.id)">[remove]</span>
111+
<span class="remove" (click)="removeById(item.id)">
112+
[remove]
113+
</span>
100114
</div>
101115
</div>
102116
</div>`
@@ -121,24 +135,41 @@ async doRemove(id: number) {
121135
}`
122136
}];
123137

124-
predicateDescription = ` this.datasource.adapter.remove(({ data }) => data.id === id);`;
138+
argumentsDescription = ` AdapterRemoveOptions {
139+
predicate: ItemsPredicate;
140+
increase?: boolean;
141+
}`;
142+
predicateDescription = ` adapter.remove(({ data }) => data.id === id);
143+
adapter.remove(({ $index }) => $index === index);`;
144+
145+
onInputChanged(target: HTMLInputElement) {
146+
const value = parseInt(target.value.trim(), 10);
147+
target.value = value.toString();
148+
this.inputValue = value;
149+
}
125150

126-
doRemoveDatasource(id: number) {
127-
this.data = this.data.reduce((acc, item) => {
128-
if (item.id !== id) {
129-
if (item.id > id) {
130-
item.id--;
131-
}
132-
acc.push(item);
133-
}
134-
return acc;
135-
}, []);
136-
this.MAX = this.data[this.data.length - 1].id;
151+
removeFromDatasource(toRemove: number, byIndex = false) {
152+
const indexToRemove = byIndex
153+
? toRemove - Math.min(this.MIN, 0) // shift!
154+
: this.data.findIndex(({ id }) => id === toRemove);
155+
if (indexToRemove >= 0) {
156+
this.data.splice(indexToRemove, 1);
157+
}
158+
this.MAX--;
159+
}
160+
161+
async removeById(id: number) {
162+
this.removeFromDatasource(id);
163+
await this.datasource.adapter.remove({
164+
predicate: ({ data }) => data.id === id
165+
});
137166
}
138167

139-
async doRemove(id: number) {
140-
await this.datasource.adapter.remove(({ data }) => data.id === id);
141-
this.doRemoveDatasource(id);
168+
async removeByIndex(index: number) {
169+
this.removeFromDatasource(index, true);
170+
await this.datasource.adapter.remove({
171+
predicate: ({ $index }) => $index === index
172+
});
142173
}
143174

144175
}

package-dist.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ngx-ui-scroll",
3-
"version": "1.9.0",
3+
"version": "1.9.1",
44
"description": "Infinite/virtual scroll for Angular",
55
"main": "./bundles/ngx-ui-scroll.umd.js",
66
"module": "./fesm5/ngx-ui-scroll.js",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"deploy-app": "npm run build-app && firebase deploy",
1515
"preinstall": "cd server && npm install",
1616
"postinstall": "npm run build-app",
17-
"pack:install": "npm run build && npm pack ./dist && npm install ngx-ui-scroll-1.9.0.tgz --no-save",
17+
"pack:install": "npm run build && npm pack ./dist && npm install ngx-ui-scroll-1.9.1.tgz --no-save",
1818
"pack:start": "npm run pack:install && npm start",
1919
"build": "node build.js",
2020
"publish:lib": "npm run build && npm publish ./dist",

src/component/classes/adapter.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
ItemsPredicate,
1818
AdapterPrependOptions,
1919
AdapterAppendOptions,
20+
AdapterRemoveOptions,
2021
AdapterClipOptions,
2122
AdapterInsertOptions,
2223
AdapterFixOptions,
@@ -37,13 +38,21 @@ const fixScalarWanted = (name: string, container: { [key: string]: boolean }) =>
3738
};
3839

3940
const convertAppendArgs = (isAppend: boolean, options: any, eof?: boolean) => {
40-
if (!(typeof options === 'object' && options.hasOwnProperty('items'))) {
41+
if (!(options !== null && typeof options === 'object' && options.hasOwnProperty('items'))) {
4142
const items = !Array.isArray(options) ? [options] : options;
4243
options = isAppend ? { items, eof } : { items, bof: eof };
4344
}
4445
return options;
4546
};
4647

48+
const convertRemoveArgs = (options: AdapterRemoveOptions | ItemsPredicate) => {
49+
if (!(options !== null && typeof options === 'object' && options.hasOwnProperty('predicate'))) {
50+
const predicate = options as ItemsPredicate;
51+
options = { predicate };
52+
}
53+
return options;
54+
};
55+
4756
export class Adapter implements IAdapter {
4857
private logger: Logger;
4958
private getWorkflow: WorkflowGetter;
@@ -109,9 +118,9 @@ export class Adapter implements IAdapter {
109118
// restore original values from the publicContext if present
110119
const adapterProps = publicContext
111120
? ADAPTER_PROPS_STUB.map(prop => ({
112-
...prop,
113-
value: (publicContext as any)[prop.name]
114-
}))
121+
...prop,
122+
value: (publicContext as any)[prop.name]
123+
}))
115124
: ADAPTER_PROPS(EMPTY_ITEM);
116125

117126
// Scalar permanent props
@@ -247,7 +256,7 @@ export class Adapter implements IAdapter {
247256
this.relax$.complete();
248257
}
249258
Object.values(this.source).forEach(observable => observable.complete());
250-
}
259+
}
251260

252261
reset(options?: IDatasourceOptional): any {
253262
this.reloadCounter++;
@@ -297,7 +306,8 @@ export class Adapter implements IAdapter {
297306
});
298307
}
299308

300-
remove(options: ItemsPredicate): any {
309+
remove(options: AdapterRemoveOptions | ItemsPredicate): any {
310+
options = convertRemoveArgs(options); // support old signature
301311
this.logger.logAdapterMethod('remove', options);
302312
this.workflow.call({
303313
process: Process.remove,

src/component/classes/buffer.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,21 +196,31 @@ export class Buffer {
196196
this.items = [...items, ...this.items];
197197
}
198198

199-
removeItems(items: Item[], immutableTop: boolean = true) {
200-
// todo: implement immutableTop option
199+
removeItems(indexes: number[], immutableTop: boolean = true) {
201200
const result: Item[] = [];
202201
const toRemove: number[] = [];
203-
this.items.forEach(item => {
204-
if (items.some(({ $index }) => $index === item.$index)) {
202+
const length = this.items.length;
203+
for (
204+
let i = immutableTop ? 0 : length - 1;
205+
immutableTop ? i < length : i >= 0;
206+
immutableTop ? i++ : i--
207+
) {
208+
const item = this.items[i];
209+
if (indexes.indexOf(item.$index) >= 0) {
205210
toRemove.push(item.$index);
206-
return;
211+
continue;
207212
}
208-
const diff = immutableTop
209-
? toRemove.reduce((acc, index) => acc - (item.$index > index ? 1 : 0), 0)
210-
: toRemove.reduce((acc, index) => acc + (item.$index < index ? 1 : 0), 0);
213+
const diff = toRemove.reduce((acc, index) => acc + (immutableTop
214+
? (item.$index > index ? -1 : 0)
215+
: (item.$index < index ? 1 : 0)
216+
), 0);
211217
item.updateIndex(item.$index + diff);
212-
result.push(item);
213-
});
218+
if (immutableTop) {
219+
result.push(item);
220+
} else {
221+
result.unshift(item);
222+
}
223+
}
214224
if (immutableTop) {
215225
this.absMaxIndex -= toRemove.length;
216226
} else {

src/component/classes/state/clip.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export class ClipModel {
22
noClip: boolean;
33
doClip: boolean;
44
simulate: boolean;
5+
increase: boolean;
56
callCount: number;
67
forceForward: boolean;
78
forceBackward: boolean;
@@ -26,6 +27,7 @@ export class ClipModel {
2627
} else {
2728
this.simulate = false;
2829
}
30+
this.increase = false;
2931
}
3032

3133
forceReset() {

0 commit comments

Comments
 (0)