Angular Grid 일괄 편집 및 거래
IgxGrid의 일괄 편집 기능은 TransactionService
. 따라가다 Transaction Service class hierarchy
주제에 대한 개요를 볼 수 있습니다. igxTransactionService
구현 방법을 자세히 설명합니다.
다음은 그리드 구성 요소에 대해 일괄 편집을 활성화하는 방법에 대한 자세한 예입니다.
最速で機能豊富な Angular Data Grid は、ページング、ソート、フィルタリング、グループ化、PDF および Excel へのエクスポートなどの機能を提供します。究極のアプリ構築エクスペリエンスとデータ操作に必要なすべてが揃っています。
Angular Grid 일괄 편집 및 거래 예
다음 샘플은 그리드에 batchEditing
활성화되어 있고 행 편집이 활성화되어 있는 시나리오를 보여줍니다. 후자는 전체 행 편집이 확인된 후 트랜잭션이 추가되도록 보장합니다.
/* eslint-disable @typescript-eslint/naming-convention */
import { Component, OnInit, ViewChild } from '@angular/core';
import { IgxDialogComponent, IgxGridComponent, Transaction, IgxColumnComponent, IgxCellTemplateDirective, IgxButtonDirective } from 'igniteui-angular';
import { DATA } from '../../data/nwindData';
import { generateRandomInteger } from '../../data/utils';
import { IgxPreventDocumentScrollDirective } from '../../directives/prevent-scroll.directive';
@Component({
selector: 'app-grid-row-edit',
styleUrls: [`grid-batch-editing-sample.component.scss`],
templateUrl: 'grid-batch-editing-sample.component.html',
imports: [IgxGridComponent, IgxPreventDocumentScrollDirective, IgxColumnComponent, IgxCellTemplateDirective, IgxButtonDirective, IgxDialogComponent]
})
export class GridBatchEditingSampleComponent implements OnInit {
@ViewChild('grid', { read: IgxGridComponent, static: true }) public grid: IgxGridComponent;
@ViewChild(IgxDialogComponent, { static: true }) public dialog: IgxDialogComponent;
public data: any[];
public transactionsData: Transaction[] = [];
private addProductId: number;
public ngOnInit(): void {
this.data = DATA;
this.addProductId = this.data.length + 1;
}
public addRow() {
this.grid.addRow({
CategoryID: generateRandomInteger(1, 10),
Discontinued: generateRandomInteger(1, 10) % 2 === 0,
OrderDate: new Date(generateRandomInteger(2000, 2050),
generateRandomInteger(0, 11), generateRandomInteger(1, 25))
.toISOString().slice(0, 10),
ProductID: this.addProductId++,
ProductName: 'Product with index ' + generateRandomInteger(0, 20),
QuantityPerUnit: (generateRandomInteger(1, 10) * 10).toString() + ' pcs.',
ReorderLevel: generateRandomInteger(10, 20),
SupplierID: generateRandomInteger(1, 20),
UnitPrice: generateRandomInteger(10, 1000),
UnitsInStock: generateRandomInteger(1, 100),
UnitsOnOrder: generateRandomInteger(1, 20)
});
}
public deleteRow(id) {
this.grid.deleteRow(id);
}
public undo() {
/* exit edit mode and commit changes */
this.grid.endEdit(true);
this.grid.transactions.undo();
}
public redo() {
/* exit edit mode and commit changes */
this.grid.endEdit(true);
this.grid.transactions.redo();
}
public openCommitDialog() {
this.transactionsData = this.grid.transactions.getAggregatedChanges(true);
this.dialog.open();
}
public commit() {
this.grid.transactions.commit(this.data);
this.dialog.close();
}
public discard() {
this.grid.transactions.clear();
this.dialog.close();
}
public stateFormatter(value: any) {
return JSON.stringify(value);
}
public typeFormatter(value: string) {
return value.toUpperCase();
}
public classFromType(type: string): string {
return `transaction--${type.toLowerCase()}`;
}
}
ts<div class="grid__wrapper">
<igx-grid [igxPreventDocumentScroll]="true" #grid [batchEditing]="true" [data]="data" [primaryKey]="'ProductID'" width="100%" height="500px"
[rowEditable]="true">
<igx-column [editable]="false">
<ng-template igxCell let-cell="cell" let-val>
<button igxButton (click)="deleteRow(cell.id.rowID)"
[disabled]="cell.row.deleted">Delete</button>
</ng-template>
</igx-column>
<igx-column field="ProductID" header="Product ID" [editable]="false"></igx-column>
<igx-column field="ProductName" header="Product Name" [dataType]="'string'"></igx-column>
<igx-column field="UnitPrice" header="Unit Price" [dataType]="'string'"></igx-column>
<igx-column field="UnitsOnOrder" header="Units On Order" dataType="number"></igx-column>
<igx-column field="UnitsInStock" header="Units In Stock" dataType="number"></igx-column>
<igx-column field="QuantityPerUnit" header="Quantity Per Unit" [dataType]="'string'"></igx-column>
<igx-column field="ReorderLevel" header="Reorder Level" dataType="number"></igx-column>
<igx-column field="SupplierID" header="Supplier ID" dataType="number"></igx-column>
<igx-column field="CategoryID" header="Category ID" dataType="number"></igx-column>
<igx-column field="Discontinued" header="Discontinued" [dataType]="'boolean'"></igx-column>
</igx-grid>
<div class="buttons-row">
<button igxButton (click)="addRow()">Add Row</button>
<div class="buttons-wrapper">
<button igxButton [disabled]="!grid.transactions.canUndo" (click)="undo()">Undo</button>
<button igxButton [disabled]="!grid.transactions.canRedo" (click)="redo()">Redo</button>
<button igxButton [disabled]="grid.transactions.getAggregatedChanges(false).length < 1"
(click)="openCommitDialog()">Commit</button>
</div>
</div>
<igx-dialog #dialog title="Submit the following transactions?">
<igx-grid [igxPreventDocumentScroll]="true" #dialogGrid [data]="transactionsData" [rowHeight]="64" [primaryKey]="'id'"
width="600px" height="300px" [emptyGridMessage]="'No available transactions'">
<igx-column field="id" header="ID" [dataType]="'string'" width="100px"></igx-column>
<igx-column field="type" header="Type" width="150px" [sortable]="true">
<ng-template igxCell let-cell="cell" let-val>
<span [class]="classFromType(val)">{{ typeFormatter(val) }}</span>
</ng-template>
</igx-column>
<igx-column field="newValue" header="Value" width="900px">
<ng-template igxCell let-cell="cell" let-val>
<span class="transaction-log">{{ stateFormatter(val) }}</span>
</ng-template>
</igx-column>
</igx-grid>
<div class="buttons-wrapper">
<button igxButton (click)="commit()">Commit</button>
<button igxButton (click)="discard()">Discard</button>
<button igxButton (click)="dialog.close()">Cancel</button>
</div>
</igx-dialog>
</div>
html.grid__wrapper {
margin: 15px;
}
h4 {
text-align: center;
padding-top: 2%;
padding-bottom: 2%;
}
.buttons-row {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 5px;
}
.buttons-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
padding: 10px 0;
}
.transaction--update, .transaction--delete, .transaction--add {
font-weight: 600;
}
.transaction--add {
color: #6b3;
}
.transaction--update {
color: #4a71b9;
}
.transaction--delete {
color: #ee4920;
}
.transaction-log {
word-wrap: none;
}
scss
이 샘플이 마음에 드시나요? 전체 Ignite UI for Angular 툴킷에 액세스하고 몇 분 안에 나만의 앱을 구축해 보세요. 무료로 다운로드하세요.
트랜잭션 상태는 업데이트, 추가, 삭제된 모든 행과 마지막 상태로 구성됩니다.
용법
시작하려면 IgxGridModule
에서 app.module.ts 파일:
// app.module.ts
...
import { IgxGridModule } from 'igniteui-angular';
@NgModule({
...
imports: [..., IgxGridModule],
...
})
export class AppModule {}
typescript
그런 다음 그리드에서 batchEditing
활성화하기만 하면 됩니다.
<igx-grid [data]="data" [batchEditing]="true">
...
</igx-grid>
html
이렇게 하면 igx-grid에 대해 적절한 Transaction
서비스 인스턴스가 제공됩니다. 적절한 TransactionService
TransactionFactory
통해 제공됩니다. 거래 주제에서 이 내부 구현에 대해 자세히 알아볼 수 있습니다.
일괄 편집이 활성화된 후 IgxGrid
바인딩된 데이터 소스와 rowEditable
true로 설정하고 바인딩합니다.
<igx-grid #grid [batchEditing]="true" [data]="data" [primaryKey]="'ProductID'" width="100%" height="500px"
[rowEditable]="true">
...
</igx-grid>
...
<button igxButton [disabled]="!grid.transactions.canUndo" (click)="undo()">Undo</button>
<button igxButton [disabled]="!grid.transactions.canRedo" (click)="redo()">Redo</button>
<button igxButton [disabled]="grid.transactions.getAggregatedChanges(false).length < 1"
(click)="openCommitDialog(dialogGrid)">Commit</button>
...
html
다음 코드는 transactions
API(실행 취소, 다시 실행, 커밋)의 사용법을 보여줍니다.
export class GridBatchEditingSampleComponent {
@ViewChild('grid', { read: IgxGridComponent }) public gridRowEditTransaction: IgxGridComponent;
public undo() {
/* exit edit mode and commit changes */
this.grid.endEdit(true);
this.grid.transactions.undo();
}
public redo() {
/* exit edit mode and commit changes */
this.grid.endEdit(true);
this.grid.transactions.redo()
}
public commit() {
this.grid.transactions.commit(this.data);
this.toggle.close();
}
}
typescript
트랜잭션 API는 편집 종료를 처리하지 않으며 사용자가 직접 처리해야 합니다. 그렇지 않으면 Grid
편집 모드로 유지됩니다. 이를 수행하는 한 가지 방법은 해당 메소드에서 endEdit
호출하는 것입니다.
rowEditable
속성을 비활성화하면 Grid
수정되어 셀 변경 시 트랜잭션이 생성되고 UI에 행 편집 오버레이가 표시되지 않습니다.
일괄 편집 데모를 통한 원격 페이징
/* eslint-disable @typescript-eslint/naming-convention */
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { IgxDialogComponent, IgxGridComponent, Transaction, IgxPaginatorComponent, IgxPaginatorContentDirective, IgxGridToolbarComponent, IgxGridToolbarActionsComponent, IgxButtonDirective, IgxColumnComponent, IgxCellTemplateDirective } from 'igniteui-angular';
import { Observable } from 'rxjs';
import { RemotePagingWithBatchEditingService } from '../../services/remotePagingWithBatchEditing.service';
import { IgxPreventDocumentScrollDirective } from '../../directives/prevent-scroll.directive';
import { AsyncPipe } from '@angular/common';
@Component({
encapsulation: ViewEncapsulation.None,
providers: [RemotePagingWithBatchEditingService],
selector: 'app-remote-paging-batch-editing',
styleUrls: ['./batch-editing-remote-paging.component.scss'],
templateUrl: './batch-editing-remote-paging.component.html',
imports: [IgxGridComponent, IgxPreventDocumentScrollDirective, IgxPaginatorComponent, IgxPaginatorContentDirective, IgxGridToolbarComponent, IgxGridToolbarActionsComponent, IgxButtonDirective, IgxColumnComponent, IgxCellTemplateDirective, IgxDialogComponent, AsyncPipe]
})
export class RemotePagingBatchEditingComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('grid1', { static: true }) public grid1: IgxGridComponent;
@ViewChild(IgxDialogComponent, { static: true }) public dialog: IgxDialogComponent;
public page = 0;
public totalCount = 0;
public data: Observable<any[]>;
public selectOptions = [5, 10, 15, 25, 50];
public transactionsData: Transaction[] = [];
private _perPage = 10;
private _dataLengthSubscriber;
private _recordsOnServer = 0;
private _totalPagesOnServer = 0;
constructor(private remoteService: RemotePagingWithBatchEditingService) {
}
public get perPage(): number {
return this._perPage;
}
public set perPage(val: number) {
this._perPage = val;
this._totalPagesOnServer = Math.floor(this._recordsOnServer / this.perPage);
this.paginate(0);
}
public ngOnInit() {
this.data = this.remoteService.data$;
this._dataLengthSubscriber = this.remoteService.getDataLength().subscribe((data) => {
this.totalCount = data;
this._recordsOnServer = data;
this._totalPagesOnServer = Math.floor(this.totalCount / this.perPage);
});
this.remoteService.getData(0, this.perPage).subscribe(() => {
this.grid1.isLoading = false;
});
}
public ngOnDestroy() {
if (this._dataLengthSubscriber) {
this._dataLengthSubscriber.unsubscribe();
}
}
public ngAfterViewInit() {
this.grid1.isLoading = true;
}
public paginate(page: number) {
this.grid1.isLoading = true;
this.grid1.endEdit(true);
if (page > this._totalPagesOnServer) {
if (this.page !== this._totalPagesOnServer) {
const skipEl = this._totalPagesOnServer * this.perPage;
this.remoteService.getData(skipEl, skipEl + this.perPage);
}
this.grid1.isLoading = false;
this.grid1.paginator.page = page - this._totalPagesOnServer;
this.page = page;
return;
} else if (this.grid1.paginator) {
const newPage = page - this._totalPagesOnServer > -1 ? page - this._totalPagesOnServer : 0;
this.grid1.paginator.page = newPage;
}
const skip = page * this.perPage;
this.remoteService.getData(skip, skip + this.perPage);
this.page = page;
}
public addRow() {
this.totalCount++;
const newID = this.generateRandomInteger(this.totalCount, this.totalCount * 100);
this.grid1.addRow({
ID: newID, ProductName: 'Product Name', QuantityPerUnit: 'Quantity per Unit',
SupplierName: 'Supplier Name', UnitsInStock: 1, Rating: 1
});
}
public deleteRow(rowID) {
const isTransaction = !this.grid1.data.some(d => d.ID === rowID);
if (isTransaction) {
this.totalCount--;
}
this.grid1.deleteRow(rowID);
if (isTransaction && this.grid1.dataView.length === 1) {
this.paginate(this.page - 1);
}
}
public generateRandomInteger(start: number, end: number) {
return Math.floor(Math.random() * (end - start + 1)) + start;
}
public undo() {
this.grid1.transactions.undo();
this.computeTotalCount();
this.preventDisplayingEmptyPages();
}
public redo() {
this.grid1.transactions.redo();
this.computeTotalCount();
this.preventDisplayingEmptyPages();
}
public openCommitDialog() {
this.transactionsData = this.grid1.transactions.getAggregatedChanges(true);
this.dialog.open();
}
public commit() {
this.grid1.isLoading = true;
this.dialog.close();
const aggregatedChanges = this.grid1.transactions.getAggregatedChanges(true);
this.remoteService.processBatch(aggregatedChanges).subscribe({
next: (count: number) => {
this.totalCount = count;
this._recordsOnServer = count;
this.grid1.transactions.commit(this.grid1.data);
this.preventDisplayingEmptyPages();
},
error: err => {
console.log(err);
},
complete: () => {
this.grid1.isLoading = false;
}
});
}
public cancel() {
this.dialog.close();
}
public discard() {
this.grid1.transactions.clear();
this.totalCount = this._recordsOnServer;
this.preventDisplayingEmptyPages();
this.dialog.close();
}
public get hasTransactions(): boolean {
return this.grid1.transactions.getAggregatedChanges(false).length > 0;
}
public stateFormatter(value: string) {
return JSON.stringify(value);
}
public typeFormatter(value: string) {
return value.toUpperCase();
}
public classFromType(type: string): string {
return `transaction--${type.toLowerCase()}`;
}
private preventDisplayingEmptyPages() {
this._totalPagesOnServer = Math.floor(this._recordsOnServer / this.perPage);
const totalPages = Math.floor(this.totalCount / this.perPage);
if (this.page > 0 &&
(this.page > totalPages ||
(this.page === totalPages &&
this.totalCount % this.perPage === 0))) {
this.paginate(totalPages - 1);
}
}
private computeTotalCount() {
this.totalCount = this._recordsOnServer + this.grid1.transactions.getAggregatedChanges(true).filter(rec => rec.type === 'add').length;
}
}
ts<div class="grid__wrapper">
<igx-grid [igxPreventDocumentScroll]="true" #grid1 [batchEditing]="true" [data]="data | async" width="100%" height="580px"
[rowEditable]="true" [primaryKey]="'ID'">
<igx-paginator [perPage]="perPage">
<igx-paginator-content>
<igx-paginator #paginator [totalRecords]="totalCount" [page]="page" [(perPage)]="perPage"
[selectOptions]="selectOptions"
(pageChange)="paginate($event)">
</igx-paginator>
</igx-paginator-content>
</igx-paginator>
<igx-grid-toolbar>
<igx-grid-toolbar-actions>
<button igxButton (click)="addRow()">Add Row</button>
<button igxButton [disabled]="!grid1.transactions.canUndo" (click)="undo()">Undo</button>
<button igxButton [disabled]="!grid1.transactions.canRedo" (click)="redo()">Redo</button>
<button igxButton [disabled]="!hasTransactions" (click)="openCommitDialog()">Commit</button>
</igx-grid-toolbar-actions>
</igx-grid-toolbar>
<igx-column [pinned]="true" [filterable]="false" [editable]="false">
<ng-template igxCell let-cell="cell" let-val>
<button igxButton (click)="deleteRow(cell.id.rowID)" [disabled]="cell.row.deleted">Delete</button>
</ng-template>
</igx-column>
<igx-column field="ID" [editable]="false"></igx-column>
<igx-column field="ProductName"></igx-column>
<igx-column field="QuantityPerUnit"></igx-column>
<igx-column field="SupplierName"></igx-column>
<igx-column field="UnitsInStock"></igx-column>
<igx-column field="Rating"></igx-column>
</igx-grid>
<igx-dialog title="Submit the following transactions?">
<igx-grid [igxPreventDocumentScroll]="true" #dialogGrid [data]="transactionsData" [rowHeight]="64" [primaryKey]="'id'"
width="600px" height="300px" [emptyGridMessage]="'No available transactions'">
<igx-column field="id" header="ID" [dataType]="'string'" width="100px"></igx-column>
<igx-column field="type" header="Type" width="150px" [sortable]="true">
</igx-column>
<igx-column field="newValue" header="Value" width="900px">
<ng-template igxCell let-cell="cell" let-val>
<span class="transaction-log">{{ stateFormatter(val) }}</span>
</ng-template>
</igx-column>
</igx-grid>
<div class="buttons-wrapper">
<button igxButton (click)="commit()">Commit</button>
<button igxButton (click)="discard()">Discard</button>
<button igxButton (click)="cancel()">Cancel</button>
</div>
</igx-dialog>
</div>
html.grid__wrapper {
margin: 0 auto;
padding: 16px;
}
.buttons-row {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 5px;
}
.buttons-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
padding: 10px 0;
}
.transaction--update, .transaction--delete, .transaction--add {
font-weight: 600;
}
.transaction--add {
color: #6b3;
}
.transaction--update {
color: #4a71b9;
}
.transaction--delete {
color: #ee4920;
}
.transaction-log {
word-wrap: none;
}
igx-paginator {
igx-paginator {
padding: 0 !important;
}
}
scss