Can some please help how we can make the p-table editable using reactive Form group/form array.
Thanks.
Pdatatable Reactive Form array editable grid
I found the following article, but it does not address using a grid.
https://kimsereyblog.blogspot.com/2017/ ... gular.html
and source found here
https://github.com/Kimserey/ng-samples/ ... playground
https://kimsereyblog.blogspot.com/2017/ ... gular.html
and source found here
https://github.com/Kimserey/ng-samples/ ... playground
-
- Posts: 2
- Joined: 30 Jun 2018, 22:17
I can achieve formarray using normal table <table> <tr>... but looking an example specifically using p-table.
-
- Posts: 1
- Joined: 06 May 2020, 10:28
Anyone to share example of using p-table with reactive form (FormArray), I couldn't find any working solution so far.
I found this example using the Car class from the PrimeNG showcase:
https://danieleyassu.com/how-to-add-a-n ... g-p-table/
The example has a number of problems, so I went a different approach. Specifically, resulting from canceling and or edit errors.
This is my approach. I placed the add data in the footer. I have a simple three column example:
In the following, I have the component, class and service:
You can place the add line in the header. I had to add the following CSS because normally the header has a th not a td:
https://danieleyassu.com/how-to-add-a-n ... g-p-table/
The example has a number of problems, so I went a different approach. Specifically, resulting from canceling and or edit errors.
This is my approach. I placed the add data in the footer. I have a simple three column example:
Code: Select all
<!-- NoteType-Grid.component.html -->
<div class='ui-grid ui-grid-responsive ui-fluid'>
<div class='nsg-content'>
<p-table #dt id='notetypes-grid' [value]='notetypes' [totalRecords]='totalRecords'
expandableRows='true' dataKey='NoteTypeId' editMode='row'
[rows]='5' [paginator]='true' [rowsPerPageOptions]='[5,10,50]'>
<ng-template pTemplate='header'>
<tr>
<th style='width: 40px;'></th>
<th style='width: 45px;'>
<button pButton type='button' (click)='onAddRowShow()' icon='pi pi-plus' class='ui-button-primary'></button>
</th>
<th style='width: 80px;' [pSortableColumn]='"NoteTypeId"'>
Id
<p-sortIcon [field]='"NoteTypeId"'></p-sortIcon>
</th>
<th style='width: 250px;' [pSortableColumn]='"NoteTypeDesc"'>
Note Type Description
<p-sortIcon [field]='"NoteTypeDesc"'></p-sortIcon>
</th>
<th style='width: 82px;' [pSortableColumn]='"NoteTypeShortDesc"'>
Short Description
<p-sortIcon [field]='"NoteTypeShortDesc"'></p-sortIcon>
</th>
<th style='width: 45px;'></th>
</tr>
</ng-template>
<ng-template pTemplate='footer'>
<tr *ngIf='addRowFooterShow'>
<td style='width: 40px;'></td>
<td style='width: 45px;'>
<button pButton type="button" icon="pi pi-check" class="ui-button-success" style="margin-right: .5em" (click)="onAddRowSave()"></button>
<button pButton type="button" icon="pi pi-times" class="ui-button-danger" (click)="onAddRowCancel()"></button>
</td>
<td style='width: 80px;'>
<div class='ui-grid-col-7 ui-inputtext'>
<input type='number' id='NoteTypeId' name='NoteTypeId' required maxlength='15' size='8'
#NoteTypeId='ngModel' [(ngModel)]='addRow.NoteTypeId' disabled />
</div>
</td>
<td style='width: 250px;'>
<div class='ui-grid-col-7 ui-inputtext'>
<input type='text' id='NoteTypeDesc' name='NoteTypeDesc' required maxlength='50' size='50'
#NoteTypeDesc='ngModel' [(ngModel)]='addRow.NoteTypeDesc'
placeholder='Note Type Desc...' />
</div>
<div class='ui-grid-col-2 nsg-alert-color' *ngIf='NoteTypeDesc.invalid && NoteTypeDesc.touched'>Required</div>
</td>
<td style='width: 82px;'>
<div class='ui-grid-col-7 ui-inputtext'>
<input type='text' id='NoteTypeShortDesc' name='NoteTypeShortDesc' required maxlength='8' size='9'
#NoteTypeShortDesc='ngModel' [(ngModel)]='addRow.NoteTypeShortDesc'
placeholder='Note Type Short Desc...' />
</div>
<div class='ui-grid-col-2 nsg-alert-color' *ngIf='NoteTypeShortDesc.invalid && NoteTypeShortDesc.touched'>Required</div>
</td>
<td style='width: 45px;'></td>
</tr>
</ng-template>
<ng-template pTemplate='body' let-rowData let-editing='editing' let-ri='rowIndex' let-columns='columns' let-expanded='expanded'>
<tr [pEditableRow]='rowData'>
<td>
<a href='#' [pRowToggler]='rowData'>
<i [ngClass]='expanded ? "pi pi-fw pi-chevron-circle-down" : "pi pi-pw pi-chevron-circle-right"' style='font-size: 1.25em'></i>
</a>
</td>
<td>
<button *ngIf="!editing" pButton type="button" pInitEditableRow icon="pi pi-pencil" class="ui-button-info" (click)="onRowEditInit(rowData)"></button>
<button *ngIf="editing" pButton type="button" pSaveEditableRow icon="pi pi-check" class="ui-button-success" style="margin-right: .5em" (click)="onRowEditSave(rowData)"></button>
<button *ngIf="editing" pButton type="button" pCancelEditableRow icon="pi pi-times" class="ui-button-danger" (click)="onRowEditCancel(rowData, ri)"></button>
</td>
<td>{{rowData.NoteTypeId}}</td>
<!-- editable data -->
<td>
<p-cellEditor>
<ng-template pTemplate="input">
<input pInputText type="text" [(ngModel)]="rowData.NoteTypeDesc">
</ng-template>
<ng-template pTemplate="output">
{{rowData.NoteTypeDesc}}
</ng-template>
</p-cellEditor>
</td>
<td>
<p-cellEditor>
<ng-template pTemplate="input">
<input pInputText type="text" [(ngModel)]="rowData.NoteTypeShortDesc">
</ng-template>
<ng-template pTemplate="output">
{{rowData.NoteTypeShortDesc}}
</ng-template>
</p-cellEditor>
</td>
<!-- -->
<td>
<button (click)="deleteItemClicked( rowData )" pButton type="button" icon="pi pi-trash" class="ui-button-danger"></button>
</td>
</tr>
</ng-template>
<ng-template let-notetype pTemplate='rowexpansion'>
<tr><td [attr.colspan]='6'>
<div class='ui-grid ui-grid-responsive ui-fluid'>
<div class='ui-grid-row ui-inputgroup'>
<div class='ui-grid-col-3 nsg-primary-color nsg-text-right'><label for='NoteTypeId'>Note Type Id: </label></div>
<div class='ui-grid-col-9' id='NoteTypeId'>{{notetype.NoteTypeId}}</div>
</div>
<div class='ui-grid-row ui-inputgroup'>
<div class='ui-grid-col-3 nsg-primary-color nsg-text-right'><label for='NoteTypeDesc'>Note Type Desc: </label></div>
<div class='ui-grid-col-9' id='NoteTypeDesc'>{{notetype.NoteTypeDesc}}</div>
</div>
<div class='ui-grid-row ui-inputgroup'>
<div class='ui-grid-col-3 nsg-primary-color nsg-text-right'><label for='NoteTypeShortDesc'>Note Type Short Desc: </label></div>
<div class='ui-grid-col-9' id='NoteTypeShortDesc'>{{notetype.NoteTypeShortDesc}}</div>
</div>
</div>
</td><tr>
</ng-template>
</p-table>
</div>
</div>
<!-- modal comfirmation popup -->
<p-confirmDialog header='Delete Confirmation' icon='pi pi-trash'></p-confirmDialog>
<!-- modal edit window -->
<!-- End of notetype.grid.component.html -->
Code: Select all
// ===========================================================================
// File: notetype-grid.component.ts
import { Component, OnInit, OnDestroy, Input, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Subscription } from 'rxjs';
//
import { Table, TableModule } from 'primeng/table';
import { ConfirmDialog } from 'primeng/confirmdialog';
import { ConfirmationService } from 'primeng/api';
//
@Component({
selector: 'app-notetype-grid',
templateUrl: './note-type-grid.component.html'
})
export class NoteTypeGridComponent implements OnInit, OnDestroy {
//
// --------------------------------------------------------------------
// Data declaration.
// Window/dialog communication (also see closeWin event)
//
private codeName: string = 'notetype-grid';
private logLevel: number;
private getAllSubscription: Subscription | undefined;
private createSubscription: Subscription | undefined;
private updateSubscription: Subscription | undefined;
private deleteSubscription: Subscription | undefined;
// Local variables
addRowFooterShow: boolean = false;
addRow: NoteType = undefined;
notetypes: NoteType[];
totalRecords: number = 0;
id: number;
@ViewChild('dt', { static: true }) dt: Table;
//
constructor(
private _data: NoteTypeService,
private _confirmationService: ConfirmationService ) { }
//
// On component initialization, get all data from the data service.
//
ngOnInit() {
// load all records
this.addRow = this._data.emptyNoteType();
this.getAllNoteTypes();
}
//
ngOnDestroy() {
if (this.getAllSubscription) {
this.getAllSubscription.unsubscribe();
}
//
if (this.createSubscription) {
this.createSubscription.unsubscribe();
}
if (this.updateSubscription) {
this.updateSubscription.unsubscribe();
}
if (this.deleteSubscription) {
this.deleteSubscription.unsubscribe();
}
}
//
// --------------------------------------------------------------------
// Events:
//
onRowEditInit(item: NoteType) {
this.id = item.NoteTypeId;
}
//
onRowEditSave(item: NoteType) {
const errMsgs = this._data.validate(item, true);
if( errMsgs.length === 0) {
this.updateSubscription =
this._data.updateNoteType( item )
.subscribe( () => {
console.log( `${this.codeName}: Updated ${item.NoteTypeId}.` );
},
error => this.serviceErrorHandler(
`${this.codeName} Update`, error ));
} else {
console.log( errMsgs );
}
}
//
onRowEditCancel(item: NoteType, index: number) {
console.log( `${this.codeName}.onRowEditCancel: ${index}` );
this.id = -1;
}
//
// Confirm component (delete)
//
deleteItemClicked( item: NoteType ): boolean {
this.id = item.NoteTypeId;
console.log( this.id );
this._confirmationService.confirm({
message: `Are you sure you want to delete ${this.id}?`,
accept: () => {
console.log( `${this.codeName}: User's response: true` );
this.deleteItem( );
},
reject: () => {
console.log( `${this.codeName}: User's dismissed.` );
}
});
return false;
}
//
// --------------------------------------------------------------------
// File access
// getAll, delete & serviceErrorHandler
//
getAllNoteTypes( ): void {
this.getAllSubscription =
this._data.getNoteTypes().subscribe((notetypeData) => {
this.notetypes = notetypeData;
this.totalRecords = this.notetypes.length;
if( this.id !== 0 ) {
const editId: number = this.id;
const item: NoteType[] = this.notetypes.filter( el => {
return el.NoteTypeId === editId;
} );
console.log( item );
}
}, ( error ) =>
this.serviceErrorHandler(
`${this.codeName}: Get All`, error ));
}
//
// Footer add row events
//
onAddRowShow(): void {
// show the footer add row
console.log( `addRowShow, entering: ${this.addRowFooterShow}` );
this.addRow = this._data.emptyNoteType();
this.addRowFooterShow = true;
console.log( `${this.codeName}.addRowShow, exiting: ${this.addRowFooterShow}` );
}
//
onAddRowSave(): void {
const errMsgs = this._data.validate(this.addRow, true);
if( errMsgs.length === 0) {
this.createSubscription =
this._data.createNoteType( this.addRow )
.subscribe( () => {
console.log( `${this.codeName}.onAddRowSave: id: ${this.addRow.NoteTypeId}` );
this.addRowFooterShow = false;
// I shouldn't have to do this!
this.dt.value = [...this.dt.value, this.addRow];
},
error => this.serviceErrorHandler(
`${this.codeName} Create`, error ));
} else {
console.log( errMsgs );
}
console.log( `${this.codeName}.addRowSave, exiting: ${this.addRowFooterShow}` );
}
//
onAddRowCancel(): void {
// collapse the footer add row
this.addRowFooterShow = false;
console.log( `${this.codeName}.addRowCancel, exiting: ${this.addRowFooterShow}` );
}
//
// Call delete data service,
// if successful then delete the row from array
//
deleteItem( ): boolean {
const delId: number = this.id;
if( this.id !== 0 ) {
this.deleteSubscription =
this._data.deleteNoteType( delId )
.subscribe(
() => {
this.notetypes = this.notetypes.filter( el => {
return el.NoteTypeId !== delId;
});
console.log(`${this.codeName}: Deleted: ${delId}`);
},
error => this.serviceErrorHandler(
`${this.codeName}: Delete`, error ));
}
return false;
}
//
// Handle an error from the data service.
//
serviceErrorHandler( where: string, error: string ) {
console.error( error );
}
//
}
// End of notetype-grid.component.ts
// ===========================================================================
// ===========================================================================
// File: NoteType.ts
// define the interface(INoteType/class(NoteType)
export interface INoteType {
NoteTypeId: number;
NoteTypeDesc: string;
NoteTypeShortDesc: string;
}
// export
class NoteType implements INoteType {
// using short-hand declaration...
constructor(
public NoteTypeId: number,
public NoteTypeDesc: string,
public NoteTypeShortDesc: string
) { }
}
// ===========================================================================
// ===========================================================================
// File: NoteType.service.ts
// Service for NoteType class
import { Injectable } from '@angular/core';
import { HttpClient, HttpRequest, HttpResponse, HttpErrorResponse } from '@angular/common/http';
//
import { Observable, of, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
//
@Injectable( { providedIn: 'root' } )
export class NoteTypeService {
//
// --------------------------------------------------------------------
// Data declaration.
//
codeName: string;
url: string = '';
fakeDatum: INoteType[] = [
new NoteType( 1, 'Long description for 1', 'Short 1' ),
new NoteType( 2, 'Long description for 2', 'Short 2' ),
new NoteType( 3, 'Long description for 3', 'Short 3' ),
new NoteType( 4, 'Long description for 4', 'Short 4' ),
new NoteType( 5, 'Long description for 5', 'Short 5' ),
new NoteType( 6, 'Long description for 6', 'Short 6' )
];
//
// Service constructor, inject http service.
//
constructor(
private http: HttpClient ) {
this.codeName = 'notetype.service';
this.url = 'http://localhost:9112/api/' + 'NoteType';
}
//
// Single place to create a new NoteType.
//
emptyNoteType( ): INoteType {
return new NoteType(
0,'',''
);
}
//
// Class validation rules.
//
validate( model: INoteType, add: boolean ): string[] {
let errMsgs: string[] = [];
//
if( model.NoteTypeId === undefined || model.NoteTypeId === null ) {
errMsgs.push( `'Note Type Id' is required.` );
}
if( model.NoteTypeId > 2147483647 ) {
errMsgs.push( `'Note Type Id' is too large, over: 2147483647` );
}
if( model.NoteTypeDesc.length === 0 || model.NoteTypeDesc === undefined ) {
errMsgs.push( `'Note Type Desc' is required.` );
}
if( model.NoteTypeDesc.length > 50 ) {
errMsgs.push( `'Note Type Desc' max length of 50.`);
}
if( model.NoteTypeShortDesc.length === 0 || model.NoteTypeShortDesc === undefined ) {
errMsgs.push( `'Note Type Short Desc' is required.` );
}
if( model.NoteTypeShortDesc.length > 8 ) {
errMsgs.push( `'Note Type Short Desc' max length of 8.` );
}
//
console.log( `${this.codeName}.validate, error count: ${errMsgs.length}` );
return errMsgs;
}
//
// CRUD (Create/Read/Update/Delete)
// Read (get) all NoteType
//
getNoteTypes( ): Observable<INoteType[]> {
const urlPath: string = `${this.url}/`;
return of( this.fakeDatum );
}
//
// Read (get) NoteType with id
//
getNoteType( NoteTypeId: number ): Observable<INoteType> {
const urlPath: string = this.url + '/' + String( NoteTypeId );
const noteType: NoteType =
this.fakeDatum.find( t => t.NoteTypeId === NoteTypeId );
if( noteType !== undefined )
return of( noteType );
const response = new HttpErrorResponse( { status: 404, statusText: 'Fake Error' } );
throwError( response );
}
//
// Create (post) NoteType
//
createNoteType( notetype: INoteType ) {
const urlPath: string = `${this.url}/`;
const response = new HttpResponse( { status: 201, statusText: 'OK' } );
const maxId: NoteType = this.fakeDatum.reduce((p, c) => p.NoteTypeId > c.NoteTypeId ? p : c);
notetype.NoteTypeId = maxId.NoteTypeId + 1;
this.fakeDatum = [...this.fakeDatum, notetype];
// console.log(this.fakeDatum);
return of( response );
}
//
// Update (put) NoteType
//
updateNoteType( notetype: INoteType ) {
const urlPath: string = this.url + '/' + String( notetype.NoteTypeId );
const _nt: NoteType =
this.fakeDatum.find( t => t.NoteTypeId === notetype.NoteTypeId );
_nt.NoteTypeDesc = notetype.NoteTypeDesc;
_nt.NoteTypeShortDesc = notetype.NoteTypeShortDesc;
const response = new HttpResponse( { status: 201, statusText: 'OK' } );
return of( response );
}
//
// Delete (delete) NoteType with id
//
deleteNoteType( NoteTypeId: number ) {
const urlPath: string = this.url + '/' + String( NoteTypeId );
const noteType: NoteType =
this.fakeDatum.find( t => t.NoteTypeId === NoteTypeId );
if( noteType !== undefined ) {
this.fakeDatum =
this.fakeDatum.filter( t => t.NoteTypeId !== NoteTypeId );
const response = new HttpResponse( { status: 201, statusText: 'OK' } );
return of( response );
}
const errResponse = new HttpErrorResponse( { status: 404, statusText: 'Fake Error' } );
throwError( errResponse );
}
//
// General error handler, should throw a string
//
handleError( error: any ) {
if ( error instanceof HttpErrorResponse ) {
console.error(
`${this.codeName}.handleError: ${JSON.stringify(error)}` );
return throwError( error.statusText || 'Service error' );
}
console.error(
`${this.codeName}.handleError: ${error}` );
return throwError( error.toString() || 'Service error' );
}
//
}
// ===========================================================================
Code: Select all
body .ui-table .ui-table-thead > tr > td {
padding: 0.571em 0.857em;
border: 1px solid #c8c8c8;
font-weight: 700;
color: #333333;
background-color: #ffffff;
}
-
- Information
-
Who is online
Users browsing this forum: No registered users and 7 guests