diff --git a/.eslintignore b/.eslintignore
index ce402928..980512ac 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,5 @@
node_modules
dist
dist-app
+package.json
+package-lock.json
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
index 8d1fd58a..eaba20b9 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,9 +1,7 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
- "plugins": [
- "@typescript-eslint"
- ],
+ "plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
@@ -11,10 +9,13 @@
],
"rules": {
"max-len": ["error", { "code": 120 }],
- "quotes": ["error", "single"],
+ "quotes": ["error", "single", { "avoidEscape": true }],
"semi": ["error", "always"],
"@typescript-eslint/no-empty-function": "off",
- "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ { "argsIgnorePattern": "^_" }
+ ]
},
"overrides": [
{
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 00000000..d24fdfc6
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx lint-staged
diff --git a/.lintstagedrc.json b/.lintstagedrc.json
new file mode 100644
index 00000000..67afd1ca
--- /dev/null
+++ b/.lintstagedrc.json
@@ -0,0 +1,4 @@
+{
+ "*.ts": "npm run lint",
+ "*.{ts,js,html,scss,md,json}": "prettier --write"
+}
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..f8637288
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "tabWidth": 2,
+ "singleQuote": true,
+ "arrowParens": "avoid",
+ "trailingComma": "none"
+}
\ No newline at end of file
diff --git a/demo/app/app-routing.module.ts b/demo/app/app-routing.module.ts
index 2f48cbb0..5509ac06 100644
--- a/demo/app/app-routing.module.ts
+++ b/demo/app/app-routing.module.ts
@@ -24,5 +24,4 @@ const routes: Routes = [
imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule]
})
-export class AppRoutingModule {
-}
+export class AppRoutingModule {}
diff --git a/demo/app/app.component.html b/demo/app/app.component.html
index 49599729..a3ee781d 100644
--- a/demo/app/app.component.html
+++ b/demo/app/app.component.html
@@ -2,13 +2,10 @@
- Adapter is a special object to assess and manipulate the Scroller. - The API it provides is discussed on this page. - In order to get an access to the Adapter API, - the Datasource has to be instantiated via operator new: + Adapter is a special object to assess and manipulate + the Scroller. The API it provides is discussed on this page. + In order to get an access to the Adapter API, the + Datasource has to be instantiated via operator new:
-{{datasourceSample}}+
{{ datasourceSample }}
For better typings it is recommended to provide also data item type argument:
-{{datasourceTypedSample}}+
{{ datasourceTypedSample }}
- The constructor argument is an object of IDatasource type which - must include get method property and - may include settings object property. - The Scroller's augments the result Datasource object - with the Adapter object property during instantiating. - So, after the Datasource object had been instantiated, we may access - this.datasource.adapter property object with its API methods and properties. + The constructor argument is an object of IDatasource + type which must include get method property and may include + settings object property. The Scroller's augments the result + Datasource object with the Adapter object property during + instantiating. So, after the Datasource object had been instantiated, + we may access this.datasource.adapter property object with its + API methods and properties.
- The Adapter.relax is a kind of empty Adapter method - that does only one thing: it resolves when the Scroller is relaxed. - If we need to run any logic that should not interfere with the Scroller internal processes, - it should be protected by the "relax" promise: + The Adapter.relax is a kind of empty Adapter method that + does only one thing: it resolves when the Scroller is relaxed. If we need + to run any logic that should not interfere with the Scroller internal + processes, it should be protected by the "relax" promise:
-{{relaxSample}}+
{{ relaxSample }}
- In this demo we simulate items replacement - by performing a sequence of 2 Adapter methods: remove and insert. - A few existed items (5, 6, 7) are being replaced with a new one (5*). - Removing is an asynchronous operation, and we can't perform - "insert" before "remove" is done (it will produce an error). - Thus, both operations must be run in sequence. - Also, we should be wary of any async processes - before running the Adapter methods chain. - So the algorithm for this demo can be written as follows: + In this demo we simulate items replacement by performing a sequence of 2 + Adapter methods: remove and insert. A few existed items (5, 6, 7) + are being replaced with a new one (5*). Removing is an asynchronous + operation, and we can't perform "insert" before "remove" is done (it will + produce an error). Thus, both operations must be run in sequence. Also, we + should be wary of any async processes before running the Adapter methods + chain. So the algorithm for this demo can be written as follows:
- (Please note, a replacement can be done via + (Please note, a replacement can be done via Adapter.replace API in a single run; - the insert-remove approach is less performant than the Adapter.replace) + fragment="{{ adapterMethodsScope.map.replace.id }}" + >Adapter.replace API + in a single run; the insert-remove approach is less performant + than the Adapter.replace)
- The purpose of the Adapter.relax method is to make sure that the uiScroll relaxes - and there are no pending internal tasks running on the Scroller's end. - The first tab "Relax" demonstrates how the desired sequence can be implemented with - the Adapter.relax method. - This method returns a promise which becomes resolved when the Scroller stops. + The purpose of the Adapter.relax method is to make sure that the + uiScroll relaxes and there are no pending internal tasks running + on the Scroller's end. The first tab "Relax" demonstrates how the desired + sequence can be implemented with the Adapter.relax method. This + method returns a promise which becomes resolved when the Scroller stops. Basically, this may be treated as a shortcut for the following approach, which is presented on the "Is loading" tab:
-{{isLoadingSample}}+
{{ isLoadingSample }}
- All the tabs are equivalent. The third tab "Callback" uses a callback signature of - the Adapter.relax method. The method accepts callback as an optional argument, - and this allows to run necessary logic right before resolving the "relax" promise. - This might be helpful if we don't want to wait until the end of the current call stack. + All the tabs are equivalent. The third tab "Callback" uses a callback + signature of the Adapter.relax method. The method accepts + callback as an optional argument, and this allows to run necessary logic + right before resolving the "relax" promise. This might be helpful if we + don't want to wait until the end of the current call stack.
At last, we may use Return value API - (which is a part of each Adapter method) - and get rid of the second "relax". This approach is demonstrated on the last tab "Return". + fragment="{{ adapterMethodsScope.map.returnValue.id }}" + >Return value API + (which is a part of each Adapter method) and get rid of the + second "relax". This approach is demonstrated on the last tab "Return".
- Along with Adapter properties there are several Adapter methods. - Each Adapter method runs the Scroller's Workflow - that handles multiple internal processes, some of which may be asynchronous. - In order to deal with such asynchronicity, - the return value of any Adapter method is a Promise - that is resolved when all internal processes - triggered by the Adapter are terminated. + Along with Adapter properties there are several + Adapter methods. Each Adapter method runs the Scroller's + Workflow that handles multiple internal processes, some of which may be + asynchronous. In order to deal with such asynchronicity, the return value + of any Adapter method is a Promise that is resolved when + all internal processes triggered by the Adapter are terminated. Return value has following type
-{{returnValueType}}+
{{ returnValueType }}
- - success: specifies whether the Adapter method completed successfully or not;
- for example, if some method is invoked with wrong arguments, no error will be thrown,
- but we'll get success: false in the result;
-
- - details: contains error message in case success is false;
-
- - immediate: specifies whether the Adapter method completed immediately or not;
- for example, if Adapter.remove won't remove any items, then it will be
- terminated immediately and we'll get immediate: true in the result.
+ - success: specifies whether the Adapter method
+ completed successfully or not; for example, if some method is invoked with
+ wrong arguments, no error will be thrown, but we'll get
+ success: false in the result;
+
+ - details: contains error message in case success is
+ false;
+
+ - immediate: specifies whether the Adapter method
+ completed immediately or not; for example, if
+ Adapter.remove won't remove any items, then it will be terminated
+ immediately and we'll get immediate: true in the result.
{{returnValueSample}}+
{{ returnValueSample }}
- Note, immediate: false does not necessarily mean that the Adapter method - worked asynchronously. For example, Adapter.clip does its work in a single call stack - regardless of whether there are items to be clipped or not. But in any case, - if there is any logic that needs to be run after the Adapter method completes its job, + Note, immediate: false does not necessarily mean that the + Adapter method worked asynchronously. For example, + Adapter.clip does its work in a single call stack regardless of + whether there are items to be clipped or not. But in any case, if there is + any logic that needs to be run after the Adapter method completes its job, it is highly recommended to implement an explicit sequence like this:
-{{explicitSequenceSample}}- +
{{ explicitSequenceSample }}
- Along with items parameter both append and prepend methods - have eof/bof parameter - which is optional and which prevents rendering of new items - when the end of the dataset (if we are speaking of append) or - beginning of the dataset (prepend case) is not reached. See also + Along with items parameter both append and prepend methods have + eof/bof parameter which is optional and which prevents + rendering of new items when the end of the dataset (if we are speaking of + append) or beginning of the dataset (prepend case) is + not reached. See also bof/eof demo. + fragment="{{ adapterPropsScope.map.bofEof.id }}" + >bof/eof demo.
- For example, if we call {{prependCallSample}} when the beginning of the dataset is reached - and no more items can be fetched in the backward direction, then the new items will be injected and rendered immediately. - But if we are not in BOF, new items will not appear, they will be virtualized: - viewport backward padding element size will be increased in accordance with - the number of new items multiplied by the default item size. - The same works for {{appendCallSample}} call + For example, if we call {{ prependCallSample }} when the + beginning of the dataset is reached and no more items can be fetched in + the backward direction, then the new items will be injected and rendered + immediately. But if we are not in BOF, new items will not appear, they + will be virtualized: viewport backward padding element size will be + increased in accordance with the number of new items multiplied by the + default item size. The same works for {{ appendCallSample }} call adjusted for forward direction and forward padding element.
- Indexes increase by default when Adapter.append and - decrease by default when Adapter.prepend. - The indexing strategy can be changed by decrease/increase params. - They are boolean and set to false by default. - For example, if we call {{prependIncreaseCallSample}}, - the topmost index remains unchanged, prepended items start with the topmost index, - the rest indexes are incremented. + Indexes increase by default when Adapter.append and decrease by + default when Adapter.prepend. The indexing strategy can be + changed by decrease/increase params. They are boolean + and set to false by default. For example, if we call + {{ prependIncreaseCallSample }}, the topmost index remains unchanged, prepended items start with the + topmost index, the rest indexes are incremented.
- The most important idea behind the Adapter update API is that - the changes made via Adapter must be synched with the Datasource. - This demo implements one of the possible ways of synching - the Datasource with the Adapter.append/prepend updates. - The external dataset is changed per each doAppend / doPrepend call - destructively by unshift and push Array's methods. - There are also some additional variables that provide index offsets and - that are used in the Datasource.get and doPrepend/doAppend methods - to guarantee a stable data flow from the App component to the Scroller. - Another examples of such synchronization could be found in + The most important idea behind the Adapter update API is that the changes + made via Adapter must be synched with the Datasource. This demo implements + one of the possible ways of synching the Datasource with the + Adapter.append/prepend updates. The external dataset is changed per each + doAppend / doPrepend call destructively by + unshift and push Array's methods. There are also some + additional variables that provide index offsets and that are used in the + Datasource.get and doPrepend/doAppend methods to guarantee a + stable data flow from the App component to the Scroller. Another examples + of such synchronization could be found in remove and + fragment="{{ adapterMethodsScope.map.remove.id }}" + >remove + and insert demos. + fragment="{{ adapterMethodsScope.map.insert.id }}" + >insert + demos.
- Adding items at the end and at the beginning of the Scroller's Buffer is possible with + Adding items at the end and at the beginning of the Scroller's Buffer is + possible with Adapter.append and Adapter.prepend methods respectively. They have the following object-arguments:
{{appendArgumentsDescription}}
{{prependArgumentsDescription}}
{{ appendArgumentsDescription }}+
{{ prependArgumentsDescription }}+
The items parameter is an array of items we want to add, eof/bof params provide virtualization, - decrease/increase define index strategy. - Here we deal with only items parameter, - other params are considered in the decrease/increase define index strategy. Here we deal + with only items parameter, other params are considered in the + next demo. - Both append and prepend methods act in the same way, + fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}" + >next demo. Both append and prepend methods act in the same way, so let's discuss prepend.
- In this demo we have 20 items on start, 5 of them (95-99) are invisible on the backward direction. - By pushing "Prepend" button we want to add 4 (which is the input value) new items - to the top of the list. After they are prepended, they become visible when scrolling up. - The Scroller accurately injects prepended items into - its Buffer consisting of 95-114 rows initially, - so 1-4 new items temporary take place of items with indexes 91-94. + In this demo we have 20 items on start, 5 of them (95-99) are invisible on + the backward direction. By pushing "Prepend" button we want to add 4 + (which is the input value) new items to the top of the list. After they + are prepended, they become visible when scrolling up. The Scroller + accurately injects prepended items into its Buffer consisting of 95-114 + rows initially, so 1-4 new items temporary take place of items with + indexes 91-94.
- But if we scroll away and make '90s items invisible and removed from the viewport, - and then scroll back, we will realise that nothing changes: + But if we scroll away and make '90s items invisible and removed from the + viewport, and then scroll back, we will realise that nothing changes: "new" items are gone and the old 91-94 items returned to initial position. - The point is that the changes we provide via Adapter over the internal Scroller's Buffer - don't affect the external Datasource we implemented on the Component level. - This is the end app developer responsibility to take care of the data consistency - during manual updates such as append, prepend, insert, remove, etc: - the Datasource.get must provide correct data, while the Scroller maintains indexes. + The point is that the changes we provide via Adapter over the + internal Scroller's Buffer don't affect the external + Datasource we implemented on the Component level. This is the end + app developer responsibility to take care of the data consistency during + manual updates such as append, prepend, insert, remove, etc: the + Datasource.get must provide correct data, while the Scroller + maintains indexes. The next sample - provides one of the approach of consistent Datasource implementation - with Adapter.append and Adapter.prepend methods usage. + fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}" + >The next sample + provides one of the approach of consistent + Datasource implementation with Adapter.append and + Adapter.prepend methods usage.
- Adapter.bof and Adapter.eof are read-only boolean properties - that indicate whether the dataset beginning (bof, begin of file) or - the dataset ending (eof, end of file) is reached or not. + Adapter.bof and Adapter.eof are read-only boolean + properties that indicate whether the dataset beginning (bof, begin of + file) or the dataset ending (eof, end of file) is reached or not.
Along with scalar properties there are also two Observable ones: - Adapter.bof$ and Adapter.eof$ which are Subjects and produce - new values when scalar properties change. + Adapter.bof$ and Adapter.eof$ which are Subjects and + produce new values when scalar properties change.
- In this demo we have limited datasource consisting of 100 items starting from index 1. - So initially we are at the left border of the dataset, and that's why we see - "Begin of file is reached" message. - After some scrolling down bof is turned to false, - the first item is destroyed and can be retrieved again. + In this demo we have limited datasource consisting of 100 items starting + from index 1. So initially we are at the left border of the dataset, and + that's why we see "Begin of file is reached" message. After some scrolling + down bof is turned to false, the first item is destroyed + and can be retrieved again.
- The edgeCounter is bof/eof changes counter. It is defined via merge(bof$, eof$) - subscription which is triggered each time bof$/eof$ emits a new value. + The edgeCounter is bof/eof changes counter. It is defined via + merge(bof$, eof$) subscription which is triggered each time + bof$/eof$ emits a new value.
- Adapter.bufferInfo is a read-only property which exposes - an object of the following type: + Adapter.bufferInfo is a read-only property which exposes an + object of the following type:
-{{bufferInfoType}}+
{{ bufferInfoType }}
firstIndex & lastIndex are the first and the last indexes in the current Buffer, - minIndex & maxIndex are min and max indexes that were present in the Buffer, - absMinIndex & absMaxIndex are min and max indexes that can be present in the Buffer. + minIndex & maxIndex are min and max indexes that were + present in the Buffer, absMinIndex & absMaxIndex are min + and max indexes that can be present in the Buffer.
- The Adapter.check method could be useful when rows heights can change after rendering. - By invoking the Adapter.check method we ask the Scroller to check - if any of items that are in the current Buffer (and present in DOM) changed their size. -
-- In this demo, the doCheck method is implemented to + The Adapter.check method could be useful when rows heights can + change after rendering. By invoking the Adapter.check method we + ask the Scroller to check if any of items that are in the current Buffer + (and present in DOM) changed their size.
+In this demo, the doCheck method is implemented to
- The Adapter.clip method allows to remove out-of-viewport items on demand. - Out-of-viewport means out of visible part of the viewport + paddings, see + The Adapter.clip method allows to remove out-of-viewport items on + demand. Out-of-viewport means out of visible part of the viewport + + paddings, see padding setting demo. - Not to be confused with Adapter.remove, - as the Adapter.clip method has no impact on the Datasource. - It just cleans up the DOM. + fragment="{{ settingsScope.map.padding.id }}" + >padding setting + demo. Not to be confused with Adapter.remove, as the + Adapter.clip method has no impact on the Datasource. It + just cleans up the DOM.
- Commonly, the Scroller runs a clipping procedure - each time new items are fetched after scrolling - and it clips old items from a side of the viewport - that is opposite to a side where new items appear. - This is one of the core parts of the virtualization concept. - By invoking the Adapter.clip method - we are telling the Scroller to run this procedure immediately - and remove items that are out-of-viewport in both directions or in specific direction. - Here is an argument object that lets to specify the clipping direction: + Commonly, the Scroller runs a clipping procedure each time new items are + fetched after scrolling and it clips old items from a side of the viewport + that is opposite to a side where new items appear. This is one of the core + parts of the virtualization concept. By invoking the + Adapter.clip method we are telling the Scroller to run this + procedure immediately and remove items that are out-of-viewport in both + directions or in specific direction. Here is an argument object that lets + to specify the clipping direction:
-{{clipOptionsDescription}}+
{{ clipOptionsDescription }}
- This could be useful when we enlarge the list of items manually, - via Adapter.append or Adapter.prepend methods. - For example, we appended 100 new items and started scrolling down, in forward direction. - We might want the items that exit the viewport in backward direction to be clipped. - Such a clipping will occur automatically only when we reach the bottom line of the buffer - and new items are fetched and rendered in forward direction. - If we don't want to wait, we just call Adapter.clip({{clipOptionsSample}}). + This could be useful when we enlarge the list of items manually, via + Adapter.append or Adapter.prepend methods. For example, + we appended 100 new items and started scrolling down, in forward + direction. We might want the items that exit the viewport in backward + direction to be clipped. Such a clipping will occur automatically only + when we reach the bottom line of the buffer and new items are fetched and + rendered in forward direction. If we don't want to wait, we just call + Adapter.clip({{ clipOptionsSample }}).
- This demo implements some artificial but quite illustrative case. - Here we disabled virtualization by turning on the + This demo implements some artificial but quite illustrative case. Here we + disabled virtualization by turning on the infinite setting. - Clipping will never happen automatically. - We see how the DOM elements counter value is getting bigger and bigger as we scroll on and on. - By pressing the "Clip" button, we let only 20-21 items to survive. - This way a kind of manual virtualization could be implemented. + fragment="{{ settingsScope.map.infiniteMode.id }}" + >infinite setting. Clipping will never happen automatically. We see how the DOM elements + counter value is getting bigger and bigger as we scroll on and on. By + pressing the "Clip" button, we let only 20-21 items to survive. This way a + kind of manual virtualization could be implemented.
- The Adapter has two read-only properties - which allow to determine the first and the last visible items: - firstVisible and lastVisible. - These are the objects of special ItemAdapter type + The Adapter has two read-only properties which allow to determine + the first and the last visible items: firstVisible and + lastVisible. These are the objects of special + ItemAdapter type
-{{itemAdapterDescription}}+
{{ itemAdapterDescription }}
$index corresponds to Datasource item index value; - data is what we were pushing to the Scroller - on Datasource.get (item's content); - element is the DOM element that is relevant to our item. + data is what we were pushing to the Scroller on + Datasource.get (item's content); element is the DOM + element that is relevant to our item.
- In addition to scalar firstVisible and lastVisible properties - there are also Observable versions: firstVisible$ and lastVisible$. - In this demo we calculate a number of visible items with the help of combineLatest RxJs method. + In addition to scalar firstVisible and + lastVisible properties there are also Observable versions: + firstVisible$ and lastVisible$. In this demo we + calculate a number of visible items with the help of + combineLatest RxJs method.
Recalculation of the first and the last items occurs per each non-empty - "inner loop" session, when a bunch of new items are fetched via Datasource.get, - rendered and unnecessary items are clipped out. + "inner loop" session, when a bunch of new items are fetched via + Datasource.get, rendered and unnecessary items are clipped out.
- Adapter.init$ is a Subject-boolean property that fires once - after the Adapter is initialized and - the Scroller is ready to run. - There is also Adapter.init read-only boolean property. - An important point is that there is a time gap between - the Datasource/Adapter instantiation and the Scroller/Adapter initialization. - In other words, we may instantiate the Datasource long before - the Scroller's viewport is rendered and it starts working. -
-- This demo displays ngx-ui-scroll version taken from the Adapter. - The Adapter.init$ subscription is used to set version property - exactly when the Adapter initializes and receives all data. -
-- The second tab (OnPush) implements OnPush strategy - with an additional changeDetector call, whish is required to update the view, - because initialization is an asynchronous process. - If we remove changeDetector call, we see 3 dots, the initial value. -
-+ Adapter.init$ is a Subject-boolean property that fires once after + the Adapter is initialized and the Scroller is ready to run. + There is also Adapter.init read-only boolean property. An + important point is that there is a time gap between the Datasource/Adapter + instantiation and the Scroller/Adapter initialization. In other words, we + may instantiate the Datasource long before the Scroller's viewport is + rendered and it starts working. +
++ This demo displays ngx-ui-scroll version taken from the + Adapter. The Adapter.init$ subscription is used to set + version property exactly when the Adapter initializes + and receives all data. +
++ The second tab (OnPush) implements OnPush strategy with an + additional changeDetector call, whish is required to update the + view, because initialization is an asynchronous process. If we remove + changeDetector call, we see 3 dots, the initial value. +
+- Adapter.isLoading is a read-only boolean property - indicating whether there are any pending processes running by the Scroller. - So when datasource.adapter.isLoading is true, it means that - the Scroller is working right now and the viewport might be updated soon. - 225ms delay was added to the Datasource.get implementation in this sample. + Adapter.isLoading is a read-only boolean property indicating + whether there are any pending processes running by the Scroller. So when + datasource.adapter.isLoading is true, it means that the + Scroller is working right now and the viewport might be updated soon. + 225ms delay was added to the Datasource.get implementation in + this sample.
In addition to read-only scalar isLoading property there is also isLoading$ property which is Observable. A Subject, to be exact. - In this demo we have a subscription and isLoadingCounter - which is incremented each time the isLoading value becomes false. - We see that the initial value of this counter on the UI (before any scrolling) is 1. - So the Scroller does initialization and first 3 fetches in a single session. + In this demo we have a subscription and isLoadingCounter which is + incremented each time the isLoading value becomes false. + We see that the initial value of this counter on the UI (before any + scrolling) is 1. So the Scroller does initialization and first 3 fetches + in a single session.
- We may use Adapter.itemsCount read-only property to see - how many items exist in the Scroller's Buffer at the moment. - The itemsCount value is the same as the value of DOM elements counter. + We may use Adapter.itemsCount read-only property to see how many + items exist in the Scroller's Buffer at the moment. The + itemsCount value is the same as the value of DOM elements + counter.
- Adapter.packageInfo represents a read-only object - which contains information about versions of - ngx-ui-scroll and its external core. Prior to ngx-ui-scroll v2, - this core was a physical part of the ngx-ui-scroll codebase. - Now it is a separate npm package "vscroll" that "ngx-ui-scroll" depends on. -
-+ Adapter.packageInfo represents a read-only object which contains + information about versions of ngx-ui-scroll and its external core. Prior + to ngx-ui-scroll v2, this core was a physical part of the ngx-ui-scroll + codebase. Now it is a separate npm package "vscroll" that "ngx-ui-scroll" + depends on. +
+- The Adapter.reload method allows to reload the Scroller. - This includes resetting the items Buffer and the Viewport params. - The reload method has 1 optional argument: index. It specifies - the index of the item to be first in the visible part of the viewport after reload. + The Adapter.reload method allows to reload the Scroller. This + includes resetting the items Buffer and the + Viewport params. The reload method has 1 optional + argument: index. It specifies the index of the item to be first + in the visible part of the viewport after reload.
- If index argument is not set, - settings.startIndex will be used as the first index. - If neither index argument not startIndex setting is present, - the default value 1 will be used. See also + If index argument is not set, settings.startIndex will + be used as the first index. If neither index argument not + startIndex setting is present, the default value 1 will be used. + See also startIndex setting demo. + fragment="{{ startIndexDemoConfig.id }}" + >startIndex setting + demo.
The Adapter.reload method is safe and needs not to be protected - with the Adapter.relax - (though it could be protected if that's the UX requirement). - This method interrupts the Scroller's flow and synchronously terminates - any pending processes on the Scroller's end. - The same is true also for the Adapter.reset method. + with the Adapter.relax (though it could be protected if that's + the UX requirement). This method interrupts the Scroller's flow and + synchronously terminates any pending processes on the Scroller's end. The + same is true also for the Adapter.reset method.
- The Adapter.remove method allows to remove items from current buffer or/and virtually. - The argument of this method is an object of the following type: + The Adapter.remove method allows to remove items from current + buffer or/and virtually. The argument of this method is an object of the + following type:
-{{argumentsDescription}}+
{{ argumentsDescription }}
- The predicate option is a function applying to each item in the buffer. - If the return value is true, the item will be removed. - The argument of the predicate is of ItemAdapter type, and - in this demo we have the following version of predicate for removing by id: + The predicate option is a function applying to each item in the + buffer. If the return value is true, the item will be removed. The + argument of the predicate is of ItemAdapter type, and in this + demo we have the following version of predicate for removing by + id:
-{{predicateDescription}}+
{{ predicateDescription }}
- Indexes are adjusted each time a delete is performed via Adapter. - For example, it's impossible to remove item with id = 5 twice, - because there is only one item with id = 5. - But we may remove item with index = 5 as many times - as many indexes we have after this one. + Indexes are adjusted each time a delete is performed via Adapter. For + example, it's impossible to remove item with id = 5 twice, because there + is only one item with id = 5. But we may remove item with index = 5 as + many times as many indexes we have after this one.
- Instead of predicate running over buffered items, - it is possible to remove items by indexes. - This method works with both buffered and virtual items. - For example, we have [1..10] buffered items (they are rendered) - and [11..100] virtual items (emulated via forward padding element - which size corresponds to 90 virtual items), and we want to remove the last item via - Adapter.remove({ indexes: [100] }), the result will be as follows: + Instead of predicate running over buffered items, it is possible + to remove items by indexes. This method works with both buffered + and virtual items. For example, we have [1..10] buffered items (they are + rendered) and [11..100] virtual items (emulated via forward padding + element which size corresponds to 90 virtual items), and we want to remove + the last item via Adapter.remove({ indexes: [100] }), + the result will be as follows:
- By default, indexes are decreased, that is, - the indexes following the deleted one(s) are decremented. - The increase option allows to change the default indexes adjustment strategy. - By setting increase to true, we tell the Scroller - that we want to increase indexes of the items before the removed one(s). + By default, indexes are decreased, that is, the indexes following the + deleted one(s) are decremented. The increase option allows to + change the default indexes adjustment strategy. By setting + increase to true, we tell the Scroller that we want to + increase indexes of the items before the removed one(s).
- The very important point is that we need to synchronize the Datasource with the changes - we are making over the Scroller's buffer via Adapter.remove. - Generally, this is the App component responsibility, - and in this demo it is done by the removeFromDatasource method. - It removes 1 item from the initial dataset, and decrements the value of the right border. + The very important point is that we need to synchronize the Datasource + with the changes we are making over the Scroller's buffer via + Adapter.remove. Generally, this is the App component + responsibility, and in this demo it is done by the + removeFromDatasource method. It removes 1 item from the initial + dataset, and decrements the value of the right border.
- The Adapter.replace method allows to perform many-to-many in-Buffer replacement. - It acts as a combination of insert and remove operations working in a single run, - which means minimal latency in comparison to applying - Adapter.insert and Adapter.remove methods one by one. - The argument of this method is an object of the following type: + The Adapter.replace method allows to perform many-to-many + in-Buffer replacement. It acts as a combination of insert and remove + operations working in a single run, which means minimal latency in + comparison to applying Adapter.insert and + Adapter.remove methods one by one. The argument of this method is + an object of the following type:
-{{argumentsDescription}}+
{{ argumentsDescription }}
The predicate option is exactly the same as in Adapter-remove and case. - The indexes options is exactly the same as in the + fragment="{{ adapterMethodsScope.map.remove.id }}" + >Adapter-remove + and case. The indexes options is exactly the same as in the Adapter-insert case. - The fixRight option allows to change - the default indexing strategy. - The default fixRight value is false, - which means the indexes of the items following the replaced ones - will be changed during the replacement. - Otherwise, if fixRight is set true, - the indexes of the items preceding the replaced ones will be changed. + fragment="{{ adapterMethodsScope.map.insert.id }}" + >Adapter-insert + case. The fixRight option allows to change the default indexing + strategy. The default fixRight value is false, which + means the indexes of the items following the replaced ones will be changed + during the replacement. Otherwise, if fixRight is set + true, the indexes of the items preceding the replaced ones will + be changed.
- Current limitations of the Adapter.replace method: - no virtual replacements are possible and only continues series are allowed. - So only a portion of items that are currently in the Scroller Buffer - can be replaced with this method and this portion must consist of a continuous list of items. + Current limitations of the Adapter.replace method: no virtual + replacements are possible and only continues series are allowed. So only a + portion of items that are currently in the Scroller Buffer can be replaced + with this method and this portion must consist of a continuous list of + items.
- In this demo we are replacing 3 items with indexes [3, 4, 5] with 2 new items. - Switching to "fixRight" strategy is not implemented here, - in order to keep natural indexes flow as simple as possible. - Note, before running the "replace" operation over the Scroller's Buffer, - the Datasource gets the update as well. + In this demo we are replacing 3 items with indexes [3, 4, 5] with 2 new + items. Switching to "fixRight" strategy is not implemented here, in order + to keep natural indexes flow as simple as possible. Note, before running + the "replace" operation over the Scroller's Buffer, the Datasource gets + the update as well.
- The Adapter.reset method is designed to reset the internal state of the Scroller. - It differs from the Adapter.reload because it re-instantiates all internal entities, - which is equivalent to destroying the Scroller and re-creating it again via ngIf. + The Adapter.reset method is designed to reset the internal state + of the Scroller. It differs from the Adapter.reload because it + re-instantiates all internal entities, which is equivalent to destroying + the Scroller and re-creating it again via ngIf.
- The method has 1 optional argument: datasource object. By passing it, - we tell the Scroller that we want to use a new datasource. - If the argument is not passed, the old Datasource will be used. + The method has 1 optional argument: datasource object. By passing + it, we tell the Scroller that we want to use a new datasource. If the + argument is not passed, the old Datasource will be used.
- All fields of the datasource argument are optional. - Missing parts of a new Datasource will be taken from the original one. - For instance, in this demo only settings field is passed: - Adapter.reset({ settings }). - This sets new startIndex and bufferSize settings - while Datasource.get remains pristine. - It affects the value of the DOM elements counter. + All fields of the datasource argument are optional. Missing parts + of a new Datasource will be taken from the original one. For instance, in + this demo only settings field is passed: + Adapter.reset({ settings }). This sets new + startIndex and bufferSize settings while + Datasource.get remains pristine. It affects the value of the DOM + elements counter.
- Important note! Do not re-assign the Datasource at the App component level, - this.datasource must keep the same reference before and after reset. - So this.datasource = ... expressions are prohibited after reset, - for it will make the Adapter subscriptions broken. - The Scroller maintains Datasource params and - provides the Adapter consistency across the reset. - Even in case of passing a new instance of Datasource: + Important note! Do not re-assign the Datasource at the App + component level, this.datasource must keep the same reference + before and after reset. So + this.datasource = ... expressions are prohibited after + reset, for it will make the Adapter subscriptions broken. The Scroller + maintains Datasource params and provides the Adapter consistency across + the reset. Even in case of passing a new instance of Datasource:
-{{resetWithNewInstanceSample}}+
{{ resetWithNewInstanceSample }}
The Adapter.update method provides access to Scroller's Buffer - and allows to perform insert/replace/remove operations on the fly. - The Adapter.update method has 1 required argument which is a predicate - that is applied to each item in the current Scroller's Buffer. - The argument of the predicate gives natural datasource $index - of the item, its data payload and its HTML element. - The return value of predicate determines what should happen with each item in the Buffer: + and allows to perform insert/replace/remove operations on the fly. The + Adapter.update method has 1 required argument which is a + predicate that is applied to each item in the current Scroller's Buffer. + The argument of the predicate gives natural datasource $index of + the item, its data payload and its HTML element. The + return value of predicate determines what should happen with each + item in the Buffer:
- In the demo the predicate is implemented to remove item 3, - replace item 5 with new A and B items, - insert new C and D items after item 7, while other items should remain pristine. - This particular predicate should provide + In the demo the predicate is implemented to remove item 3, replace item 5 + with new A and B items, insert new C and D items after item 7, while other + items should remain pristine. This particular predicate should provide [1..10] to [1, 2, 4, A, B, 6, 7, C, D, 8] contents transition.
- On index shifting. - The second option of the Adapter.update is fixRight. - This is a non-required boolean setting that defines the indexing strategy. - If fixRight is false (which is default), - indexes to the right of the updated ones will be affected. - Since the first updated item has index 3, - the indexes of the first two items should not change after update when fixRight = false. - But the index of the 8 item should grow up to 10: - 2 removals (3, 5) and 4 insertions (A, B, C, D) results in +2 shift. - If fixRight is true, indexes to the left of the updated ones will be affected. - And we'll see -2 shift on the first two items when fixRight = true. + On index shifting. The second option of the Adapter.update is + fixRight. This is a non-required boolean setting that defines the + indexing strategy. If fixRight is false (which is default), + indexes to the right of the updated ones will be affected. Since the first + updated item has index 3, the indexes of the first two items should not + change after update when fixRight = false. But the index of the + 8 item should grow up to 10: 2 removals (3, 5) and 4 insertions (A, B, C, + D) results in +2 shift. If fixRight is true, indexes to the + left of the updated ones will be affected. And we'll see -2 shift on + the first two items when fixRight = true.
- The very important thing is to synchronize each update we perform via Adapter API - over the Scroller's Buffer with the Datasource outside the Scroller. - This demo does not have such synchronization due to high complexity of the operations. - But there are some examples in other demos: + The very important thing is to synchronize each update we perform via + Adapter API over the Scroller's Buffer with the Datasource outside the + Scroller. This demo does not have such synchronization due to high + complexity of the operations. But there are some examples in other demos: append, + fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}" + >append, remove and + fragment="{{ adapterMethodsScope.map.remove.id }}" + >remove + and insert. + fragment="{{ adapterMethodsScope.map.insert.id }}" + >insert.
Since v2, the Adapter.insert and Adapter.replace methods - use the Adapter.update method under hood. + use the Adapter.update method under hood.
- Speaking of what impact could Settings have on the Scroller behavior, - we need to get an idea of its behavior before any settings applied. - This demo is the very basic example with no Settings provided. + Speaking of what impact could Settings have on the Scroller + behavior, we need to get an idea of its behavior before any settings + applied. This demo is the very basic example with no + Settings provided.
- Let's first investigate the demo's log. - The visible part of the viewport contains 10 items in accordance with CSS: - 10 items of 25px each are equal to 250px of the viewport height. - Datasource.get log shows that there were 3 requests - of 5, 10 and 5 items on the initial load (before any scrolling). - So we have 20 DOM elements initially: - 15 downward items with positive indexes and 5 upward negative items. - 20 items result in 500px and this is the initial viewport scrollable size. + Let's first investigate the demo's log. The visible part of the viewport + contains 10 items in accordance with CSS: 10 items of 25px each are equal + to 250px of the viewport height. + Datasource.get log shows that there were 3 requests of 5, 10 and + 5 items on the initial load (before any scrolling). So we have 20 DOM + elements initially: 15 downward items with positive indexes and 5 upward + negative items. 20 items result in 500px and this is the initial viewport + scrollable size.
- The viewport scrollable size value can increase only; - as we scroll more and more, items flow through the Datasource.get. - In this demo the Datasource is unlimited, - it produces items with indexes from -Infinity to +Infinity. - The number of DOM elements in the viewport is not constant, - but it fluctuates around the initial value during scrolling (20-31). - The core concept of the virtualization is to keep as small number of real items as needed - while the scrollable size of the viewport can be very big. + The viewport scrollable size value can increase only; as we scroll more + and more, items flow through the Datasource.get. In this demo the + Datasource is unlimited, it produces items with indexes from + -Infinity to +Infinity. The number of DOM elements in the viewport is not + constant, but it fluctuates around the initial value during scrolling + (20-31). The core concept of the virtualization is to keep as small number + of real items as needed while the scrollable size of the viewport can be + very big.
- Why do we have 3 requests initially, - why the first one is about only 5 items, - why at least 10 items are invisible? - This is how the *uiScroll works and further reading should shed light on the details. - But we need to know that the default behavior could be changed via Settings object. + Why do we have 3 requests initially, why the first one is about only 5 + items, why at least 10 items are invisible? This is how the + *uiScroll works and further reading should shed light on the + details. But we need to know that the default behavior could be changed + via Settings object.