Skip to content

Commit 81e307c

Browse files
authored
AAE-32587 new custom screen registration api (#11120)
1 parent 0e4fced commit 81e307c

File tree

12 files changed

+257
-66
lines changed

12 files changed

+257
-66
lines changed

.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ module.exports = {
200200
files: ['*.spec.ts'],
201201
plugins: ['@alfresco/eslint-angular'],
202202
rules: {
203-
'@alfresco/eslint-angular/no-angular-material-selectors': 'error'
203+
'@alfresco/eslint-angular/no-angular-material-selectors': 'error',
204+
'@angular-eslint/component-class-suffix': 'off'
204205
}
205206
},
206207
{

lib/process-services-cloud/.eslintrc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@
8484
"@angular-eslint/prefer-standalone": "off"
8585
}
8686
},
87+
{
88+
"files": ["*.spec.ts"],
89+
"plugins": ["@alfresco/eslint-angular"],
90+
"rules": {
91+
"@angular-eslint/component-class-suffix": "off"
92+
}
93+
},
8794
{
8895
"files": ["*.html"],
8996
"extends": ["plugin:@nx/angular-template"],

lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616
*/
1717

1818
import { ComponentFixture, TestBed } from '@angular/core/testing';
19-
import { CommonModule } from '@angular/common';
2019
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
2120
import { By } from '@angular/platform-browser';
22-
import { ScreenRenderingService } from '../../../services/public-api';
21+
import { ScreenRenderingService } from '../../services/screen-rendering.service';
2322
import { TaskScreenCloudComponent } from './screen-cloud.component';
2423

2524
@Component({
@@ -31,8 +30,7 @@ import { TaskScreenCloudComponent } from './screen-cloud.component';
3130
<div class="adf-cloud-test-container-rootProcessInstanceId">{{ rootProcessInstanceId }}</div>
3231
<button class="adf-cloud-test-container-complete-btn" (click)="onComplete()">complete</button>
3332
</div>
34-
`,
35-
imports: [CommonModule]
33+
`
3634
})
3735
class TestComponent {
3836
@Input() taskId = '';
@@ -59,7 +57,7 @@ class TestComponent {
5957
(taskCompleted)="onTaskCompleted()"
6058
/>
6159
`,
62-
imports: [CommonModule, TaskScreenCloudComponent]
60+
imports: [TaskScreenCloudComponent]
6361
})
6462
class TestWrapperComponent {
6563
@Input() screenId = '';

lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717

1818
import { CommonModule } from '@angular/common';
1919
import { Component, ComponentRef, DestroyRef, EventEmitter, inject, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core';
20-
import { ScreenRenderingService } from '../../../services/public-api';
20+
import { ScreenRenderingService } from '../../services/screen-rendering.service';
2121
import { MatCardModule } from '@angular/material/card';
22-
import { UserTaskCustomUi } from '../../models/screen-cloud.model';
22+
import { UserTaskCustomUi } from './screen-cloud.model';
2323
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2424
import { MatCheckboxChange } from '@angular/material/checkbox';
2525

lib/process-services-cloud/src/lib/screen/public-api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@
1515
* limitations under the License.
1616
*/
1717

18-
export * from './models/screen-cloud.model';
18+
export * from './components/screen-cloud/screen-cloud.model';
19+
export * from './services/screen-rendering.service';
20+
export * from './services/provide-screen';
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*!
2+
* @license
3+
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { InjectionToken, Provider, Type } from '@angular/core';
19+
20+
export interface CustomScreen {
21+
key: string;
22+
component: Type<any>;
23+
}
24+
25+
/**
26+
* Injection token for custom screens.
27+
* This token can be used to inject custom screen components into the application.
28+
* It allows for multiple screen components to be registered and injected.
29+
*/
30+
export const APP_CUSTOM_SCREEN_TOKEN = new InjectionToken<CustomScreen>('Injection token for custom screens.');
31+
32+
/**
33+
* Provides a custom screen component to be used in the application.
34+
* This function allows you to register a custom screen component that can be injected
35+
* into the application using the `APP_CUSTOM_SCREEN_TOKEN`.
36+
*
37+
* @param key - A unique key to identify the screen component.
38+
* @param component - The screen component to be registered.
39+
* @returns A provider that can be used in the Angular dependency injection system.
40+
*/
41+
export function provideScreen(key: string, component: Type<any>): Provider {
42+
return {
43+
provide: APP_CUSTOM_SCREEN_TOKEN,
44+
multi: true,
45+
useValue: {
46+
key,
47+
component
48+
}
49+
};
50+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*!
2+
* @license
3+
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { TestBed } from '@angular/core/testing';
19+
import { Component } from '@angular/core';
20+
import { ScreenRenderingService } from './screen-rendering.service';
21+
import { APP_CUSTOM_SCREEN_TOKEN, provideScreen } from './provide-screen';
22+
23+
@Component({
24+
template: '<div>Test Component 1</div>'
25+
})
26+
class TestComponent1 {}
27+
28+
@Component({
29+
template: '<div>Test Component 2</div>'
30+
})
31+
class TestComponent2 {}
32+
33+
describe('ScreenRenderingService', () => {
34+
let service: ScreenRenderingService;
35+
36+
describe('without custom screens', () => {
37+
beforeEach(() => {
38+
TestBed.configureTestingModule({});
39+
service = TestBed.inject(ScreenRenderingService);
40+
});
41+
42+
it('should be created', () => {
43+
expect(service).toBeTruthy();
44+
});
45+
46+
it('should handle empty custom screens array', () => {
47+
expect(service).toBeTruthy();
48+
expect(() => service).not.toThrow();
49+
});
50+
});
51+
52+
describe('with custom screens', () => {
53+
beforeEach(() => {
54+
TestBed.configureTestingModule({
55+
providers: [provideScreen('test-screen-1', TestComponent1), provideScreen('test-screen-2', TestComponent2)]
56+
});
57+
});
58+
59+
it('should be created with custom screens', () => {
60+
const service = TestBed.inject(ScreenRenderingService);
61+
expect(service).toBeTruthy();
62+
});
63+
64+
it('should register custom screens on initialization', () => {
65+
const spy = spyOn(ScreenRenderingService.prototype, 'setComponentTypeResolver');
66+
TestBed.inject(ScreenRenderingService);
67+
68+
expect(spy).toHaveBeenCalledTimes(2);
69+
expect(spy).toHaveBeenCalledWith('test-screen-1', jasmine.any(Function), true);
70+
expect(spy).toHaveBeenCalledWith('test-screen-2', jasmine.any(Function), true);
71+
});
72+
73+
it('should register component resolvers that return correct components', () => {
74+
const resolverCalls: any[] = [];
75+
spyOn(ScreenRenderingService.prototype, 'setComponentTypeResolver').and.callFake((key, resolver, override) => {
76+
resolverCalls.push({ key, resolver: resolver({ type: key }), override });
77+
});
78+
TestBed.inject(ScreenRenderingService);
79+
80+
expect(resolverCalls).toEqual([
81+
{ key: 'test-screen-1', resolver: TestComponent1, override: true },
82+
{ key: 'test-screen-2', resolver: TestComponent2, override: true }
83+
]);
84+
});
85+
});
86+
87+
describe('with single custom screen', () => {
88+
beforeEach(() => {
89+
TestBed.configureTestingModule({
90+
providers: [provideScreen('single-screen', TestComponent1)]
91+
});
92+
});
93+
94+
it('should handle single custom screen', () => {
95+
const spy = spyOn(ScreenRenderingService.prototype, 'setComponentTypeResolver');
96+
TestBed.inject(ScreenRenderingService);
97+
98+
expect(spy).toHaveBeenCalledTimes(1);
99+
expect(spy).toHaveBeenCalledWith('single-screen', jasmine.any(Function), true);
100+
});
101+
});
102+
103+
describe('with null custom screens', () => {
104+
beforeEach(() => {
105+
TestBed.configureTestingModule({
106+
providers: [
107+
{
108+
provide: APP_CUSTOM_SCREEN_TOKEN,
109+
useValue: null,
110+
multi: true
111+
}
112+
]
113+
});
114+
});
115+
116+
it('should handle null custom screens gracefully', () => {
117+
expect(() => {
118+
service = TestBed.inject(ScreenRenderingService);
119+
}).not.toThrow();
120+
expect(service).toBeTruthy();
121+
});
122+
});
123+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*!
2+
* @license
3+
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { DynamicComponentMapper } from '@alfresco/adf-core';
19+
import { inject, Injectable } from '@angular/core';
20+
import { APP_CUSTOM_SCREEN_TOKEN, CustomScreen } from './provide-screen';
21+
22+
/**
23+
* Service for managing and rendering custom screen components.
24+
*
25+
* Custom screens can be registered using the {@link provideScreen} helper function
26+
* and the {@link APP_CUSTOM_SCREEN_TOKEN} injection token.
27+
*
28+
* @example
29+
* ```
30+
* // Register a custom screen in your Angular module providers:
31+
* import { provideScreen, ScreenRenderingService } from '@alfresco/adf-process-services-cloud';
32+
*
33+
* @Component({
34+
* template: '<div>My Custom Screen</div>'
35+
* })
36+
* class MyCustomScreenComponent {}
37+
*
38+
* @NgModule({
39+
* providers: [
40+
* provideScreen('my-custom-screen', MyCustomScreenComponent)
41+
* ]
42+
* })
43+
* export class MyModule {}
44+
*
45+
* // The custom screen can now be resolved and rendered by ScreenRenderingService.
46+
* ```
47+
*/
48+
@Injectable({
49+
providedIn: 'root'
50+
})
51+
export class ScreenRenderingService extends DynamicComponentMapper {
52+
private customScreens = inject<CustomScreen[]>(APP_CUSTOM_SCREEN_TOKEN, { optional: true }) || [];
53+
54+
constructor() {
55+
super();
56+
this.registerCustomScreens();
57+
}
58+
59+
private registerCustomScreens() {
60+
if (this.customScreens && this.customScreens.length > 0) {
61+
this.customScreens.forEach((screen) => {
62+
if (!screen) return; // Skip null or undefined screens
63+
this.setComponentTypeResolver(screen.key, () => screen.component, true);
64+
});
65+
}
66+
}
67+
}

lib/process-services-cloud/src/lib/services/public-api.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export * from './form-fields.interfaces';
2121
export * from './local-preference-cloud.service';
2222
export * from './notification-cloud.service';
2323
export * from './preference-cloud.interface';
24-
export * from './screen-rendering.service';
2524
export * from './task-list-cloud.service.interface';
2625
export * from './user-preference-cloud.service';
2726
export * from './variable-mapper.sevice';

0 commit comments

Comments
 (0)