在我们之前的项目中,我们使用了 primeng 的 p-table。现在我们尝试创建一个自定义的。
所以,我想创建一个类似于https://www.primefaces.org/primeng/#/table
的自定义表格我需要一个带有复选框的表格,我不需要排序、分页、过滤和其他功能。
我搜索创建自定义表格,大多数都无法自定义我们要显示的列。 我的意思是,自定义表格组件已设计为静态或从 api 响应绑定。
即自定义组件看起来像这样。 (我不喜欢这个)
在app.component.ts中
<custom-table [options]="tableOptions">Custom table here</custom-table>
自定义表.ts
<table class="table-striped table-hover custom-table">
<thead>
<tr>
<th class="th-checkbox">
<tri-state-checkbox class="toggle-all" [items]="filteredDataObservable"></tri-state-checkbox>
</th>
<th *ngFor="let column of options.columns" (click)="sortHeaderClick(column.value)" [ngClass]="{ 'sorting': isSorting(column.value), 'sorting_asc': isSortAsc(column.value), 'sorting_desc': isSortDesc(column.value) }">
<span [innerHTML]="column.name"></span>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of filteredData">
<td class="td-checkbox">
<input type="checkbox" [(ngModel)]="row.isSelected">
</td>
<td *ngFor="let column of options.columns">{{getCellValue(row, column)}}</td>
</tr>
</tbody>
</table>
我需要如下所示。我们可以自定义 tbody,tbody 与我们要显示的内容
在app.component.ts中
<custom-table
#table
[value]="holidays"
[(selection)]="selectedItems"
>
<ng-template pTemplate="colgroup" let-columns>
<colgroup>
<col style="width: 100px" />
<col style="width: 150px" />
<col style="width: 100px" />
<col style="width: 120px" />
</colgroup>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th >
<tableHeaderCheckbox></tableHeaderCheckbox>
</th>
<th pSortableColumn="id">
Id <p-sortIcon field="id"></p-sortIcon>
</th>
<th pSortableColumn="title">
Title <p-sortIcon field="title"></p-sortIcon>
</th>
<th pSortableColumn="slug">
Slug <p-sortIcon field="slug"></p-sortIcon>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-data>
<tr>
<td>
<tableCheckbox [value]="data"></tableCheckbox>
</td>
<td>{{ data.id }}</td>
<td>{{ data.title }}</td>
<td>{{ data.slug }}</td>
</tr>
</ng-template>
</custom-table>
我想创建一个自定义TableComponent来像这样绑定。
请帮助我如何做到这一点。有例子吗?
您可以使用 Angular 中的 TemplateRef 机制来做到这一点。您可以使用 @ContentChildren 访问模板。
或者您可以使用指令来访问模板。
首先,您需要一个 Provider 来在模板和表之间进行通信(也适用于其他组件)
import { InjectionToken, TemplateRef } from "@angular/core";
export interface TemplateConsumer{
setTemplate(name: string, template: TemplateRef<unknown>);
}
export const APP_TEMPLATE_CONSUMER_ACCESSOR =
new InjectionToken<ReadonlyArray<TemplateConsumer>>('TemplateConsumerAccessor');
然后您应该创建一个指令来注入接口(来自父提供程序)并调用 setTemplate 函数。
import { Directive, Inject, Input, TemplateRef } from "@angular/core";
import { APP_TEMPLATE_CONSUMER_ACCESSOR, TemplateConsumer } from "./models/TemplateConsumer";
@Directive({
selector: '[appTemplate]'
})
export class TemplateDirective {
constructor(
// Injecting the component instance that provides APP_TEMPLATE_CONSUMER_ACCESSOR
@Inject(APP_TEMPLATE_CONSUMER_ACCESSOR) private readonly templateConsumer: TemplateConsumer,
private readonly templateRef: TemplateRef<unknown>
) { }
private _appTemplate: string;
@Input()
set appTemplate(value: string) {
this._appTemplate = value;
this.templateConsumer.setTemplate(value, this.templateRef);
}
get appTemplate(): string {
return this._appTemplate;
}
}
您应该实现 TemplateConsumer 接口并在表组件中为其提供 APP_TEMPLATE_CONSUMER_ACCESSOR 令牌。
import { Component, forwardRef, Input, TemplateRef } from '@angular/core';
import { APP_TEMPLATE_CONSUMER_ACCESSOR, TemplateConsumer } from '../models/TemplateConsumer';
@Component({
selector: 'app-my-alternate-list',
template: `
<div class="my-list">
<ng-container *ngIf="!!data && data.length > 0 else emptyTemplate">
<div class="list-item" *ngFor="let item of data">
<ng-container *ngTemplateOutlet="itemTemplate;context:{item:item}"></ng-container>
</div>
</ng-container>
</div>
`,
// Providing the component instance as TemplateConsumer
providers: [{
provide: APP_TEMPLATE_CONSUMER_ACCESSOR,
useExisting: forwardRef(() => MyAlternateListComponent)
}]
})
export class MyAlternateListComponent implements TemplateConsumer {
@Input()
data: Array<unknown>;
itemTemplate: TemplateRef<unknown>;
emptyTemplate: TemplateRef<unknown>;
private readonly templateNameSetters = {
item: (templateRef: TemplateRef<unknown>) => {
this.itemTemplate = templateRef;
},
empty: (templateRef: TemplateRef<unknown>) => {
this.emptyTemplate = templateRef;
}
};
setTemplate(name: string, template: TemplateRef<unknown>) {
if (!!this.templateNameSetters[name]) {
this.templateNameSetters[name](template);
} else {
// Optional, Throwing an error can cause usability difficulties in some cases.
throw new Error(`'${name}' template is not supported by '${MyAlternateListComponent.name}'`);
}
}
}
例如,我更喜欢基本列表,但您也可以将其用于表格。 有关 Angular 模板以及 primeng 如何使用这些模板的更多信息,您可以查看我在 Medium 上的帖子。 https://medium.com/javascript-in-plain-english/an-alternative-way-to-use-angular-templates-97becc84e109
<div class="container-fluid mt-5">
<div class="table-responsive shadow">
<table class="table table-bordereless mb-0">
<thead class="table-head">
<tr>
@for (head of thead; track head.displayName) {
<th [class]="head.thClass" [ngClass]="{ 'pointer': head?.sortable }" [style]="head.thStyle">
@switch (head.elementType)
{
<!-- #region Text -->
@case ('text') {
<span [id]="head?.id" (click)="head?.event == 'click'? eventTriggered($event, head.displayName): '' "
[class]="head?.class" [style]="head?.style">
@if (head?.sortable) {
<i class="fa-solid fa-sort"></i>
}
{{head.displayName}}
</span>
}
<!-- #endregion -->
<!-- #region innerHTML -->
@case ('innerHTML') {
<span (click)="head?.event === 'click' ? eventTriggered($event,head.displayName) : ''"
[innerHTML]="head.displayName" [id]="head?.id" [class]="head?.class" [style]="head?.style">
</span>
}
<!-- #endregion -->
<!-- #region input -->
@case ('input') {
<input [type]="head?.inputType" [id]="head?.id" [class]="head?.class" [style]="head?.style"
(click)="head?.event === 'click' ? eventTriggered($event,head.displayName,'click') : ''"
(change)="head?.event === 'change' ? eventTriggered($event,head.displayName,'change') : ''">
<label [for]="head?.id" [class]="head?.class" [innerHTML]="head?.inputLabel"></label>
}
<!-- #endregion -->
<!-- #region icon -->
@case ('icon') {
<button class="btn border-0" [id]="head?.id" [style]="head?.style"
(click)="head.event === 'click' ? eventTriggered($event,head.displayName):''">
<i [class]="head?.iconClass"></i>
</button>
}
@case ('iconWithText') {
<button class="btn border-0" [id]="head?.id" [style]="head?.style"
(click)="head.event === 'click' ? eventTriggered($event,head.displayName):''">
<i [class]="head?.iconClass"></i>
</button>
<span>{{head.iconText}}</span>
}
<!-- #endregion -->
}
</th>
}
</tr>
</thead>
<tbody>
@for (data of dataArr | paginate:{itemsPerPage: pagination.pageSize , currentPage: pagination.page , totalItems:
pagination.totalItems}; track data?.id;) {
<tr>
@for (body of tbody; track $index; ) {
<td [style]="body?.tdStyle" [class]="body.tdClass"
[ngClass]="{'pointer':body?.routerLink || body?.clickFunction}"
(click)="(body?.clickFunction && body.parameter) ? eventTriggered($event,data[body.parameter],'click') : ''"
[routerLink]="body?.routerLink">
@switch (body.elementType) {
<!-- #region Text -->
@case ('text') {
<span [class]="body?.class" [style]="body?.style" [id]="body?.id"
[pTooltip]="data[body.attrName]?.length >=40 ? data[body.attrName] : ''" tooltipPosition="top">
{{data[body.attrName]?.slice(0,40)}}
</span>
@if (data[body.attrName]?.length >= 40) {
<span>...</span>
}
}
<!-- #endregion -->
<!-- #region Serial No -->
@case ('serialNo') {
<span [class]="body?.class" [style]="body?.style" [id]="body?.id">
{{($index + 1 ) }}
</span>
}
<!-- #endregion -->
<!-- #region input -->
@case ('input') {
<input [type]="body?.inputType" [class]="body?.inputClass"
[id]="body.inputId ? 'id-'+data[body.inputId] : 'id'+body.attrName"
(click)="body?.event === 'click' ? eventTriggered($event,(body.parameter ? data[body.parameter]:data[body.attrName]),body.action ? body.action : 'click') : ''"
(change)="body?.event === 'change' ? eventTriggered($event,(body.parameter ? data[body.parameter] : data[body.attrName]),body.action ? body.action : 'change'):''">
@if (body.inputLabel) {
<label [for]="body.inputId ? body.inputId : 'id'+body.attrName">{{body.inputLabel}}</label>
}
}
<!-- #endregion -->
<!-- #region innerHTML -->
@case ('innerHTML') {
<span [innerHTML]="body.attrName ? data[body.attrName] : body?.innerHTML" [class]="body?.class"
[style]="body?.style" [id]="body?.id"
(click)="body?.event === 'click' ? eventTriggered($event,(body.parameter ? data[body.parameter] : data[body.attrName])) : ''">
</span>
}
<!-- #endregion -->
<!-- #region Dropdown -->
@case ('dropdown') {
<div class="dropdown">
<button class="btn p-0 px-3 border-0" data-bs-toggle="dropdown">
<i class="fa-solid fa-ellipsis-vertical"></i>
</button>
<ul class="dropdown-menu dropdown-overflow">
@for (dd of body?.dropdownData; track dd.content) {
<li>
<span class="dropdown-item pointer" [attr.data-bs-toggle]="dd.modelId ? 'modal' : ''"
[attr.data-bs-target]="dd.modelId ? '#'+dd.modelId : ''"
[innerHTML]="(dd.icon ? iconArr[dd['icon']] : '') +' '+dd?.content"
(click)="eventTriggered($event,data[dd.parameter],dd?.icon+(body?.attrName ? '-' +body.attrName : ''))"></span>
</li>
}
</ul>
</div>
}
<!-- #endregion -->
<!-- #region Icon -->
@case ('icon') {
<button class="btn border-0" [id]="body?.id" [style]="body?.style"
[attr.data-bs-toggle]="body.modelId ? 'modal' : ''"
[attr.data-bs-targrt]="body.modelId ? '#'+body.modelId : ''"
(click)="body?.event === 'click' ? eventTriggered($event,body.parameter && data[body.parameter],'click '+body.attrName) : ''"
(dblclick)="body?.event == 'dblclick' ? eventTriggered($event,body.parameter && data[body.parameter],'dblclick'+body.attrName) : ''">
<i [class]="body?.iconClass"></i>
</button>
}
@case ('iconWithText') {
<button class="btn border-0" [id]="body?.id" [style]="body?.style"
[attr.data-bs-toggle]="body.modelId ? 'modal' : ''"
[attr.data-bs-targrt]="body.modelId ? '#'+body.modelId : ''"
(click)="body?.event === 'click' ? eventTriggered($event,body.parameter && data[body.parameter],'click'+body.attrName) : ''"
(dblclick)="body?.event == 'dblclick' ? eventTriggered($event,body.parameter && data[body.parameter],'dblclick'+body.attrName) : ''">
<i [class]="body?.iconClass"></i>
</button>
<span>{{body?.iconText}}</span>
}
<!-- #endregion -->
<!-- #region Select -->
@case ('select') {
<select [class]="body?.class" [style]="body?.style"
[id]="body?.id+'_'+(body.parameter ? data[body.parameter] : '')"
(change)="eventTriggered($event,body.parameter ? data[body.parameter] : '','change')">
@for (option of body?.optionArr; track option) {
<option [value]="body.optionValue ? option[body.optionValue] : option"
[selected]="body.selecterOption && body.optionValue ? data[body.selecterOption] == option[body.optionValue] : false ">
{{body.optionLabel ? option[body.optionLabel] : option}}
</option>
}
</select>
}
<!-- #endregion -->
<!-- #region Conditional -->
@case ('conditional') {
<span [class]="body?.class" [style]="body?.style">
@if (data[body.attrName] == body.condition) {
<span [innerHTML]="body?.trueStatement"></span>
}@else {
<span [innerHTML]="body?.falseStatement"></span>
}
</span>
}
<!-- #endregion -->
}
</td>
}
</tr>
}
</tbody>
</table>
</div>
<div class="table-bottom">
<div class="row align-items-center mt-3 px-3">
<div class="col-lg-3">
<span>
Displaying
{{ ( ( pagination.page - 1 ) * ( pagination.pageSize ) ) + 1 }}
to
{{ ( ( pagination.page - 1 ) * ( pagination.pageSize ) + (pagination.pageSize) >
pagination.totalItems )
? pagination.totalItems
: ( pagination.page - 1 ) * ( pagination.pageSize ) + (pagination.pageSize) }}
of {{ pagination.totalItems }}
</span>
</div>
<div class="col-lg-9 text-end">
<pagination-controls (pageChange)="changePage($event)" previousLabel="" nextLabel="">
</pagination-controls>
</div>
</div>
</div>
</div>
<!-- #region typescript -->
type Thead = {
displayName: string;
sortable?: boolean;
sortItem?: string;
thClass: string | '';
thStyle?: string;
inputClass?: string;
inputId?: string;
iconClass?: string;
class?: string;
id?: string;
style?: string;
// for elements and events
elementType: Exclude<typeElement, 'serialNo' | 'conditional' | 'dropdown'>;
iconText?: string;
inputType?: string;
inputLabel?: string;
event?: typeEvent;
action?: string;
};
type Tbody = {
attrName: string;
tdClass: string | '';
tdStyle?: string;
inputClass?: string;
inputId?: string;
iconClass?: string;
class?: string;
id?: string;
style?: string;
innerHTML?: string;
//for elements and events
elementType: typeElement;
iconText?: string;
inputType?: string;
inputLabel?: string;
event?: typeEvent;
modelId?: string;
//for routers and parameters
routerLink?: string;
clickFunction?: string;
parameter?: string;
action?: string;
//for dropdown
dropdownData?: typeDropdown[];
//for conditional
condition?: unknown;
trueStatement?: unknown;
falseStatement?: unknown;
//for select
optionArr?: any[];
optionValue?: string;
optionLabel?: string;
selecterOption?: string;
};
type typeElement =
| 'input'
| 'icon'
| 'text'
| 'serialNo'
| 'iconWithText'
| 'innerHTML'
| 'dropdown'
| 'conditional'
| 'select';
type typeEvent = 'click' | 'change' | 'input' | 'dblclick';
type TriggeredEvent = { event: Event; action?: string; parameter: string };
type typeDropdown = {
icon?: string;
content: string;
parameter: string;
modelId?: string;
};
type Pagination = {
totalItems: number;
page: number;
pageSize: number;
optimization: boolean;
getPagination?: boolean;
};
type PaginationOutput = {
page: number;
pageSize: number;
};
export { Thead, Tbody, TriggeredEvent, Pagination, PaginationOutput };
this is a custom table i created how but the $index serial number is not working how do i fix it