65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Angular NgExTable 实现客户端和服务器端数据过滤、排序和分页

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2018年2月7日

CPOL

18分钟阅读

viewsIcon

64810

downloadIcon

1981

一个自定义且可配置的 Angular 数据网格工具和演示应用程序,展示了客户端和服务器端的数据过滤、排序和分页(已更新至 Angular 11)

引言

市面上提供了许多 Angular 网格工具,来自商业市场或开源社区。然而,其中大多数工具基于 JavaScript API,开发者无法灵活地为数据网格编写自己的 HTML 模板和数据绑定选项。对于我的项目,特别是从 AngularJS 的 ngTable 迁移到 Angular,我需要一个可以从表格模板的顶层属性指令开始初始化的网格工具,类似于旧版 ngTable 的场景和模板布局,但完全在 Angular 生态系统中。因此,我创建了自己的数据网格 NgExTable,用于显示具有数据搜索、排序和分页功能的数据记录列表。随着 Angular 发展到更高版本,示例演示应用程序也已升级为使用最新的 Angular 11 CLI。如果您需要包含早期 Angular 版本的源代码文件,请参阅本文末尾“历史”部分中的详细信息。

NgExTable 具有以下特性

  • 使用基本的 HTML 代码,而不是 JavaScript API,用于 `table` 标签,因此可以轻松定义自己的表格属性、样式和 Angular 管道。
  • 简单直接地绑定数据到 `tr` 和 `td` 标签。
  • 通过 `th` 标签中的 `ColumnSortComponent` 设置列排序,并带有可更改的排序图标。
  • 已准备好多列排序功能,但本文和示例应用程序仅展示了单列排序演示的一般数据网格工作流程(有关多列排序实现的详细信息和描述,请参阅文章 多列排序:从 Angular NgExTable 到源数据列表管理)。
  • 灵活可定制的分页组件和显示。
  • 网格工具使用者端的代码更灵活,代码行数更少(请参阅表格宿主组件 `client-paging.component.ts`、`server-paging.component.ts` 以及配套的 HTML 模板以获取详细信息)。
  • 可配置的表格参数,特别是与搜索、排序和页面大小命令相关的参数。
  • 核心库代码位于单个文件夹中,方便复制并直接供任何项目使用,即使在不同平台上。代码的任何部分都可以根据需要轻松修改。

以下是 NgExTable 库和演示项目文件夹中列出的所有文件

设置和运行示例应用程序

下载的源代码包含两种不同的 Visual Studio 解决方案/项目类型。请选择您想要的一种或两种,并在本地计算机上进行设置。您还需要在本地计算机上全局安装 node.js(推荐版本 14.x LTS 或更高版本)和 Angular CLI(推荐版本 11.x 或更高版本)。请查看 node.jsAngular CLI 文档以获取详细信息。

您可以在 `C:\Program Files (x86)\Microsoft SDKs\TypeScript` 文件夹中检查 Visual Studio 中可用的 TypeScript 版本。示例应用程序的 ASP.NET 和 Core 类型都在 `SM.NgExTable.Web.csproj` 文件的 `TypeScriptToolsVersion` 节点中将 Visual Studio 的 TypeScript 版本设置为 4.0。如果您没有安装 4.0 版本,请从 Microsoft 网站下载 安装包,或安装包含 TypeScript 4.0 的 Visual Studio 2019 版本 16.8.x。

NgExTable_AspNetCore_Cli

  1. 您需要在本地机器上使用 Visual Studio 2019(版本 16.8.x)。.NET Core 5.0 SDK 包含在 Visual Studio 安装中。

  2. 下载源代码文件并将其解压到您的本地工作区。

  3. 转到本地工作空间的物理位置,依次双击 `SM.NgExTable.Web\AppDev` 文件夹下的 `npm_install.bat` 和 `ng_build.bat`(如果未全局安装 Angular CLI,则为 `ng_build_local.bat`)。

    注意:在对 TypeScript/JavaScript 代码进行任何更改后,可能需要每次都执行 `ng build` 命令,而 `npm install` 的执行仅在 node 模块包有任何更新时才需要。我没有启用 CLI/Webpack 的热模块替换,因为它可能会破坏 Visual Studio 中调试的源代码映射。

  4. 使用Visual Studio 2019打开解决方案,并使用Visual Studio重新构建解决方案。

  5. 单击 **IIS Express** 工具栏命令(或按 **F5**)以启动示例应用程序。

NgExTable_AspNet_Cli

  1. 下载源代码文件并将其解压到您的本地工作区。

  2. 转到本地工作空间的物理位置,依次双击 `SM.NgExTable.Web\ClientApp` 文件夹下的 `npm_install.bat` 和 `ng_build.bat`(如果未全局安装 Angular CLI,则为 `ng_build_local.bat`)(另请参阅设置 `NgTable_AspNetCore_Cli` 项目的相同注意事项)。

  3. 使用 Visual Studio 2017 或 2019 打开解决方案,并使用 Visual Studio 重新构建解决方案。

  4. 单击 **IIS Express** 工具栏命令(或按 **F5**)以启动示例应用程序。

`NgTable_Ng11_Scripts_AnyPlatform.zip` 文件仅包含客户端脚本源代码文件夹和文件。如果您使用 Microsoft 平台以外的系统,您可以将纯 Angular 源代码文件复制到您自己的网站项目中,使用 `npm` 和 Angular CLI 命令执行必要的设置,然后在您的系统上运行示例应用程序。

示例应用程序的第一个浏览器屏幕如下所示

您无需为 **服务器端分页** 演示设置数据提供服务应用程序的服务器端数据源。默认情况下,示例应用程序使用 `server-mock-data.service.ts` 来模拟服务器端数据请求和响应模式及结果。这也有利于将 `NgExTable_Scripts_AnyPlatform` 代码文件添加到其网站项目中并在非 Microsoft 平台上运行 NgExTable 示例的受众。

如果您使用 Visual Studio 运行示例应用程序并希望调用真实服务器数据源进行 **服务器端分页** 演示,我建议执行以下步骤

  1. 请参阅我的另一篇文章 ASP.NET Core:从 ASP.NET Web API 迁移的多层数据服务应用程序,并按照说明设置 Core API 数据服务。任何版本的数据服务应用程序都是兼容的数据源提供者。

  2. 使用 Visual Studio 启动数据服务应用程序,并在后台保持解决方案以 IIS Express 运行。

  3. 在 `../app/NgExTableDemo/app.config.ts` 文件中,将 `ServerPagingDataSource` 从 `mock` 更改为 `server`。

    export const ServerPagingDataSource: 
        string = 'server'; //'mock' for simulating server data.
  4. 如果服务 URL 不符合您的设置,请编辑并启用活动服务 URL。

    export const WebApiRootUrl: string = "https://:7200/api/";  //Core 5.0
    
  5. 通过执行 `ng_build.bat` 或 `ng_build_local.bat` 来重新构建 Angular CLI。

  6. 按 **F5** 运行 NgExTable 演示应用程序,选择 **服务器端分页** 左侧菜单项,然后单击 **搜索产品** 面板上的 **转到** 按钮。

表格结构和工作机制

大多数其他 Angular 数据网格或表格使用基于 Angular 组件树结构的表格级和列级组件。然而,这种实现方法对于 Web 应用程序的开发似乎不够灵活。它限制了可以直接添加到 HTML 元素中的许多自定义选项,例如属性、样式、列中的额外元素,甚至用于数据绑定 `td` 标签的 Angular 管道。

NgExTable 本身不是一个顶层 Angular 组件。相反,它作为一个服务提供附加服务、指令和单元处理组件。它还依赖事件来响应用户操作,并将参数数据发送回父表格宿主组件。在示例应用程序中,`ClientPagingComponent` 或 `ServerPagingComponent` 都是表格宿主组件。

  1. 与基本表格相关的主要代码结构包括用于 `table` 标签的 `table-main-params` 属性指令,用于 `th` 标签的 `ColumnSort` 组件(`column-sort` 标签),以及从 `TableMainDirective` 类触发的 `tableChanged` 事件。示例应用程序的 `client-paging.component.html` 和 `server-paging.component.html` 文件(表格宿主视图)都展示了在 HTML 模板中使用指令、组件和事件的示例。

    <table class="table table-condensed table-striped bottom-border top-space"
            [table-main-params]="pagingParams" (tableChanged)="onChangeTable($event)">
    	<thead>
            <tr>
                <th>Product Name<column-sort sortBy="ProductName"></column-sort></th>
                <th>Category ID<column-sort sortBy="CategoryId"></column-sort></th>
                <th>Category Name<column-sort sortBy="CategoryName"></column-sort></th>
                <th>Unit Price<column-sort sortBy="UnitPrice"></column-sort></th>      
                <th>Status<column-sort sortBy="StatusDescription"></column-sort></th>
                <th>Available Since<column-sort sortBy="AvailableSince"></column-sort></th>
            </tr>            
    	</thead>
    	<tbody>
    		<tr *ngFor="let item of rows; let last = last">
    			<td>{{item.ProductName}}</td>
    			<td align="center">{{item.CategoryId}}</td>
                <td>{{item.CategoryName}}</td>
                <td>{{item.UnitPrice | currency:"USD":true:"1.2-2"}}</td>
                <td>{{item.StatusDescription}}</td>
                <td>{{item.AvailableSince | date:"MM/dd/yyyy"}}</td>                
    		</tr>
    	</tbody>
    </table>
  2. 用于分页、排序甚至数据过滤的最重要的对象是 `pagingParams`。该对象的定义在 `model-interface.ts` 中

    export interface PagingParams {
        pageSize: number;
        pageNumber: number;
        sortList: Array<SortItem>;
        changeType: any;
    }

    `PagingParams` 的实例在表格宿主组件的 `ngOnInit` 方法中初始化并包含初始值。然后,该对象实例从多个地方更新,包括表格宿主组件及其子结构 `TableMainDirective` 和 `PaginationComponent`,以响应页面上的用户请求操作。

    this.pagingParams = {
        pageSize: pageSize !== undefined ? pageSize : 10,
        pageNumber: 1,
        sortList: Array<SortItem>;
        changeType: TableChange.init
    }

    `changeType` 值定义在 `TableChange` `enum` 中,并根据相应的处理状态进行更新。

    export const enum TableChange {
        init,
        search,
        pageNumber,
        pageSize,
        sorting
    }
  3. 表格宿主组件中的 `onChangeTable` 方法接收 `pagingParams` 中的更改,执行一些任务,然后调用以获取将被加载到表格中的过滤、排序和分页的数据项。

    onChangeTable(params: PagingParams ):any {    
        this.pagingParams = params;
    
        //Perform any needed task.
        - - -
    
        //Call to get data and bind data items to table.
        - - -     
    }
  4. 传递给 `ColumnSortComponent` 类的 `sortBy` 值用于 `column-sort` 标签,在该类中设置了排序的列定义和 CSS 图标。响应用户请求操作而更新的 `pagingParams.sortList` 数组对象中的 `sortItem` 元素中的 `sortBy` 和 `sortDirection` 值也通过 `TableMainDirective` 类中的 `sortChanged` 方法和 `tableChanged` 事件依次发送回表格宿主组件(如果您对代码逻辑的深层细节感兴趣,请参阅 `column-sort.component.ts` 和 `table-main.directives.ts`)。

  5. 对于任何不允许排序的列,可以简单地在 `th` 标签中省略 `column-sort` 标签。

分页组件

分页器是一个单独的组件,其视图模板由详细的 HTML 元素组成。它使用与主表格类似的工作流程,即通过 `@Input` 将 `pagingParams` 对象实例传递给 `PaginationComponent` 类,并通过 `pageChanged` 事件将更改后的对象实例发送回来。表格宿主视图中的代码简单明了

<pagination *ngIf="pagingEnabled"
            [pagingParams]="pagingParams"
            [pagedLength]="pagedLength"                
            (pageChanged)="onChangeTable($event)">
</pagination>

由于页面编号、大小和排序更改之间可能存在代码逻辑关联,`pageChanged` 事件被路由到与主表格的 `tableChanged` 事件相同的目标方法 `onChangeTable`。如果您愿意,可以使用诸如 `onChangePage` 之类的方法来处理分页器。

页面编号的更改和数字按钮的高亮显示来自两个触发源。

  1. 点击任何页面编号。这将直接调用 `PaginationComponent` 中的 `selectPage` 方法。代码流程完全在分页器内部,因此您通常不必在客户端调用逻辑中关注它。

    在视图模板中

    (click)="selectPage(page.number, $event)"

    在 `PaginationComponent` 类中

    selectPage(pageNumber: number, event?: Event): void {
        if (event) {
            //Set change type.
            this.pagingParams.changeType = TableChange.pageNumber;        
    	}
    
        //Update value in base pagingParams.
        this.pagingParams.pageNumber = pageNumber;
    
        //Set pagers.
        this.pages = this.getPages(); 
    
        //Fire event for pageNumber changeType.
        if (params.changeType == TableChange.pageNumber || 
                          params.changeType == TableChange.init)     {            
            this.pageChanged.emit(params); 
        }        
    }
  2. 其他操作,例如数据过滤、排序或页面大小更改。页面编号应被动重置并高亮显示正确的数字按钮。您需要在表格宿主组件中添加代码,并在相应的事件方法(例如 `onTableChange`)或数据访问后的处理逻辑中执行代码。例如,表格宿主组件中的以下代码块通过 `TableMainDirective` 的方法调用 `PaginationComponent` 中的 `selectPage` 方法来更新分页器(如果您有兴趣,请参阅后续部分“重置分页器例程”中的详细信息)。

    //Call to update pager. 
    this.tableMainDirective.updatePagerAfterData(this.pagingParams, this.totalLength);

页面大小的更改是分页器上的另一个用户操作。当从页面大小下拉列表中选择数字时,会触发 `onSizeChange` 事件并遵循相应的处理。

在 `pagination.component.html` 模板的 `select` 标签中

(ngModelChange)="onSizeChange($event)">

在 `PaginationComponent` 类中

onSizeChange(event: any) {
    this.pagingParams.pageSize = event.value;
    //Set change type.
    this.pagingParams.changeType = TableChange.pageSize;
    - - -
    //Emit event for refresh data and pager.
    this.pageChanged.emit(params);
}

您也无需修改 `PaginationComponent` 中的代码。然而,在表格宿主组件的 `pageChanged` 目标方法中,您可能需要调用 `PaginationComponent` 中的 `setPagerForSizeChange` 方法,以及使用已更改的页面大小值进行数据访问处理(如果您有兴趣,请参阅 `pagination.component.ts` 文件中的方法)。

if (this.pagingParams.changeType == TableChange.pageSize) {    
    //Reset pager.            
    this.paginationComponent.setPagerForSizeChange();                       
}

内置的分页器 HTML 模板包含在 `NgExTable` 文件夹中。如果您想用不同的布局和样式更新它,您可以直接编辑内置模板,或在现有的 `NgExTable` 文件夹中用您自己的模板替换它。

搜索组件

严格来说,搜索组件是 `NgExTable` 的独立应用程序单元。我没有直接在列中添加过滤功能,因为在列内数据过滤时选项有限。我更愿意使用 Angular 组件代码实现一个搜索引擎。示例应用程序中的 `SearchComponent` 类及其 HTML 视图模板展示了这些结构和功能如何与 NgExTable 连接,并为分页网格显示执行数据过滤。

  1. 展示了多种输入类型,包括多个下拉选择框和双日期选择器用于日期范围输入。输入将被构建成 JSON 字符串或对象,然后作为数据检索的请求参数对象。这些结构和代码实际上是从 AngularJS 的 搜索面板 迁移到 Angular 搜索组件的。

  2. 在表格宿主视图中,`search` 标签定义如下

    <search *ngIf="searchEnabled" 
            [searchType] = "searchType"
            (searchChanged)="onChangeSearch($event)">
    </search>
  3. `searchChanged` 事件会随着 `searchParams` 对象传递给目标方法而触发。

    //Construct searchParams object with input data.
    - - -
    //Raise event
    this.searchChanged.emit(this.searchParams);
  4. 在表格宿主组件的事件目标方法 `onChangeSearch` 中,进行处理逻辑并发出 AJAX 调用以获取数据结果集。

    onChangeSearch(searchParams: any) {
        this.pagingParams.changeType = TableChange.search;
        this.searchParams = searchParams;
    
        //Convert searchParams to request JSON object.
        - - - 
        //AJAX call to get data result sets.
        - - -
    } 
  5. 与 `PaginationComponent` 不同,您需要负责编写 `SearchComponent` 的所有部分、其视图模板以及表格宿主组件中的相关部分。您还需要处理客户端分页和服务器端分页之间搜索逻辑的一些差异。

  6. 与页面编号、大小和排序的更改一样,搜索的更改也需要在数据访问之前和之后重置分页器。您可以调用后续部分“重置分页器例程”中描述的 `TableMainDirective` 中的方法。

列排序组件

`ColumnSortingComponent` 对象实例为每个启用了排序的列创建一个,方法是在该列的 `<column-sort>` 标签中指定。该组件在列标题上设置排序图标。单击图标将发送升序、降序和无排序操作的排序命令更改。组件类中的 `toggleSort` 方法处理这些操作。

toggleSort() {
    let pThis: any = this;                
    switch (this.sortableItem.sortDirection) {
        case "asc":
            this.sortableItem.sortDirection = "desc";
            break;
        case "desc":
            this.sortableItem.sortDirection = this.toggleWithOriginalDataOrder ? "" : "asc";
            break;
        default:
            //Existing sortDirection is ''.
            this.sortableItem.sortDirection = "asc";
            break;
    }
    - - -
}

通过调用 `TableMainDirective` 类中的 `refreshSortingIcon` 方法,排序图标的样式也随用户操作而改变。

refreshSortingIcon() {
    //For both single and multiple column sorting.
    if (this.sortableItem.sequence == -1) {
        this.sortableItem.sortDirection = '';
    }

    if (this.sortableItem.sortDirection == '') {
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingAscIcon);
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingDescIcon);
        this.renderer.addClass(this.sortIcon.nativeElement, this.config.sortingBaseIcon);
    }
    else if (this.sortableItem.sortDirection == 'asc') {
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingBaseIcon);
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingDescIcon);
        this.renderer.addClass(this.sortIcon.nativeElement, this.config.sortingAscIcon);
    }
    else if (this.sortableItem.sortDirection == 'desc') {
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingBaseIcon);
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingAscIcon);
        this.renderer.addClass(this.sortIcon.nativeElement, this.config.sortingDescIcon);
    }
}

从 Angular 版本 8 开始,NgExTable 支持多列排序功能。NgExTable 库和 Angular 版本 8 或更高版本的演示项目包含处理多列排序逻辑和操作的所有组件和选项。然而,本文仍然侧重于基本数据网格工具的一般工作流程和处理逻辑。本文的配套示例应用程序也仅设置为单列排序类型演示。有关多列排序的详细讨论和功能演示,请参阅另一篇专用文章 多列排序:从 Angular NgExTable 到源数据列表管理

客户端分页工作流程

客户端分页模式将所有数据记录加载到客户端缓存,并根据页面大小设置一次显示一页。在示例应用程序中,`client-paging.component.ts` 及其相关文件和结构展示了此模式如何用于获取和显示过滤、排序和分页后的数据。

  1. AJAX 调用发生在第一次加载所有必需的数据列表项时,不使用搜索组件。演示使用 `local-data-products.json` 文件中的本地数据源(有关详细信息,请参阅 `getData` 方法)。

  2. 调用 `clientPaginationService.processData` 方法,传入 `pagingParams` 对象实例,以从缓存的数据源中获取活动页面所需的数据行,然后将加载的分页数据行设置到表格中。

    let rtn = this.clientPaginationService.processData
                   (this.pagingParams, this.currentDataList);
    this.rows = rtn.dataList;
  3. 当用户执行过滤操作时,过滤条件将传递给 `searchChanged` 事件目标方法,该方法中的逻辑基本重复了上述步骤 #2。分页数据行将根据过滤条件进行刷新。

  4. 分页按钮和标签将根据数据行的更改进行刷新。

  5. 当用户单击另一个页面编号按钮、更改列排序或选择不同的页面大小时,`pagingParams` 对象实例中的更新信息将传递给 `tableChanged` 事件目标方法。该方法中的逻辑会重复类似于上述步骤 #2 - 4 的过程。然后,表格行和分页器将根据新请求再次刷新。

服务器端分页工作流程

对于使用大型或动态增长数据库的应用程序,尤其是企业业务应用程序,服务器端分页模式因其分块数据处理和每次请求传输的特性,是一种实用且最优的解决方案。示例应用程序中的 `server-paging.component.ts` 及其相关文件和结构展示了此模式如何用于获取和显示过滤、排序和分页后的数据。

  1. `pagingParams` 和/或 `searchParams`(具有默认值或更新值)始终被设置并用于每次 AJAX 调用以获取分页数据行。

  2. 在进行任何数据访问调用之前,会构建请求对象 `input`(有关详细信息,请参阅 `getProductListRequest` 方法)。

    let input = this.getProductListRequest();
  3. 在应用程序开始时以及每当用户执行搜索操作、更改页面编号、应用列排序或选择不同的页面大小时,都会执行 AJAX 调用。返回的响应对象 `data` 仅包含活动页面的数据列表以及基于搜索条件数据库中数据行的总计数。示例应用程序可以通过调用 `serverMockDataService.getPagedDataList()` 方法使用模拟服务器数据,该方法从本地数据源中以相同的服务器端分页模式检索数据。以下是条件代码逻辑

    let pThis = this;
    if (ServerPagingDataSource == 'server') {
        let input = this.getProductListRequest();
        this.httpDataService.post(ApiUrlForProductList, input) //Real server data
        .subscribe(data => {
            pThis.processDataReturn(pThis, data);
        },
        - - -     
    }
    else if (ServerPagingDataSource == 'mock') { 
        //Mock server data
        this.serverMockDataService.getPagedDataList(this.searchParams, this.pagingParams)  
        .subscribe(data => {
            pThis.processDataReturn(pThis, data);
        },
        - - - 
    } 
  4. 分页按钮和标签将根据数据行的更改进行刷新。

重置分页器例程

可以通过 `PaginationComponent` 直接重置分页参数和分页器以更改页面编号。然而,对于搜索、排序甚至页面大小更改等其他操作,表格宿主组件端的代码逻辑需要与这些子组件协作,这可能会使客户端调用者的代码变得冗长且重复。为了解决这个问题,并且没有创建额外的 Angular 服务,中心化方法被添加到了 `TableMainDirective` 类中,用于表格宿主组件与不同子组件之间的通信。

在数据访问之前和之后分别需要调用两个方法。

  1. `setPagingParamsBeforeData()` 方法用于根据某些条件和配置值重新设置 `pagingParams`。

    //Method called from table-hosting component before data retrieval.
    setPagingParamsBeforeData(pagingParams?: PagingParams) {
        - - -
        if (pagingParams.changeType == TableChange.search) {
            //Set pageNumber based on config.
            if (this.config.pageNumberWhenSearchChange != -1) {
                pagingParams.pageNumber = this.config.pageNumberWhenSearchChange;
            }
    
            //Set to init sortList if not using current.
            if (this.config.sortingWhenSearchChange != "current") {
                this.resetToInitSortList();
            }
            - - -
        }
        else if (pagingParams.changeType == TableChange.sorting) {
            ////For sorting change, set pageNumber based on config.
            if (this.config.pageNumberWhenSortingChange != -1) {
                pagingParams.pageNumber = this.config.pageNumberWhenSortingChange;
            }
        }
        else if (pagingParams.changeType == TableChange.pageSize) {
            //Set to init sortList if not using current.
            if (this.config.sortingWhenPageSizeChange != "current") {
                this.resetToInitSortList();
            }
            - - -
        }    
    }
  2. `updatePagerAfterData` 方法用于根据获取的数据返回更新 `pageNumber`。

    //Method called from table-hosting component after obtaining data.
    updatePagerAfterData(pagingParams: PagingParams, totalLength: number) {
        if (pagingParams.changeType == TableChange.search) {
            //Data items can only fit page 1.
            if (totalLength && 
             (totalLength <= pagingParams.pageSize && pagingParams.pageNumber != 1)) {
                pagingParams.pageNumber = 1;
            }
            //Call PaginationComponent to set changeType and run selectPage method.
            this.updatePagerForChangeType(pagingParams.changeType, pagingParams.pageNumber);
        }
        else {
            //For sorting or pageSize change, set pageNumber based on config.
            if (pagingParams.changeType == TableChange.sorting ||
                pagingParams.changeType == TableChange.pageSize) {
                this.updatePagerForChangeType
                     (pagingParams.changeType, pagingParams.pageNumber);
            }
        }        
    }
    
    //Generic method.
    private updatePagerForChangeType(changeType: TableChange, pageNumber: number) {
        //Call PaginationComponent to set changeType and run selectPage method.
        let paramesToUpdatePager: ParamsToUpdatePager = {
            changeType: changeType,
            pageNumber: pageNumber
        };
        this.messageService.broadcast
               ('tableMain_paginationComponent', paramesToUpdatePager);
    }

    请注意,`rxjs` 的 `Subject` 和 `Observable` 基础的 `messageService`(`message-transfer.service.ts`)用于从 `TableMainDirective` 访问任何其他组件,例如 `PaginationComponent`。

通常,您只需要在表格宿主组件的两个地方调用这两个方法

  1. 在数据访问之前调用的 `onChangeSearch` 事件处理程序方法

    onChangeSearch(searchParams: any) {
        - - -
        this.tableMainDirective.setPagingParamsBeforeData(this.pagingParams);
        
        //Call for data here.
    } 

    请注意,您不必在 `onChangeTable` 事件处理程序方法中调用此方法。`setPagingParamsBeforeData()` 在 `TableMainDirective` 类本身的表格更改过程中会被内部调用。

  2. 获取数据并知道 `dataset` 的总记录长度后的位置

    //Call library method to update pager. Also need to pass current data length.
    this.tableMainDirective.updatePagerAfterData(this.pagingParams, this.totalLength); 

配置

示例应用程序为使用 `NgExTable` 提供了两个级别的配置。

  • 库级别:`NgExTable/ngex-table.config.ts`
  • 使用者级别:`NgExTableDemo/app.config.ts`

配置项在启动 `AppComponent` 类时合并,方法是将使用者级别的 `TableConfig` 对象分配给库级别的 `NgExTableConfig.base` 对象。如果存在相同的键名,则使用者级别的设置始终优先。

`app.component.ts` 中的代码

this.ngExTableConfig.appConfig = TableConfig;

`ngex-table.config.ts` 中的代码

private _appConfig: any = {};
get appConfig(): any {
    return this._appConfig;
}
set appConfig(v: any) {
    this._appConfig = v;
    this.main = Object.keys(this._appConfig).length ? 
    Object.assign(this.base, this._appConfig) : this.base;
}

以下是使用者级别配置的完整 `TableConfig` 对象。注释行说明了所有配置项的详细信息。通常,如果您愿意,只需要在此文件中更改任何值。您不必修改库级别 `NgExTableConfig` 类中的任何默认设置。

//Remove or comment out item for using default setting.
export const TableConfig: any = {    
    pageSize: 10, /* number: number of rows per page */

    toggleWithOriginalDataOrder: true, /*boolean: true: no order, ascending, 
                                         and descending; false: ascending and descending */

    previousText: "&laquo;", /*string: page previous button label. Could be "PREV" */

    nextText: "&raquo;", /*string: page next button label. Could be "NEXT" */

    paginationMaxBlocks: 5, /* number: maximum number of page number buttons if "..." 
                               shown on both sides */

    paginationMinBlocks: 2, /* number: minimum number of page number buttons if "..." 
                               shown on both sides */

    pageNumberWhenPageSizeChange: -1, /*number: 1: reset to first page when changing page size; 
                                       -1: use current pageNumber */

    pageNumberWhenSortingChange: 1, /*number: 1: reset to first page 
                                      when changing column sorting; 
                                      -1: use current pageNumber */

    sortingWhenPageSizeChange: "current", /*string: "": reset to no order 
                                            when changing page size; 
                                            "current": use current sorting */
    
    //Related to data search (no default setting in library-level, NgExTableConfig base).
    //-------------------------------
    pageNumberWhenSearchChange: 1, /*number: 1: reset to first page when 
                                     search or filtering change; 
                                     -1: use current pageNumber */

    sortingWhenSearchChange: "", /*string: "": reset to no order 
                                   when search or filtering change; 
                                   "current": use current sorting */
    //-------------------------------

    sortingIconCssLib: "fa", /*string, or custom value */  

    sortingAscIcon: "fa-chevron-up", /*string, or custom value */

    sortingDescIcon: "fa-chevron-down", /*string, or custom value */ 

    sortingBaseIcon: "fa-sort", /*string, or custom value */ 

    sortingIconColor: "#c5c5c5" /*string: "#c5c5c5" (default), "#999999", or custom value */    
}

样式指南

自 2019 年 6 月更新以来,NgExTable 使用 `bootstrap.css` 版本 4.3.1 作为网格和分页器的基础样式。它还使用应用程序级别的自定义 `site.css` 来覆盖 `bootstrap.css` 的一些默认样式,或在需要时添加新样式(请参阅 `site.css` 文件中的详细信息)。在示例应用程序中,这些 CSS 文件被导入到 Angular CLI 定义的 `style.css` 文件中。带有可缩放矢量图形(SVG)图标库的样式类代码在构建后被捆绑到 `dist` 目录。

/* You can add global styles to this file, and also import other style files */
@import "~bootstrap/dist/css/bootstrap.css";
@import "~font-awesome/css/font-awesome.css";
@import "assets/site.css";

您可以修改 `site.css`,或添加您自己的全局或局部(组件级别)CSS 文件以满足您的需求。典型的例子是为表格中的交替行设置背景颜色。`bootstrap.css` 设置了默认类和值,如下所示

.table-striped > tbody > tr:nth-of-type(odd) {
    background-color: #f9f9f9;
}

您可以将此类代码复制到 `site.css` 或本地 CSS 文件中,并将“`odd`”替换为“`even`”以将交替颜色应用于奇数行。您可以修改全局或本地 CSS 文件中的 `background-color` 值来更改交替行的不同颜色。

摘要

NgExTable 是一个指令初始化、基于 HTML 模板的 Angular 网格工具。它非常适合业务 Web 应用程序,特别是那些使用 AngularJS ngTable 并计划迁移到 Angular 版本的应用程序。本文的讨论和示例应用程序的演示可以帮助开发者更好地理解 NgExTable 在客户端和服务器端分页模式下的结构和工作流程,以便开发者能够有效地将 NgExTable 整合到自己的项目中。开发者甚至可以添加新的和/或修改现有的结构来满足他们的需求。有关 NgExTable 高级实践的用户案例,请参阅此帖子 使用响应式表单的高级实践进行 Angular 数据 CRUD

历史

  • 2018年 2 月 7 日
    • 原始帖子使用 Angular 5
  • 2018年 12 月 5 日
    • 更新了源代码
    • 升级到 Angular 6 CLI 设置
    • 添加了“重置分页服务”部分,并且
    • 编辑了其他一些部分
  • 2019年 6 月 19 日
    • 更新了 Angular 7 和 Bootstrap 4.3 的源代码(下载 NgExTable_All_Ng7.zip
    • 将 `ResetPageService` 更改为调用 `TableMainDirective` 类中的内置方法
    • 添加了 `ColumnSortingComponent` 来处理排序过程
    • 编辑或重写了许多部分的文本
  • 2019年 9 月 28 日
    • 更新了 Angular 8 的源代码
    • 编辑了某些部分的文本
  • 2020年 12 月 20 日

    • 使用 Angular 11 CLI 更新了源代码
    • 使用 ASP.NET Core 5.0 更新了网站项目
    • 编辑了部分章节的文章文本以进行更新
    • 如果您需要使用早期 Angular 版本 8、9 或 10 运行示例应用程序,您可以下载应用程序的 `package.json` 文件,Package.json_Ng8-9-10.zip,用您想要的版本替换现有应用程序中的 `package.json` 文件,然后根据“设置和运行示例应用程序”部分中的说明进行操作。示例应用程序的 Angular 11 源代码与 Angular 版本 8、9 和 10 完全兼容,没有重大的破坏性更改。
© . All rights reserved.