import { Component, OnInit, Input, NgZone, forwardRef, OnDestroy, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { interval, Subscription } from 'rxjs'; import { AngularPConnectData, AngularPConnectService } from '@pega/angular-sdk-components'; import { ServerConfigService } from '@pega/angular-sdk-components'; import { ProgressSpinnerService } from '@pega/angular-sdk-components'; import { PreviewViewContainerComponent } from '@pega/angular-sdk-components'; import { ModalViewContainerComponent } from '@pega/angular-sdk-components'; import { ComponentMapperComponent } from '@pega/angular-sdk-components'; /** * WARNING: This file is part of the infrastructure component responsible for working with Redux and managing the creation and update of Redux containers and PConnect. * You may override Material components within this component if needed, but do not modify any container-related logic. Changing this logic can lead to unexpected behavior. */ const options = { context: 'app' }; @Component({ selector: 'app-root-container', templateUrl: './root-container.component.html', styleUrls: ['./root-container.component.scss'], imports: [ CommonModule, MatProgressSpinnerModule, ModalViewContainerComponent, PreviewViewContainerComponent, forwardRef(() => ComponentMapperComponent) ] }) export class RootContainerComponent implements OnInit, OnDestroy { @Input() pConn$: typeof PConnect; @Input() displayOnlyFA$: boolean; @Input() isMashup$: boolean; scService = inject(ServerConfigService); // For interaction with AngularPConnect angularPConnectData: AngularPConnectData = {}; componentName$: any; bIsProgress$ = false; // preview and modalview pConn pvConn$: any = null; mConn$: any = null; bShowRoot$ = true; progressSpinnerSubscription: Subscription; spinnerTimer: any = null; viewContainerPConn$: any = null; localizedVal: any; localeCategory = 'Messages'; constructor( private angularPConnect: AngularPConnectService, private psService: ProgressSpinnerService, private ngZone: NgZone ) {} ngOnInit(): void { const { containers } = PCore.getStore().getState(); const items = Object.keys(containers).filter(item => item.includes('root')); PCore.getContainerUtils().getContainerAPI().addContainerItems(items); // add preview and modalview containers to redux // keep local copies of the the pConnect that is related const configObjPreview = PCore.createPConnect({ meta: { type: 'PreviewViewContainer', config: { name: 'preview' } }, options }); this.pvConn$ = configObjPreview.getPConnect(); this.configureModalContainer(); // clear out hasViewContainer sessionStorage.setItem('hasViewContainer', 'false'); // First thing in initialization is registering and subscribing to the AngularPConnect service this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); // handle showing and hiding the progress spinner this.progressSpinnerSubscription = this.psService.getMessage().subscribe(message => { this.showHideProgress(message.show); }); this.localizedVal = PCore.getLocaleUtils().getLocaleValue; } ngOnDestroy() { if (this.angularPConnectData.unsubscribeFn) { this.angularPConnectData.unsubscribeFn(); } } // Callback passed when subscribing to store change onStateChange() { // Should always check the bridge to see if the component should update itself (re-render) const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); if (bUpdateSelf) { this.updateSelf(); } } updateSelf() { // need to call this.getCurrentCompleteProps (not this.thePConn.getConfigProps) // to get full set of props that affect this component in Redux const myProps: any = this.angularPConnect.getCurrentCompleteProps(this); const renderingModes = ['portal', 'view']; const noPortalMode = 'noPortal'; const { renderingMode, children, skeleton, routingInfo } = myProps; if (routingInfo && renderingModes.includes(renderingMode)) { const { accessedOrder, items } = routingInfo; if (accessedOrder && items) { // bootstrap loadPortal resolves to here const key = accessedOrder[accessedOrder.length - 1]; if (items[key] && items[key].view && Object.keys(items[key].view).length > 0) { const itemView = items[key].view; const rootObject = PCore.createPConnect({ meta: itemView, options: { context: items[key].context } }); setTimeout(() => { // makes sure Angular tracks these changes this.ngZone.run(() => { const theNewPConn = rootObject.getPConnect(); // update ComponentName$ before we update pConn$ to make sure they're in sync // when rendering... this.componentName$ = theNewPConn.getComponentName(); this.pConn$ = theNewPConn; // this.pConn$ = rootObject.getPConnect(); console.log(`RootContainer updated pConn$ to be: ${this.componentName$}`); }); }); } } } else if (renderingMode === noPortalMode) { // console.log(`RootContainer: renderingMode === noPortalMode: ${noPortalMode}`); this.generateViewContainerForNoPortal(); } else if (children && children.length > 0) { // haven't resolved to here } else if (skeleton !== undefined) { // TODO: need to update once skeletons are available; } } async configureModalContainer() { const sdkConfig = await this.scService.getSdkConfig(); const showModalsInEmbeddedMode = sdkConfig.serverConfig.showModalsInEmbeddedMode; if (!this.displayOnlyFA$ || showModalsInEmbeddedMode) { const configObjModal = PCore.createPConnect({ meta: { type: 'ModalViewContainer', config: { name: 'modal' } }, options }); this.mConn$ = configObjModal.getPConnect(); } } generateViewContainerForNoPortal() { // bootstrap loadMashup resolves to here const arChildren = this.pConn$.getChildren() as any[]; if (arChildren && arChildren.length == 1) { // have to have a quick timeout or get an "expressions changed" angular error setTimeout(() => { this.ngZone.run(() => { const localPConn = arChildren[0].getPConnect(); this.componentName$ = localPConn.getComponentName(); if (this.componentName$ === 'ViewContainer') { const configProps = this.pConn$.getConfigProps(); const viewContConfig = { meta: { type: 'ViewContainer', config: configProps }, options }; this.viewContainerPConn$ = PCore.createPConnect(viewContConfig).getPConnect(); } this.bShowRoot$ = true; }); }); } } showHideProgress(bShow: boolean) { // only show spinner after 500ms wait, so if server fast, won't see if (bShow) { if (!this.bIsProgress$) { // makes sure Angular tracks these changes if (!this.spinnerTimer || this.spinnerTimer.isStopped) { this.spinnerTimer = interval(500).subscribe(() => { try { this.spinnerTimer.unsubscribe(); } catch (ex) { console.log(ex); } this.ngZone.run(() => { this.bIsProgress$ = true; }); }); } } } else { if (this.spinnerTimer && !this.spinnerTimer.isStopped) { this.spinnerTimer.unsubscribe(); } // don't touch bIsProgress$ unless differnent if (bShow != this.bIsProgress$) { // makes sure Angular tracks these changes this.ngZone.run(() => { this.bIsProgress$ = bShow; }); } } } }