Table of contents
Problem statement and goals
High-level user flow (workflow diagram)
Flowchart (runtime behaviour)
Core concepts and UX considerations
JSON model: mapping and template format
Angular architecture and components
Client-side implementation — parsing, mapping, preview, validation
Server-side considerations — validation, transactions, idempotency
Handling large files and streaming import
Audit, error reporting, and reconciliation
Security and compliance considerations
Testing strategy and developer checklist
Sequence diagram (import lifecycle)
Example: production-ready code snippets (Angular + Node)
Conclusion and recommended next steps
1. Problem statement and goals
Enterprises often need to import data from many external systems or ad-hoc Excel/CSV files. Each file can have different column names, different formats, and dirty data. Building a separate uploader for each format is slow and error-prone.
A Universal Data Import Wizard lets users:
upload a CSV/XLSX file,
map file columns to your domain fields (drag/drop or dropdown),
preview mapped rows with transformations,
run validation (client and server),
fix or ignore invalid rows, and
import data safely (transactionally) with audit logs and rollback.
Goals for this article:
Provide a robust Angular-focused implementation plan.
Give concrete code patterns and architecture.
Cover both small and large file strategies.
Give best practices for UX, security and operations.
2. High-level user flow (workflow diagram)
[User opens Wizard] --> [Upload file] --> [Parse file] --> [Auto-map columns]
--> [User adjusts mapping] --> [Preview mapped rows]
--> [Run client validations] --> [User fixes/filters rows]
--> [Submit to server] --> [Server validation & transform]
--> [Import transactionally] --> [Result / Audit report]
3. Flowchart (runtime behaviour)
Start
|
v
Select file?
|
v
Parse file -> Parse success? --No--> Show parse error -> End
|
Yes
|
Auto-detect columns -> Generate suggested mapping
|
User reviews mapping -> Confirm mapping?
| \
Yes No
| |
Preview (first N rows) <- User edits mapping
|
Run client-side validation
|
Any critical errors?
| \
No Yes
| |
Submit User fixes errors -> back to Preview
|
Server receives mapped rows -> server validation
|
Validation pass?
| \
Yes No
| |
Import (transaction) Return validation errors + sample rows
|
Show import summary + audit log
|
End
4. Core concepts and UX considerations
Before jumping into code, design is critical.
1. Mapping UX
Auto-detect columns using header matching (case-insensitive, fuzzy match).
Provide a list of target fields grouped by domain (required vs optional).
Allow drag-and-drop or dropdown to map each source column.
Save mappings as named templates (for repeat imports).
2. Preview and sample size
Show top 100 rows by default; allow sampling from random offsets for larger files.
Show original value, transformed value, and validation status per cell.
Allow inline editing for quick fixes.
3. Validation levels
Soft validation: warnings (e.g., inconsistent casing) — user can proceed.
Hard validation: errors that block import (e.g., missing required field, foreign key not found).
Support row-level and cell-level validations.
4. Transformations
Simple inline transforms: trim, uppercase, date parsing with format, number parse, lookup (map text to id).
Support expression-based transforms but restrict to a safe expression language or pre-approved functions.
5. Error handling & recovery
Allow users to download failed rows as CSV for offline correction.
Offer “import partial data” when errors are limited and user accepts.
6. Reusability
Mapping templates must be exportable/importable.
Provide per-tenant templates if multi-tenant product.
5. JSON model: mapping and template format
Define a compact mapping model you’ll persist:
{
"templateName": "Employee Import v1",
"fileSettings": {
"delimiter": ",",
"hasHeader": true,
"encoding": "utf-8"
},
"mappings": [
{
"source": "First Name",
"target": "employee.firstName",
"transform": "trim",
"required": true
},
{
"source": "DOB",
"target": "employee.dateOfBirth",
"transform": "date:DD/MM/YYYY",
"required": false
},
{
"source": "Manager Email",
"target": "employee.managerId",
"transform": "lookup:users.email->users.id",
"required": false
}
],
"options": {
"skipHeaderRows": 0,
"previewRows": 100
}
}
Notes
targetcan be a dotted path for nested objects.transformis either a named transform (trim) ordate:FORMATorlookup:ENTITY.field->ENTITY.id. Only allow pre-approved transforms.
6. Angular architecture and components
Deploy a modular structure:
ImportWizardModule(lazy-loaded)ImportWizardComponent— host and stepper controlFileUploadComponent— file input, basic validationParsingService— uses PapaParse / SheetJS to parse files client-sideMappingEditorComponent— auto-detect headers, map UI, save templatesPreviewTableComponent— show preview with validation badges and inline editingTransformService— apply transforms safely on client previewImportService— handle uploads and server interactionsValidationService— client-side validators and rules
Prefer standalone components (Angular 17+). Use Signals/messages for reactive state between steps.
7. Client-side implementation — parsing, mapping, preview, validation
7.1 Parsing files (CSV and Excel)
Use papaparse for CSV and xlsx (SheetJS) for Excel. Example parsing service skeleton:
@Injectable({providedIn: 'root'})
export class ParsingService {
parseCsv(file: File, options = {}): Promise<{headers: string[], rows: string[][]}> {
return new Promise((resolve, reject) => {
Papa.parse(file, {
header: false,
dynamicTyping: false,
skipEmptyLines: true,
complete: (res) => {
const rows = res.data as string[][];
const headers = rows.shift() || []; // if has header
resolve({ headers, rows });
},
error: (err) => reject(err)
});
});
}
async parseExcel(file: File, sheetIndex = 0) {
const arrayBuffer = await file.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
const sheetName = workbook.SheetNames[sheetIndex];
const sheet = workbook.Sheets[sheetName];
const json = XLSX.utils.sheet_to_json(sheet, { header: 1 }); // rows as array
const headers = json.shift() || [];
return { headers, rows: json };
}
}

Join the conversation! Your thoughts help the community grow.