From a8fb5c24dfe710058d763e87b1f12cd6478aecaa Mon Sep 17 00:00:00 2001 From: Bastian Jakobi <55296998+bastianjakobi@users.noreply.github.com> Date: Tue, 23 Apr 2024 13:03:54 +0200 Subject: [PATCH] feat: improve remote component loading + add first skeleton loaders (#226) * refactor: improve remote component loading and add first skeleton loaders * refactor: remove unnecessary export * fix: fix remote component loading + add workspace name property to currentWorkspace/currentPortal * fix: fix mock * fix: fix test mocks to match latest workspace model --- .../page-header/page-header.component.spec.ts | 1 + .../search-header.component.spec.ts | 1 + .../mocks/app-state-service-mock.ts | 3 +- .../lib/components/slot/slot.component.html | 4 +- .../src/lib/components/slot/slot.component.ts | 49 +++++++++++++------ .../src/lib/services/slot.service.ts | 10 ++-- .../current-workspace/v1/workspace.model.ts | 1 + .../announcement-banner.component.spec.ts | 1 + .../portal-footer.component.spec.ts | 1 + .../portal-viewport.component.spec.ts | 1 + .../search-criteria.component.spec.ts | 1 + .../initializer/standalone.initializer.ts | 5 +- .../portal-header/header.component.html | 25 +++++++--- libs/shell-core/src/lib/shell-core.module.ts | 3 +- 14 files changed, 75 insertions(+), 31 deletions(-) diff --git a/libs/angular-accelerator/src/lib/components/page-header/page-header.component.spec.ts b/libs/angular-accelerator/src/lib/components/page-header/page-header.component.spec.ts index f1498edc..8abc869e 100644 --- a/libs/angular-accelerator/src/lib/components/page-header/page-header.component.spec.ts +++ b/libs/angular-accelerator/src/lib/components/page-header/page-header.component.spec.ts @@ -92,6 +92,7 @@ describe('PageHeaderComponent', () => { await appStateService.currentPortal$.publish({ id: 'i-am-test-portal', portalName: 'test', + workspaceName: 'test', baseUrl: '', microfrontendRegistrations: [], }) diff --git a/libs/angular-accelerator/src/lib/components/search-header/search-header.component.spec.ts b/libs/angular-accelerator/src/lib/components/search-header/search-header.component.spec.ts index 646b8291..555a5847 100644 --- a/libs/angular-accelerator/src/lib/components/search-header/search-header.component.spec.ts +++ b/libs/angular-accelerator/src/lib/components/search-header/search-header.component.spec.ts @@ -53,6 +53,7 @@ describe('SearchHeaderComponent', () => { await appStateService.currentPortal$.publish({ id: 'i-am-test-portal', portalName: 'test', + workspaceName: 'test', baseUrl: '', microfrontendRegistrations: [], }) diff --git a/libs/angular-integration-interface/mocks/app-state-service-mock.ts b/libs/angular-integration-interface/mocks/app-state-service-mock.ts index 367ea2e9..2d1ecfb2 100644 --- a/libs/angular-integration-interface/mocks/app-state-service-mock.ts +++ b/libs/angular-integration-interface/mocks/app-state-service-mock.ts @@ -21,6 +21,7 @@ export class AppStateServiceMock { globalLoading$ = new FakeTopic(false) currentMfe$ = new FakeTopic({ mountPath: '/', remoteBaseUrl: '.', baseHref: '/', shellName: 'test' }) currentPage$ = new FakeTopic(undefined) - currentPortal$ = new FakeTopic({ baseUrl: '/', microfrontendRegistrations: [], portalName: 'Test portal' }) + currentPortal$ = new FakeTopic({ baseUrl: '/', microfrontendRegistrations: [], portalName: 'Test portal', workspaceName: 'Test portal' }) + currentWorkspace$ = new FakeTopic({ baseUrl: '/', microfrontendRegistrations: [], portalName: 'Test workspace', workspaceName: 'Test workspace' }) isAuthenticated$ = new FakeTopic(null) } diff --git a/libs/angular-remote-components/src/lib/components/slot/slot.component.html b/libs/angular-remote-components/src/lib/components/slot/slot.component.html index 85229487..c974cbb3 100644 --- a/libs/angular-remote-components/src/lib/components/slot/slot.component.html +++ b/libs/angular-remote-components/src/lib/components/slot/slot.component.html @@ -1 +1,3 @@ -
\ No newline at end of file +
+ +
\ No newline at end of file diff --git a/libs/angular-remote-components/src/lib/components/slot/slot.component.ts b/libs/angular-remote-components/src/lib/components/slot/slot.component.ts index 863e78d1..dd94ea0f 100644 --- a/libs/angular-remote-components/src/lib/components/slot/slot.component.ts +++ b/libs/angular-remote-components/src/lib/components/slot/slot.component.ts @@ -1,16 +1,18 @@ import { Component, + ContentChild, Inject, Input, OnDestroy, OnInit, QueryList, + TemplateRef, Type, ViewChildren, ViewContainerRef, } from '@angular/core' import { BehaviorSubject, Subscription, Observable, combineLatest } from 'rxjs' -import { RemoteComponentInfo, SLOT_SERVICE, SlotService } from '../../services/slot.service' +import { RemoteComponentInfo, SLOT_SERVICE, SlotComponentConfiguration, SlotService } from '../../services/slot.service' import { ocxRemoteComponent } from '../../model/remote-component' @Component({ @@ -27,12 +29,10 @@ export class SlotComponent implements OnInit, OnDestroy { this._viewContainers$.next(value) } + @ContentChild('skeleton') skeleton: TemplateRef | undefined + subscription: Subscription | undefined - components$: - | Observable< - { componentType: Type | undefined; remoteComponent: RemoteComponentInfo; permissions: string[] }[] - > - | undefined + components$: Observable | undefined constructor(@Inject(SLOT_SERVICE) private slotService: SlotService) {} @@ -43,16 +43,9 @@ export class SlotComponent implements OnInit, OnDestroy { if (viewContainers && viewContainers.length === components.length) { components.forEach((componentInfo, i) => { if (componentInfo.componentType) { - const componentRef = viewContainers.get(i)?.createComponent(componentInfo.componentType) - if (componentRef && 'ocxInitRemoteComponent' in componentRef.instance) { - ;(componentRef.instance as ocxRemoteComponent).ocxInitRemoteComponent({ - appId: componentInfo.remoteComponent.appId, - productName: componentInfo.remoteComponent.productName, - baseUrl: componentInfo.remoteComponent.baseUrl, - permissions: componentInfo.permissions, - }) - } - componentRef?.changeDetectorRef.detectChanges() + Promise.all([Promise.resolve(componentInfo.componentType), Promise.resolve(componentInfo.permissions)]).then(([componentType, permissions]) => { + this.createComponent(componentType, componentInfo, permissions, viewContainers, i) + }) } }) } @@ -60,6 +53,30 @@ export class SlotComponent implements OnInit, OnDestroy { ) } + private createComponent( + componentType: Type | undefined, + componentInfo: { remoteComponent: RemoteComponentInfo; }, + permissions: string[], + viewContainers: QueryList, + i: number + ) { + const viewContainer = viewContainers.get(i); + viewContainer?.clear() + viewContainer?.element.nativeElement.replaceChildren() + if (componentType) { + const componentRef = viewContainer?.createComponent(componentType) + if (componentRef && 'ocxInitRemoteComponent' in componentRef.instance) { + ;(componentRef.instance as ocxRemoteComponent).ocxInitRemoteComponent({ + appId: componentInfo.remoteComponent.appId, + productName: componentInfo.remoteComponent.productName, + baseUrl: componentInfo.remoteComponent.baseUrl, + permissions: permissions, + }) + } + componentRef?.changeDetectorRef.detectChanges() + } + } + ngOnDestroy(): void { this.subscription?.unsubscribe() } diff --git a/libs/angular-remote-components/src/lib/services/slot.service.ts b/libs/angular-remote-components/src/lib/services/slot.service.ts index 5f4cd32d..c753556f 100644 --- a/libs/angular-remote-components/src/lib/services/slot.service.ts +++ b/libs/angular-remote-components/src/lib/services/slot.service.ts @@ -5,11 +5,15 @@ export const SLOT_SERVICE: InjectionToken = new InjectionToken('SLO export type RemoteComponentInfo = { appId: string; productName: string; baseUrl: string } +export type SlotComponentConfiguration = { + componentType: Promise | undefined> | Type | undefined; + remoteComponent: RemoteComponentInfo; + permissions: Promise | string[]; +} + export interface SlotService { init(): Promise getComponentsForSlot( slotName: string - ): Observable< - { componentType: Type | undefined; remoteComponent: RemoteComponentInfo; permissions: string[] }[] - > + ): Observable } diff --git a/libs/integration-interface/src/lib/topics/current-workspace/v1/workspace.model.ts b/libs/integration-interface/src/lib/topics/current-workspace/v1/workspace.model.ts index ee0c277b..60ee78ac 100644 --- a/libs/integration-interface/src/lib/topics/current-workspace/v1/workspace.model.ts +++ b/libs/integration-interface/src/lib/topics/current-workspace/v1/workspace.model.ts @@ -6,6 +6,7 @@ export interface Workspace { * @deprecated will be renamed to workspaceName */ portalName: string + workspaceName: string /** * @deprecated will be removed */ diff --git a/libs/portal-integration-angular/src/lib/core/components/announcement-banner/announcement-banner.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/announcement-banner/announcement-banner.component.spec.ts index c9f2d390..b3da205e 100644 --- a/libs/portal-integration-angular/src/lib/core/components/announcement-banner/announcement-banner.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/announcement-banner/announcement-banner.component.spec.ts @@ -48,6 +48,7 @@ describe('AnnouncementBannerComponent', () => { await appStateService.currentPortal$.publish({ id: 'i-am-test-portal', portalName: 'test', + workspaceName: 'test', baseUrl: '', microfrontendRegistrations: [], }) diff --git a/libs/portal-integration-angular/src/lib/core/components/portal-footer/portal-footer.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/portal-footer/portal-footer.component.spec.ts index d31e20ce..02e74100 100644 --- a/libs/portal-integration-angular/src/lib/core/components/portal-footer/portal-footer.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/portal-footer/portal-footer.component.spec.ts @@ -40,6 +40,7 @@ describe('PortalFooterComponent', () => { await appStateService.currentPortal$.publish({ id: 'i-am-test-portal', portalName: 'test', + workspaceName: 'test', baseUrl: '', microfrontendRegistrations: [], }) diff --git a/libs/portal-integration-angular/src/lib/core/components/portal-viewport/portal-viewport.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/portal-viewport/portal-viewport.component.spec.ts index 00e69813..6cade19d 100644 --- a/libs/portal-integration-angular/src/lib/core/components/portal-viewport/portal-viewport.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/portal-viewport/portal-viewport.component.spec.ts @@ -98,6 +98,7 @@ describe('PortalViewportComponent', () => { await appStateService.currentPortal$.publish({ id: 'i-am-test-portal', portalName: 'test', + workspaceName: 'test', baseUrl: '', microfrontendRegistrations: [], }) diff --git a/libs/portal-integration-angular/src/lib/core/components/search-criteria/search-criteria.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/search-criteria/search-criteria.component.spec.ts index 9210c847..b46411b4 100644 --- a/libs/portal-integration-angular/src/lib/core/components/search-criteria/search-criteria.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/search-criteria/search-criteria.component.spec.ts @@ -55,6 +55,7 @@ describe('SearchCriteriaComponent', () => { await appStateService.currentPortal$.publish({ id: 'i-am-test-portal', portalName: 'test', + workspaceName: 'test', baseUrl: '', microfrontendRegistrations: [], }) diff --git a/libs/portal-integration-angular/src/lib/core/initializer/standalone.initializer.ts b/libs/portal-integration-angular/src/lib/core/initializer/standalone.initializer.ts index 7c929611..5fe774fd 100644 --- a/libs/portal-integration-angular/src/lib/core/initializer/standalone.initializer.ts +++ b/libs/portal-integration-angular/src/lib/core/initializer/standalone.initializer.ts @@ -64,7 +64,10 @@ export function standaloneInitializer( throw e } console.log(`📃 portal OK? `, portal) - await appStateService.currentPortal$.publish(portal) + await appStateService.currentPortal$.publish({ + ...portal, + workspaceName: portal.portalName + }) const standaloneMfeInfo: MfeInfo = { mountPath: '/', diff --git a/libs/shell-core/src/lib/components/portal-header/header.component.html b/libs/shell-core/src/lib/components/portal-header/header.component.html index 90aa15d4..5f77e90d 100644 --- a/libs/shell-core/src/lib/components/portal-header/header.component.html +++ b/libs/shell-core/src/lib/components/portal-header/header.component.html @@ -34,7 +34,12 @@ - + @@ -44,17 +49,21 @@
- + +
+ +
+ +
+
+
+
    - - - + -
  • - -
  • +
diff --git a/libs/shell-core/src/lib/shell-core.module.ts b/libs/shell-core/src/lib/shell-core.module.ts index 337713d0..ffec2656 100644 --- a/libs/shell-core/src/lib/shell-core.module.ts +++ b/libs/shell-core/src/lib/shell-core.module.ts @@ -8,9 +8,10 @@ import { GlobalErrorComponent } from './components/error-component/global-error. import { PortalFooterComponent } from './components/portal-footer/portal-footer.component' import { HeaderComponent } from './components/portal-header/header.component' import { PortalViewportComponent } from './components/portal-viewport/portal-viewport.component' +import { SkeletonModule } from 'primeng/skeleton' @NgModule({ - imports: [CommonModule, RouterModule, AngularRemoteComponentsModule, AngularAcceleratorModule, ToastModule], + imports: [CommonModule, RouterModule, AngularRemoteComponentsModule, AngularAcceleratorModule, ToastModule, SkeletonModule], declarations: [PortalViewportComponent, HeaderComponent, PortalFooterComponent, GlobalErrorComponent], exports: [PortalViewportComponent, HeaderComponent, PortalFooterComponent, ToastModule, GlobalErrorComponent], })