AAE-50665 Handling dynamic component destroy (#12183)

* AAE-50665 Handling dynamic component destroy

* AAE-50665 Code improvements
This commit is contained in:
Ehsan Rezaei
2026-08-19 22:33:38 +02:00
committed by GitHub
parent 5e0c52afcb
commit cc39b19eea
6 changed files with 507 additions and 67 deletions
@@ -0,0 +1,198 @@
/*!
* @license
* Copyright © 2005-2026 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 { Component, ComponentRef, OnDestroy } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { BaseScreenCloudComponent } from './base-screen-cloud.component';
import { provideScreen } from '../../../services/provide-screen';
import { ScreenRenderingService } from '../../../services/screen-rendering.service';
@Component({
selector: 'adf-cloud-test-dynamic-screen',
template: `<div class="adf-cloud-test-dynamic-screen">dynamic screen</div>`
})
class TestDynamicScreenComponent implements OnDestroy {
destroyed = false;
ngOnDestroy(): void {
this.destroyed = true;
}
}
@Component({
selector: 'adf-cloud-test-host-screen',
template: `<ng-container #container />`
})
class TestHostScreenComponent extends BaseScreenCloudComponent<TestDynamicScreenComponent> {
setInputsCalls: ComponentRef<TestDynamicScreenComponent>[] = [];
subscribeToOutputsCalls: ComponentRef<TestDynamicScreenComponent>[] = [];
get dynamicComponentRef(): ComponentRef<TestDynamicScreenComponent> | undefined {
return this.componentRef;
}
get dynamicComponentRefSignalValue(): ComponentRef<TestDynamicScreenComponent> | undefined {
return this.componentRefChanged();
}
protected override setInputsForDynamicComponent(componentRef: ComponentRef<TestDynamicScreenComponent>): void {
this.setInputsCalls.push(componentRef);
}
protected subscribeToOutputs(componentRef: ComponentRef<TestDynamicScreenComponent>): void {
this.subscribeToOutputsCalls.push(componentRef);
}
}
/** Same host component, but without the `#container` anchor in its template. */
@Component({
selector: 'adf-cloud-test-host-screen-without-container',
template: `<div class="adf-cloud-no-container"></div>`
})
class TestHostScreenWithoutContainerComponent extends TestHostScreenComponent {}
describe('BaseScreenCloudComponent', () => {
const screenId = 'test-screen';
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestHostScreenComponent, TestHostScreenWithoutContainerComponent, TestDynamicScreenComponent],
providers: [provideScreen(screenId, TestDynamicScreenComponent)]
});
});
describe('when a screenId is provided', () => {
let fixture: ComponentFixture<TestHostScreenComponent>;
let component: TestHostScreenComponent;
beforeEach(() => {
fixture = TestBed.createComponent(TestHostScreenComponent);
component = fixture.componentInstance;
fixture.componentRef.setInput('screenId', screenId);
fixture.detectChanges();
});
it('should create the dynamic component and expose it through the signal', () => {
expect(component.dynamicComponentRef).toBeDefined();
expect(component.dynamicComponentRefSignalValue).toBe(component.dynamicComponentRef);
expect(fixture.debugElement.query(By.css('.adf-cloud-test-dynamic-screen'))).toBeTruthy();
});
it('should wire inputs and outputs once, passing the created component reference', () => {
expect(component.setInputsCalls).toEqual([component.dynamicComponentRef!]);
expect(component.subscribeToOutputsCalls).toEqual([component.dynamicComponentRef!]);
});
it('should destroy the dynamic component reference on destroy', () => {
const destroySpy = spyOn(component.dynamicComponentRef!, 'destroy').and.callThrough();
fixture.destroy();
expect(destroySpy).toHaveBeenCalledTimes(1);
});
it('should run the ngOnDestroy hook of the dynamic component on destroy', () => {
const dynamicComponentInstance = component.dynamicComponentRef?.instance;
expect(dynamicComponentInstance?.destroyed).toBeFalse();
fixture.destroy();
expect(dynamicComponentInstance?.destroyed).toBeTrue();
});
it('should clear the dynamic component reference and the signal on destroy', () => {
fixture.destroy();
expect(component.dynamicComponentRef).toBeUndefined();
expect(component.dynamicComponentRefSignalValue).toBeUndefined();
});
it('should destroy the dynamic component reference only once when ngOnDestroy runs again', () => {
const destroySpy = spyOn(component.dynamicComponentRef!, 'destroy');
component.ngOnDestroy();
component.ngOnDestroy();
expect(destroySpy).toHaveBeenCalledTimes(1);
});
});
describe('when no screenId is provided', () => {
let fixture: ComponentFixture<TestHostScreenComponent>;
let component: TestHostScreenComponent;
beforeEach(() => {
fixture = TestBed.createComponent(TestHostScreenComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should not create any dynamic component nor wire inputs and outputs', () => {
expect(component.dynamicComponentRef).toBeUndefined();
expect(component.dynamicComponentRefSignalValue).toBeUndefined();
expect(component.setInputsCalls).toEqual([]);
expect(component.subscribeToOutputsCalls).toEqual([]);
expect(fixture.debugElement.query(By.css('.adf-cloud-test-dynamic-screen'))).toBeNull();
});
it('should not throw on destroy', () => {
expect(() => fixture.destroy()).not.toThrow();
expect(component.dynamicComponentRef).toBeUndefined();
});
});
describe('when the container anchor is missing', () => {
let fixture: ComponentFixture<TestHostScreenWithoutContainerComponent>;
let component: TestHostScreenWithoutContainerComponent;
beforeEach(() => {
fixture = TestBed.createComponent(TestHostScreenWithoutContainerComponent);
component = fixture.componentInstance;
fixture.componentRef.setInput('screenId', screenId);
});
it('should not throw and should not create any dynamic component', () => {
expect(() => fixture.detectChanges()).not.toThrow();
expect(component.container).toBeUndefined();
expect(component.dynamicComponentRef).toBeUndefined();
expect(component.dynamicComponentRefSignalValue).toBeUndefined();
});
it('should not wire inputs and outputs when no dynamic component was created', () => {
fixture.detectChanges();
expect(component.setInputsCalls).toEqual([]);
expect(component.subscribeToOutputsCalls).toEqual([]);
});
it('should not resolve any component type', () => {
const resolveComponentTypeSpy = spyOn(TestBed.inject(ScreenRenderingService), 'resolveComponentType').and.callThrough();
fixture.detectChanges();
expect(resolveComponentTypeSpy).not.toHaveBeenCalled();
});
it('should not throw on destroy', () => {
fixture.detectChanges();
expect(() => fixture.destroy()).not.toThrow();
});
});
});
@@ -15,20 +15,20 @@
* limitations under the License.
*/
import { Component, ComponentRef, inject, Input, OnInit, signal, ViewChild, ViewContainerRef } from '@angular/core';
import { Component, ComponentRef, inject, Input, OnDestroy, OnInit, signal, ViewChild, ViewContainerRef } from '@angular/core';
import { ScreenRenderingService } from '../../../services/screen-rendering.service';
@Component({
template: ''
})
export abstract class BaseScreenCloudComponent<TScreenComponent = unknown> implements OnInit {
export abstract class BaseScreenCloudComponent<TScreenComponent = unknown> implements OnInit, OnDestroy {
@Input()
screenId: string = '';
@ViewChild('container', { read: ViewContainerRef, static: true })
container: ViewContainerRef;
container: ViewContainerRef | undefined;
protected componentRef: ComponentRef<TScreenComponent>;
protected componentRef: ComponentRef<TScreenComponent> | undefined;
private readonly _componentRefChanged = signal<ComponentRef<TScreenComponent> | undefined>(undefined);
protected readonly componentRefChanged = this._componentRefChanged.asReadonly();
protected readonly screenRenderingService = inject(ScreenRenderingService);
@@ -37,17 +37,27 @@ export abstract class BaseScreenCloudComponent<TScreenComponent = unknown> imple
this.createDynamicComponent();
}
private createDynamicComponent(): void {
if (this.screenId) {
const componentType = this.screenRenderingService.resolveComponentType({ type: this.screenId });
this.componentRef = this.container.createComponent(componentType);
this._componentRefChanged.set(this.componentRef);
this.setInputsForDynamicComponent();
this.subscribeToOutputs();
}
ngOnDestroy(): void {
this.componentRef?.destroy();
this.componentRef = undefined;
this._componentRefChanged.set(undefined);
}
protected setInputsForDynamicComponent(): void {}
private createDynamicComponent(): void {
if (!this.screenId || !this.container) {
return;
}
protected abstract subscribeToOutputs(): void;
const componentType = this.screenRenderingService.resolveComponentType({ type: this.screenId });
const componentRef: ComponentRef<TScreenComponent> = this.container.createComponent(componentType);
this.componentRef = componentRef;
this._componentRefChanged.set(componentRef);
this.setInputsForDynamicComponent(componentRef);
this.subscribeToOutputs(componentRef);
}
protected setInputsForDynamicComponent(_componentRef: ComponentRef<TScreenComponent>): void {}
protected abstract subscribeToOutputs(componentRef: ComponentRef<TScreenComponent>): void;
}
@@ -16,6 +16,7 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, Input, OnDestroy, input, output } from '@angular/core';
import { StartProcessScreenCloudComponent } from './start-process-screen-cloud.component';
import { MockedTaskScreenCloudComponent } from '../../../../testing/start-process-screen-mock.component';
import { provideScreen } from '../../../services/provide-screen';
@@ -66,11 +67,11 @@ describe('StartProcessScreenCloudComponent', () => {
it('should set appName', () => {
const screenInstance: StartProcessScreenCloud = fixture.debugElement.query(By.directive(MockedTaskScreenCloudComponent)).componentInstance;
expect(screenInstance.appName()).toEqual('');
expect(screenInstance.appName?.()).toEqual('');
const newValue = 'new-app-name';
fixture.componentRef.setInput('appName', newValue);
fixture.detectChanges();
expect(screenInstance.appName()).toEqual(newValue);
expect(screenInstance.appName?.()).toEqual(newValue);
});
it('should set process definition id', () => {
@@ -84,10 +85,125 @@ describe('StartProcessScreenCloudComponent', () => {
it('should set resolvedValues', () => {
const screenInstance: StartProcessScreenCloud = fixture.debugElement.query(By.directive(MockedTaskScreenCloudComponent)).componentInstance;
expect(screenInstance.resolvedValues()).toBeUndefined();
expect(screenInstance.resolvedValues?.()).toBeUndefined();
const newValues = [new TaskVariableCloud({ id: 'new-id', name: 'new-name' })];
fixture.componentRef.setInput('resolvedValues', newValues);
fixture.detectChanges();
expect(screenInstance.resolvedValues()).toEqual(newValues);
expect(screenInstance.resolvedValues?.()).toEqual(newValues);
});
});
@Component({
selector: 'adf-cloud-destroy-tracking-screen',
template: `<div class="adf-cloud-destroy-tracking-screen">screen</div>`
})
class DestroyTrackingScreenComponent implements StartProcessScreenCloud, OnDestroy {
readonly appName = input('');
processDefinitionId = input('');
readonly resolvedValues = input<TaskVariableCloud[] | undefined>();
defaultStartProcessButtonsConfigurationChange = output<StartProcessScreenDefaultButtons>();
startProcessPayloadChanged = output<unknown>();
destroyed = false;
ngOnDestroy(): void {
this.destroyed = true;
}
}
@Component({
selector: 'adf-cloud-test-start-process-wrapper',
template: `
@if (showScreen) {
<adf-cloud-start-process-screen-cloud [screenId]="screenId" [processDefinitionId]="'definition-id'" />
}
`,
imports: [StartProcessScreenCloudComponent]
})
class TestStartProcessWrapperComponent {
@Input() screenId = '';
showScreen = true;
}
describe('StartProcessScreenCloudComponent - destroy', () => {
let fixture: ComponentFixture<TestStartProcessWrapperComponent>;
let component: TestStartProcessWrapperComponent;
const screenId = 'screen-1234-5678-121212-123456';
const getScreenInstance = (): DestroyTrackingScreenComponent =>
fixture.debugElement.query(By.directive(DestroyTrackingScreenComponent)).componentInstance;
const destroyScreen = () => {
component.showScreen = false;
fixture.detectChanges();
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestStartProcessWrapperComponent],
providers: [provideScreen(screenId, DestroyTrackingScreenComponent)]
});
fixture = TestBed.createComponent(TestStartProcessWrapperComponent);
component = fixture.componentInstance;
fixture.componentRef.setInput('screenId', screenId);
fixture.detectChanges();
});
it('should destroy the screen component when the host is destroyed', () => {
const screenInstance = getScreenInstance();
expect(screenInstance.destroyed).toBeFalse();
destroyScreen();
expect(screenInstance.destroyed).toBeTrue();
});
it('should remove the screen component from the DOM when the host is destroyed', () => {
expect(fixture.debugElement.query(By.css('.adf-cloud-destroy-tracking-screen'))).toBeTruthy();
destroyScreen();
expect(fixture.debugElement.query(By.css('.adf-cloud-destroy-tracking-screen'))).toBeNull();
});
it('should create a new screen component instance when the host is re-created', () => {
const firstInstance = getScreenInstance();
destroyScreen();
component.showScreen = true;
fixture.detectChanges();
const secondInstance = getScreenInstance();
expect(secondInstance).not.toBe(firstInstance);
expect(secondInstance.destroyed).toBeFalse();
expect(secondInstance.processDefinitionId()).toBe('definition-id');
});
});
describe('StartProcessScreenCloudComponent - without screenId', () => {
let fixture: ComponentFixture<StartProcessScreenCloudComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [StartProcessScreenCloudComponent]
});
fixture = TestBed.createComponent(StartProcessScreenCloudComponent);
});
it('should not create any screen component and should not throw', () => {
expect(() => fixture.detectChanges()).not.toThrow();
expect(fixture.debugElement.query(By.directive(DestroyTrackingScreenComponent))).toBeNull();
});
it('should not throw when inputs change or on destroy', () => {
fixture.detectChanges();
expect(() => {
fixture.componentRef.setInput('appName', 'new-app-name');
fixture.componentRef.setInput('resolvedValues', [new TaskVariableCloud({ id: 'id', name: 'name' })]);
fixture.detectChanges();
}).not.toThrow();
expect(() => fixture.destroy()).not.toThrow();
});
});
@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { ChangeDetectionStrategy, Component, effect, input, output, signal } from '@angular/core';
import { ChangeDetectionStrategy, Component, ComponentRef, effect, input, output, signal } from '@angular/core';
import { BaseScreenCloudComponent } from '../base-screen/base-screen-cloud.component';
import { MatCardModule } from '@angular/material/card';
import { CommonModule } from '@angular/common';
@@ -43,7 +43,7 @@ export class StartProcessScreenCloudComponent extends BaseScreenCloudComponent<S
super();
effect(() => {
const componentRef = this.componentRefChanged();
if (componentRef.instance && 'appName' in componentRef.instance) {
if (componentRef?.instance && 'appName' in componentRef.instance) {
componentRef.setInput('appName', this.appName());
}
});
@@ -56,9 +56,9 @@ export class StartProcessScreenCloudComponent extends BaseScreenCloudComponent<S
});
}
protected subscribeToOutputs(): void {
this.componentRef.instance.startProcessPayloadChanged.subscribe((payload) => this.screenStartProcessPayloadChange.emit(payload));
this.componentRef.instance.defaultStartProcessButtonsConfigurationChange.subscribe((config) => {
protected subscribeToOutputs(componentRef: ComponentRef<StartProcessScreenCloud>): void {
componentRef.instance.startProcessPayloadChanged.subscribe((payload) => this.screenStartProcessPayloadChange.emit(payload));
componentRef.instance.defaultStartProcessButtonsConfigurationChange.subscribe((config) => {
this.showStartProcessButtons.set(config.show);
this.disableStartProcessButton.emit(config.disable);
});
@@ -16,7 +16,7 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { Component, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ScreenRenderingService } from '../../../services/screen-rendering.service';
import { TaskScreenCloudComponent } from './screen-cloud.component';
@@ -32,18 +32,22 @@ import { TaskScreenCloudComponent } from './screen-cloud.component';
</div>
`
})
class TestComponent {
class TestComponent implements OnDestroy {
@Input() taskId = '';
@Input() screenId = '';
@Input() rootProcessInstanceId = '';
@Output() taskCompleted = new EventEmitter();
displayMode: string;
displayMode: string | undefined;
destroyed = false;
onComplete() {
this.taskCompleted.emit();
}
switchToDisplayMode(newDisplayMode?: string) {
this.displayMode = newDisplayMode;
}
ngOnDestroy(): void {
this.destroyed = true;
}
}
@Component({
@@ -61,7 +65,7 @@ class TestComponent {
})
class TestWrapperComponent {
@Input() screenId = '';
@ViewChild('adfCloudTaskScreen') adfCloudTaskScreen: TaskScreenCloudComponent;
@ViewChild('adfCloudTaskScreen') adfCloudTaskScreen: TaskScreenCloudComponent | undefined;
onTaskCompleted() {}
switchToDisplayMode(newDisplayMode?: string): void {
if (this.adfCloudTaskScreen) {
@@ -118,6 +122,118 @@ describe('TaskScreenCloudComponent', () => {
component.switchToDisplayMode();
fixture.detectChanges();
expect(component.adfCloudTaskScreen.switchToDisplayMode).toHaveBeenCalled();
expect(component.adfCloudTaskScreen?.switchToDisplayMode).toHaveBeenCalled();
});
});
@Component({
selector: 'adf-cloud-test-conditional-component',
template: `
@if (showTaskScreen) {
<adf-cloud-task-screen [taskId]="'1'" [appName]="'app-name-test'" [screenId]="'test'" (taskCompleted)="onTaskCompleted()" />
}
`,
imports: [TaskScreenCloudComponent]
})
class TestConditionalWrapperComponent {
showTaskScreen = true;
onTaskCompleted() {}
}
describe('TaskScreenCloudComponent - destroy', () => {
let fixture: ComponentFixture<TestConditionalWrapperComponent>;
let component: TestConditionalWrapperComponent;
const getDynamicComponentInstance = (): TestComponent => fixture.debugElement.query(By.directive(TestComponent)).componentInstance;
const getTaskScreen = (): TaskScreenCloudComponent => fixture.debugElement.query(By.directive(TaskScreenCloudComponent)).componentInstance;
const destroyTaskScreen = () => {
component.showTaskScreen = false;
fixture.detectChanges();
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TaskScreenCloudComponent, TestComponent, TestConditionalWrapperComponent]
});
TestBed.inject(ScreenRenderingService).register({ ['test']: () => TestComponent });
fixture = TestBed.createComponent(TestConditionalWrapperComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should destroy the dynamic component when the task screen is destroyed', () => {
const dynamicComponentInstance = getDynamicComponentInstance();
expect(dynamicComponentInstance.destroyed).toBeFalse();
destroyTaskScreen();
expect(dynamicComponentInstance.destroyed).toBeTrue();
});
it('should remove the dynamic component from the DOM when the task screen is destroyed', () => {
expect(fixture.debugElement.query(By.css('.adf-cloud-test-container'))).toBeTruthy();
destroyTaskScreen();
expect(fixture.debugElement.query(By.css('.adf-cloud-test-container'))).toBeNull();
});
it('should not emit outputs of the dynamic component after the task screen is destroyed', () => {
const onTaskCompletedSpy = spyOn(component, 'onTaskCompleted');
const dynamicComponentInstance = getDynamicComponentInstance();
destroyTaskScreen();
dynamicComponentInstance.taskCompleted.emit();
expect(onTaskCompletedSpy).not.toHaveBeenCalled();
});
it('should not call the dynamic component when switching display mode after destroy', () => {
const taskScreen = getTaskScreen();
const switchToDisplayModeSpy = spyOn(getDynamicComponentInstance(), 'switchToDisplayMode');
destroyTaskScreen();
expect(() => taskScreen.switchToDisplayMode('mode')).not.toThrow();
expect(switchToDisplayModeSpy).not.toHaveBeenCalled();
});
it('should create a new dynamic component instance when the task screen is re-created', () => {
const firstInstance = getDynamicComponentInstance();
destroyTaskScreen();
component.showTaskScreen = true;
fixture.detectChanges();
const secondInstance = getDynamicComponentInstance();
expect(secondInstance).not.toBe(firstInstance);
expect(secondInstance.destroyed).toBeFalse();
expect(secondInstance.taskId).toBe('1');
});
});
describe('TaskScreenCloudComponent - without screenId', () => {
let fixture: ComponentFixture<TaskScreenCloudComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TaskScreenCloudComponent]
});
fixture = TestBed.createComponent(TaskScreenCloudComponent);
});
it('should not create any dynamic component and should not throw', () => {
expect(() => fixture.detectChanges()).not.toThrow();
expect(fixture.debugElement.query(By.directive(TestComponent))).toBeNull();
});
it('should not throw when switching display mode or destroying', () => {
fixture.detectChanges();
expect(() => fixture.componentInstance.switchToDisplayMode('mode')).not.toThrow();
expect(() => fixture.destroy()).not.toThrow();
});
});
@@ -16,7 +16,7 @@
*/
import { CommonModule } from '@angular/common';
import { Component, DestroyRef, EventEmitter, inject, Input, Output } from '@angular/core';
import { Component, ComponentRef, DestroyRef, EventEmitter, inject, Input, Output } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { UserTaskCustomUi } from './screen-cloud.model';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -102,65 +102,65 @@ export class TaskScreenCloudComponent extends BaseScreenCloudComponent<UserTaskC
private readonly destroyRef = inject(DestroyRef);
protected override setInputsForDynamicComponent(): void {
if (this.taskId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'taskId')) {
this.componentRef.setInput('taskId', this.taskId);
protected override setInputsForDynamicComponent(componentRef: ComponentRef<UserTaskCustomUi>): void {
if (this.taskId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'taskId')) {
componentRef.setInput('taskId', this.taskId);
}
if (this.appName && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'appName')) {
this.componentRef.setInput('appName', this.appName);
if (this.appName && Object.prototype.hasOwnProperty.call(componentRef.instance, 'appName')) {
componentRef.setInput('appName', this.appName);
}
if (this.screenId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'screenId')) {
this.componentRef.setInput('screenId', this.screenId);
if (this.screenId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'screenId')) {
componentRef.setInput('screenId', this.screenId);
}
if (this.processInstanceId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'processInstanceId')) {
this.componentRef.setInput('processInstanceId', this.processInstanceId);
if (this.processInstanceId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'processInstanceId')) {
componentRef.setInput('processInstanceId', this.processInstanceId);
}
if (this.taskName && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'taskName')) {
this.componentRef.setInput('taskName', this.taskName);
if (this.taskName && Object.prototype.hasOwnProperty.call(componentRef.instance, 'taskName')) {
componentRef.setInput('taskName', this.taskName);
}
if (this.canClaimTask && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'canClaimTask')) {
this.componentRef.setInput('canClaimTask', this.canClaimTask);
if (this.canClaimTask && Object.prototype.hasOwnProperty.call(componentRef.instance, 'canClaimTask')) {
componentRef.setInput('canClaimTask', this.canClaimTask);
}
if (this.canUnclaimTask && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'canUnclaimTask')) {
this.componentRef.setInput('canUnclaimTask', this.canUnclaimTask);
if (this.canUnclaimTask && Object.prototype.hasOwnProperty.call(componentRef.instance, 'canUnclaimTask')) {
componentRef.setInput('canUnclaimTask', this.canUnclaimTask);
}
if (this.showCancelButton && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'showCancelButton')) {
this.componentRef.setInput('showCancelButton', this.showCancelButton);
if (this.showCancelButton && Object.prototype.hasOwnProperty.call(componentRef.instance, 'showCancelButton')) {
componentRef.setInput('showCancelButton', this.showCancelButton);
}
if (this.rootProcessInstanceId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'rootProcessInstanceId')) {
this.componentRef.setInput('rootProcessInstanceId', this.rootProcessInstanceId);
if (this.rootProcessInstanceId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'rootProcessInstanceId')) {
componentRef.setInput('rootProcessInstanceId', this.rootProcessInstanceId);
}
if (this.showNextTaskCheckbox && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'showNextTaskCheckbox')) {
this.componentRef.setInput('showNextTaskCheckbox', this.showNextTaskCheckbox);
if (this.showNextTaskCheckbox && Object.prototype.hasOwnProperty.call(componentRef.instance, 'showNextTaskCheckbox')) {
componentRef.setInput('showNextTaskCheckbox', this.showNextTaskCheckbox);
}
if (this.isNextTaskCheckboxChecked && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'isNextTaskCheckboxChecked')) {
this.componentRef.setInput('isNextTaskCheckboxChecked', this.isNextTaskCheckboxChecked);
if (this.isNextTaskCheckboxChecked && Object.prototype.hasOwnProperty.call(componentRef.instance, 'isNextTaskCheckboxChecked')) {
componentRef.setInput('isNextTaskCheckboxChecked', this.isNextTaskCheckboxChecked);
}
}
protected override subscribeToOutputs(): void {
if (this.componentRef.instance?.taskSaved) {
this.componentRef.instance.taskSaved.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskSaved.emit());
protected override subscribeToOutputs(componentRef: ComponentRef<UserTaskCustomUi>): void {
if (componentRef.instance?.taskSaved) {
componentRef.instance.taskSaved.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskSaved.emit());
}
if (this.componentRef.instance?.taskCompleted) {
this.componentRef.instance.taskCompleted
if (componentRef.instance?.taskCompleted) {
componentRef.instance.taskCompleted
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((openNextTask) => this.taskCompleted.emit(openNextTask));
}
if (this.componentRef.instance?.error) {
this.componentRef.instance.error.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.error.emit(data));
if (componentRef.instance?.error) {
componentRef.instance.error.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.error.emit(data));
}
if (this.componentRef.instance?.claimTask) {
this.componentRef.instance.claimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.claimTask.emit(data));
if (componentRef.instance?.claimTask) {
componentRef.instance.claimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.claimTask.emit(data));
}
if (this.componentRef.instance?.unclaimTask) {
this.componentRef.instance.unclaimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.unclaimTask.emit(data));
if (componentRef.instance?.unclaimTask) {
componentRef.instance.unclaimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.unclaimTask.emit(data));
}
if (this.componentRef.instance?.cancelTask) {
this.componentRef.instance.cancelTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.cancelTask.emit(data));
if (componentRef.instance?.cancelTask) {
componentRef.instance.cancelTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.cancelTask.emit(data));
}
if (this.componentRef.instance?.nextTaskCheckboxCheckedChanged) {
this.componentRef.instance.nextTaskCheckboxCheckedChanged
if (componentRef.instance?.nextTaskCheckboxCheckedChanged) {
componentRef.instance.nextTaskCheckboxCheckedChanged
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data) => this.nextTaskCheckboxCheckedChanged.emit(data));
}