Angular Integration: Complete Example
An Angular component and service for the Wedding Band Builder.
Service
typescript
// wedding-band.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
const SCRIPT_URL = 'https://releases.ijewel3d.com/libs/mini-viewer/latest/bundle.iife.js';
@Injectable({ providedIn: 'root' })
export class WeddingBandService {
private scriptLoaded = false;
api$ = new BehaviorSubject<any>(null);
price$ = new BehaviorSubject<any>(null);
ready$ = new BehaviorSubject<boolean>(false);
loadScript(): Promise<void> {
if (this.scriptLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = SCRIPT_URL;
script.onload = () => { this.scriptLoaded = true; resolve(); };
script.onerror = reject;
document.head.appendChild(script);
});
}
initViewer(container: HTMLElement, options: {
basePath?: string;
manifestUrl?: string;
showUI?: boolean;
theme?: string | object;
} = {}) {
const {
basePath = 'https://your-cdn.com/wbb-assets/',
manifestUrl = 'wedding-band-project.json',
showUI = true,
theme = null,
} = options;
const handleReady = (e: Event) => {
const viewer = (e as CustomEvent).detail.viewer;
const api = viewer.getPluginByType('WeddingBandBuilder')?.controller;
if (!api) return;
if (theme) api.setTheme(theme);
api.events.on('price:updated', (data: any) => {
if (data.pricing) this.price$.next(data.pricing);
});
this.api$.next(api);
this.ready$.next(true);
window.removeEventListener('ijewel-viewer-ready', handleReady);
};
window.addEventListener('ijewel-viewer-ready', handleReady);
new (window as any).ijewelViewer.Viewer(container, {
name: 'Wedding Band Builder',
version: 'v5',
basePath,
plugins: {
WeddingBandBuilder: { manifestUrl, showUI },
},
}, {
showCard: false,
showSwitchNode: false,
showUiButtons: showUI,
showConfigurator: false,
showZoomButtons: showUI,
enableZoom: true,
});
}
}Component
typescript
// wedding-band-builder.component.ts
import { Component, ElementRef, Input, Output, EventEmitter, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import { WeddingBandService } from './wedding-band.service';
import { Subscription } from 'rxjs';
import { filter, take } from 'rxjs/operators';
@Component({
selector: 'app-wedding-band-builder',
template: `
<div class="wbb-wrapper">
<div #viewer class="wbb-viewer"></div>
</div>
`,
styles: [`
.wbb-wrapper { width: 100%; height: 100%; }
.wbb-viewer { width: 100%; height: 100%; }
`],
})
export class WeddingBandBuilderComponent implements AfterViewInit, OnDestroy {
@ViewChild('viewer') viewerEl!: ElementRef<HTMLElement>;
@Input() basePath = 'https://your-cdn.com/wbb-assets/';
@Input() showUI = true;
@Input() theme: string | object | null = null;
@Output() wbbReady = new EventEmitter<any>();
@Output() priceChange = new EventEmitter<any>();
private subs: Subscription[] = [];
constructor(private wbb: WeddingBandService) {}
async ngAfterViewInit() {
await this.wbb.loadScript();
this.wbb.initViewer(this.viewerEl.nativeElement, {
basePath: this.basePath,
showUI: this.showUI,
theme: this.theme,
});
this.subs.push(
this.wbb.ready$.pipe(filter(Boolean), take(1)).subscribe(() => {
this.wbbReady.emit(this.wbb.api$.value);
}),
this.wbb.price$.pipe(filter(Boolean)).subscribe((pricing) => {
this.priceChange.emit(pricing);
}),
);
}
ngOnDestroy() {
this.subs.forEach(s => s.unsubscribe());
}
}Usage
typescript
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div style="width: 100vw; height: 80vh;">
<app-wedding-band-builder
[showUI]="true"
theme="luxury-gold"
(wbbReady)="onReady($event)"
(priceChange)="onPrice($event)"
/>
</div>
<div style="padding: 24px; font-size: 24px; font-weight: 600;">
{{ price ? '$' + price.toFixed(2) : 'Loading...' }}
</div>
`,
})
export class AppComponent {
price: number | null = null;
api: any = null;
onReady(api: any) {
this.api = api;
console.log('Profiles:', api.getAvailableProfiles());
}
onPrice(pricing: any) {
this.price = pricing.totalUsd;
}
}Module Declaration
typescript
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { WeddingBandBuilderComponent } from './wedding-band-builder.component';
@NgModule({
declarations: [AppComponent, WeddingBandBuilderComponent],
imports: [BrowserModule],
bootstrap: [AppComponent],
})
export class AppModule {}