UNPKG

@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
// 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'; @Component({ selector: 'app-iframe', template: ` <iframe #iframe [src]="url" [style.height]="height" *ngIf="url"> </iframe> ` }) export class GenericIFrameComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('iframe') 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 } }