diff --git a/.eslintrc.js b/.eslintrc.js
index 44c7f82106..84e296e047 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -200,7 +200,8 @@ module.exports = {
files: ['*.spec.ts'],
plugins: ['@alfresco/eslint-angular'],
rules: {
- '@alfresco/eslint-angular/no-angular-material-selectors': 'error'
+ '@alfresco/eslint-angular/no-angular-material-selectors': 'error',
+ '@angular-eslint/component-class-suffix': 'off'
}
},
{
diff --git a/lib/process-services-cloud/.eslintrc.json b/lib/process-services-cloud/.eslintrc.json
index ae38e26e86..1547f99639 100644
--- a/lib/process-services-cloud/.eslintrc.json
+++ b/lib/process-services-cloud/.eslintrc.json
@@ -84,6 +84,13 @@
"@angular-eslint/prefer-standalone": "off"
}
},
+ {
+ "files": ["*.spec.ts"],
+ "plugins": ["@alfresco/eslint-angular"],
+ "rules": {
+ "@angular-eslint/component-class-suffix": "off"
+ }
+ },
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts
index ffe2aed05c..d55281891b 100644
--- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts
@@ -16,10 +16,9 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { By } from '@angular/platform-browser';
-import { ScreenRenderingService } from '../../../services/public-api';
+import { ScreenRenderingService } from '../../services/screen-rendering.service';
import { TaskScreenCloudComponent } from './screen-cloud.component';
@Component({
@@ -31,8 +30,7 @@ import { TaskScreenCloudComponent } from './screen-cloud.component';
{{ rootProcessInstanceId }}
- `,
- imports: [CommonModule]
+ `
})
class TestComponent {
@Input() taskId = '';
@@ -59,7 +57,7 @@ class TestComponent {
(taskCompleted)="onTaskCompleted()"
/>
`,
- imports: [CommonModule, TaskScreenCloudComponent]
+ imports: [TaskScreenCloudComponent]
})
class TestWrapperComponent {
@Input() screenId = '';
diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts
index 9bc9b4ef9f..eca14fcec7 100644
--- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts
@@ -17,9 +17,9 @@
import { CommonModule } from '@angular/common';
import { Component, ComponentRef, DestroyRef, EventEmitter, inject, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core';
-import { ScreenRenderingService } from '../../../services/public-api';
+import { ScreenRenderingService } from '../../services/screen-rendering.service';
import { MatCardModule } from '@angular/material/card';
-import { UserTaskCustomUi } from '../../models/screen-cloud.model';
+import { UserTaskCustomUi } from './screen-cloud.model';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatCheckboxChange } from '@angular/material/checkbox';
diff --git a/lib/process-services-cloud/src/lib/screen/models/screen-cloud.model.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.model.ts
similarity index 100%
rename from lib/process-services-cloud/src/lib/screen/models/screen-cloud.model.ts
rename to lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.model.ts
diff --git a/lib/process-services-cloud/src/lib/screen/public-api.ts b/lib/process-services-cloud/src/lib/screen/public-api.ts
index b12d0e7c06..c8decd7901 100644
--- a/lib/process-services-cloud/src/lib/screen/public-api.ts
+++ b/lib/process-services-cloud/src/lib/screen/public-api.ts
@@ -15,4 +15,6 @@
* limitations under the License.
*/
-export * from './models/screen-cloud.model';
+export * from './components/screen-cloud/screen-cloud.model';
+export * from './services/screen-rendering.service';
+export * from './services/provide-screen';
diff --git a/lib/process-services-cloud/src/lib/screen/services/provide-screen.ts b/lib/process-services-cloud/src/lib/screen/services/provide-screen.ts
new file mode 100644
index 0000000000..b83299eeaa
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/screen/services/provide-screen.ts
@@ -0,0 +1,50 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { InjectionToken, Provider, Type } from '@angular/core';
+
+export interface CustomScreen {
+ key: string;
+ component: Type;
+}
+
+/**
+ * Injection token for custom screens.
+ * This token can be used to inject custom screen components into the application.
+ * It allows for multiple screen components to be registered and injected.
+ */
+export const APP_CUSTOM_SCREEN_TOKEN = new InjectionToken('Injection token for custom screens.');
+
+/**
+ * Provides a custom screen component to be used in the application.
+ * This function allows you to register a custom screen component that can be injected
+ * into the application using the `APP_CUSTOM_SCREEN_TOKEN`.
+ *
+ * @param key - A unique key to identify the screen component.
+ * @param component - The screen component to be registered.
+ * @returns A provider that can be used in the Angular dependency injection system.
+ */
+export function provideScreen(key: string, component: Type): Provider {
+ return {
+ provide: APP_CUSTOM_SCREEN_TOKEN,
+ multi: true,
+ useValue: {
+ key,
+ component
+ }
+ };
+}
diff --git a/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.spec.ts b/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.spec.ts
new file mode 100644
index 0000000000..ef3b96187f
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.spec.ts
@@ -0,0 +1,123 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { Component } from '@angular/core';
+import { ScreenRenderingService } from './screen-rendering.service';
+import { APP_CUSTOM_SCREEN_TOKEN, provideScreen } from './provide-screen';
+
+@Component({
+ template: 'Test Component 1
'
+})
+class TestComponent1 {}
+
+@Component({
+ template: 'Test Component 2
'
+})
+class TestComponent2 {}
+
+describe('ScreenRenderingService', () => {
+ let service: ScreenRenderingService;
+
+ describe('without custom screens', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(ScreenRenderingService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('should handle empty custom screens array', () => {
+ expect(service).toBeTruthy();
+ expect(() => service).not.toThrow();
+ });
+ });
+
+ describe('with custom screens', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [provideScreen('test-screen-1', TestComponent1), provideScreen('test-screen-2', TestComponent2)]
+ });
+ });
+
+ it('should be created with custom screens', () => {
+ const service = TestBed.inject(ScreenRenderingService);
+ expect(service).toBeTruthy();
+ });
+
+ it('should register custom screens on initialization', () => {
+ const spy = spyOn(ScreenRenderingService.prototype, 'setComponentTypeResolver');
+ TestBed.inject(ScreenRenderingService);
+
+ expect(spy).toHaveBeenCalledTimes(2);
+ expect(spy).toHaveBeenCalledWith('test-screen-1', jasmine.any(Function), true);
+ expect(spy).toHaveBeenCalledWith('test-screen-2', jasmine.any(Function), true);
+ });
+
+ it('should register component resolvers that return correct components', () => {
+ const resolverCalls: any[] = [];
+ spyOn(ScreenRenderingService.prototype, 'setComponentTypeResolver').and.callFake((key, resolver, override) => {
+ resolverCalls.push({ key, resolver: resolver({ type: key }), override });
+ });
+ TestBed.inject(ScreenRenderingService);
+
+ expect(resolverCalls).toEqual([
+ { key: 'test-screen-1', resolver: TestComponent1, override: true },
+ { key: 'test-screen-2', resolver: TestComponent2, override: true }
+ ]);
+ });
+ });
+
+ describe('with single custom screen', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [provideScreen('single-screen', TestComponent1)]
+ });
+ });
+
+ it('should handle single custom screen', () => {
+ const spy = spyOn(ScreenRenderingService.prototype, 'setComponentTypeResolver');
+ TestBed.inject(ScreenRenderingService);
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ expect(spy).toHaveBeenCalledWith('single-screen', jasmine.any(Function), true);
+ });
+ });
+
+ describe('with null custom screens', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ {
+ provide: APP_CUSTOM_SCREEN_TOKEN,
+ useValue: null,
+ multi: true
+ }
+ ]
+ });
+ });
+
+ it('should handle null custom screens gracefully', () => {
+ expect(() => {
+ service = TestBed.inject(ScreenRenderingService);
+ }).not.toThrow();
+ expect(service).toBeTruthy();
+ });
+ });
+});
diff --git a/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.ts b/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.ts
new file mode 100644
index 0000000000..57498d1ee8
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.ts
@@ -0,0 +1,67 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { DynamicComponentMapper } from '@alfresco/adf-core';
+import { inject, Injectable } from '@angular/core';
+import { APP_CUSTOM_SCREEN_TOKEN, CustomScreen } from './provide-screen';
+
+/**
+ * Service for managing and rendering custom screen components.
+ *
+ * Custom screens can be registered using the {@link provideScreen} helper function
+ * and the {@link APP_CUSTOM_SCREEN_TOKEN} injection token.
+ *
+ * @example
+ * ```
+ * // Register a custom screen in your Angular module providers:
+ * import { provideScreen, ScreenRenderingService } from '@alfresco/adf-process-services-cloud';
+ *
+ * @Component({
+ * template: 'My Custom Screen
'
+ * })
+ * class MyCustomScreenComponent {}
+ *
+ * @NgModule({
+ * providers: [
+ * provideScreen('my-custom-screen', MyCustomScreenComponent)
+ * ]
+ * })
+ * export class MyModule {}
+ *
+ * // The custom screen can now be resolved and rendered by ScreenRenderingService.
+ * ```
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class ScreenRenderingService extends DynamicComponentMapper {
+ private customScreens = inject(APP_CUSTOM_SCREEN_TOKEN, { optional: true }) || [];
+
+ constructor() {
+ super();
+ this.registerCustomScreens();
+ }
+
+ private registerCustomScreens() {
+ if (this.customScreens && this.customScreens.length > 0) {
+ this.customScreens.forEach((screen) => {
+ if (!screen) return; // Skip null or undefined screens
+ this.setComponentTypeResolver(screen.key, () => screen.component, true);
+ });
+ }
+ }
+}
diff --git a/lib/process-services-cloud/src/lib/services/public-api.ts b/lib/process-services-cloud/src/lib/services/public-api.ts
index d6151a2318..8c819b32e1 100644
--- a/lib/process-services-cloud/src/lib/services/public-api.ts
+++ b/lib/process-services-cloud/src/lib/services/public-api.ts
@@ -21,7 +21,6 @@ export * from './form-fields.interfaces';
export * from './local-preference-cloud.service';
export * from './notification-cloud.service';
export * from './preference-cloud.interface';
-export * from './screen-rendering.service';
export * from './task-list-cloud.service.interface';
export * from './user-preference-cloud.service';
export * from './variable-mapper.sevice';
diff --git a/lib/process-services-cloud/src/lib/services/screen-rendering.service.spec.ts b/lib/process-services-cloud/src/lib/services/screen-rendering.service.spec.ts
deleted file mode 100644
index d4631e8ff5..0000000000
--- a/lib/process-services-cloud/src/lib/services/screen-rendering.service.spec.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*!
- * @license
- * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { TestBed } from '@angular/core/testing';
-import { ScreenRenderingService } from './screen-rendering.service';
-
-describe('ScreenRenderingService', () => {
- let service: ScreenRenderingService;
-
- beforeEach(() => {
- TestBed.configureTestingModule({});
- service = TestBed.inject(ScreenRenderingService);
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-});
diff --git a/lib/process-services-cloud/src/lib/services/screen-rendering.service.ts b/lib/process-services-cloud/src/lib/services/screen-rendering.service.ts
deleted file mode 100644
index 79032e1557..0000000000
--- a/lib/process-services-cloud/src/lib/services/screen-rendering.service.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*!
- * @license
- * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { DynamicComponentMapper } from '@alfresco/adf-core';
-import { Injectable } from '@angular/core';
-
-@Injectable({
- providedIn: 'root'
-})
-export class ScreenRenderingService extends DynamicComponentMapper {}