.NET4.6TypescriptAngularJsVisual Studio 2013ArchitectAjaxBeginnerHTMLIntermediateDevVisual StudioJavascriptWindows.NETASP.NETC#
Angular 4 数据网格(支持排序、过滤和导出 CSV)





5.00/5 (16投票s)
本文档旨在帮助您了解在 Angular 4 中开发的简单数据网格的架构和用法。
Content
- 第 1 部分:在 Visual Studio 2017 中设置 Angular2,基本的 CRUD 应用程序,第三方模态弹出窗口控件
- 第 2 部分:使用 Angular2 管道进行过滤/搜索,全局错误处理,客户端调试
- 第三部分:Angular 2 到 Angular 4,集成 Angular Material UI 组件
- 第 4 部分:Angular 4 数据网格,支持导出到 Excel、排序和过滤
引言
在本文中,我将解释带有排序、格式化和导出 CSV 功能的简单数据网格控件。我假设您也阅读了我之前的 Angular 2 和 Angular 4 文章,如果还没有,请先阅读它们,因为我将基于上一篇文章的项目来添加数据网格控件。
排序和部分格式化逻辑取自以下文章(感谢 Cory Shaw)
背景
这将是一个简单的数据网格控件,它将接收 data
、columns
、buttons
和几个布尔变量来控制数据网格的布局。由于我将继续使用之前文章中的 用户管理
项目,因此如果您是 Angular 2/4 新手,我强烈建议您先阅读它们。
与之前的文章相比,我将不再一步一步地解释开发过程,因为所有概念,例如 Input
、Output
变量、Angular 4 的 If-Else
等,都已在之前的文章中解释过。
开始吧
- 请下载文章中 附件的项目,解压并打开
solution
文件。 - 重新生成
solution
以下载所有 NuGet 和客户端包以用于 Angular,运行项目并转到 用户管理 屏幕。您应该会看到以下屏幕: - 此屏幕与上一篇文章 《Angular 2 到 Angular 4,集成 Angular Material UI 组件》 的屏幕几乎相同,除了 导出到 Excel 按钮和 排序 上/下图标。好消息是这是一个数据网格控件,可用于任何类型的数据,并且可以通过
input
变量轻松处理导出
、添加/编辑/删除
按钮、它们的标题
和搜索
控件的可见性。让我们先了解UserComponents
并查看我们传递给数据控件的变量,然后我们再探讨DataGrid
组件。
UserComponent
//Grid Vars start
columns: any[] = [
{
display: 'First Name',
variable: 'FirstName',
filter: 'text',
},
{
display: 'Last Name',
variable: 'LastName',
filter: 'text'
},
{
display: 'Gender',
variable: 'Gender',
filter: 'text'
},
{
display: 'Date of Birth',
variable: 'DOB',
filter: 'date'
}
];
sorting: any = {
column: 'FirstName',
descending: false
};
hdrbtns: any[] = [];
gridbtns: any[] = [];
initGridButton() {
this.hdrbtns = [
{
title: 'Add',
keys: [''],
action: DBOperation.create,
ishide: this.isREADONLY
}];
this.gridbtns = [
{
title: 'Edit',
keys: ['Id'],
action: DBOperation.update,
ishide: this.isREADONLY
},
{
title: 'X',
keys: ["Id"],
action: DBOperation.delete,
ishide: this.isREADONLY
}
];
}
//Grid Vars end
- 编辑
app->Components->user.component.ts
。 - 您可以看到我们引入了数据网格变量,这些变量不言自明。在
initGridButton
方法中,我们初始化了 Header 和 Grid 按钮,按钮对象具有title
、key(s)
、action
和ishide
属性。如果您有多个键需要使用,请添加逗号分隔的键,例如:keys:['Id','DOB']
。我们为什么要有initGridButton
方法而不是直接赋值,这将在以后的文章中看到。这种方法将帮助我们在从数据库读取数据后管理只读
角色。请自行实验添加更多列、更改排序变量、添加更多 Header 或 Grid 按钮。 - 另一个有趣的方法是
gridaction
,它订阅了click
事件,当您单击任何hdrbtns
或gridbtns
时(请记住Output
变量)就会调用它。它以GridAction
对象作为参数,该对象来自DataGrid
组件,并在click
方法中填充。gridaction.values[0].value
包含第一个键值,如果您有多个键,您可以像gridaction.values[1].value
这样获取值,依此类推。gridaction(gridaction: any): void { switch (gridaction.action) { case DBOperation.create: this.addUser(); break; case DBOperation.update: this.editUser(gridaction.values[0].value); break; case DBOperation.delete: this.deleteUser(gridaction.values[0].value); break; } }
UserComponent 模板
- 编辑
app->Components
文件夹中的 user.component.html。 - 您会看到代码相当简洁,只有一个
data-grid
控件,我们向其传递了在UserComponent
类中定义的所有变量。<data-grid [columns]="columns" [data]="users" [gridbtns]="gridbtns" [hdrbtns]="hdrbtns" [sort]="sorting" [isshowfilter]=true [isExporttoCSV]=true [exportFileName]="exportFileName" [filter] = userfilter (btnclick)="gridaction($event)"> </data-grid>
DataGrid Component
- 既然我们已经了解了传递给 Data Grid 组件的变量,让我们来了解一下数据网格控件。展开
app->Shared
文件夹,您会在其中找到datagrid
文件夹,其中包含数据网格控件的所有组件和样式表。展开此文件夹,然后双击 datagrid.component.ts 文件。 - 第一个
import
语句不言自明,我们已经在之前的文章中学习了Input
、OutPut
和EventEmitter
装饰器。在第二个和第三个import
语句中,我们添加了自定义类DataGridUtil
,该类具有导出到 Excel
功能,以及Format
类,该类具有数据格式函数,例如Date
、Text
和CSV
(逗号分隔)string
。我们将在接下来的步骤中研究这些类。
import { Component, Input, Output, EventEmitter} from '@angular/core';
import {DataGridUtil} from './datagrid.util'
import { Format } from './format';
- 接下来,我们创建了一个
interface
,其中包含操作字符串(例如Add
、Update
、Delete
或我们传递给gridbtns
或hdrbtns
对象的任何内容)。我们将GridAction
接口作为Output
变量发送。如果您查看GridAction
接口,我们会有一个action
字符串和一个(键值对)values
变量,这就是它的工作原理。您将传递gridbtns
集合,其中包含按钮title
和key(s)
名称(例如主键或唯一键)。当用户单击任何记录的Edit
或Delete
按钮时,GridAction
(包含键名和值或键名和值列表,取决于您发送的键数)将被发送回父组件(User Component
),您可以在其中使用它来进行更新或删除操作。这将在接下来的步骤中变得清晰。export interface GridAction { action: string, values: { key: string, value: string }[] }
- 接下来是组件
meta data
,即selector
名称、style sheet
和 htmltemplate
。如果您不喜欢我的简单网格,请随意修改样式表。@Component({ selector: 'data-grid', styleUrls: ['app/shared/datagrid/datagrid.style.css'], templateUrl: 'app/shared/datagrid/datagrid.component.html' })
- 在组件的元数据之后,是实际的类体。有九个 Input 变量用于加载数据、添加操作按钮、启用只读、导出到 Excel 以及显示搜索过滤器选项。
columns: any[]
:包含要显示的列列表,以及title
和format
。data: any[]
:要显示的数据。sort: any
:用于在首次加载时对数据进行排序的列名和顺序(asc/desc)。gridbtns: any[]
:包含网格中按钮列表(在本例中为编辑和删除),以及按钮title
、要关联的key(s)
、hide/show
和action
字符串。无论您在gridbtns
对象中发送什么操作和键字符串值,它都将添加到上面给出的GridAction
中并发送回User Component
,其中键值将用于 CRUD 或任何其他操作。hdrbtns: any[]
:描述与gridbtns
相同,只是它将显示在网格顶部,在本例中为 Add 按钮。isshowfilter: boolean
:控制Search
条的显示/隐藏。isExporttoCSV: boolean
:控制Export to Excel
按钮的显示/隐藏。exportFileName: string
:Export to CSV
文件的名称,后面会附加当前日期和时间。filter: any
:将用于过滤数据的pipe
对象。我们在上一篇文章中学习了pipe
。
- 在
Output
装饰器中,我们定义了一个变量btnclick
,每当gridbtns
或hdrbtns
中的任何按钮被单击时,它都会emit
GridAction
对象。@Output() btnclick: EventEmitter<GridAction> = new EventEmitter<GridAction>();
- 接下来是三个局部变量,用于获取
data
的克隆并处理Search
功能,这是我们在上一篇文章中开发的相同Search
组件。唯一的区别是我们将其作为数据网格的一部分,并将UserFilterPipe
作为输入参数来处理其功能。pdata: any[]; listFilter: string; searchTitle: string = "Search:";
- 接下来,我们实现了 ngOnChanges 事件,该事件在
data
变量加载数据时发生。此数据被分配给局部变量pdata
,因为每次搜索时,我们仍然需要原始数据。如果我们直接对 data 变量应用搜索过滤器,我们就会丢失data
变量中的原始数据。过滤或搜索逻辑定义在criteriaChange
方法中。ngOnChanges(changes: any) { if (JSON.stringify(changes).indexOf("data") != -1) this.pdata = this.data; this.criteriaChange(this.listFilter); }
selectedClass
方法用于控制网格上的上/下图标,每次单击排序图标时都会调用changeSorting
。changeSorting
绑定到每个列,它接受列名,检查网格是否已按同一列排序,如果是,则切换sort
,否则将单击的列名存储在sort
变量中,该变量被发送到orderby pipe
以执行排序逻辑。selectedClass(columnName: string): any { return columnName == this.sort.column ? 'sort-' + this.sort.descending : false; } changeSorting(columnName: string): void { var sort = this.sort; if (sort.column == columnName) { sort.descending = !sort.descending; } else { sort.column = columnName; sort.descending = false; } } convertSorting(): string { return this.sort.descending ? '-' + this.sort.column : this.sort.column; }
- 接下来是
click
方法,当hdrbtns
或gridbtns
列表中的任何按钮被单击时,都会调用该方法。从 HTMLtemplates
传递的参数是被单击的btn
对象和当前row
。在函数体内,我们创建了GridAction
类型变量,从btn
对象中获取所有keys
,从row
对象中搜索key(s) value
,并将它(们)推送到GridAction
的 values(键值对)变量中。您可以为每个按钮关联任意数量的键。我将在接下来的步骤中展示如何发送keys
名称。click(btn: any, row: any): void { let keyds = <GridAction>{}; keyds.action = btn.action; if (row != null) { keyds.values = []; btn.keys.forEach((key: any) => { keyds.values.push({ key: key, value: row[key] }); }); } this.btnclick.emit(keyds); }
- 如果您阅读了我之前的文章,您可能会熟悉
criteriaChange
方法,它用于在网格内搜索。我们通过将原始data
和输入search
字符串传递给输入过滤器变量来显式调用其transform
方法。这就是为什么我创建了局部变量pdata
来保存原始data
变量,希望现在您能理解。criteriaChange(value: any) { if (this.filter != null) { if (value != '[object Event]') { this.listFilter = value; this.pdata = this.filter.transform(this.data, this.listFilter); } } }
- 下一个方法是
exporttoCSV
,它接受data
变量,根据输入的column
变量过滤特定列的值,调用Format
类的transform
方法将数据格式化(例如文本、日期、csv 等),数据导出对象加载完成后,它被发送到DataGridUtil
类的downloadcsv
方法。exporttoCSV() { let exprtcsv: any[] = []; (<any[]>JSON.parse(JSON.stringify(this.data))).forEach(x => { var obj = new Object(); var frmt = new Format(); for (var i = 0; i < this.columns.length; i++) { let transfrmVal = frmt.transform(x[this.columns[i].variable], this.columns[i].filter); obj[this.columns[i].display] = transfrmVal; } exprtcsv.push(obj); } ); DataGridUtil.downloadcsv(exprtcsv, this.exportFileName); }
DataGrid 模板
- 编辑
app->shared->datagrid
文件夹中的datagrid.component.html
。让我们一步一步地理解它。 - 以下代码用于根据
isshowfilter
布尔输入变量和数据加载来显示Search
控件。其余部分与上一篇文章相同,即更改事件等。<div *ngIf="isshowfilter && data"> <search-list [title]="searchTitle" (change)="criteriaChange($event)"></search-list> </div>
- 下一段代码是显示
header
按钮并附加click
事件。我们循环遍历hdrbtns
列表,检查ishide
按钮是否为 false,并将单个hdrbtns
项目作为参数传递,以检查action
。<ng-container>
是一个逻辑容器,可用于对节点进行分组,但不会作为节点渲染到 DOM 树中。<div *ngIf="data" class="add-btn-postion"> <div> <ng-container *ngFor="let hdrbtn of hdrbtns"> <button *ngIf="!hdrbtn.ishide" type="button" class="btn btn-primary" (click)="click(hdrbtn,null)">{{hdrbtn.title}}</button> </ng-container> <button *ngIf="isExporttoCSV && (data!=null && data.length>0)" type="button" class="btn btn-primary" (click)="exporttoCSV()">Export to Excel</button> </div> </div>
- 接下来,整个表格用于数据网格,让我们理解其中的重要部分。
- 我们循环遍历列列表,附加
click
事件,该事件调用changeSorting
方法,并将其作为数据列名称作为参数。然后循环遍历gridbtns
以创建用于操作按钮的空td
(以使 Header 和数据行的td
数量相等)。<tr> <th *ngFor="let column of columns" [class]="selectedClass(column.variable)" (click)="changeSorting(column.variable)"> {{column.display}} </th> <ng-container *ngFor="let btn of gridbtns"> <td *ngIf="!btn.ishide"></td> </ng-container> </tr>
- 在
tbody
块中,我们循环遍历实际数据,并应用orderby
过滤器,该过滤器从convertSorting
方法获取参数。convertSorting
方法从输入sort
变量获取sort column
。在第二次循环中,我们遍历columns
,从每个数据row
中获取单个列的值,同时调用format pipe
。请记住,我们为每一列定义了format
。在第三次循环中,就像 Header 按钮一样,我们循环遍历 Grid 按钮,通过提供当前按钮和当前行(以获取键)作为参数来附加click
事件,并检查ishide
属性以检查是否显示当前按钮。<tr *ngFor="let row of pdata | orderby : convertSorting()"> <td *ngFor="let column of columns"> {{row[column.variable] | format : column.filter}} </td> <ng-container *ngFor="let btn of gridbtns"> <td *ngIf="!btn.ishide"> <button type="button" class="btn btn-primary" (click)="click(btn,row)">{{btn.title}}</button> </td> </ng-container> </tr>
- 我们循环遍历列列表,附加
DataGridUtil Class
- 编辑
app->shared->datagrid
文件夹中的 datagrid.util.ts。 DataGridUtil
包含三个功能,它们非常不言自明,第一个是downloadcsv
,它调用converttoCSV
方法将data
对象转换为 CSV,并调用createFileName
将日期和时间附加到文件名中以使其唯一。此下载功能已在 IE 11、Firefox 和 Chrome 中进行了测试。public static downloadcsv(data: any, exportFileName: string) { var csvData = this.convertToCSV(data); var blob = new Blob([csvData], { type: "text/csv;charset=utf-8;" }); if (navigator.msSaveBlob) { // IE 10+ navigator.msSaveBlob(blob, this.createFileName(exportFileName)) } else { var link = document.createElement("a"); if (link.download !== undefined) { // feature detection // Browsers that support HTML5 download attribute var url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", this.createFileName(exportFileName)); //link.style = "visibility:hidden"; document.body.appendChild(link); link.click(); document.body.removeChild(link); } } } private static convertToCSV(objarray: any) { var array = typeof objarray != 'object' ? JSON.parse(objarray) : objarray; var str = ''; var row = ""; for (var index in objarray[0]) { //Now convert each value to string and comma-separated row += index + ','; } row = row.slice(0, -1); //append Label row with line break str += row + '\r\n'; for (var i = 0; i < array.length; i++) { var line = ''; for (var index in array[i]) { if (line != '') line += ',' line += JSON.stringify(array[i][index]); } str += line + '\r\n'; } return str; } private static createFileName(exportFileName: string): string { var date = new Date(); return (exportFileName + date.toLocaleDateString() + "_" + date.toLocaleTimeString() + '.csv') }
Format Pipe
- 编辑
app->shared->datagrid
文件夹中的 format.cs。 Format
是实现PipeTransform
interface
的pipe
。我们处理text
、date
和CSV
数据。如果您需要,请随意添加更多格式。
export class Format implements PipeTransform {
datePipe: DatePipe = new DatePipe('yMd');
transform(input: any, args: any): any {
if (input == null) return '';
var format = '';
var parsedFloat = 0;
var pipeArgs = args.split(':');
for (var i = 0; i < pipeArgs.length; i++) {
pipeArgs[i] = pipeArgs[i].trim(' ');
}
switch (pipeArgs[0].toLowerCase()) {
case 'text':
return input;
case 'date':
return this.getDate(input);
case 'csv':
if (input.length == 0)
return "";
if (input.length == 1)
return input[0].text;
let finalstr: string = "";
for (let i = 0; i < input.length; i++) {
finalstr = finalstr + input[i].text + ", ";
}
return finalstr.substring(0, finalstr.length - 2);
default:
return input;
}
}
private getDate(date: string): any {
return new Date(date).toLocaleDateString();
}
}
OrderBy Pipe
- 编辑
app->shared->datagrid
并编辑 orderby.ts 文件。 - 我直接从这篇 文章 中获取了这个
pipe
,所以您可以从那里阅读它的描述。
摘要
这是一个非常简单的数据网格,可以帮助您显示带有操作按钮的格式化数据。尽管如此,仍有很大的改进空间,例如级联、PDF 导出、就地编辑(虽然我个人不喜欢)、分页等。还有一件事我真的很想改进,那就是摆脱输入 filter
变量,并使其对任何数据通用,因为我们有足够的输入数据信息,例如 列名
和 格式
。
历史
- 创建于 2017/9/7