Batch add data from csv to API server
UI modules: MatProgressBarModule, MatCheckboxModule
```html
<h2 mat-dialog-title align="center">Batch add <b class="text-info">Product</b> records from csv file</h2>
<div style="padding-left: 10px;">
<input type="file" #csvReader name="Upload CSV" id="txtFileUpload" (change)="uploadListener($event)" accept=".csv" />
</div>
<div class="controls" *ngIf="isLoading">
<mat-progress-bar mode="indeterminate" color="primary" aria-label="Loading content"> </mat-progress-bar>
</div>
<hr>
<mat-dialog-content>
<table class="table table-bordered table-striped table-hover" >
<thead class="table-dark">
<tr align="center" class="align-middle center">
<th *ngFor="let title of headerTitle">
{{title}}
</th>
<th class="text-info">Result</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let record of records" scope="row">
<td>{{record.Id}}</td>
<td>{{record.Description}}</td>
<td>
<span class="text-success" *ngIf="record.Result; else failed">
Success
</span>
<ng-template #failed>
<span *ngIf="record.Result == false" class="text-danger">
Failed
</span>
</ng-template>
</td>
</tr>
</tbody>
</table>
</mat-dialog-content>
<div mat-dialog-actions align="center">
<button class="btn btn-block btn-outline-secondary btn-sm" mat-button mat-dialog-close>Cancel</button>
<span> </span> <span> </span>
<button class="btn btn-block btn-outline-warning btn-sm" [disabled]="!submitClicked" mat-button
(click)="onImportClick()">Import</button>
</div>
```
Preview batch data, and display result (success/failed) after connecting to API
// .ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { Subject } from 'rxjs';
import { ProductService } from 'src/app/services/product.service';
@Component({
selector: 'app-product-batch-add',
templateUrl: './product-batch-add.component.html',
styleUrls: ['./product-batch-add.component.scss']
})
export class ProductBatchAddComponent implements OnInit {
dtOptions: any = {};
dtTrigger: Subject<any> = new Subject<any>();
isLoading = false;
submitClicked = false;
public records: any[] = [];
@ViewChild('csvReader') csvReader: any;
headerTitle: any = [];
frm!: FormGroup;
csvRecordsArray: any;
failedArray: any = [];
successedCount = 0;
constructor(
private productService: ProductService,
private toastr: ToastrService,
private dialog: MatDialog,
public router: Router,
public fb: FormBuilder,
) { }
ngOnInit(): void {
}
uploadListener($event: any): void {
let text = [];
let files = $event.srcElement.files;
if (this.isValidCSVFile(files[0])) {
this.isLoading = true;
let input = $event.target;
let reader = new FileReader();
reader.readAsText(input.files[0]);
reader.onload = () => {
let csvData = reader.result;
this.csvRecordsArray = (<string>csvData).split(/\r\n|\n/);
let headerRow = this.getHeaderArray(this.csvRecordsArray);
this.records = this.getDataRecordsArrayFromCSVFile(this.csvRecordsArray, headerRow.length);
this.isLoading = false;
if (this.headerTitle.length > 0 && this.records.length > 0)
this.submitClicked = true;
};
reader.onerror = function () {
console.log('error is occured while reading file!');
};
} else {
alert("Please import valid .csv file.");
this.fileReset();
}
}
getDataRecordsArrayFromCSVFile(csvRecordsArray: any, headerLength: any) {
let csvArray: any = [];
for (let i = 1; i < csvRecordsArray.length; i++) {
let currentRecord = (<string>csvRecordsArray[i]).split(',');
if (currentRecord.length == headerLength) {
let csvRecord: any = []; // = new Product();
csvRecord.Id = Number(currentRecord[0].trim());
csvRecord.Description = currentRecord[1].trim();
csvRecord.Result = null;
csvArray.push(csvRecord);
}
}
return csvArray;
}
isValidCSVFile(file: any) {
return file.name.endsWith(".csv");
}
getHeaderArray(csvRecordsArr: any) {
let headers = (<string>csvRecordsArr[0]).split(',');
let headerArray: any = [];
for (let j = 0; j < headers.length; j++) {
headerArray.push(headers[j]);
if (headers[j] != '')
this.headerTitle.push(headers[j]);
}
return headerArray;
}
fileReset() {
this.csvReader.nativeElement.value = "";
this.records = [];
}
//addBatchProducts
async onImportClick() {
this.isLoading = true;
this.productService.addBatchProducts(this.records)
.subscribe({
next: data => {
if (data.status == 200) {
let newRecords: any[] = [];
let failedList = data.body;
this.records.forEach(element => {
let csvRecord: any = [];
csvRecord.Id = Number(element.Id);
csvRecord.Description = element.Description;
if (failedList.indexOf(element.Description) < 0)
csvRecord.Result = true;
else
csvRecord.Result = false;
newRecords.push(csvRecord);
});
//order by id
// this.newRecords.sort((a, b) => (a.Id > b.Id) ? 1 : ((b.Id > a.Id) ? -1 : 0));
this.records = newRecords;
this.isLoading = false;
let failedLength = failedList.length;
if (failedLength == 0)
this.toastr.success('All new records (' + (this.records.length).toString() + ') have added successfully !', 'Success');
else if (this.records.length != failedLength)
this.toastr.info((this.records.length - failedLength).toString() + '/' + (failedLength).toString() + ' records have been successfully added !', 'Success');
else
this.toastr.warning('No records have been added !', 'Warning');
this.submitClicked = false;
this.reload(this.router.url);
}
},
error: error => {
console.error('There was an error in update !', error);
this.toastr.error('Add product fail !', 'Error')
}
})
}
async reload(url: string): Promise<boolean> {
await this.router.navigateByUrl('weighing', { skipLocationChange: true });
return this.router.navigateByUrl(url);
}
}
```
Convert to JSON and post to API
// Service
```typescript
addBatchProducts(batchData: any): Observable<any> {
// var formData = this.buildFormData(formGroupData);
let endPoint = this.rootURL + 'api/product/batch/create';
let jsonOption = { 'content-type': 'application/json', 'charset': 'utf-8' };
let header = new HttpHeaders(jsonOption);
//to JSON -- use FromBody on .NET API
var jsonData: any = [];
// Iterate over each inner array
for (var i = 0; i < batchData.length; i++) {
var innerArray = batchData[i];
// Convert the JSON array to a string
var obj = Object.assign({}, innerArray);
// Add the object to the JSON array
jsonData.push(obj);
}
return this.httpClient
.post<HttpResponse<Object>>(endPoint, jsonData, { observe: 'response', headers: header })
.pipe((resp) => resp);
};
```
API add record, return failed record
// API
[HttpPost]
[Route("api/product/batch/create")]
public async Task<IActionResult> CreateUpdateProducts([FromBody] List<ProductViewModel> productVMs)
{
List<string> failedArray = new List<string>();
//Post JSON input using FromBody
if (!ModelState.IsValid)
{
_logger.LogInformation($"Model invalid!");
return BadRequest(ModelState);
}
try
{
foreach (var productVM in productVMs)
{
// check Product name is unique
string productName = productVM.Description;
var oldRecord = await _repository.Find(i => i.Description == productVM.Description);
if (oldRecord != null)
{
//update
int productId = (int)oldRecord.Id;
Product product = new Product();
product = _mapper.Map(productVM, oldRecord);
product.Id = productId;
_repository.Update(product);
failedArray.Add(oldRecord.Description); // only for no other columns
}
else
{
//insert
Product product = new Product();
product = _mapper.Map(productVM, product);
product.Id = 0;
await _repository.AddAsync(product);
}
var result = await _unitOfWork.SaveAsyc();
if (result != 1)
{
_logger.LogInformation($"Product Name: {productName} can not be created");
failedArray.Add(oldRecord.Description);
}
}
return Ok(failedArray);
}
catch (Exception ex)
{
_logger.LogInformation($"Exception: " + ex.Message);
}
_logger.LogInformation($"Could not create product.");
return
BadRequest("Could not create product");
}
Last updated