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 @@

Angular DataTable componentAngular DataTable component
- by - PascalHonegger -

- + >
+ by + PascalHonegger + + -
-
- - -
-
- - -
-
- - +
+
+ + +
+
+ + +
+
+ + +
+
+
+
User information
+ +
+
+ +
+
-
-
User information
+
-
-
- -
- -
-
-
-
+ @if(data.value()) { - - - - - - - - - - - - @for (item of mf.data; track item) { - - - - - - - - } - - - - - - -
- Name - - Email - - Age - - City -
- - {{ item.name }}{{ item.email }}{{ item.age }}{{ item.city | uppercase }}
- -
-
-
+ + + + + + + + + + + + @for (item of mf.data; track $index) { + + + + + + + + } + + + + + + +
+ Name + + Email + + Age + + City +
+ + {{ item.name }}{{ item.email }}{{ item.age }}{{ item.city | uppercase }}
+ +
+ }
+
+
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()) { -
    - @for (rows of rowsOnPageSet(); track rows) { -
  • - {{rows}} -
  • - } -
+ @if (p.dataLength() > p.rowsOnPage()) { + + } @if (p.dataLength() > minRowsOnPage()) { +
    + @for (rows of rowsOnPageSet(); track rows) { +
  • + {{ rows }} +
  • + } +
}
- `, - 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"] }