Applet Development Guide
Applet Development Guide
Build custom Angular applets that integrate seamlessly with the BigLedger platform. This comprehensive guide covers everything from development setup to deployment and distribution.
ℹ️
What are BigLedger Applets? Applets are modular Angular applications that extend BigLedger’s functionality. They run within the BigLedger interface and have full access to the platform’s APIs and services.
Overview
BigLedger applets are Angular applications that:
- Run within the BigLedger platform interface
- Have full access to BigLedger APIs and data
- Share authentication and user context
- Follow consistent UI/UX patterns
- Can be distributed through the BigLedger App Store
Getting Started
Prerequisites
- Node.js 18+ with npm or yarn
- Angular CLI 15+
- BigLedger Developer Account with applet development permissions
- Git for version control
Development Environment Setup
# Install BigLedger Applet CLI
npm install -g @bigledger/applet-cli
# Create new applet project
bl-applet create my-custom-applet --template=business
# Navigate to project directory
cd my-custom-applet
# Install dependencies
npm install
# Start development server
npm run dev
Project Structure
my-custom-applet/
├── src/
│ ├── app/
│ │ ├── components/ # Custom components
│ │ ├── services/ # Business logic services
│ │ ├── models/ # TypeScript interfaces
│ │ ├── pages/ # Route components
│ │ └── app.module.ts # Main module
│ ├── assets/ # Static assets
│ └── environments/ # Environment configs
├── applet.config.json # Applet configuration
├── package.json
└── README.md
Applet Configuration
The applet.config.json
file defines your applet’s metadata and capabilities:
{
"id": "custom-inventory-manager",
"name": "Custom Inventory Manager",
"version": "1.0.0",
"description": "Advanced inventory management with custom workflows",
"author": {
"name": "Your Company",
"email": "support@yourcompany.com",
"website": "https://yourcompany.com"
},
"category": "inventory",
"permissions": [
"inventory:read",
"inventory:write",
"products:read",
"reports:generate"
],
"navigation": {
"menu": "Inventory",
"icon": "cube",
"routes": [
{
"path": "/inventory/custom",
"title": "Custom Manager",
"component": "CustomInventoryComponent"
}
]
},
"apis": {
"required": ["inventory", "products", "reports"],
"optional": ["notifications", "webhooks"]
},
"dependencies": {
"@angular/core": "^15.0.0",
"@bigledger/applet-sdk": "^2.0.0"
}
}
BigLedger Applet SDK
Installation and Setup
npm install @bigledger/applet-sdk
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BigLedgerAppletModule } from '@bigledger/applet-sdk';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BigLedgerAppletModule.forRoot({
appletId: 'custom-inventory-manager',
apiVersion: 'v1'
})
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Core Services
The SDK provides several core services for applet development:
BigLedgerAPI Service
import { Component, OnInit } from '@angular/core';
import { BigLedgerAPIService } from '@bigledger/applet-sdk';
@Component({
selector: 'app-inventory-dashboard',
templateUrl: './inventory-dashboard.component.html'
})
export class InventoryDashboardComponent implements OnInit {
constructor(private blApi: BigLedgerAPIService) {}
async ngOnInit() {
// Access BigLedger APIs
const inventory = await this.blApi.inventory.list({
location: 'warehouse-1',
lowStock: true
});
const products = await this.blApi.products.list({
category: 'electronics'
});
console.log('Low stock items:', inventory);
console.log('Electronics products:', products);
}
async createStockAdjustment(itemId: string, quantity: number) {
const adjustment = await this.blApi.inventory.adjust({
itemId,
adjustmentType: 'increase',
quantity,
reason: 'Manual adjustment from custom applet',
location: 'warehouse-1'
});
return adjustment;
}
}
User Context Service
import { Component, OnInit } from '@angular/core';
import { UserContextService } from '@bigledger/applet-sdk';
@Component({
selector: 'app-user-dashboard',
templateUrl: './user-dashboard.component.html'
})
export class UserDashboardComponent implements OnInit {
constructor(private userContext: UserContextService) {}
ngOnInit() {
// Access current user information
const user = this.userContext.getCurrentUser();
console.log('Current user:', user);
// Access company information
const company = this.userContext.getCurrentCompany();
console.log('Current company:', company);
// Check permissions
const canManageInventory = this.userContext.hasPermission('inventory:write');
console.log('Can manage inventory:', canManageInventory);
}
}
Navigation Service
import { Component } from '@angular/core';
import { NavigationService } from '@bigledger/applet-sdk';
@Component({
selector: 'app-navigation-example',
templateUrl: './navigation-example.component.html'
})
export class NavigationExampleComponent {
constructor(private navigation: NavigationService) {}
navigateToInvoice(invoiceId: string) {
// Navigate to BigLedger's invoice page
this.navigation.navigateTo('/invoices', { id: invoiceId });
}
openCustomerDialog(customerId: string) {
// Open BigLedger's customer dialog
this.navigation.openDialog('customer', {
customerId,
mode: 'edit'
});
}
showNotification(message: string) {
// Show platform notification
this.navigation.showNotification({
message,
type: 'success',
duration: 3000
});
}
}
UI Components and Styling
Using BigLedger UI Components
import { Component } from '@angular/core';
import {
BLButtonModule,
BLTableModule,
BLFormModule,
BLCardModule
} from '@bigledger/ui-components';
@Component({
selector: 'app-inventory-list',
template: `
<bl-card title="Inventory Management">
<bl-form [formGroup]="filterForm">
<bl-form-field>
<bl-input placeholder="Search products..." formControlName="search"></bl-input>
</bl-form-field>
<bl-form-field>
<bl-select placeholder="Category" formControlName="category">
<bl-option value="electronics">Electronics</bl-option>
<bl-option value="furniture">Furniture</bl-option>
</bl-select>
</bl-form-field>
<bl-button type="primary" (click)="searchProducts()">Search</bl-button>
</bl-form>
<bl-table [dataSource]="inventoryItems" [columns]="tableColumns">
<ng-template blColumnDef="actions" let-item>
<bl-button size="small" (click)="adjustStock(item)">Adjust</bl-button>
<bl-button size="small" type="secondary" (click)="viewDetails(item)">Details</bl-button>
</ng-template>
</bl-table>
</bl-card>
`
})
export class InventoryListComponent {
// Component implementation
}
Custom Styling
// styles.scss
@import '@bigledger/ui-components/themes/default';
.custom-inventory-manager {
// Your custom styles that follow BigLedger's design system
.inventory-card {
@include bl-card-elevation(2);
margin: 16px;
padding: 24px;
}
.low-stock-warning {
@include bl-status-color('warning');
}
.out-of-stock-alert {
@include bl-status-color('danger');
}
}
Data Models and TypeScript Interfaces
Define TypeScript interfaces that extend BigLedger’s base models:
// models/inventory.models.ts
import { BaseEntity, Product, Location } from '@bigledger/applet-sdk';
export interface CustomInventoryItem extends BaseEntity {
id: string;
productId: string;
product?: Product;
locationId: string;
location?: Location;
currentStock: number;
minimumStock: number;
maximumStock: number;
reorderLevel: number;
reorderQuantity: number;
unitCost: number;
totalValue: number;
lastStockTake: Date;
customFields: {
binLocation?: string;
serialNumbers?: string[];
expiryDate?: Date;
supplier?: string;
};
}
export interface StockAdjustmentRequest {
itemId: string;
adjustmentType: 'increase' | 'decrease' | 'set';
quantity: number;
reason: string;
reference?: string;
notes?: string;
effectiveDate?: Date;
}
export interface InventoryReport {
reportId: string;
generatedAt: Date;
totalItems: number;
totalValue: number;
lowStockItems: number;
outOfStockItems: number;
items: CustomInventoryItem[];
}
Services and Business Logic
Create services to handle business logic and API interactions:
// services/custom-inventory.service.ts
import { Injectable } from '@angular/core';
import { BigLedgerAPIService } from '@bigledger/applet-sdk';
import { Observable, BehaviorSubject } from 'rxjs';
import { CustomInventoryItem, StockAdjustmentRequest } from '../models/inventory.models';
@Injectable({
providedIn: 'root'
})
export class CustomInventoryService {
private inventorySubject = new BehaviorSubject<CustomInventoryItem[]>([]);
public inventory$ = this.inventorySubject.asObservable();
constructor(private blApi: BigLedgerAPIService) {}
async loadInventory(filters?: any): Promise<CustomInventoryItem[]> {
try {
const response = await this.blApi.inventory.list({
...filters,
includeCustomFields: true
});
const items = response.data.map(item => this.mapToCustomInventoryItem(item));
this.inventorySubject.next(items);
return items;
} catch (error) {
console.error('Error loading inventory:', error);
throw error;
}
}
async adjustStock(request: StockAdjustmentRequest): Promise<CustomInventoryItem> {
try {
const adjustment = await this.blApi.inventory.adjust(request);
// Refresh inventory data
await this.loadInventory();
return this.mapToCustomInventoryItem(adjustment.inventoryItem);
} catch (error) {
console.error('Error adjusting stock:', error);
throw error;
}
}
async generateCustomReport(filters?: any): Promise<any> {
try {
// Use BigLedger's reporting API with custom parameters
const report = await this.blApi.reports.generate({
type: 'inventory_valuation',
format: 'json',
filters: {
includeZeroStock: false,
...filters
},
customFields: [
'binLocation',
'expiryDate',
'serialNumbers'
]
});
return report;
} catch (error) {
console.error('Error generating report:', error);
throw error;
}
}
private mapToCustomInventoryItem(item: any): CustomInventoryItem {
return {
...item,
customFields: {
binLocation: item.customFields?.binLocation || '',
serialNumbers: item.customFields?.serialNumbers || [],
expiryDate: item.customFields?.expiryDate ? new Date(item.customFields.expiryDate) : undefined,
supplier: item.customFields?.supplier || ''
}
};
}
}
Testing
Unit Testing
// custom-inventory.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { BigLedgerAPIService } from '@bigledger/applet-sdk';
import { CustomInventoryService } from './custom-inventory.service';
describe('CustomInventoryService', () => {
let service: CustomInventoryService;
let mockBlApi: jasmine.SpyObj<BigLedgerAPIService>;
beforeEach(() => {
const blApiSpy = jasmine.createSpyObj('BigLedgerAPIService', ['inventory']);
TestBed.configureTestingModule({
providers: [
CustomInventoryService,
{ provide: BigLedgerAPIService, useValue: blApiSpy }
]
});
service = TestBed.inject(CustomInventoryService);
mockBlApi = TestBed.inject(BigLedgerAPIService) as jasmine.SpyObj<BigLedgerAPIService>;
});
it('should load inventory items', async () => {
const mockData = [
{ id: '1', productId: 'prod-1', currentStock: 100 },
{ id: '2', productId: 'prod-2', currentStock: 50 }
];
mockBlApi.inventory.list.and.returnValue(Promise.resolve({ data: mockData }));
const result = await service.loadInventory();
expect(result).toHaveLength(2);
expect(mockBlApi.inventory.list).toHaveBeenCalled();
});
it('should adjust stock correctly', async () => {
const mockItem = { id: '1', productId: 'prod-1', currentStock: 110 };
mockBlApi.inventory.adjust.and.returnValue(Promise.resolve({ inventoryItem: mockItem }));
mockBlApi.inventory.list.and.returnValue(Promise.resolve({ data: [mockItem] }));
const request: StockAdjustmentRequest = {
itemId: '1',
adjustmentType: 'increase',
quantity: 10,
reason: 'Test adjustment'
};
const result = await service.adjustStock(request);
expect(result.currentStock).toBe(110);
});
});
Integration Testing
// app.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BigLedgerAppletModule } from '@bigledger/applet-sdk';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [
BigLedgerAppletModule.forRoot({
appletId: 'test-applet',
apiVersion: 'v1',
testMode: true
})
]
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
});
it('should create the applet', () => {
expect(component).toBeTruthy();
});
it('should initialize with BigLedger context', () => {
fixture.detectChanges();
expect(component.isInitialized).toBe(true);
});
});
Building and Packaging
Build Configuration
// angular.json
{
"projects": {
"my-custom-applet": {
"architect": {
"build": {
"builder": "@bigledger/applet-builders:build",
"options": {
"outputPath": "dist/my-custom-applet",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"optimization": true,
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"buildOptimizer": true,
"appletConfig": "applet.config.json"
}
}
}
}
}
}
Build Commands
# Development build
npm run build:dev
# Production build
npm run build:prod
# Create distributable package
npm run package
# Validate applet package
bl-applet validate dist/my-custom-applet.blapp
Deployment
Local Development Deployment
# Deploy to local BigLedger development environment
bl-applet deploy --target=local --env=development
# Watch for changes and auto-deploy
bl-applet deploy --watch --target=local
Staging Deployment
# Deploy to BigLedger staging environment
bl-applet deploy --target=staging --env=staging
# Run integration tests
bl-applet test --integration --env=staging
Production Deployment
# Create production package
npm run build:prod && npm run package
# Submit to BigLedger App Store
bl-applet publish --package=dist/my-custom-applet.blapp
# Deploy to private company store
bl-applet deploy --target=company --company-id=your-company-id
Best Practices
Performance Optimization
- Lazy Loading: Implement lazy loading for large modules
- OnPush Change Detection: Use OnPush strategy for better performance
- Track By Functions: Use trackBy functions in *ngFor loops
- Image Optimization: Optimize images and use WebP format
- Bundle Analysis: Regular bundle size analysis
Security Guidelines
- Input Validation: Always validate user inputs
- XSS Prevention: Use Angular’s built-in sanitization
- API Security: Follow BigLedger’s API security guidelines
- Permissions: Respect user permissions and roles
- Data Privacy: Handle sensitive data according to regulations
Code Organization
src/app/
├── core/ # Core services and guards
├── shared/ # Shared components and utilities
├── features/ # Feature modules
│ ├── inventory/
│ │ ├── components/
│ │ ├── services/
│ │ ├── models/
│ │ └── inventory.module.ts
│ └── reports/
├── layouts/ # Layout components
└── app-routing.module.ts
Error Handling
// error.service.ts
import { Injectable } from '@angular/core';
import { NotificationService } from '@bigledger/applet-sdk';
@Injectable({
providedIn: 'root'
})
export class ErrorService {
constructor(private notification: NotificationService) {}
handleError(error: any, userMessage?: string) {
console.error('Applet Error:', error);
// Log to BigLedger's error tracking
this.logToBigLedger(error);
// Show user-friendly message
this.notification.showError(
userMessage || 'An unexpected error occurred. Please try again.'
);
}
private logToBigLedger(error: any) {
// Implementation for logging errors to BigLedger's system
}
}
Distribution
BigLedger App Store
Submission Requirements:
- Complete applet.config.json
- Comprehensive documentation
- Screenshots and demo videos
- Test results and validation reports
Review Process:
- Automated security scan
- Functionality testing
- UI/UX review
- Performance analysis
Publishing:
- Version management
- Release notes
- Update distribution
Private Distribution
# Create private company package
bl-applet package --private --company=your-company
# Install in specific BigLedger instances
bl-applet install --package=my-applet.blapp --instance=production
Monitoring and Analytics
Built-in Analytics
import { AnalyticsService } from '@bigledger/applet-sdk';
@Injectable()
export class InventoryAnalyticsService {
constructor(private analytics: AnalyticsService) {}
trackStockAdjustment(itemId: string, adjustment: number) {
this.analytics.track('stock_adjustment', {
itemId,
adjustmentAmount: adjustment,
timestamp: new Date().toISOString()
});
}
trackReportGeneration(reportType: string) {
this.analytics.track('report_generated', {
reportType,
userId: this.analytics.getCurrentUserId(),
timestamp: new Date().toISOString()
});
}
}
Performance Monitoring
import { PerformanceService } from '@bigledger/applet-sdk';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html'
})
export class DashboardComponent implements OnInit {
constructor(private performance: PerformanceService) {}
async ngOnInit() {
const startTime = this.performance.mark('dashboard-load-start');
try {
await this.loadDashboardData();
this.performance.measure('dashboard-load-time', startTime);
} catch (error) {
this.performance.recordError('dashboard-load-failed', error);
}
}
}
Migration and Updates
Version Management
// applet.config.json
{
"version": "1.2.0",
"compatibility": {
"bigledger": ">=2.0.0",
"angular": "^15.0.0"
},
"migrations": [
{
"from": "1.0.0",
"to": "1.1.0",
"script": "migrations/v1.1.0-migration.js"
},
{
"from": "1.1.0",
"to": "1.2.0",
"script": "migrations/v1.2.0-migration.js"
}
]
}
Migration Scripts
// migrations/v1.2.0-migration.ts
import { AppletMigration } from '@bigledger/applet-sdk';
export class V120Migration implements AppletMigration {
async up(): Promise<void> {
// Migration logic for upgrading to v1.2.0
console.log('Migrating to version 1.2.0...');
// Update local storage structure
this.migrateLocalStorage();
// Update user preferences
await this.migrateUserPreferences();
console.log('Migration to 1.2.0 completed successfully');
}
async down(): Promise<void> {
// Rollback logic if needed
console.log('Rolling back from version 1.2.0...');
}
private migrateLocalStorage() {
// Implementation for localStorage migration
}
private async migrateUserPreferences() {
// Implementation for user preferences migration
}
}
Ready to start building your custom BigLedger applet? Follow this guide step by step, and don’t hesitate to reach out to our developer support team for assistance!