EatSheet.xlsx
npm
A1
=PREVIEW(file, in="angular")

Preview an XLSX file in Angular

To preview an XLSX file in Angular, serve the EatSheet worker as an asset and pass it to openWorkbook. This step matters: without it, a production build fails with WORKER_FAILED, even if the development server works.

1. Install it

npm install eatsheet

2. Serve the worker as an asset

In angular.json, add this to the build target's assets list:
{ "glob": "**/*.js", "input": "node_modules/eatsheet/lib", "output": "eatsheet" }

3. Add the styles

Add this line to src/styles.css:
@import 'eatsheet/eatsheet.css';

4. Add src/app/excel-preview.ts

import { Component, ElementRef, effect, input, signal, viewChild } from '@angular/core';
import { openWorkbook } from 'eatsheet/worker';

@Component({
  selector: 'excel-preview',
  template: `
    @if (error(); as failure) {
      <p role="alert">Couldn't open this file: {{ failure.code ?? failure.message }}</p>
    }
    <div #box tabindex="0" [hidden]="error()" style="height: 480px; overflow: auto"></div>
  `,
})
export class ExcelPreview {
  file = input.required<File>();
  box = viewChild.required<ElementRef<HTMLElement>>('box');
  error = signal<any>(null);

  constructor() {
    effect(onCleanup => {
      const file = this.file();
      const element = this.box().nativeElement;
      let book: any, gone = false, frame = 0;
      this.error.set(null);

      const draw = async () => {
        const { html } = await book.render(0, { offset: element.scrollTop });
        if (!gone) element.innerHTML = html; // EatSheet escapes cell content
      };
      const onScroll = () => {
        cancelAnimationFrame(frame);
        frame = requestAnimationFrame(() => draw().catch(failure => this.error.set(failure)));
      };

      // Angular's build doesn't copy the worker from node_modules, so it's served as an asset
      const worker = new Worker('/eatsheet/worker-entry.js', { type: 'module' });

      openWorkbook(file, { worker }).then(opened => {
        if (gone) return opened.close();
        book = opened;
        element.addEventListener('scroll', onScroll);
        return draw();
      }).catch(failure => { if (!gone) this.error.set(failure); });

      onCleanup(() => {
        gone = true;
        cancelAnimationFrame(frame);
        element.removeEventListener('scroll', onScroll);
        book?.close();
      });
    });
  }
}

5. Use it with a file input

import { Component, signal } from '@angular/core';
import { ExcelPreview } from './excel-preview';

@Component({
  selector: 'app-root',
  imports: [ExcelPreview],
  template: `
    <input type="file" accept=".xlsx,.csv,.tsv" (change)="choose($event)">
    @if (file(); as chosen) {
      <excel-preview [file]="chosen" />
    }
  `,
})
export class App {
  file = signal<File | null>(null);
  choose(event: Event) {
    this.file.set((event.target as HTMLInputElement).files?.[0] ?? null);
  }
}
The component sets innerHTML on the element directly because Angular's [innerHTML] binding removes inline styles, and EatSheet's output uses them. If your app isn't at the site root, change the worker's address to match.
Tested on November 7, 2026 with EatSheet 0.2.3 and Angular 22, using ng serve and ng build: a valid file, a corrupt file, then a valid file again.