diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58b1125..53c602b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,26 @@
# Changelog
+## 20.1.0 (2025-06-21)
+
+Changes
+
+- Breaking using Angular 20 standalone, signals, zoneless
+
+```typescript
+imports: [
+ DataTable,
+ DefaultSorter,
+ BootstrapPaginator,
+ ],
+```
+
## 20.0.0 (2025-06-20)
Changes
- Use Angular 20 (closes #20)
-*Note: This library does not work with zoneless at the moment, contributions are welcome!*
+_Note: This library does not work with zoneless at the moment, contributions are welcome!_
## 19.0.0 (2025-01-28)
@@ -44,11 +58,13 @@ Changes
- Use Angular 16
## 15.1.2 (2023-04-24)
+
Changes
- Update dependencies to latest, fixes security alert
## 15.1.1 (2023-02-18)
+
Changes
- Update dependencies to latest (closes #13)
@@ -109,31 +125,31 @@ Changes:
## 11.1.0 (2020-11-23)
Changes:
-
+
- Remove dependency to lodash
## 11.0.1 (2020-11-23)
Changes:
-
+
- Specify correct peer dependency version
## 11.0.0 (2020-11-15)
Changes:
-
+
- Use Angular 11
## 10.0.0 (2020-06-27)
Changes:
-
+
- Use Angular 10
## 9.0.1 (2019-11-06)
Changes:
-
+
- Use Angular 9
- Use Angular CLI for build pipeline
- Fix some linting issues
@@ -141,42 +157,42 @@ Changes:
## 8.0.0 (2019-11-06)
Changes:
-
+
- Use Angular 8
## 2.0.0 (2018-07-10)
Changes:
-
+
- Published as package @pascalhonegger/ng-datatable
- Use Angular 7
## 0.7.3 (2018-10-04)
Changes:
-
+
- Security updates in dependencies.
## 0.7.2 (2018-10-03)
Changes:
-
+
- Updated examples dependencies. (Thanks to @PascalHonegger)
## 0.7.1 (2018-10-03)
Changes:
-
+
- Angular and RxJS get updated to version 6 (see package.json). The only code changes include changes to systemjs.config and two import statements.
(Pull Request #3. Thanks to @PascalHonegger)
## 0.7.0 (2018-03-15)
Changes:
-
+
- First commit of forked project (@cmglez10/ng-datatable)
- Updated to Angular 5
-
+
## 0.6.0 (2017-03-27)
Fixes:
@@ -186,9 +202,9 @@ Fixes:
## 0.5.2 (2016-11-13)
Changes:
-
+
- added inputs/outputs for sorting (#14)
-
+
Bugfixes:
- detect changes in inputData array (#10)
@@ -198,10 +214,10 @@ Bugfixes:
## 0.5.1 (2016-10-25)
Changes:
-
+
- changed the old "typings" system to the new "@types" system
- added support for AOT compilation
-
+
Bugfixes:
- sorting by child properties (#41)
@@ -211,12 +227,12 @@ Bugfixes:
Breaking changes:
- update angular library to 2.0.0
-
+
Bugfixes:
- sort case insensitive
- fixed pagination, fix #29, #33
-
+
#Changelog
## 0.4.2 (2016-05-11)
@@ -254,10 +270,9 @@ Bugfixes:
- remove `href` attribute from DefaultSorter
- add style `cursor: pointer` to links in DefaultSorter and BootstrapPaginator
-
+
## 0.2.2 (2016-03-21)
Bugfixes:
- remove `href` attribute from BootstrapPaginator template
-
diff --git a/angular.json b/angular.json
index ae5f1dc..675ec85 100644
--- a/angular.json
+++ b/angular.json
@@ -30,10 +30,7 @@
"test": {
"builder": "@angular/build:karma",
"options": {
- "polyfills": [
- "zone.js",
- "zone.js/testing"
- ],
+ "polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "projects/ng-datatable/tsconfig.spec.json"
}
},
@@ -85,17 +82,10 @@
"outputPath": "dist/demo",
"index": "projects/demo/src/index.html",
"browser": "projects/demo/src/main.ts",
- "polyfills": [
- "zone.js"
- ],
"tsConfig": "projects/demo/tsconfig.app.json",
- "assets": [
- "projects/demo/src/data.json"
- ],
+ "assets": ["projects/demo/src/data.json"],
"scripts": [],
- "styles": [
- "node_modules/bootstrap/dist/css/bootstrap.min.css"
- ]
+ "styles": ["node_modules/bootstrap/dist/css/bootstrap.min.css"]
},
"configurations": {
"production": {
diff --git a/package.json b/package.json
index 44a9adc..8b3eee9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ng-datatable",
- "version": "20.0.0",
+ "version": "20.1.0",
"packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac",
"scripts": {
"ng": "ng",
diff --git a/projects/demo/src/app.component.html b/projects/demo/src/app.component.html
index f44dbb2..53b91e0 100644
--- a/projects/demo/src/app.component.html
+++ b/projects/demo/src/app.component.html
@@ -3,106 +3,114 @@
-
+ >
+ by
+ PascalHonegger
+
+
-
-
-
-
-
-
-
-
-
-
diff --git a/projects/demo/src/app.component.ts b/projects/demo/src/app.component.ts
index b58beb1..f7e4131 100644
--- a/projects/demo/src/app.component.ts
+++ b/projects/demo/src/app.component.ts
@@ -1,52 +1,50 @@
-import { Component, OnInit, inject } from "@angular/core";
-import {HttpClient} from "@angular/common/http";
-import {DataTableModule, SortBy, SortOrder} from "ng-datatable";
+import { Component, model } from "@angular/core";
+import { httpResource } from "@angular/common/http";
+import {
+ BootstrapPaginator,
+ DataTable,
+ DefaultSorter,
+ SortBy,
+ SortOrder,
+} from "ng-datatable";
import { FormsModule } from "@angular/forms";
import { DataFilterPipe } from "./data-filter.pipe";
import { UpperCasePipe } from "@angular/common";
@Component({
- selector: "app-root",
- templateUrl: "./app.component.html",
- imports: [
- DataTableModule,
- FormsModule,
- DataFilterPipe,
- UpperCasePipe
- ]
+ selector: "app-root",
+ templateUrl: "./app.component.html",
+ imports: [
+ FormsModule,
+ DataFilterPipe,
+ UpperCasePipe,
+ DataTable,
+ DefaultSorter,
+ BootstrapPaginator,
+ ],
})
-export class AppComponent implements OnInit {
- private http = inject(HttpClient);
-
-
- public data: any[];
- public filterQuery = "";
- public rowsOnPage = 10;
- public sortBy: SortBy = "email";
- public sortOrder: SortOrder = "asc";
-
- ngOnInit(): void {
- this.http.get
("/data.json")
- .subscribe((data) => {
- setTimeout(() => {
- this.data = data;
- }, 2000);
- });
- }
-
- public toInt(num: string) {
- return +num;
+export class AppComponent {
+ filterQuery = model("");
+ rowsOnPage = model(10);
+ sortBy = model("email");
+ sortOrder = model("asc");
+
+ data = httpResource(() => "/data.json", {
+ defaultValue: [],
+ });
+
+ toInt(num: string) {
+ return +num;
+ }
+
+ sortByWordLength = (a: any) => {
+ return a.city.length;
+ };
+
+ remove(item: any) {
+ const index = this.data.value()?.indexOf(item);
+ if (index && index > -1) {
+ this.data.value()?.splice(index, 1);
}
-
- public sortByWordLength = (a: any) => {
- return a.city.length;
- }
-
- public remove(item: any) {
- const index = this.data.indexOf(item);
- if (index > -1) {
- this.data.splice(index, 1);
- }
- }
-
+ }
}
diff --git a/projects/demo/src/main.ts b/projects/demo/src/main.ts
index 567630e..9ebcf00 100644
--- a/projects/demo/src/main.ts
+++ b/projects/demo/src/main.ts
@@ -1,6 +1,15 @@
-import { bootstrapApplication } from '@angular/platform-browser';
-import { AppComponent } from './app.component';
-import { provideHttpClient } from '@angular/common/http';
+import { bootstrapApplication } from "@angular/platform-browser";
+import { AppComponent } from "./app.component";
+import { provideHttpClient } from "@angular/common/http";
+import {
+ provideBrowserGlobalErrorListeners,
+ provideZonelessChangeDetection,
+} from "@angular/core";
-bootstrapApplication(AppComponent, { providers: [provideHttpClient()] })
- .catch(err => console.error(err));
+bootstrapApplication(AppComponent, {
+ providers: [
+ provideHttpClient(),
+ provideZonelessChangeDetection(),
+ provideBrowserGlobalErrorListeners(),
+ ],
+}).catch((err) => console.error(err));
diff --git a/projects/ng-datatable/src/lib/BootstrapPaginator.ts b/projects/ng-datatable/src/lib/BootstrapPaginator.ts
index 6fc82d0..a50ead0 100644
--- a/projects/ng-datatable/src/lib/BootstrapPaginator.ts
+++ b/projects/ng-datatable/src/lib/BootstrapPaginator.ts
@@ -1,83 +1,95 @@
-import {Component, computed, input} from "@angular/core";
-import {DataTable} from "./DataTable";
+import { Component, computed, input } from "@angular/core";
+import { DataTable } from "./DataTable";
import { Paginator } from "./Paginator";
@Component({
- selector: "mfBootstrapPaginator",
- template: `
+ selector: "mfBootstrapPaginator",
+ template: `
- @if (p.dataLength > p.rowsOnPage) {
-
- }
- @if (p.dataLength > minRowsOnPage()) {
-
+ @if (p.dataLength() > p.rowsOnPage()) {
+
+ } @if (p.dataLength() > minRowsOnPage()) {
+
}
- `,
- styles: [
- ".page-link { cursor: pointer; }"
- ],
- imports: [Paginator]
+ `,
+ styles: [".page-link { cursor: pointer; }"],
+ imports: [Paginator],
})
export class BootstrapPaginator {
- rowsOnPageSet = input([]);
- mfTable = input();
+ rowsOnPageSet = input([]);
+ mfTable = input();
- minRowsOnPage = computed(() => this.rowsOnPageSet().reduce((previous, current) => current < previous ? current : previous, 0));
+ minRowsOnPage = computed(() =>
+ this.rowsOnPageSet().reduce(
+ (previous, current) => (current < previous ? current : previous),
+ 0
+ )
+ );
}
diff --git a/projects/ng-datatable/src/lib/DataTable.spec.ts b/projects/ng-datatable/src/lib/DataTable.spec.ts
index 71d3e11..2ec06a5 100644
--- a/projects/ng-datatable/src/lib/DataTable.spec.ts
+++ b/projects/ng-datatable/src/lib/DataTable.spec.ts
@@ -25,7 +25,7 @@ describe("DataTable directive tests", () => {
{id: 5, name: "Ðrone"},
{id: 4, name: "Ananas"}
];
- datatable.ngOnChanges({inputData: new SimpleChange(null, datatable.inputData, false)});
+ datatable.ngOnChanges({inputData: new SimpleChange(null, datatable.inputData(), false)});
});
describe("initializing", () => {
@@ -38,7 +38,7 @@ describe("DataTable directive tests", () => {
it("data should be equal to inputData", () => {
datatable.ngDoCheck();
- expect(datatable.data).toEqual(datatable.inputData);
+ expect(datatable.data).toEqual(datatable.inputData());
});
it("data should be 2 first items", () => {
@@ -131,8 +131,8 @@ describe("DataTable directive tests", () => {
datatable.sortBy = "id";
datatable.sortOrder = "asc";
datatable.ngOnChanges({
- sortBy: new SimpleChange(null, datatable.sortBy, true),
- sortOrder: new SimpleChange(null, datatable.sortOrder, true)
+ sortBy: new SimpleChange(null, datatable.sortBy(), true),
+ sortOrder: new SimpleChange(null, datatable.sortOrder(), true)
});
datatable.ngDoCheck();
expect(datatable.data).toEqual([
@@ -154,8 +154,8 @@ describe("DataTable directive tests", () => {
datatable.sortBy = "id";
datatable.sortOrder = "desc";
datatable.ngOnChanges({
- sortBy: new SimpleChange(null, datatable.sortBy, false),
- sortOrder: new SimpleChange(null, datatable.sortOrder, false)
+ sortBy: new SimpleChange(null, datatable.sortBy(), false),
+ sortOrder: new SimpleChange(null, datatable.sortOrder(), false)
});
datatable.ngDoCheck();
@@ -170,10 +170,10 @@ describe("DataTable directive tests", () => {
datatable.ngDoCheck();
datatable.sortBy = "id";
datatable.ngOnChanges({
- sortBy: new SimpleChange(null, datatable.sortBy, false)
+ sortBy: new SimpleChange(null, datatable.sortBy(), false)
});
datatable.ngDoCheck();
- expect(datatable.sortOrder).toEqual("asc");
+ expect(datatable.sortOrder()).toEqual("asc");
});
it("should set sortOrder to 'asc' if provided something else than 'asc' or 'desc'", (done) => {
@@ -185,12 +185,13 @@ describe("DataTable directive tests", () => {
datatable.ngDoCheck();
datatable.sortBy = "id";
datatable.sortOrder = "bulb" as "asc";
+ const sortOrder = datatable.sortOrder();
datatable.ngOnChanges({
- sortBy: new SimpleChange(null, datatable.sortBy, false),
- sortOrder: new SimpleChange(null, datatable.sortOrder, false)
+ sortBy: new SimpleChange(null, datatable.sortBy(), false),
+ sortOrder: new SimpleChange(null, sortOrder, false)
});
datatable.ngDoCheck();
- expect(datatable.sortOrder).toEqual("asc");
+ expect(sortOrder).toEqual("asc");
expect(datatable.data).toEqual([
{id: 1, name: "Duck"},
{id: 2, name: "ącki"},
@@ -204,7 +205,7 @@ describe("DataTable directive tests", () => {
datatable.setSort("id", "bulb" as "desc");
expect(datatable.getSort()).toEqual({sortBy: "id", sortOrder: "asc"});
datatable.ngDoCheck();
- expect(datatable.sortOrder).toEqual("asc");
+ expect(datatable.sortOrder()).toEqual("asc");
expect(datatable.data).toEqual([
{id: 1, name: "Duck"},
{id: 2, name: "ącki"},
@@ -221,9 +222,9 @@ describe("DataTable directive tests", () => {
});
datatable.ngDoCheck();
datatable.sortOrder = "desc";
- datatable.ngOnChanges({sortOrder: new SimpleChange(null, datatable.sortOrder, false)});
+ datatable.ngOnChanges({sortOrder: new SimpleChange(null, datatable.sortOrder(), false)});
datatable.ngDoCheck();
- expect(datatable.data).toEqual(datatable.inputData);
+ expect(datatable.data).toEqual(datatable.inputData());
});
it("should call output event when sorting changed", (done) => {
@@ -246,7 +247,7 @@ describe("DataTable directive tests", () => {
});
done();
datatable.sortOrder = "bulb" as "desc";
- datatable.ngOnChanges({sortOrder: new SimpleChange(null, datatable.sortOrder, false)});
+ datatable.ngOnChanges({sortOrder: new SimpleChange(null, datatable.sortOrder(), false)});
datatable.ngDoCheck();
});
// Wywołanie outputa gdy zmiana z innej strony
@@ -293,7 +294,7 @@ describe("DataTable directive tests", () => {
{name: "Claire", age: 7},
{name: "Anna", age: 12}
];
- datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData, newData, false)});
+ datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData(), newData, false)});
datatable.setSort(["name", "age"], "asc");
datatable.ngDoCheck();
@@ -316,7 +317,7 @@ describe("DataTable directive tests", () => {
{name: "Claire", city: {zip: "11111"}},
{name: "Anna", city: {zip: "21111"}}
];
- datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData, newData, false)});
+ datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData(), newData, false)});
datatable.setSort("city.zip", "asc");
datatable.ngDoCheck();
@@ -334,25 +335,25 @@ describe("DataTable directive tests", () => {
describe("data change", () => {
it("should refresh data when inputData change", () => {
const newData = [{id: 5, name: "Ðrone"}, {id: 4, name: "Ananas"}];
- datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData, newData, false)});
+ datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData(), newData, false)});
datatable.ngDoCheck();
expect(datatable.data).toEqual([{id: 5, name: "Ðrone"}, {id: 4, name: "Ananas"}]);
});
it("should refresh data when rows removed from inputData", () => {
datatable.ngDoCheck();
- expect(datatable.data).toEqual(datatable.inputData);
- datatable.inputData.pop();
+ expect(datatable.data).toEqual(datatable.inputData());
+ datatable.inputData().pop();
datatable.ngDoCheck();
- expect(datatable.data).toEqual(datatable.inputData);
+ expect(datatable.data).toEqual(datatable.inputData());
});
it("should refresh data when rows added to inputData", () => {
datatable.ngDoCheck();
- expect(datatable.data).toEqual(datatable.inputData);
- datatable.inputData.push({id: 6, name: "Furby"});
+ expect(datatable.data).toEqual(datatable.inputData());
+ datatable.inputData().push({id: 6, name: "Furby"});
datatable.ngDoCheck();
- expect(datatable.data).toEqual(datatable.inputData);
+ expect(datatable.data).toEqual(datatable.inputData());
});
it("should fire onPageChange event after inputData change", (done) => {
@@ -366,7 +367,7 @@ describe("DataTable directive tests", () => {
done();
});
const newData = [{id: 5, name: "Ðrone"}, {id: 4, name: "Ananas"}];
- datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData, newData, false)});
+ datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData(), newData, false)});
datatable.ngDoCheck();
});
@@ -380,7 +381,7 @@ describe("DataTable directive tests", () => {
expect(opt.rowsOnPage).toEqual(2);
done();
});
- datatable.inputData.push({id: 6, name: "Furby"});
+ datatable.inputData().push({id: 6, name: "Furby"});
datatable.ngDoCheck();
});
@@ -394,7 +395,7 @@ describe("DataTable directive tests", () => {
expect(opt.rowsOnPage).toEqual(2);
done();
});
- range(0, 3).forEach(() => datatable.inputData.pop());
+ range(0, 3).forEach(() => datatable.inputData().pop());
datatable.ngDoCheck();
});
@@ -403,7 +404,7 @@ describe("DataTable directive tests", () => {
datatable.ngDoCheck();
const newData = [{id: 5, name: "Ðrone"}, {id: 4, name: "Ananas"}];
- datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData, newData, false)});
+ datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData(), newData, false)});
datatable.ngDoCheck();
expect(datatable.data).toEqual(newData);
});
@@ -413,9 +414,9 @@ describe("DataTable directive tests", () => {
datatable.ngDoCheck();
expect(datatable.data).toEqual([{id: 2, name: "ącki"}, {id: 5, name: "Ðrone"}]);
- datatable.inputData.pop();
- datatable.inputData.pop();
- datatable.inputData.pop();
+ datatable.inputData().pop();
+ datatable.inputData().pop();
+ datatable.inputData().pop();
datatable.ngDoCheck();
expect(datatable.data).toEqual([{id: 3, name: "banana"}, {id: 1, name: "Duck"}]);
});
@@ -425,7 +426,7 @@ describe("DataTable directive tests", () => {
datatable.ngDoCheck();
const newData = [{id: 5, name: "Ðrone"}, {id: 1, name: "Duck"}, {id: 4, name: "Ananas"}];
- datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData, newData, false)});
+ datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData(), newData, false)});
datatable.ngDoCheck();
expect(datatable.data).toEqual([{id: 1, name: "Duck"}]);
});
@@ -435,7 +436,7 @@ describe("DataTable directive tests", () => {
datatable.ngDoCheck();
expect(datatable.data).toEqual([{id: 1, name: "Duck"}]);
- datatable.inputData.pop();
+ datatable.inputData().pop();
datatable.ngDoCheck();
expect(datatable.data).toEqual([{id: 1, name: "Duck"}]);
});
@@ -445,7 +446,7 @@ describe("DataTable directive tests", () => {
datatable.ngDoCheck();
expect(datatable.data).toEqual([{id: 1, name: "Duck"}]);
- datatable.inputData.push({id: 6, name: "Furby"});
+ datatable.inputData().push({id: 6, name: "Furby"});
datatable.ngDoCheck();
expect(datatable.data).toEqual([{id: 1, name: "Duck"}]);
});
@@ -454,19 +455,19 @@ describe("DataTable directive tests", () => {
datatable.setPage(2, 1);
datatable.ngDoCheck();
- datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData, [], false)});
+ datatable.ngOnChanges({inputData: new SimpleChange(datatable.inputData(), [], false)});
datatable.ngDoCheck();
- expect(datatable.activePage).toEqual(1);
+ expect(datatable.activePage()).toEqual(1);
});
it("shouldn't change page to 0 when data is empty after removed rows", () => {
datatable.setPage(2, 1);
datatable.ngDoCheck();
- range(0, 5).forEach(() => datatable.inputData.pop());
+ range(0, 5).forEach(() => datatable.inputData().pop());
datatable.ngDoCheck();
- expect(datatable.inputData.length).toEqual(0);
- expect(datatable.activePage).toEqual(1);
+ expect(datatable.inputData().length).toEqual(0);
+ expect(datatable.activePage()).toEqual(1);
});
});
});
diff --git a/projects/ng-datatable/src/lib/DataTable.ts b/projects/ng-datatable/src/lib/DataTable.ts
index cae45a0..48a896f 100644
--- a/projects/ng-datatable/src/lib/DataTable.ts
+++ b/projects/ng-datatable/src/lib/DataTable.ts
@@ -1,211 +1,259 @@
-import { Directive, Input, EventEmitter, OnChanges, DoCheck, IterableDiffers, IterableDiffer, Output, inject, SimpleChanges } from "@angular/core";
+import {
+ Directive,
+ EventEmitter,
+ OnChanges,
+ DoCheck,
+ IterableDiffers,
+ IterableDiffer,
+ inject,
+ SimpleChanges,
+ model,
+ output,
+} from "@angular/core";
import { ReplaySubject } from "rxjs";
-
export type SortOrder = "asc" | "desc";
export type SortByFunction = (data: T) => any;
-export type SortBy = string | SortByFunction | (string | SortByFunction)[];
+export type SortBy =
+ | string
+ | SortByFunction
+ | (string | SortByFunction)[];
export interface SortEvent {
- sortBy: SortBy;
- sortOrder: string;
+ sortBy: SortBy;
+ sortOrder: string;
}
export interface PageEvent {
- activePage: number;
- rowsOnPage: number;
- dataLength: number;
+ activePage: number;
+ rowsOnPage: number;
+ dataLength: number;
}
export interface DataEvent {
- length: number;
+ length: number;
}
@Directive({
- selector: "table[mfData]",
- exportAs: "mfDataTable"
+ selector: "table[mfData]",
+ exportAs: "mfDataTable",
})
export class DataTable implements OnChanges, DoCheck {
+ #diff: IterableDiffer;
+ readonly inputData = model([], { alias: "mfData" });
- private diff: IterableDiffer;
- @Input("mfData") public inputData: T[] = [];
+ readonly sortBy = model>("", { alias: "mfSortBy" });
+ readonly sortOrder = model("asc", { alias: "mfSortOrder" });
+ readonly sortByChange = output>({ alias: "mfSortByChange" });
+ readonly sortOrderChange = output({ alias: "mfSortOrderChange" });
- @Input("mfSortBy") public sortBy: SortBy = "";
- @Input("mfSortOrder") public sortOrder: SortOrder = "asc";
- @Output("mfSortByChange") public sortByChange = new EventEmitter>();
- @Output("mfSortOrderChange") public sortOrderChange = new EventEmitter();
+ readonly rowsOnPage = model(1000, { alias: "mfRowsOnPage" });
+ readonly activePage = model(1, { alias: "mfActivePage" });
- @Input("mfRowsOnPage") public rowsOnPage = 1000;
- @Input("mfActivePage") public activePage = 1;
+ #mustRecalculateData = false;
- private mustRecalculateData = false;
+ data: T[];
- public data: T[];
+ onSortChange = new ReplaySubject(1);
+ onPageChange = new EventEmitter();
- public onSortChange = new ReplaySubject(1);
- public onPageChange = new EventEmitter();
+ constructor() {
+ const differs = inject(IterableDiffers);
- public constructor() {
- const differs = inject(IterableDiffers);
+ this.#diff = differs.find([]).create();
+ }
- this.diff = differs.find([]).create();
- }
+ getSort(): SortEvent {
+ return { sortBy: this.sortBy(), sortOrder: this.sortOrder() };
+ }
- public getSort(): SortEvent {
- return { sortBy: this.sortBy, sortOrder: this.sortOrder };
+ setSort(sortBy: SortBy, sortOrder: SortOrder): void {
+ const sortByValue = this.sortBy();
+ const sortOrderValue = this.sortOrder();
+ if (sortByValue !== sortBy || sortOrderValue !== sortOrder) {
+ this.sortBy.set(sortBy);
+ this.sortOrder.set(
+ ["asc", "desc"].indexOf(sortOrder) >= 0 ? sortOrder : "asc"
+ );
+ this.#mustRecalculateData = true;
+ this.onSortChange.next({
+ sortBy: sortByValue,
+ sortOrder: sortOrderValue,
+ });
+ this.sortByChange.emit(sortByValue);
+ this.sortOrderChange.emit(sortOrderValue);
}
+ }
- public setSort(sortBy: SortBy, sortOrder: SortOrder): void {
- if (this.sortBy !== sortBy || this.sortOrder !== sortOrder) {
- this.sortBy = sortBy;
- this.sortOrder = ["asc", "desc"].indexOf(sortOrder) >= 0 ? sortOrder : "asc";
- this.mustRecalculateData = true;
- this.onSortChange.next({ sortBy: this.sortBy, sortOrder: this.sortOrder });
- this.sortByChange.emit(this.sortBy);
- this.sortOrderChange.emit(this.sortOrder);
- }
- }
+ getPage(): PageEvent {
+ return {
+ activePage: this.activePage(),
+ rowsOnPage: this.rowsOnPage(),
+ dataLength: this.inputData().length,
+ };
+ }
- public getPage(): PageEvent {
- return { activePage: this.activePage, rowsOnPage: this.rowsOnPage, dataLength: this.inputData.length };
+ setPage(activePageIn: number, rowsOnPageIn: number): void {
+ if (
+ this.rowsOnPage() !== rowsOnPageIn ||
+ this.activePage() !== activePageIn
+ ) {
+ this.activePage.set(
+ this.activePage() !== activePageIn
+ ? activePageIn
+ : this.#calculateNewActivePage(this.rowsOnPage(), rowsOnPageIn)
+ );
+ this.rowsOnPage.set(rowsOnPageIn);
+ this.#mustRecalculateData = true;
+ const inputData = this.inputData();
+ this.onPageChange.emit({
+ activePage: this.activePage(),
+ rowsOnPage: this.rowsOnPage(),
+ dataLength: inputData ? inputData.length : 0,
+ });
}
+ }
- public setPage(activePage: number, rowsOnPage: number): void {
- if (this.rowsOnPage !== rowsOnPage || this.activePage !== activePage) {
- this.activePage = this.activePage !== activePage ? activePage : this.calculateNewActivePage(this.rowsOnPage, rowsOnPage);
- this.rowsOnPage = rowsOnPage;
- this.mustRecalculateData = true;
- this.onPageChange.emit({
- activePage: this.activePage,
- rowsOnPage: this.rowsOnPage,
- dataLength: this.inputData ? this.inputData.length : 0
- });
- }
- }
+ #calculateNewActivePage(
+ previousRowsOnPage: number,
+ currentRowsOnPage: number
+ ): number {
+ const firstRowOnPage = (this.activePage() - 1) * previousRowsOnPage + 1;
+ const newActivePage = Math.ceil(firstRowOnPage / currentRowsOnPage);
+ return newActivePage;
+ }
- private calculateNewActivePage(previousRowsOnPage: number, currentRowsOnPage: number): number {
- const firstRowOnPage = (this.activePage - 1) * previousRowsOnPage + 1;
- const newActivePage = Math.ceil(firstRowOnPage / currentRowsOnPage);
- return newActivePage;
- }
+ #recalculatePage() {
+ const lastPage = Math.ceil(this.inputData().length / this.rowsOnPage());
+ this.activePage.set(
+ lastPage < this.activePage() ? lastPage : this.activePage()
+ );
+ this.activePage.set(this.activePage() || 1);
- private recalculatePage() {
- const lastPage = Math.ceil(this.inputData.length / this.rowsOnPage);
- this.activePage = lastPage < this.activePage ? lastPage : this.activePage;
- this.activePage = this.activePage || 1;
+ this.onPageChange.emit({
+ activePage: this.activePage(),
+ rowsOnPage: this.rowsOnPage(),
+ dataLength: this.inputData().length,
+ });
+ }
- this.onPageChange.emit({
- activePage: this.activePage,
- rowsOnPage: this.rowsOnPage,
- dataLength: this.inputData.length
- });
+ ngOnChanges(changes: SimpleChanges): any {
+ if (changes["rowsOnPage"]) {
+ this.rowsOnPage.set(changes["rowsOnPage"].previousValue);
+ this.setPage(this.activePage(), changes["rowsOnPage"].currentValue);
+ this.#mustRecalculateData = true;
}
-
- public ngOnChanges(changes: SimpleChanges): any {
- if (changes["rowsOnPage"]) {
- this.rowsOnPage = changes["rowsOnPage"].previousValue;
- this.setPage(this.activePage, changes["rowsOnPage"].currentValue);
- this.mustRecalculateData = true;
- }
- if (changes["sortBy"] || changes["sortOrder"]) {
- if (["asc", "desc"].indexOf(this.sortOrder) < 0) {
- console.warn("ng-datatable: value for input mfSortOrder must be one of ['asc', 'desc'], but is:", this.sortOrder);
- this.sortOrder = "asc";
- }
- if (this.sortBy) {
- this.onSortChange.next({ sortBy: this.sortBy, sortOrder: this.sortOrder });
- }
- this.mustRecalculateData = true;
- }
- if (changes["inputData"]) {
- this.inputData = changes["inputData"].currentValue || [];
- this.diff.diff(this.inputData); // Update diff to prevent duplicate update in ngDoCheck
- this.recalculatePage();
- this.mustRecalculateData = true;
- }
+ if (changes["sortBy"] || changes["sortOrder"]) {
+ const sortOrder = this.sortOrder();
+ if (["asc", "desc"].indexOf(this.sortOrder()) < 0) {
+ console.warn(
+ "ng-datatable: value for input mfSortOrder must be one of ['asc', 'desc'], but is:",
+ sortOrder
+ );
+ this.sortOrder.set("asc");
+ }
+ const sortBy = this.sortBy();
+ if (sortBy) {
+ this.onSortChange.next({ sortBy: sortBy, sortOrder: sortOrder });
+ }
+ this.#mustRecalculateData = true;
}
-
- public ngDoCheck(): any {
- const changes = this.diff.diff(this.inputData);
- if (changes) {
- this.recalculatePage();
- this.mustRecalculateData = true;
- }
- if (this.mustRecalculateData) {
- this.fillData();
- this.mustRecalculateData = false;
- }
+ if (changes["inputData"]) {
+ this.inputData.set(changes["inputData"].currentValue || []);
+ this.#diff.diff(this.inputData()); // Update diff to prevent duplicate update in ngDoCheck
+ this.#recalculatePage();
+ this.#mustRecalculateData = true;
}
+ }
- private fillData(): void {
- // this.activePage = this.activePage;
- // this.rowsOnPage = this.rowsOnPage;
-
- const offset = (this.activePage - 1) * this.rowsOnPage;
- // let data = this.inputData;
- // const sortBy = this.sortBy;
- // if (typeof sortBy === "string" || sortBy instanceof String) {
- // data = orderBy(data, this.caseInsensitiveIteratee(sortBy as string), [this.sortOrder]);
- // } else {
- // data = orderBy(data, sortBy, [this.sortOrder]);
- // }
- // data = slice(data, offset, offset + this.rowsOnPage);
-
- this.data = [...this.inputData]
- .sort(this.sorter(this.sortBy, this.sortOrder))
- .slice(offset, offset + this.rowsOnPage);
+ ngDoCheck(): any {
+ const changes = this.#diff.diff(this.inputData());
+ if (changes) {
+ this.#recalculatePage();
+ this.#mustRecalculateData = true;
}
-
- private caseInsensitiveIteratee(sortBy: string | SortByFunction) {
- return (row: any): any => {
- let value = row;
- if (typeof sortBy === "string" || sortBy instanceof String) {
- for (const sortByProperty of sortBy.split(".")) {
- if (value) {
- value = value[sortByProperty];
- }
- }
- } else if (typeof sortBy === "function") {
- value = sortBy(value);
- }
-
- if (value && typeof value === "string" || value instanceof String) {
- return value.toLowerCase();
- }
-
- return value;
- };
+ if (this.#mustRecalculateData) {
+ this.#fillData();
+ this.#mustRecalculateData = false;
}
+ }
- private compare(left: any, right: any): number {
- if (left === right) {
- return 0;
- }
- if (left == null && right != null) {
- return -1;
- }
- if (right == null) {
- return 1;
+ #fillData(): void {
+ // this.activePage = this.activePage;
+ // this.rowsOnPage = this.rowsOnPage;
+
+ const offset = (this.activePage() - 1) * this.rowsOnPage();
+ // let data = this.inputData;
+ // const sortBy = this.sortBy;
+ // if (typeof sortBy === "string" || sortBy instanceof String) {
+ // data = orderBy(data, this.caseInsensitiveIteratee(sortBy as string), [this.sortOrder]);
+ // } else {
+ // data = orderBy(data, sortBy, [this.sortOrder]);
+ // }
+ // data = slice(data, offset, offset + this.rowsOnPage);
+
+ this.data = [...this.inputData()]
+ .sort(this.sorter(this.sortBy(), this.sortOrder()))
+ .slice(offset, offset + this.rowsOnPage());
+ }
+
+ #caseInsensitiveIteratee(sortBy: string | SortByFunction) {
+ return (row: any): any => {
+ let value = row;
+ if (typeof sortBy === "string" || sortBy instanceof String) {
+ for (const sortByProperty of sortBy.split(".")) {
+ if (value) {
+ value = value[sortByProperty];
+ }
}
- return left > right ? 1 : -1;
+ } else if (typeof sortBy === "function") {
+ value = sortBy(value);
+ }
+
+ if ((value && typeof value === "string") || value instanceof String) {
+ return value.toLowerCase();
+ }
+
+ return value;
+ };
+ }
+
+ #compare(left: any, right: any): number {
+ if (left === right) {
+ return 0;
+ }
+ if (left == null && right != null) {
+ return -1;
+ }
+ if (right == null) {
+ return 1;
}
+ return left > right ? 1 : -1;
+ }
- private sorter(sortBy: SortBy, sortOrder: SortOrder): (left: T, right: T) => number {
- const order = sortOrder === "desc" ? -1 : 1;
- if (Array.isArray(sortBy)) {
- const iteratees = sortBy.map((entry) => this.caseInsensitiveIteratee(entry));
- return (left, right) => {
- for (const iteratee of iteratees) {
- const comparison = this.compare(iteratee(left), iteratee(right)) * order;
- if (comparison !== 0) {
- return comparison;
- }
- }
- return 0;
- };
- } else {
- const iteratee = this.caseInsensitiveIteratee(sortBy);
- return (left, right) => this.compare(iteratee(left), iteratee(right)) * order;
+ sorter(
+ sortBy: SortBy,
+ sortOrder: SortOrder
+ ): (left: T, right: T) => number {
+ const order = sortOrder === "desc" ? -1 : 1;
+ if (Array.isArray(sortBy)) {
+ const iteratees = sortBy.map((entry) =>
+ this.#caseInsensitiveIteratee(entry)
+ );
+ return (left, right) => {
+ for (const iteratee of iteratees) {
+ const comparison =
+ this.#compare(iteratee(left), iteratee(right)) * order;
+ if (comparison !== 0) {
+ return comparison;
+ }
}
+ return 0;
+ };
+ } else {
+ const iteratee = this.#caseInsensitiveIteratee(sortBy);
+ return (left, right) =>
+ this.#compare(iteratee(left), iteratee(right)) * order;
}
+ }
}
diff --git a/projects/ng-datatable/src/lib/DataTableModule.ts b/projects/ng-datatable/src/lib/DataTableModule.ts
deleted file mode 100644
index d0eaf15..0000000
--- a/projects/ng-datatable/src/lib/DataTableModule.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {NgModule} from "@angular/core";
-
-import {DataTable} from "./DataTable";
-import {DefaultSorter} from "./DefaultSorter";
-import {Paginator} from "./Paginator";
-import {BootstrapPaginator} from "./BootstrapPaginator";
-
-@NgModule({
- imports: [
- DataTable,
- DefaultSorter,
- Paginator,
- BootstrapPaginator
- ],
- exports: [
- DataTable,
- DefaultSorter,
- Paginator,
- BootstrapPaginator
- ]
-})
-export class DataTableModule {
-
-}
diff --git a/projects/ng-datatable/src/lib/DefaultSorter.ts b/projects/ng-datatable/src/lib/DefaultSorter.ts
index 0b04b15..71dafb8 100644
--- a/projects/ng-datatable/src/lib/DefaultSorter.ts
+++ b/projects/ng-datatable/src/lib/DefaultSorter.ts
@@ -1,42 +1,49 @@
-import { Component, OnInit, inject, input } from "@angular/core";
-import {DataTable, SortBy, SortEvent} from "./DataTable";
+import { Component, OnInit, inject, input, signal } from "@angular/core";
+import { DataTable, SortBy, SortEvent } from "./DataTable";
@Component({
- selector: "mfDefaultSorter",
- template: `
-
-
- @if (isSortedByMeAsc) {
- ▲
- } @else if (isSortedByMeDesc) {
- ▼
- }
- `,
- styles: [
- "a { cursor: pointer; }"
- ]
+ selector: "mfDefaultSorter",
+ template: `
+
+ @if (isSortedByMeAsc()) {
+ ▲
+ } @else if (isSortedByMeDesc()) {
+ ▼
+ }
+ `,
+ styles: ["a { cursor: pointer; }"],
})
export class DefaultSorter implements OnInit {
- private mfTable = inject(DataTable);
+ #mfTable = inject(DataTable);
- readonly sortBy = input.required>({ alias: "by" });
+ readonly sortBy = input.required>({ alias: "by" });
- isSortedByMeAsc = false;
- isSortedByMeDesc = false;
+ isSortedByMeAsc = signal(false);
+ isSortedByMeDesc = signal(false);
- public ngOnInit(): void {
- this.mfTable.onSortChange.subscribe((event: SortEvent) => {
- this.isSortedByMeAsc = (event.sortBy == this.sortBy() && event.sortOrder === "asc");
- this.isSortedByMeDesc = (event.sortBy == this.sortBy() && event.sortOrder === "desc");
- });
- }
+ ngOnInit(): void {
+ this.#mfTable.onSortChange.subscribe((event: SortEvent) => {
+ this.isSortedByMeAsc.set(
+ event.sortBy == this.sortBy() && event.sortOrder === "asc"
+ );
+ this.isSortedByMeDesc.set(
+ event.sortBy == this.sortBy() && event.sortOrder === "desc"
+ );
+ });
+ }
- sort() {
- if (this.isSortedByMeAsc) {
- this.mfTable.setSort(this.sortBy(), "desc");
- } else {
- this.mfTable.setSort(this.sortBy(), "asc");
- }
- return false;
+ sort() {
+ if (this.isSortedByMeAsc()) {
+ this.#mfTable.setSort(this.sortBy(), "desc");
+ } else {
+ this.#mfTable.setSort(this.sortBy(), "asc");
}
+ return false;
+ }
}
diff --git a/projects/ng-datatable/src/lib/Paginator.ts b/projects/ng-datatable/src/lib/Paginator.ts
index 3467d74..95bb8f5 100644
--- a/projects/ng-datatable/src/lib/Paginator.ts
+++ b/projects/ng-datatable/src/lib/Paginator.ts
@@ -1,40 +1,50 @@
-import { Component, OnChanges, inject, input } from "@angular/core";
-import {DataTable, PageEvent} from "./DataTable";
+import {
+ Component,
+ OnChanges,
+ computed,
+ inject,
+ input,
+ signal,
+} from "@angular/core";
+import { DataTable, PageEvent } from "./DataTable";
@Component({
- selector: "mfPaginator",
- template: ``
+ selector: "mfPaginator",
+ template: ``,
})
export class Paginator implements OnChanges {
- private injectMfTable = inject(DataTable, { optional: true })!;
-
- readonly inputMfTable = input(undefined, { alias: "mfTable" });
-
- private mfTable: DataTable;
-
- public activePage: number;
- public rowsOnPage: number;
- public dataLength = 0;
- public lastPage: number;
-
- public ngOnChanges(): any {
- this.mfTable = this.inputMfTable() ?? this.injectMfTable;
- this.onPageChangeSubscriber(this.mfTable.getPage());
- this.mfTable.onPageChange.subscribe(this.onPageChangeSubscriber);
- }
-
- public setPage(pageNumber: number): void {
- this.mfTable.setPage(pageNumber, this.rowsOnPage);
- }
-
- public setRowsOnPage(rowsOnPage: number): void {
- this.mfTable.setPage(this.activePage, rowsOnPage);
- }
-
- private onPageChangeSubscriber = (event: PageEvent) => {
- this.activePage = event.activePage;
- this.rowsOnPage = event.rowsOnPage;
- this.dataLength = event.dataLength;
- this.lastPage = Math.ceil(this.dataLength / this.rowsOnPage);
- }
+ #injectMfTable = inject(DataTable, { optional: true })!;
+
+ readonly inputMfTable = input(undefined, { alias: "mfTable" });
+
+ #mfTable: DataTable;
+
+ activePage = signal(0);
+ rowsOnPage = signal(0);
+ dataLength = signal(0);
+ lastPage = computed(() =>
+ this.rowsOnPage() === 0
+ ? 0
+ : Math.ceil(this.dataLength() / this.rowsOnPage())
+ );
+
+ ngOnChanges(): any {
+ this.#mfTable = this.inputMfTable() ?? this.#injectMfTable;
+ this.#onPageChangeSubscriber(this.#mfTable.getPage());
+ this.#mfTable.onPageChange.subscribe(this.#onPageChangeSubscriber);
+ }
+
+ setPage(pageNumber: number): void {
+ this.#mfTable.setPage(pageNumber, this.rowsOnPage());
+ }
+
+ setRowsOnPage(rowsOnPage: number): void {
+ this.#mfTable.setPage(this.activePage(), rowsOnPage);
+ }
+
+ #onPageChangeSubscriber = (event: PageEvent) => {
+ this.activePage.set(event.activePage);
+ this.rowsOnPage.set(event.rowsOnPage);
+ this.dataLength.set(event.dataLength);
+ };
}
diff --git a/projects/ng-datatable/src/public-api.ts b/projects/ng-datatable/src/public-api.ts
index 849ca95..16ab967 100644
--- a/projects/ng-datatable/src/public-api.ts
+++ b/projects/ng-datatable/src/public-api.ts
@@ -6,4 +6,4 @@ export * from "./lib/BootstrapPaginator";
export * from "./lib/Paginator";
export * from "./lib/DefaultSorter";
export * from "./lib/DataTable";
-export * from "./lib/DataTableModule";
+
diff --git a/projects/ng-datatable/tsconfig.lib.json b/projects/ng-datatable/tsconfig.lib.json
index 60ee0a4..212d0f5 100644
--- a/projects/ng-datatable/tsconfig.lib.json
+++ b/projects/ng-datatable/tsconfig.lib.json
@@ -1,20 +1,29 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
- "declarationMap": true,
"declaration": true,
+ "declarationMap": true,
"inlineSources": true,
"types": []
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
- "skipTemplateCodegen": true,
- "strictMetadataEmit": true,
- "enableResourceInlining": true
+ "strictStandalone": true,
+ "strictDomEventTypes": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "typeCheckHostBindings": true,
+ "strictTemplates": true,
+ "extendedDiagnostics": {
+ "checks": {
+ "invalidBananaInBox": "error",
+ "nullishCoalescingNotNullable": "error"
+ }
+ }
},
- "exclude": [
- "src/test.ts",
- "**/*.spec.ts"
- ]
+ "include": ["src/**/*.ts"],
+ "exclude": ["**/*.spec.ts"]
}