Pdatatable Reactive Form array editable grid

UI Components for Angular
Post Reply
brijeshbihani
Posts: 2
Joined: 30 Jun 2018, 22:17

18 Oct 2018, 03:20

Can some please help how we can make the p-table editable using reactive Form group/form array.


Thanks.

PhilHuhn
Posts: 177
Joined: 19 Sep 2018, 02:52
Location: Ann Arbor, Michigan USA
Contact:

18 Oct 2018, 16:13

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

brijeshbihani
Posts: 2
Joined: 30 Jun 2018, 22:17

06 Nov 2018, 03:26

I can achieve formarray using normal table <table> <tr>... but looking an example specifically using p-table.

alexander.kolev
Posts: 1
Joined: 06 May 2020, 10:28

06 May 2020, 19:28

Anyone to share example of using p-table with reactive form (FormArray), I couldn't find any working solution so far.

PhilHuhn
Posts: 177
Joined: 19 Sep 2018, 02:52
Location: Ann Arbor, Michigan USA
Contact:

13 May 2020, 14:18

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:

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:&nbsp;</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:&nbsp;</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:&nbsp;</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 -->
In the following, I have the component, class and service:

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' );
	}
	//
}
// ===========================================================================
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:

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;
}

Post Reply

Return to “PrimeNG”

  • Information
  • Who is online

    Users browsing this forum: No registered users and 7 guests