Native Angular widget for Sea.dev document processing
npm install @sea-dev/widget-ngNative Angular Widget for Sea.dev Document Processing
A fully native Angular implementation of the Sea.dev document processing widget. Built with Angular standalone components, Signals, and RxJS for optimal performance and developer experience.
- โ
Zero React dependency - Pure Angular implementation
- โ
Modern Angular - Angular 17+ block templates, Standalone components, Signals, RxJS
- โ
Type-safe - Full TypeScript support with strict mode
- โ
Lightweight - Small bundle size (~50-80KB)
- โ
SSR ready - Compatible with Angular Universal
- โ
Accessible - WCAG 2.1 compliant
- โ
OnPush - Optimized change detection
- โ
Reactive - Declarative state management with Signals
``bash`
npm install @sea-dev/widget-ng
Or with pnpm:
`bash`
pnpm add @sea-dev/widget-ng
This package requires:
- @angular/core >= 17.0.0@angular/common
- >= 17.0.0@angular/cdk
- >= 17.0.0rxjs
- ^7.0.0
Set up your Sea.dev API key at application startup:
`typescript
import { bootstrapApplication } from '@angular/platform-browser';
import { APP_INITIALIZER } from '@angular/core';
import { SeaWidgetConfigService } from '@sea-dev/widget-ng';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useFactory: (config: SeaWidgetConfigService) => () => {
config.setApiKey('your-sea-dev-api-key');
},
deps: [SeaWidgetConfigService],
},
],
});
`
Import and use the widget component:
`typescript
import { Component } from '@angular/core';
import { SeaWidgetComponent } from '@sea-dev/widget-ng';
@Component({
selector: 'app-root',
standalone: true,
imports: [SeaWidgetComponent],
template:
,
})
export class AppComponent {
formId = 'form_123';
dealId = 'deal_456';
}
`๐ Components
$3
The main widget component providing the full document processing workflow.
#### Inputs
| Input | Type | Required | Description |
|-------|------|----------|-------------|
|
apiKey | string | Yes | Sea.dev API key |
| formId | string | No | Filter submissions by form ID |
| dealId | string | No | Filter submissions by deal ID |
| submissionId | string | No | Pre-select a specific submission |#### Example
`typescript
import { Component } from '@angular/core';
import { SeaWidgetComponent } from '@sea-dev/widget-ng';@Component({
selector: 'app-documents',
standalone: true,
imports: [SeaWidgetComponent],
template:
,
})
export class DocumentsComponent {
apiKey = 'sea_live_xxx';
submissionId = 'sub_123';
}
`---
$3
Standalone file upload component with drag-and-drop support.
#### Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
|
submissionId | string | - | Submission ID to upload to |
| acceptedTypes | string | '.pdf,.png,.jpg,.jpeg,.xlsx,.xls,.csv' | Accepted file types |
| maxSize | number | 52428800 (50MB) | Maximum file size in bytes |
| multiple | boolean | false | Allow multiple file selection |#### Outputs
| Output | Type | Description |
|--------|------|-------------|
|
uploaded | EventEmitter | Emits when file is successfully uploaded |
| uploadError | EventEmitter | Emits when upload fails |#### Example
`typescript
import { Component } from '@angular/core';
import { SeaFileUploadComponent } from '@sea-dev/widget-ng';
import type { UploadResult, ApiError } from '@sea-dev/widget-ng';@Component({
selector: 'app-upload',
standalone: true,
imports: [SeaFileUploadComponent],
template:
,
})
export class UploadComponent {
submissionId = 'sub_123'; onUploaded(result: UploadResult): void {
console.log('File uploaded:', result);
}
onError(error: ApiError): void {
console.error('Upload failed:', error);
}
}
`---
$3
Read-only or editable data viewer for extracted submission data.
#### Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
|
submissionId | string | - | Submission ID to display |
| editable | boolean | false | Enable inline field editing |#### Outputs
| Output | Type | Description |
|--------|------|-------------|
|
fieldChanged | EventEmitter<{field: Field, value: unknown}> | Emits when field is edited |
| citationClicked | EventEmitter | Emits when citation is clicked |#### Example
`typescript
import { Component } from '@angular/core';
import { SeaDataViewerComponent } from '@sea-dev/widget-ng';
import type { Field } from '@sea-dev/widget-ng';@Component({
selector: 'app-viewer',
standalone: true,
imports: [SeaDataViewerComponent],
template:
,
})
export class ViewerComponent {
submissionId = 'sub_123'; onFieldChanged(event: { field: Field; value: unknown }): void {
console.log('Field updated:', event.field.name, event.value);
}
onCitationClicked(field: Field): void {
console.log('Navigate to:', field.citation);
}
}
`๐ง Services
$3
Global configuration service for the widget.
#### Methods
`typescript
// Set API key
config.setApiKey('sea_live_xxx');// Set custom API base URL
config.setApiBaseUrl('https://api.sea.dev');
// Set theme
config.setTheme('dark');
// Configure multiple values at once
config.configure({
apiKey: 'sea_live_xxx',
apiBaseUrl: 'https://api.sea.dev',
theme: 'light',
});
// Clear configuration
config.clear();
`#### Signals
`typescript
// Read-only signals
readonly apiKey: Signal;
readonly apiBaseUrl: Signal;
readonly theme: Signal<'light' | 'dark'>;
`#### Example
`typescript
import { Component, inject } from '@angular/core';
import { SeaWidgetConfigService } from '@sea-dev/widget-ng';@Component({
selector: 'app-settings',
template:
API Key: {{ apiKey() || 'Not set' }}
Theme: {{ theme() }}
,
})
export class SettingsComponent {
private readonly config = inject(SeaWidgetConfigService); readonly apiKey = this.config.apiKey;
readonly theme = this.config.theme;
toggleTheme(): void {
const newTheme = this.theme() === 'light' ? 'dark' : 'light';
this.config.setTheme(newTheme);
}
}
`---
$3
Low-level API client for advanced usage.
#### Signals
`typescript
readonly submissions: Signal;
readonly loading: Signal;
readonly error: Signal;
readonly hasSubmissions: Signal;
readonly hasError: Signal;
`#### Methods
`typescript
// Get single submission
getSubmission(id: string): Observable// List submissions
listSubmissions(filters?: {
formId?: string;
dealId?: string;
status?: string;
limit?: number;
offset?: number;
}): Observable
// Create submission
createSubmission(data: {
formId?: string;
dealId?: string;
}): Observable
// Update field
updateSubmissionField(
submissionId: string,
fieldId: string,
value: unknown
): Observable
// Upload document
uploadDocument(
submissionId: string,
file: File,
onProgress?: (progress: number) => void
): Observable
// Delete document
deleteDocument(
submissionId: string,
documentId: string
): Observable
// Get documents
getDocuments(submissionId: string): Observable
// Clear cache
clearCache(): void
`#### Example
`typescript
import { Component, inject, OnInit } from '@angular/core';
import { SeaApiClientService } from '@sea-dev/widget-ng';@Component({
selector: 'app-custom-integration',
template:
Loading...
,
})
export class CustomIntegrationComponent implements OnInit {
private readonly api = inject(SeaApiClientService); readonly submissions = this.api.submissions;
readonly loading = this.api.loading;
ngOnInit(): void {
this.api.listSubmissions({ limit: 10 }).subscribe();
}
}
`๐จ Styling
The widget components use scoped styles with no global CSS conflicts. You can customize the appearance using CSS custom properties or by overriding component styles.
$3
`css
/ Add to your global styles /
sea-widget {
--sea-primary-color: #3b82f6;
--sea-border-radius: 8px;
--sea-spacing: 1rem;
}
`$3
`css
/ Override component styles /
sea-widget::ng-deep .sea-widget__header {
background: #your-color;
}
`๐ Security
- Never expose API keys in client-side code in production
- Use environment variables for API keys
- Consider server-side API key proxying for enhanced security
`typescript
// Good: Load from environment
const apiKey = environment.seaApiKey;// Better: Fetch from server on startup
this.http.get<{apiKey: string}>('/api/sea-config')
.subscribe(config => {
this.seaConfig.setApiKey(config.apiKey);
});
`๐งช Testing
$3
`typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SeaWidgetComponent, SeaWidgetConfigService } from '@sea-dev/widget-ng';
import { provideHttpClient } from '@angular/common/http';describe('SeaWidgetComponent', () => {
let component: SeaWidgetComponent;
let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SeaWidgetComponent],
providers: [provideHttpClient()],
}).compileComponents();
fixture = TestBed.createComponent(SeaWidgetComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should require API key', () => {
fixture.detectChanges();
const element = fixture.nativeElement;
expect(element.textContent).toContain('API Key Required');
});
});
`๐ TypeScript Types
All components and services are fully typed. Import types from the package:
`typescript
import type {
Submission,
SubmissionStatus,
Field,
FieldType,
Document,
DocumentStatus,
UploadResult,
ApiError,
Citation,
BoundingBox,
} from '@sea-dev/widget-ng';
`๐ SSR Support
The widget is compatible with Angular Universal (SSR). Use platform checks for browser-only APIs:
`typescript
import { Component, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';@Component({...})
export class MyComponent {
private readonly platformId = inject(PLATFORM_ID);
ngOnInit(): void {
if (isPlatformBrowser(this.platformId)) {
// Browser-only code
}
}
}
`๐ Comparison with React Widget
| Feature | Native Angular | React Wrapper |
|---------|----------------|---------------|
| Bundle Size | ~50-80KB | ~180-220KB |
| Framework Dependencies | Angular only | Angular + React |
| Developer Experience | Native Angular | React in Angular |
| Type Safety | Full | Full |
| Change Detection | OnPush | Zone coordination |
| DevTools Support | Angular DevTools | Both |
| SSR Support | Native | Complex |
๐ฆ Bundle Size
Production build (gzipped):
- Core widget: ~50KB
- File upload: ~15KB
- Data viewer: ~15KB
- Total: ~80KB (excluding Angular framework which customers already have)
Compare to React wrapper: ~180-220KB (includes React runtime)
๐ค Migration from React Widget
See MIGRATION.md for detailed migration guide from
@sea-dev/widget (React) to @sea-dev/widget-ng (Angular).Quick comparison:
`typescript
// React widget
import { SeaWidget } from '@sea-dev/widget';
// Angular widget
import { SeaWidgetComponent } from '@sea-dev/widget-ng';
`๐ Examples
$3
`typescript
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { SeaWidgetComponent, SeaWidgetConfigService } from '@sea-dev/widget-ng';
import { APP_INITIALIZER } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';@Component({
selector: 'app-root',
standalone: true,
imports: [SeaWidgetComponent],
template:
,
styles: [],
})
export class AppComponent {
formId = 'form_financial_statements';
}bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(),
{
provide: APP_INITIALIZER,
multi: true,
useFactory: (config: SeaWidgetConfigService) => () => {
config.setApiKey('sea_live_your_key_here');
},
deps: [SeaWidgetConfigService],
},
],
});
`๐ Troubleshooting
$3
Ensure you're setting the API key before the widget initializes:
`typescript
// Use APP_INITIALIZER to set key on startup
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useFactory: (config: SeaWidgetConfigService) => () => {
config.setApiKey(environment.seaApiKey);
},
deps: [SeaWidgetConfigService],
},
]
`$3
Make sure your domain is whitelisted in your Sea.dev dashboard settings.
$3
Ensure you're not using ViewEncapsulation.ShadowDom which may block styles:
`typescript
@Component({
// Don't use ShadowDom
encapsulation: ViewEncapsulation.None // or Emulated
})
``MIT
- Documentation
- API Reference
- GitHub Repository
- Support
For questions and support:
- Documentation: https://docs.sea.dev
- Email: support@sea.dev
- GitHub Issues: https://github.com/seadotdev/app/issues