Paginated, sorted and filtered lists from observables for Angular. The base library is agnostic as to styling and controls; a set of Bootstrap 4 controls is available as a separately imported module.
Install the library and Lodash.
npm i -S @nowzoo/ngx-list lodash
Problem with depending on Lodash? Read this note.
If you are planning on using the Bootstrap 4 components, you need to include Bootstrap css somewhere in your build process. None of the Bootstrap components depend on Bootstrap javascript.
The base library does not expose any components or services, so there's no module to import. There's just the NgxList
class that you import and instantiate in your component code...
import { Component, OnInit, OnDestroy } from '@angular/core';
import { NgxList, NgxListResult } from '@nowzoo/ngx-list';
import { MyDataService } from '../my-data.service';
@Component({
selector: 'app-demo',
templateUrl: './demo.component.html',
styles: []
})
export class DemoComponent implements OnInit, OnDestroy {
list: NgxList;
result: NgxListResult = null;
constructor(
private dataService: MyDataService
) { }
ngOnInit() {
// assuming dataService.data$ is an observable
// of an array of records
this.list = new NgxList({
src$: this.dataService.data$, //required
idKey: 'id' //required
});
this.list.results$.subscribe(result => this.result = result);
}
ngOnDestroy() {
this.list.destroy();
}
}
<!-- demo.component.html -->
<pre>{{result | json}}</pre>
Result:
{
"page": 0,
"recordsPerPage": 10,
"sort": {
"key": "id",
"reversed": false
},
"filterValues": {},
"recordCount": 88,
"pageCount": 9,
"unfilteredRecordCount": 88,
"records": [...]
}
At this point, result.records
is the array of sorted and filtered records from your src$
observable that belong on the current page.
It's up to you to layout the records, for example in a table...
<table class="table">
<tbody>
<tr *ngFor="let record of result.records">
...
</tr>
</tbody>
</table>
The library provides a set of Bootstrap themed components for sorting and pagination.
NgxListBoostrapPaginationComponent
: An input group with prev/next and first/last buttons, and a dropdown with page numbers.NgxListBoostrapRppComponent
: A dropdown to set therecordsPerPage
property of a list.NgxListBootstrapSortComponent
: Sort a list by a key.
To use these components import the module:
import { NgxListBootstrapModule } from '@nowzoo/ngx-list';
@NgModule({
imports: [
NgxListBootstrapModule,
]
})
export class MyModule { }
// component...
ngOnInit() {
this.list = new NgxList({src$: mySource, idKey: 'id'})
}
<!-- pagination... -->
<ngx-list-bootstrap-pagination
[list]="list"></ngx-list-bootstrap-pagination>
<!-- rpp... -->
<ngx-list-bootstrap-rpp
[list]="list"></ngx-list-bootstrap-rpp>
<!-- sort components as table headers... -->
<table class="table">
<thead>
<tr>
<th>
<ngx-list-bootstrap-sort
[list]="list"
key="id">ID</ngx-list-bootstrap-sort>
</th>
<th>
<ngx-list-bootstrap-sort
[list]="list"
key="name">Name</ngx-list-bootstrap-sort>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let record of list.records">
...
</tr>
</tbody>
</table>
See the Bootstrap Components API for more options.
The main class.
const list = new NgxList(init);
The NgxList
constructor takes an initializing object in the shape of INgxListInit
. The only required properties are src$
and idKey
. All other properties are optional.
src$: Observable<any[]>
Required. An observable of records from your data source.idKey: string
Required. The key of some unique record identifier. This is used as the fallback sort key.page?: number
Optional. The initial page. Default0
.recordsPerPage?: number
Optional. The initial recordsPerPage. Default10
.sort?: {key?: string, reversed?: boolean}
Optional. The initial sort params.key
defaults to whatever you passed asidKey
(see above)reversed
defaults tofalse
filterValues?: {[filterKey: string]: any}
Optional. The initial values for the filters. For example, you could pass{search: 'foo'}
if initializing the list from a query param.filters?: {[filterKey: string]: NgxListFilterFn}
Optional. A map of filter functions. You can roll your own NgxListFilterFn or use the factories:sortFn?: NgxListSortFn
Optional. If nothing is passed, the list creates a sort function with some sensible defaults. You can roll your own function of type NgxListSortFn, use theNgxListFnFactory.sortFn
factory.
result$: Observable<INgxListResult>
The list result as an observable. See INgxListResult.result: INgxListResult
The latest list result.
Additionally, the class exposes the individual properties of the latest result:
records: any[]
The records that belong on the current page.recordCount: number
The number of records that match the current filters.unfilteredRecordCount: number
The total number of records, before filtering.pageCount: number
The number of pages.page: number
The current page. If there are records, this will be between0
andpageCount - 1
.recordsPerPage: number
The value used to page the result.sort: {key: string, reversed: boolean}
The parameters used to sort the list.filterValues: {[key: string]: any}
The filter values used to filter the result.
setPage(page: number): void
Set the page number. Note that whatever you pass here will be eventually be constrained to between0
andpageCount - 1
.setRecordsPerPage(recordsPerPage: number): void
Pass0
for no paging.setSort(sort: {key: string, reversed: boolean}): void
Set the sort params.key
can use dot notation to access nested properties of your records. Ifreversed
is true, then the list will be sorted in descending (z-a) order.setFilterValue(key: string, value: any): void
Set the value of a particular filter, e.g.list.setFilterValue('search', 'foo bar')
A class with static methods for creating filter and sort functions.
Static method to create a sort function of type NgxListSortFn. NgxList
uses this to create the default sort function. You can use this factory to replace the default sort function, or roll your own.
static sortFn(options?): NgxListSortFn
options
can be an object with the following properties:
fallbackSortColumn?: string
Optional. The key to sort by if two records are the same by the current sort key.caseSensitive?: boolean
Optional. Defaultfalse
. If true, record keys containing strings will be sorted case-sensitively.valueFns?: {[key: string]: NgxListColumnValueFn}
Optional. Use this if you want to mess with the values for sorting, or add a sort key that does not exist in your raw records.
Static method to create a search filter of type NgxListFilterFn. Use this to create a search filter top pass to NgxList
:
const list = new NgxList({
src$: myDataSrc$,
idKey: 'id',
filters: {
// using the default options...
search: NgxListFnFactory.searchFilter()
}
});
static searchFilter(options?): NgxListFilterFn
options
can be an object with the following properties:
caseSensitive?: boolean
Optional. Defaultfalse
. Set totrue
if you want to match string values case-sensitively.ignoredKeys?: string[]
Optional. By default the function will search all of the scalar keys in an object, including deeply nested ones. Pass an array of dot-notated keys to ignore single or multiple paths. Note that this is hierarchical: if you pass['id', 'profile']
, all the keys under profile (e.g.profile.firstName
) will be ignored as well.valueFns?: {[key: string]: NgxListColumnValueFn}
Optional. Use this if you want to mess with the values before searching (e.g. formatting dates to provide something more meaningful). Pass a map from dot-notated key to NgxListColumnValueFn
Create a generic filter function of type NgxListFilterFn
const list = new NgxList({
src$: myDataSrc$,
idKey: 'id',
filters: {
verified: NgxListFnFactory.comparisonFilter({
value: (rec) => rec.verified === true
})
}
});
static comparisonFilter(options): NgxListFilterFn
.
options
is an object with the following properties:
value: string | NgxListColumnValueFn
Required. A dot-notated key pointing to a record value, or (recommended) a function that, given a record, returns a value.compare?: NgxListCompare
Optional. See NgxListCompare.What comparison operator to use. DefaultNgxListCompare.eq
.ignoreFilterWhen?: (filterValue: any) => boolean
Optional. By default, the filter will be ignored when the filter value isnull
,undefined
or an empty string (''
). If this logic doesn't suit pass your own function here.
type NgxListColumnValueFn = (record: any) => any
A function that, given a record, returns a value for purposes of sorting, search or filtering. Used by the factory functions.
type NgxListFilterFn = (records: any[], value: any) => any[]
The signature of a filter function. You should return a new array of records that match your filter logic. (Don't mutate the array passed in the parameter.)
records
contains the unfiltered records.value
is the current filter value.
The signature of a sort function. You should return a separate array sorted by your logic. (Don't mutate the array passed in the parameter.)
type NgxListSortFn = (records: any[], key: string) => any[]
records
are the unsorted records.key
is the sort key.
Note that reversing the list, if necessary, happens separately.
Used by the NgxList.comparisonFilter
factory.
enum NgxListCompare
eq
Use===
to compare values.neq
Use!==
to compare values.gte
Use>=
to compare values.gt
Use>
to compare values.lte
Use<=
to compare values.lt
Use<
to compare values.
The end product of the list.
interface INgxListResult
records: any[]
The records that belong on the current page.recordCount: number
The number of records that match the current filters.unfilteredRecordCount: number
The total number of records, before filtering.pageCount: number
The number of pages.page: number
The current page. If there are records, this will be between0
andpageCount - 1
.recordsPerPage: number
The value used to page the result.sort: {key: string, reversed: boolean}
The parameters used to sort the list.filterValues: {[key: string]: any}
The filter values used to filter the result.
An input group with prev/next and first/last buttons, and a dropdown with page numbers.
selector: 'ngx-list-bootstrap-pagination'
list: NgxList
Required. The list.selectId: string
Required. The id you want to be attached to the page dropdown.buttonClass: string
Optional. The Boostrap button class. Default:'btn btn-outline-secondary'
.bootstrapSize: 'sm' | 'lg'
Optional. The Bootstrap size for the input group. Default:null
.options: INgxListBoostrapOptions
Optional. Default:null
. Pass options for this instance. Will override whatever wasprovide
d forNGX_LIST_BOOTSTRAP_OPTIONS
in the module or component.
A dropdown to set the recordsPerPage
of a list.
selector: 'ngx-list-bootstrap-rpp'
list: NgxList
Required. The list.selectId: string
Required. The id you want to be attached to the dropdown.bootstrapSize: 'sm' | 'lg'
Optional. The Bootstrap size for the select. Default:null
.options: INgxListBoostrapOptions
Optional. Default:null
. Pass options for this instance. Will override whatever wasprovide
d forNGX_LIST_BOOTSTRAP_OPTIONS
in the module or component. See INgxListBoostrapOptions
A sort link with indicators, sutable for use in table headers.
selector: 'ngx-list-bootstrap-sort'
list: NgxList
Required. The list.key: string
Required. The dot-notated key of the column to sort by.defaultReversed: boolean
Optional. Whether the sort should be in reverse order when the key is selected. (Note that selecting the key when it is already selected togglesreversed
. Default:false
.options: INgxListBoostrapOptions
Optional. Default:null
. Pass options for this instance. Will override whatever wasprovide
d forNGX_LIST_BOOTSTRAP_OPTIONS
in the module or component.
Options to control language, markup, etc. for the bootstrap components. Pass them directly to the components as inputs, or use the NGX_LIST_BOOTSTRAP_OPTIONS
token to provide your default options.
firstPageButtonTitle?: string
Optional. Default:'First Page'
. Used fortitle
andaria-label
.firstPageButtonHTML?: string
Optional. Default: ⇤ ('⇤'
). TheinnerHTML
of the button.lastPageButtonTitle?: string
Optional. Default:'Last Page'
. Used fortitle
andaria-label
.lastPageButtonHTML?: string
Optional. Default: ⇥ ('⇥'
). TheinnerHTML
of the button.prevPageButtonTitle?: string
Optional. Default:'Previous Page'
. Used fortitle
andaria-label
.prevPageButtonHTML?: string
Optional. Default: ← ('←'
). TheinnerHTML
of the button.nextPageButtonTitle?: string
Optional. Default:'Next Page'
. Used fortitle
andaria-label
.nextPageButtonHTML?: string
Optional. Default: → ('→'
). TheinnerHTML
of the button.currentPageTitle?: string
Optional. Default:'Current Page'
. Used as thetitle
andaria-label
of the pagination dropdown.recordsPerPageOptions?: number[]
Optional. The options for the rpp component. Default:[10, 25, 50, 100]
recordsPerPageNoPagingLabel?: string
Optional. The label for the 'no paging option'. Default:'No paging'
. Note that this will only have effect if you pass0
as one of therecordsPerPageOptions
.recordsPerPageLabel?: string
Optional. Default:' per page'
. Inserted after the number in each option of the recordsPerPage dropdown.sortLabel?: string
Optional. Default:'Sort List'
. Used as thetitle
andaria-label
for sort components.sortDescHTML?: string
Optional. Default: ↑ ('↑'
). The html to be used as the indicator when the sort component is selected and the the list is sorted in descending order (reversed).sortDescLabel?: string
Optional. Default:'sorted in z-a order'
. Screen reader text to be used when the sort component is selected and the the list is sorted in descending order (reversed).sortAscHTML?: string
Optional. Default: ↓ ('↓'
). The html to be used as the indicator when the sort component is selected and the the list is sorted in ascending order (not reversed).sortAscLabel?: string
Optional. Default:'sorted in a-z order'
. Screen reader text to be used when the sort component is selected and the the list is sorted in ascending order (not reversed).
const NGX_LIST_BOOTSTRAP_OPTIONS: InjectionToken<INgxListBoostrapOptions>
Use this to set some or all of the component options, either in a module or in a component. You don't have to provide all the options; the options you provide will override the defaults. Example:
import {
NgxListBootstrapModule,
NGX_LIST_BOOTSTRAP_OPTIONS,
INgxListBoostrapOptions
} from '@nowzoo/ngx-list';
// let's use font awesome icons
const myOptions: INgxListBoostrapOptions = {
firstPageButtonHTML: '<i class="fas fa-arrow-to-left"></i>',
lastPageButtonHTML: '<i class="fas fa-arrow-to-right"></i>',
prevPageButtonHTML: '<i class="fas fa-arrow-left"></i>',
nextPageButtonHTML: '<i class="fas fa-arrow-right"></i>',
sortAscHTML: '<i class="fas fa-arrow-down"></i>',
sortDescHTML: '<i class="fas fa-arrow-up"></i>',
};
@NgModule({
imports: [
NgxListBootstrapModule,
],
providers: [
{provide: NGX_LIST_BOOTSTRAP_OPTIONS, useValue: myOptions}
]
})
export class MyModule { }
Yeah, I know I can do everything Lodash does natively. But I can't do it as well or as consistently. So, Lodash.
The library uses a minimal set of Lodash functions, which will add about 25kB (7.6kB gzipped) to the payload of your app, if you don't use other Lodash functions elsewhere. If you do use it elsewhere, make sure to use the following tree-shakeable import syntax:
// good
import chunk from 'lodash/chunk';
import sortBy from 'lodash/sortBy';
// bad
// import * as _ from 'lodash';
// just as bad...
// import { chunk, sortBy } from 'lodash';
To make this compile for your code, you will probably have to add "esModuleInterop": true, "allowSyntheticDefaultImports": true
to compilerOptions
in tsconfig.json
. (You don't need to add it if you are not using Lodash elsewhere, only using the ngx-list
library.)
Contributions and suggestions are welcome.
This library was generated with Angular CLI version 7.2.0.
The code for the library itself is in projects/ngx-list/src
.
The demo app code is in projects/ngx-list-demo/src
.
# clone the library...
git clone https://github.com/nowzoo/ngx-list.git
# install deps...
npm i
# build the library...
ng build ngx-list
# unit tests...
ng test ngx-list
# note there's also a wallaby.js config at projects/ngx-list/wallaby.js
# serve the demo app ...
ng serve ngx-list-demo