npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ng2-excel-viewer

v0.1.2

Published

Read-only Excel (.xlsx) viewer for Angular — multiple sheets, merged cells, column/row sizing, and number/date/currency formatting. Built on SheetJS + Angular CDK virtual scroll.

Readme

ng2-excel-viewer

A lightweight, read-only Excel (.xlsx) viewer component for Angular. It loads a workbook and renders it as a clean, performant grid with sheet tabs, merged cells, column/row sizing, resizable columns, and number/date/currency formatting.

It is built on top of SheetJS for parsing and Angular CDK for virtual scrolling — the package itself is the Angular UI layer, MIT licensed.

Scope: read-only viewing. No editing, no formula recalculation. See the roadmap.

Features

  • Open .xlsx, .xls, and .csv (any format SheetJS can read) from a File/Blob, a URL, raw bytes, or a pre-parsed workbook
  • Multiple sheets with a sheet-tab strip
  • Merged cells, column widths, row heights
  • Number / date / currency formatting
  • Virtual scrolling for large sheets (tens of thousands of rows)
  • Drag-to-resize columns
  • Themeable via CSS variables
  • Standalone component — works in standalone and NgModule apps

Requirements

  • Angular 20 (peer dependency ^20.0.0)
  • @angular/cdk 20
  • SheetJS (xlsx) — see install note below

Installation

npm install ng2-excel-viewer @angular/cdk

That's it — SheetJS is bundled in as a dependency (pulled from the SheetJS CDN, since the public npm xlsx package is abandoned at 0.18.5), so it installs automatically. No separate SheetJS install, no NgModule registration, and no provide*() setup required.

Restricted/offline networks: because SheetJS is fetched from cdn.sheetjs.com at install time, environments that only allow your private registry may block it. In that case, mirror the tarball in your registry, or use the peer-dependency setup instead — remove xlsx from this package's resolution and install it yourself: npm install https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz. Providing your own xlsx (including a SheetJS Pro build for full styling) also takes precedence.


Quick start (standalone app)

Import the component directly into any standalone component's imports:

import { Component, signal } from '@angular/core';
import { ExcelViewerComponent, ViewWorkbook } from 'ng2-excel-viewer';

@Component({
  selector: 'app-root',
  imports: [ExcelViewerComponent],
  template: `
    <input type="file" accept=".xlsx" (change)="pick($event)" />

    <ng2-excel-viewer
      [file]="file()"
      height="600px"
      (loaded)="onLoaded($event)"
      (error)="onError($event)"
    />
  `,
})
export class App {
  readonly file = signal<File | undefined>(undefined);

  pick(event: Event) {
    this.file.set((event.target as HTMLInputElement).files?.[0]);
  }

  onLoaded(wb: ViewWorkbook) {
    console.log('Sheets:', wb.sheetNames);
  }

  onError(err: Error) {
    console.error('Failed to load workbook:', err);
  }
}

Give the viewer a bounded height. Virtual scrolling needs one. Either pass the height input (default '480px'), or set height="100%" and give the parent an explicit height.


Using it in an NgModule-based app

ExcelViewerComponent is a standalone component, so add it to the imports array of the NgModule (or component) where you use it — not declarations:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ExcelViewerComponent } from 'ng2-excel-viewer';
import { ReportComponent } from './report.component';

@NgModule({
  declarations: [ReportComponent],
  imports: [CommonModule, ExcelViewerComponent], // <-- import the standalone component
})
export class ReportModule {}

Then use <ng2-excel-viewer> in ReportComponent's template as shown above.


Recipes

Load from a URL — local asset, API endpoint, or a public/remote URL. The component fetches it and parses the response:

<!-- local asset -->
<ng2-excel-viewer src="assets/report.xlsx" height="70vh" />

<!-- public/remote URL -->
<ng2-excel-viewer
  src="https://example.com/files/sales.xlsx"
  height="70vh"
/>
// or bind it from the component
readonly url = signal('https://example.com/files/sales.xlsx');
<ng2-excel-viewer [src]="url()" />

Cross-origin URLs are fetched with the browser fetch API, so the file's server must send permissive CORS headers (Access-Control-Allow-Origin). If it doesn't, proxy the file through your own backend, or download it yourself and pass a Blob (below). Authenticated downloads are the same story — fetch the bytes with your auth and pass a Blob/ArrayBuffer.

Load a Blob (e.g. from fetch, an HttpClient blob response, or any API). A Blob goes to the file input (it accepts File | Blob):

// with fetch
const blob = await fetch('https://example.com/files/sales.xlsx').then((r) => r.blob());
this.file.set(blob);

// with HttpClient
this.http.get('/api/export', { responseType: 'blob' })
  .subscribe((blob) => this.file.set(blob));
<ng2-excel-viewer [file]="file()" />

Drag & drop:

onDrop(event: DragEvent) {
  event.preventDefault();
  this.file.set(event.dataTransfer?.files?.[0]);
}
<div (drop)="onDrop($event)" (dragover)="$event.preventDefault()">
  <ng2-excel-viewer [file]="file()" />
</div>

Raw bytes (e.g. from an HttpClient arrayBuffer response):

this.http.get('/api/export', { responseType: 'arraybuffer' })
  .subscribe(buf => this.data.set(buf));
<ng2-excel-viewer [data]="data()" />

Open a specific sheet and react to switches:

<ng2-excel-viewer
  [src]="url"
  [(activeSheet)]="active"
  (sheetChange)="onSheetChange($event)"
/>

Disable column resizing / hide sheet tabs:

<ng2-excel-viewer
  [file]="file()"
  [options]="{ resizableColumns: false, showSheetTabs: false }"
/>

Configuration in code

Everything is configured through component inputs — there is no global config or module setup. Here is a complete component that wires up the common options:

import { Component, signal } from '@angular/core';
import {
  ExcelViewerComponent,
  ExcelViewerOptions,
  ViewWorkbook,
  ViewCell,
} from 'ng2-excel-viewer';

@Component({
  selector: 'app-report',
  imports: [ExcelViewerComponent],
  template: `
    <ng2-excel-viewer
      [src]="url()"
      [width]="'100%'"
      [height]="'70vh'"
      [(activeSheet)]="activeSheet"
      [options]="options"
      (loaded)="onLoaded($event)"
      (error)="onError($event)"
      (sheetChange)="onSheetChange($event)"
      (cellClick)="onCellClick($event)"
    />
  `,
})
export class ReportComponent {
  readonly url = signal('assets/report.xlsx');

  // Two-way bound: read or set the active sheet by index OR name.
  activeSheet: number | string = 0;

  // Only override what you need; the rest fall back to defaults.
  readonly options: Partial<ExcelViewerOptions> = {
    showSheetTabs: true,
    virtualScroll: true,
    resizableColumns: true,
    defaultColWidthPx: 80,
    defaultRowHeightPx: 24,
    maxColumns: 100,
  };

  openSheetByName() {
    this.activeSheet = 'Summary'; // jump to a sheet programmatically
  }

  onLoaded(wb: ViewWorkbook) {
    console.log(`Loaded ${wb.sheets.length} sheets:`, wb.sheetNames);
  }
  onError(err: Error) {
    console.error(err.message);
  }
  onSheetChange(e: { index: number; name: string }) {
    console.log('Active sheet:', e.name);
  }
  onCellClick(cell: ViewCell) {
    console.log(`Clicked ${cell.address} = ${cell.formatted}`, cell.value);
  }
}

Parsing without the UI. If you only need the data (e.g. to feed your own grid or export), inject ExcelParserService directly:

import { inject } from '@angular/core';
import { ExcelParserService } from 'ng2-excel-viewer';

const parser = inject(ExcelParserService);

const workbook = await parser.parseFile(file);        // File | Blob
// const workbook = await parser.parseUrl('a.xlsx');  // from a URL
// const workbook = parser.parseArrayBuffer(bytes);   // ArrayBuffer | Uint8Array

for (const sheet of workbook.sheets) {
  console.log(sheet.name, sheet.rowCount, 'x', sheet.colCount);
  const a1 = sheet.cells[0][0];        // first cell
  console.log(a1.value, a1.formatted); // raw value + display text
}

You can also pass a pre-parsed ViewWorkbook straight to the viewer via [workbook].


API

Inputs

Provide one source (resolution priority: workbook > data > file > src):

| Input | Type | Default | Description | | ------------- | ----------------------------- | ---------- | --------------------------------------------- | | src | string | — | URL to fetch and render. | | file | File \| Blob | — | From a file input or drag-drop. | | data | ArrayBuffer \| Uint8Array | — | Raw workbook bytes. | | workbook | ViewWorkbook | — | A pre-parsed workbook (skips parsing). | | activeSheet | number \| string | 0 | Active sheet by index or name; two-way bindable. | | width | number \| string | '100%' | Viewer width — number = px, string verbatim ('40rem'). | | height | number \| string | '480px' | Viewer height — number = px, string verbatim ('70vh'). | | options | Partial<ExcelViewerOptions> | see below | Behavior options (merged over defaults). |

Outputs

| Output | Payload | Fires when | | ------------- | ---------------------------------- | ---------------------------------- | | loaded | ViewWorkbook | A workbook finishes parsing. | | error | Error | Parsing/fetching fails. | | sheetChange | { index: number; name: string } | The active sheet changes. | | cellClick | ViewCell | A cell is clicked. |

Options (ExcelViewerOptions)

| Option | Type | Default | Description | | ------------------- | --------- | ------- | ------------------------------------------------------ | | showSheetTabs | boolean | true | Show the sheet-tab strip when there's more than one sheet. | | virtualScroll | boolean | true | Use virtualization for large sheets (> 200 rows). | | resizableColumns | boolean | true | Allow dragging column borders to resize. | | defaultColWidthPx | number | 64 | Fallback column width when none is specified. | | defaultRowHeightPx| number | 22 | Fallback row height (also the virtual-scroll item size). | | maxColumns | number | 200 | Cap on rendered columns (extra columns are dropped with a notice). |


Theming

The viewer is styled with CSS custom properties — override them on a parent element (or :root) to match your app:

ng2-excel-viewer {
  --ngx-excel-font: 'Inter', system-ui, sans-serif;
  --ngx-excel-accent: #2563eb;       /* active tab / resize guide */
  --ngx-excel-border: #d0d0d0;       /* outer + header borders */
  --ngx-excel-gridline: #e4e4e4;     /* cell gridlines */
  --ngx-excel-header-bg: #f3f3f3;    /* column/row headers */
  --ngx-excel-cell-bg: #ffffff;      /* cell background */
  --ngx-excel-zebra-bg: #fafafa;     /* alternating rows */
  --ngx-excel-tabbar-bg: #f3f3f3;
  --ngx-excel-tab-hover-bg: #e8e8e8;
  --ngx-excel-tab-active-bg: #ffffff;
}

Important: styling fidelity

SheetJS Community Edition does not decode most cell styling — author fonts, fills / background colors, bold/italic, borders, and alignment overrides are a SheetJS Pro ("Style") feature and are not present in the parsed data.

What this viewer does render faithfully from the community edition:

  • Cell values and types
  • Number / date / currency formatting (via the cell's format code)
  • Merged cells
  • Column widths and row heights
  • Multiple sheets and sheet names
  • Our own clean default grid styling (gridlines, header, zebra striping, numbers right-aligned)

The style plumbing is fully wired, so if you drop in a SheetJS Pro build (identical xlsx import surface), author styling lights up with no code change.


Notes & limitations (v1)

  • Server-Side Rendering (SSR): parsing relies on browser APIs (File, fetch, ArrayBuffer). Render the viewer on the client (e.g. guard with afterNextRender or an isPlatformBrowser check) — don't feed it a source during server rendering.
  • Wide sheets: columns are not virtualized; they scroll horizontally. The maxColumns cap (default 200) protects against pathologically wide sheets.
  • Large sheets (> 200 rows) use vertical virtual scrolling with a fixed row height. A vertical merge whose anchor has scrolled out of the viewport shows blank covered cells until the anchor returns. Small sheets use a plain table with full merge fidelity.
  • Resizing is view-only — it doesn't modify the file, and widths reset when a new workbook loads.

Roadmap

  • True 2D virtualization for very wide sheets
  • Variable row heights under virtualization
  • Optional light editing + .xlsx export

License

MIT