@quartal/bridge-client
Version:
Universal client library for embedding applications with URL-configurable transport support (iframe, postMessage) and framework adapters for Angular and Vue
246 lines (209 loc) • 7.96 kB
text/typescript
// Example: Generic Angular iframe component using ParentClientManager
// This example shows the essential patterns without app-specific details
import { Component, OnInit, AfterViewInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { parentClientManager, ParentClient } from '@quartal/client';
import { Subject } from 'rxjs';
import { filter, map, distinctUntilChanged, takeUntil } from 'rxjs/operators';
export class GenericIFrameComponent implements OnInit, AfterViewInit, OnDestroy {
iframe: ElementRef<HTMLIFrameElement>;
private parentClient: ParentClient | null = null;
private destroy$ = new Subject<void>();
private alive = true;
private initialized = false;
private isNavigatingFromChild = false;
url: string = 'https://your-embedded-app.com';
height: string | number = '100vh';
constructor(
private router: Router,
private cdr: ChangeDetectorRef
) {}
ngOnInit(): void {
if (this.initialized) {
return;
}
this.initialized = true;
// Listen to route changes to sync with embedded app
this.router.events.pipe(
filter(event => event instanceof NavigationEnd),
map((event: NavigationEnd) => event.urlAfterRedirects),
distinctUntilChanged(),
takeUntil(this.destroy$)
).subscribe((url: string) => {
if (!this.isNavigatingFromChild && this.parentClient && this.alive) {
this.openUrl(url);
}
});
}
ngAfterViewInit(): void {
// Initialize iframe after view is ready
this.cdr.detectChanges();
// Use setTimeout to ensure DOM is ready
setTimeout(() => this.initIframeListener(), 0);
}
ngOnDestroy(): void {
// Stop any ongoing operations
this.alive = false;
// Complete the destroy subject to unsubscribe from all observables
// This is critical for preventing memory leaks from RxJS subscriptions
this.destroy$.next();
this.destroy$.complete();
// Unregister from the parent client manager (decrements reference count)
parentClientManager.unregisterIframeComponent();
// Clear component reference to parent client (helps with garbage collection)
this.parentClient = null;
}
private async initIframeListener(): Promise<void> {
// Get the iframe element
const iframeElement = this.getIframe();
if (!iframeElement) {
console.warn('Iframe element not available');
return;
}
// Check if there's already a client from the manager
const existingClient = parentClientManager.getParentClient();
if (existingClient) {
// Update the iframe element reference in the existing client
const updateSuccess = parentClientManager.updateIframeElement(iframeElement);
if (updateSuccess) {
this.parentClient = existingClient;
// Register this component with the manager
parentClientManager.registerIframeComponent();
// Since we're reusing the client, manually trigger navigation to current URL
this.openUrl(this.router.routerState.snapshot.url);
return;
}
}
try {
// Create the parent client using ParentClientManager
this.parentClient = parentClientManager.initializeParentClient(
{
iframeElement,
appPrefix: 'APP', // Your app prefix
showNavigation: false,
showTopNavbar: false,
showFooter: false,
showLoading: false,
showMessages: false,
user: this.getCurrentUser(), // Optional user data
customActions: this.getCustomActions(), // Optional custom actions
customEntities: [], // Optional custom entities
customTabs: {}, // Optional custom tabs
customTables: {} // Optional custom tables
},
{
onInited: (data) => {
console.log('Parent client inited with data:', data);
this.openUrl(this.router.routerState.snapshot.url);
},
onFastActions: (actions) => this.handleFastActions(actions),
onUrlChange: (url) => this.handleUrlChange(url),
onHeightChange: (height) => this.handleHeightChange(height),
onAlert: (alert) => this.handleAlert(alert),
onTitleChange: (title) => this.handleTitleChange(title),
onError: (error) => console.error('Parent client error:', error),
onMouseClick: () => this.handleMouseClick(),
onPendingRequest: (pending) => this.handlePendingRequest(pending),
onDialogChanged: (dialogState) => this.handleDialogChanged(dialogState)
}
);
console.log('Parent client initialized successfully');
console.log('Parent client manager stats:', parentClientManager.getStats());
} catch (error) {
console.error('Failed to initialize parent client:', error);
}
}
private getIframe(): HTMLIFrameElement | null {
try {
return this.iframe?.nativeElement || null;
} catch (error) {
console.warn('Could not get iframe element:', error);
return null;
}
}
private openUrl(url: string): void {
if (!this.parentClient) {
return;
}
// Transform parent URL to child route
const childPath = this.transformUrlToChildPath(url);
this.parentClient.redirect([childPath]);
}
private transformUrlToChildPath(url: string): string {
// Example: Transform '/app/dashboard' to '/dashboard'
// Customize this based on your URL structure
return url.replace('/app', '') || '/';
}
private getCurrentUser(): any {
// Return current user data if available
return {
id: 'user123',
email: 'user@example.com',
name: 'Example User'
};
}
private getCustomActions(): any {
// Return custom actions configuration
return [];
}
private handleFastActions(actions: any[]): void {
console.log('Fast actions received:', actions);
// Handle fast actions from child
}
private handleUrlChange(url: string): void {
console.log('URL change from child:', url);
// Set flag to prevent sending openUrl back to child
this.isNavigatingFromChild = true;
// Navigate in parent application
const parentUrl = this.transformChildPathToUrl(url);
this.router.navigate([parentUrl]);
// Reset flag after navigation
setTimeout(() => {
this.isNavigatingFromChild = false;
}, 0);
}
private transformChildPathToUrl(childPath: string): string {
// Example: Transform '/dashboard' to '/app/dashboard'
// Customize this based on your URL structure
return '/app' + (childPath.startsWith('/') ? childPath : '/' + childPath);
}
private handleHeightChange(height: number): void {
console.log('Height change from child:', height);
// Update iframe height
setTimeout(() => {
this.height = height + 'px';
}, 0);
}
private handleAlert(alert: any): void {
console.log('Alert from child:', alert);
// Handle alerts (success, error, warning, info)
// Integrate with your notification system
}
private handleTitleChange(title: string): void {
console.log('Title change from child:', title);
// Update page title or breadcrumb
document.title = title;
}
private handleMouseClick(): void {
// Handle mouse clicks from child
// Useful for closing dropdowns, modals, etc.
}
private handlePendingRequest(pending: boolean): void {
console.log('Pending request state:', pending);
// Show/hide loading indicators
}
private handleDialogChanged(dialogState: any): void {
console.log('Dialog state changed:', dialogState);
// Handle dialog state changes from child
}
}