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: slot service implementation in angular-remote-components #251

Merged
merged 8 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion libs/angular-remote-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
"@angular/common": "^15.2.7",
"@angular/core": "^15.2.7",
"@onecx/angular-accelerator": "^4",
"@onecx/integration-interface": "^4",
"@ngx-translate/core": "^14.0.0",
"rxjs": "~7.8.0"
"rxjs": "~7.8.0",
"@angular-architects/module-federation": "15.0.0"
},
"dependencies": {},
"publishConfig": {
Expand Down
3 changes: 2 additions & 1 deletion libs/angular-remote-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from './lib/model/injection-tokens'
export * from './lib/components/slot/slot.component'
export * from './lib/angular-remote-components.module'
export * from './lib/services/slot.service'
export * from './lib/utils/provide-translate-service-for-root.utils'
export * from './lib/services/permission.service'
export * from './lib/utils/provide-translate-service-for-root.utils'
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ import { AppConfigService } from '@onecx/angular-accelerator'
imports: [CommonModule],
declarations: [SlotComponent],
exports: [SlotComponent],
providers: [AppConfigService]
providers: [AppConfigService],
})
export class AngularRemoteComponentsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Injectable } from '@angular/core'
import { PermissionsRpcTopic } from '@onecx/integration-interface'
import { filter, firstValueFrom, map } from 'rxjs'

@Injectable({ providedIn: 'root' })
export class PermissionService {
private permissionsTopic$ = new PermissionsRpcTopic()

async getPermissions(appId: string, productName: string): Promise<string[]> {
const permissions = firstValueFrom(
this.permissionsTopic$.pipe(
filter(
(message) =>
message.appId === appId && message.productName === productName && Array.isArray(message.permissions)
),
map((message) => message.permissions ?? [])
)
)
this.permissionsTopic$.publish({ appId: appId, productName: productName })
return permissions
}
}
74 changes: 66 additions & 8 deletions libs/angular-remote-components/src/lib/services/slot.service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,77 @@
import { InjectionToken, Type } from '@angular/core'
import { Observable } from 'rxjs'
import { loadRemoteModule } from '@angular-architects/module-federation'
import { Injectable, InjectionToken, Type } from '@angular/core'
import { RemoteComponent, RemoteComponentsTopic } from '@onecx/integration-interface'
import { Observable, map, shareReplay } from 'rxjs'
import { PermissionService } from './permission.service'

export const SLOT_SERVICE: InjectionToken<SlotService> = new InjectionToken('SLOT_SERVICE')

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[];
componentType: Promise<Type<unknown> | undefined> | Type<unknown> | undefined
remoteComponent: RemoteComponentInfo
permissions: Promise<string[]> | string[]
}

export interface SlotService {
init(): Promise<void>
getComponentsForSlot(
slotName: string
): Observable<SlotComponentConfiguration[]>
getComponentsForSlot(slotName: string): Observable<SlotComponentConfiguration[]>
isSomeComponentDefinedForSlot(slotName: string): Observable<boolean>
}

@Injectable({ providedIn: 'root' })
export class SlotService implements SlotService {
remoteComponents$ = new RemoteComponentsTopic()

constructor(private permissionsService: PermissionService) {}

async init(): Promise<void> {
return Promise.resolve()
}

getComponentsForSlot(slotName: string): Observable<SlotComponentConfiguration[]> {
return this.remoteComponents$.pipe(
map((remoteComponentsInfo) =>
(remoteComponentsInfo.slots?.find((slotMapping) => slotMapping.name === slotName)?.components ?? [])
.map((remoteComponentName) => remoteComponentsInfo.components.find((rc) => rc.name === remoteComponentName))
.filter((remoteComponent): remoteComponent is RemoteComponent => !!remoteComponent)
.map((remoteComponent) => remoteComponent)
),
map((infos) =>
infos.map((remoteComponent) => ({
componentType: this.loadComponent(remoteComponent),
remoteComponent,
permissions: this.permissionsService.getPermissions(remoteComponent.appId, remoteComponent.productName),
}))
),
shareReplay()
)
}

isSomeComponentDefinedForSlot(slotName: string): Observable<boolean> {
return this.remoteComponents$.pipe(
map((remoteComponentsInfo) => remoteComponentsInfo.slots.some((slotMapping) => slotMapping.name === slotName))
)
}

private async loadComponent(component: {
remoteEntryUrl: string
exposedModule: string
}): Promise<Type<unknown> | undefined> {
try {
const exposedModule = component.exposedModule.startsWith('./')
? component.exposedModule.slice(2)
: component.exposedModule
const m = await loadRemoteModule({
type: 'module',
remoteEntry: component.remoteEntryUrl,
exposedModule: './' + exposedModule,
})
return m[exposedModule]
} catch (e) {
console.log('Failed to load remote module ', component.exposedModule, component.remoteEntryUrl, e)
return undefined
}
}
}
5 changes: 5 additions & 0 deletions libs/integration-interface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ export * from './lib/topics/message/v1/message.model'
export * from './lib/topics/message/v1/message.topic'

export * from './lib/topics/remote-components/v1/remote-component.model'
export * from './lib/topics/remote-components/v1/remote-components-info.model'
export * from './lib/topics/remote-components/v1/slot.model'
export * from './lib/topics/remote-components/v1/remote-components.topic'

export * from './lib/topics/permissions/v1/permissions.topic'

export * from './lib/topics/permissions-rpc/v1/permissions-rpc.topic'
export * from './lib/topics/permissions-rpc/v1/permissions-rpc.model'

export * from './lib/topics/events/v1/events-topic'
export * from './lib/topics/events/v1/topic-event-type'
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface PermissionsRpc {
appId: string
productName: string
permissions?: Array<string>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Topic } from '@onecx/accelerator'
import { PermissionsRpc } from './permissions-rpc.model'

export class PermissionsRpcTopic extends Topic<PermissionsRpc> {
constructor() {
super('permissionsRpc', 1)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SyncableTopic } from "@onecx/accelerator";
import { SyncableTopic } from '@onecx/accelerator'

export class PermissionsTopic extends SyncableTopic<string[]> {
constructor() {
super('permissions', 1)
}
}
constructor() {
super('permissions', 1)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export interface RemoteComponent {
name: string;
baseUrl: string;
remoteEntryUrl: string;
appId: string;
productName: string;
exposedModule: string;
}
export type RemoteComponent = {
name: string
baseUrl: string
remoteEntryUrl: string
appId: string
productName: string
exposedModule: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { RemoteComponent } from './remote-component.model'
import { Slot } from './slot.model'

export type RemoteComponentsInfo = { components: RemoteComponent[]; slots: Slot[] }
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Topic } from '@onecx/accelerator'
import { RemoteComponent } from './remote-component.model'
import { RemoteComponentsInfo } from './remote-components-info.model'

export class RemoteComponentsTopic extends Topic<RemoteComponent[]> {
export class RemoteComponentsTopic extends Topic<RemoteComponentsInfo> {
constructor() {
super('remoteComponents', 1)
super('remoteComponentsInfo', 1)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type Slot = {
name: string
components: Array<string>
}
Loading