Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve remote component loading + add first skeleton loaders #226

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class AppStateServiceMock {
globalLoading$ = new FakeTopic(false)
currentMfe$ = new FakeTopic({ mountPath: '/', remoteBaseUrl: '.', baseHref: '/', shellName: 'test' })
currentPage$ = new FakeTopic<PageInfo | undefined>(undefined)
currentPortal$ = new FakeTopic<Workspace>({ baseUrl: '/', microfrontendRegistrations: [], portalName: 'Test portal' })
currentPortal$ = new FakeTopic<Workspace>({ baseUrl: '/', microfrontendRegistrations: [], portalName: 'Test portal', workspaceName: 'Test portal' })
currentWorkspace$ = new FakeTopic<Workspace>({ baseUrl: '/', microfrontendRegistrations: [], portalName: 'Test workspace', workspaceName: 'Test workspace' })
isAuthenticated$ = new FakeTopic<null>(null)
}
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<div *ngFor="let component of (components$ | async)" #slot></div>
<div *ngFor="let component of (components$ | async);" #slot>
<ng-container *ngIf="skeleton" [ngTemplateOutlet]="skeleton"></ng-container>
</div>
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -27,12 +29,10 @@ export class SlotComponent implements OnInit, OnDestroy {
this._viewContainers$.next(value)
}

@ContentChild('skeleton') skeleton: TemplateRef<any> | undefined

subscription: Subscription | undefined
components$:
| Observable<
{ componentType: Type<unknown> | undefined; remoteComponent: RemoteComponentInfo; permissions: string[] }[]
>
| undefined
components$: Observable<SlotComponentConfiguration[]> | undefined
SchettlerKoehler marked this conversation as resolved.
Show resolved Hide resolved

constructor(@Inject(SLOT_SERVICE) private slotService: SlotService) {}

Expand All @@ -43,23 +43,40 @@ 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<any>(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)
SchettlerKoehler marked this conversation as resolved.
Show resolved Hide resolved
})
}
})
}
}
)
}

private createComponent(
componentType: Type<unknown> | undefined,
componentInfo: { remoteComponent: RemoteComponentInfo; },
permissions: string[],
viewContainers: QueryList<ViewContainerRef>,
i: number
) {
const viewContainer = viewContainers.get(i);
viewContainer?.clear()
viewContainer?.element.nativeElement.replaceChildren()
if (componentType) {
const componentRef = viewContainer?.createComponent<any>(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()
}
Expand Down
10 changes: 7 additions & 3 deletions libs/angular-remote-components/src/lib/services/slot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ export const SLOT_SERVICE: InjectionToken<SlotService> = new InjectionToken('SLO

export type RemoteComponentInfo = { appId: string; productName: string; baseUrl: string }

export type SlotComponentConfiguration = {
componentType: Promise<Type<unknown> | undefined> | Type<unknown> | undefined;
remoteComponent: RemoteComponentInfo;
permissions: Promise<string[]> | string[];
}

export interface SlotService {
init(): Promise<void>
getComponentsForSlot(
slotName: string
): Observable<
{ componentType: Type<unknown> | undefined; remoteComponent: RemoteComponentInfo; permissions: string[] }[]
>
): Observable<SlotComponentConfiguration[]>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface Workspace {
* @deprecated will be renamed to workspaceName
*/
portalName: string
workspaceName: string
/**
* @deprecated will be removed
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: '/',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@
</svg>
</a>

<a class="layout-menu-button shadow-6" (click)="onMenuButtonClick($event)" pRipple [title]="menuButtonTitle ?? ('OCX_HEADER.MENU_TOGGLE')">
<a
class="layout-menu-button shadow-6"
(click)="onMenuButtonClick($event)"
pRipple
[title]="menuButtonTitle ?? ('OCX_HEADER.MENU_TOGGLE')"
>
<i class="pi pi-chevron-right"></i>
</a>
</div>
Expand All @@ -44,17 +49,21 @@
<ng-content></ng-content>
</div>
<div class="layout-topbar-actions-right">
<ocx-slot name="headerRight" class="layout-topbar-items"></ocx-slot>
<ocx-slot name="headerRight" class="layout-topbar-items">
<div class="flex flex-row justify-content-between w-full gap-3 h-full">
<ng-template #skeleton>
<div class="flex align-items-center h-full justify-content-center" style="width: 56px;">
<p-skeleton shape="circle" size="2.5rem" class="h-full flex align-items-center"></p-skeleton>
</div>
</ng-template>
</div>
</ocx-slot>
<ul class="layout-topbar-items">
<!-- Only desktop: Actions (favorites, support, search, ...) as icon buttons -->
<ng-container *ocxIfBreakpoint="'desktop'">

</ng-container>
<ng-container *ocxIfBreakpoint="'desktop'"> </ng-container>

<!-- Only mobile: Actions (favorites, support, search, ...) as 'more' button + overlay -->
<li class="layout-topbar-item" *ocxIfBreakpoint="'mobile'">

</li>
<li class="layout-topbar-item" *ocxIfBreakpoint="'mobile'"></li>
</ul>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion libs/shell-core/src/lib/shell-core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
})
Expand Down
Loading