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 lodashProblem 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 therecordsPerPageproperty 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: stringRequired. The key of some unique record identifier. This is used as the fallback sort key.page?: numberOptional. The initial page. Default0.recordsPerPage?: numberOptional. The initial recordsPerPage. Default10.sort?: {key?: string, reversed?: boolean}Optional. The initial sort params.keydefaults to whatever you passed asidKey(see above)reverseddefaults 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?: NgxListSortFnOptional. 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.sortFnfactory.
result$: Observable<INgxListResult>The list result as an observable. See INgxListResult.result: INgxListResultThe 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: numberThe number of records that match the current filters.unfilteredRecordCount: numberThe total number of records, before filtering.pageCount: numberThe number of pages.page: numberThe current page. If there are records, this will be between0andpageCount - 1.recordsPerPage: numberThe 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): voidSet the page number. Note that whatever you pass here will be eventually be constrained to between0andpageCount - 1.setRecordsPerPage(recordsPerPage: number): voidPass0for no paging.setSort(sort: {key: string, reversed: boolean}): voidSet the sort params.keycan use dot notation to access nested properties of your records. Ifreversedis true, then the list will be sorted in descending (z-a) order.setFilterValue(key: string, value: any): voidSet 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?: stringOptional. The key to sort by if two records are the same by the current sort key.caseSensitive?: booleanOptional. 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?: booleanOptional. Defaultfalse. Set totrueif 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 | NgxListColumnValueFnRequired. A dot-notated key pointing to a record value, or (recommended) a function that, given a record, returns a value.compare?: NgxListCompareOptional. See NgxListCompare.What comparison operator to use. DefaultNgxListCompare.eq.ignoreFilterWhen?: (filterValue: any) => booleanOptional. By default, the filter will be ignored when the filter value isnull,undefinedor 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.)
recordscontains the unfiltered records.valueis 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[]
recordsare the unsorted records.keyis the sort key.
Note that reversing the list, if necessary, happens separately.
Used by the NgxList.comparisonFilter factory.
enum NgxListCompare
eqUse===to compare values.neqUse!==to compare values.gteUse>=to compare values.gtUse>to compare values.lteUse<=to compare values.ltUse<to compare values.
The end product of the list.
interface INgxListResult
records: any[]The records that belong on the current page.recordCount: numberThe number of records that match the current filters.unfilteredRecordCount: numberThe total number of records, before filtering.pageCount: numberThe number of pages.page: numberThe current page. If there are records, this will be between0andpageCount - 1.recordsPerPage: numberThe 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: NgxListRequired. The list.selectId: stringRequired. The id you want to be attached to the page dropdown.buttonClass: stringOptional. The Boostrap button class. Default:'btn btn-outline-secondary'.bootstrapSize: 'sm' | 'lg'Optional. The Bootstrap size for the input group. Default:null.options: INgxListBoostrapOptionsOptional. Default:null. Pass options for this instance. Will override whatever wasprovided forNGX_LIST_BOOTSTRAP_OPTIONSin the module or component.
A dropdown to set the recordsPerPage of a list.
selector: 'ngx-list-bootstrap-rpp'
list: NgxListRequired. The list.selectId: stringRequired. The id you want to be attached to the dropdown.bootstrapSize: 'sm' | 'lg'Optional. The Bootstrap size for the select. Default:null.options: INgxListBoostrapOptionsOptional. Default:null. Pass options for this instance. Will override whatever wasprovided forNGX_LIST_BOOTSTRAP_OPTIONSin the module or component. See INgxListBoostrapOptions
A sort link with indicators, sutable for use in table headers.
selector: 'ngx-list-bootstrap-sort'
list: NgxListRequired. The list.key: stringRequired. The dot-notated key of the column to sort by.defaultReversed: booleanOptional. 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: INgxListBoostrapOptionsOptional. Default:null. Pass options for this instance. Will override whatever wasprovided forNGX_LIST_BOOTSTRAP_OPTIONSin 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?: stringOptional. Default:'First Page'. Used fortitleandaria-label.firstPageButtonHTML?: stringOptional. Default: ⇤ ('⇤'). TheinnerHTMLof the button.lastPageButtonTitle?: stringOptional. Default:'Last Page'. Used fortitleandaria-label.lastPageButtonHTML?: stringOptional. Default: ⇥ ('⇥'). TheinnerHTMLof the button.prevPageButtonTitle?: stringOptional. Default:'Previous Page'. Used fortitleandaria-label.prevPageButtonHTML?: stringOptional. Default: ← ('←'). TheinnerHTMLof the button.nextPageButtonTitle?: stringOptional. Default:'Next Page'. Used fortitleandaria-label.nextPageButtonHTML?: stringOptional. Default: → ('→'). TheinnerHTMLof the button.currentPageTitle?: stringOptional. Default:'Current Page'. Used as thetitleandaria-labelof the pagination dropdown.recordsPerPageOptions?: number[]Optional. The options for the rpp component. Default:[10, 25, 50, 100]recordsPerPageNoPagingLabel?: stringOptional. The label for the 'no paging option'. Default:'No paging'. Note that this will only have effect if you pass0as one of therecordsPerPageOptions.recordsPerPageLabel?: stringOptional. Default:' per page'. Inserted after the number in each option of the recordsPerPage dropdown.sortLabel?: stringOptional. Default:'Sort List'. Used as thetitleandaria-labelfor sort components.sortDescHTML?: stringOptional. 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?: stringOptional. 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?: stringOptional. 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?: stringOptional. 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