Why Dromo?

The CSV importer built for Angular

Drop a CSV and Excel importer into your Angular app. Works with standalone components and NgModules, AI column mapping, inline validation, 30+ languages.

View docs
import { DromoUploaderModule } from "dromo-uploader-angular";

@NgModule({
  imports: [BrowserModule, DromoUploaderModule],
})
export class AppModule {}

Add a CSV importer to Angular in 10 minutes

Angular apps use the framework-agnostic JavaScript SDK, wrapped in a service so the rest of your application never touches the uploader directly.

npm install dromo-uploader-js

Define the schema and the service together. The service constructs an uploader on demand, opens it, and resolves a promise when the user finishes. Running the callback through NgZone keeps change detection predictable, which matters more than it sounds and is covered in the gotchas below.

// src/app/import/dromo-import.service.ts
import { Injectable, NgZone, inject } from "@angular/core";
import DromoUploader, { type IDeveloperField } from "dromo-uploader-js";
import { environment } from "../../environments/environment";

@Injectable({ providedIn: "root" })
export class DromoImportService {
  private zone = inject(NgZone);

  private readonly employeeFields: IDeveloperField[] = [
    { label: "Employee ID", key: "employee_id", type: "string", validators: [{ validate: "required" }, { validate: "unique" }] },
    { label: "Work email", key: "work_email", type: "email", validators: [{ validate: "required" }] },
    { label: "Hire date", key: "hire_date", type: "date" },
    {
      label: "Department",
      key: "department",
      type: "select",
      selectOptions: [
        { label: "Engineering", value: "eng" },
        { label: "Sales", value: "sales" },
        { label: "Operations", value: "ops" },
      ],
    },
  ];

  openEmployeeImport(userId: string): Promise<Record<string, unknown>[]> {
    return new Promise((resolve) => {
      const uploader = new DromoUploader(
        environment.dromoLicenseKey,
        this.employeeFields,
        { importIdentifier: "employees", developmentMode: !environment.production },
        { id: userId }
      );
      uploader.onResults((rows) => this.zone.run(() => resolve(rows)));
      uploader.open();
    });
  }
}

The component that triggers the import stays small. It injects the service and an HttpClient, awaits the rows, and posts them.

// src/app/import/import-button.component.ts
import { Component, inject, signal } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { DromoImportService } from "./dromo-import.service";

@Component({
  selector: "app-import-button",
  standalone: true,
  template: `
    <button type="button" (click)="start()">Import employees</button>
    @if (imported() > 0) {
      <p>{{ imported() }} employees imported.</p>
    }
  `,
})
export class ImportButtonComponent {
  private importer = inject(DromoImportService);
  private http = inject(HttpClient);
  imported = signal(0);

  async start() {
    const rows = await this.importer.openEmployeeImport("user_42");
    this.http.post("/api/employees/bulk", rows).subscribe(() => this.imported.set(rows.length));
  }
}

That is the whole integration: a service, a component, and an endpoint that trusts the rows because every validator has already run.

How it fits the Angular model

Wrapping the SDK in an injectable is the idiomatic move. Dependency injection gives you one place to read the license key from environment, one place to attach hooks, and a seam you can replace with a fake in unit tests. Components ask for a result and stay ignorant of how the modal works.

Standalone components need no module registration, as shown above. Projects still on NgModules import nothing special either; the SDK is a plain class, so it works wherever a service can be provided. Nothing in the uploader requires CommonModule or a specific Angular version beyond what the SDK's TypeScript targets.

The uploader renders its own modal outside your component tree, so ViewEncapsulation does not interfere with its styles and your styles do not leak into it. Appearance is set through the SDK's style overrides rather than through Angular stylesheets.

Angular gotchas Dromo handles for you

Callbacks outside the zone. Third-party widgets fire completion events from code Angular did not patch, so the view does not update when the import finishes. Developers spend an hour wondering why the row count stays at zero until they click somewhere. The zone.run wrapper in the service above fixes it; on zoneless applications, writing the result into a signal triggers the update on its own.

ExpressionChangedAfterItHasBeenCheckedError. Opening a modal and mutating bound state during the same change detection pass throws this in development. Because the uploader is opened from an event handler and reports back asynchronously, the two never collide.

Web Workers under the CLI. Parsing a large file without freezing the UI means a worker, and the Angular CLI's worker configuration trips up most teams the first time. Dromo processes the file inside the widget, and for uploads that are still too big for one sitting, the user can download an annotated Excel workbook, fix it offline, and resume.

Memory on large uploads. Keeping a hundred thousand parsed rows in a component property and rendering them with *ngFor will stall the browser. The review grid is virtualized inside the uploader and your component only ever receives the final array.

Reactive Forms and file inputs. FormControl does not bind to <input type="file">, so custom importers end up with a ViewChild and manual event handling. There is no file input in your template at all.

Dromo vs building it with PapaParse

Build it yourself Dromo
Column mapping A mapping component with a mat-select per header, and no memory of last time Header-to-field suggestions that are remembered between imports
Validation Validators duplicated between a Reactive Form and the backend Type, regex, range, uniqueness, and allowed-value checks, plus hooks that call your API
Error correction UI A data table with an editable cell renderer and custom error state Inline highlighting in a review grid; users fix cells without leaving the flow
Large files A worker, a streaming parser, and a virtual scroll strategy Handled in the widget, with offline fix-and-resume for the worst files
Internationalization @angular/localize extraction for every string in the wizard Thirty-plus languages included
Ongoing maintenance New customer format, new bug ticket Schema edits, often made by a PM without a release

A team ingesting a fixed-format CSV from their own export job should reach for PapaParse and a worker. That is a solved problem and it costs nothing. The importer earns its keep once the spreadsheets are authored by customers, because then the hard part is the human in the loop, not the parser.

Ship it to your users

Every plan includes white labeling at no additional charge. Fonts, colors, and copy are configurable so the modal matches an Angular Material theme or a custom design system, and no Dromo mark appears.

More than thirty languages are supported out of the box. If your app already sets a locale with LOCALE_ID, pass the same value to the uploader and the entire flow follows it.

Private Mode runs the complete import in the browser and retains nothing on Dromo's infrastructure. It is a settings change, not an architecture change, and the service above needs no edits beyond that flag.

What Our Customers Say

G2 High Performer Award 2025 badge for Dromo
4.9 out of 5 stars
5 out of 5 stars
Ryan Chan logo
Ryan Chan
CEO at Upkeep.com
E&Y Entrepreneur of the Year finalist
"We have now rolled out Dromo across our entire customer onboarding experience, and it has made our lives so much easier. Most importantly, our customers love the new experience!"

FAQ

Frequently asked questions

  • Yes. Import the JavaScript SDK in a standalone component or an NgModule and launch it from any method or event handler.
  • The onResults callback receives clean JSON. Push it into a service, a signal, or a store like any other async result.
  • Any version that can load a modern JavaScript module, which covers every currently supported Angular release.
  • Yes. Every plan includes white labeling: colors, fonts, and copy are configurable, and there is no Dromo branding.

Ready to Ship a Better Data Import Experience?

AI-powered mapping. Real-time validation. SOC 2 certified. Deploys in minutes.