feat(dashboards-ng): support widgets as standalone component#1036
feat(dashboards-ng): support widgets as standalone component#1036spike-rabbit merged 1 commit intomainfrom
Conversation
WalkthroughThe PR migrates the dashboards widget system from module-based loading to dual provisioning, supporting both Angular modules and standalone components. Modules are removed, widget descriptors are refactored to use component loaders, the loader service is updated with conditional routing logic, and type definitions enforce mutually exclusive configurations. Changes
Sequence Diagram(s)sequenceDiagram
participant WidgetHost
participant Loader as Widget Loader Service
participant ModuleLoader as Module Loader
participant ComponentLoader as Component Loader
participant ModuleRef as NgModuleRef
participant CompFactory as Component Factory
WidgetHost->>Loader: loadAndAttachComponent(config)
alt Has moduleName
Loader->>ModuleLoader: moduleLoader()
ModuleLoader->>ModuleRef: createNgModuleRef()
ModuleRef->>CompFactory: componentFactoryResolver.get(component)
CompFactory-->>Loader: ComponentRef
else Has componentLoader
Loader->>ComponentLoader: componentLoader(name)
ComponentLoader-->>Loader: ComponentClass
Loader->>CompFactory: createComponent(ComponentClass)
CompFactory-->>Loader: ComponentRef
else Error
Loader-->>WidgetHost: Invalid factory config
end
Loader-->>WidgetHost: ComponentRef (mounted)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
35c191a to
df66eba
Compare
|
Documentation. Coverage Reports: |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (8)
projects/dashboards-demo/src/app/widgets/hello-widget/widget-descriptors.ts (1)
7-13: Migration to async component loading looks good.The loader function correctly implements dynamic component loading with appropriate error handling.
Consider two optional improvements:
Type safety: Replace
Promise<any>with a more specific return type to preserve type information.Code reuse: This loader pattern is duplicated in
contact-descriptors.tsandcharts/widget-descriptors.ts. Consider extracting a shared factory function:function createComponentLoader(modulePath: string, allowedComponents: string[]) { return async (name: string): Promise<any> => { if (allowedComponents.includes(name)) { return import(modulePath).then(m => m[name as keyof typeof m]); } throw new Error(`Unknown component to be loaded ${name}`); }; } const loaderFunction = createComponentLoader('./index', [ 'HelloWidgetComponent', 'HelloWidgetEditorComponent' ]);projects/dashboards-demo/src/app/widgets/contact-widget/contact-descriptors.ts (1)
7-13: Duplicate loader pattern detected.This loader function is nearly identical to the one in
hello-widget/widget-descriptors.ts(lines 7-13). Consider the shared factory approach suggested in that file's review to eliminate this duplication.projects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts (1)
7-21: Loader function follows established pattern but scales poorly.The implementation is correct, but this file demonstrates why the duplicated loader pattern creates maintenance overhead - the component list must be manually kept in sync with exports.
The shared factory approach suggested in
hello-widget/widget-descriptors.tswould be especially beneficial here given the larger number of components to maintain.projects/dashboards-ng/src/model/widgets.model.ts (1)
5-5: Widget factory typing for module vs standalone looks consistent; consider tightening componentLoader typingThe new
WidgetComponentTypeBaseConfig+ModuleOptions/StandaloneOptionsunion correctly enforces mutual exclusivity ofmoduleLoaderandcomponentLoaderand matches the loader logic. You could optionally:
- Narrow
componentLoader’s return type away fromPromise<Type<unknown>>/anytoward a more specific generic (e.g.,Type<WidgetInstance | WidgetInstanceEditor>) to improve editor/compile‑time help.- Reconsider the
[index: string]: anyon the base config if you want stronger type safety for future consumers; right now it hides many mistakes at compile time.These are refinements rather than blockers; the current shape is functionally fine.
Also applies to: 59-79
projects/dashboards-ng/src/widget-loader.ts (1)
117-151: Add a defensive guard when neither moduleLoader nor componentLoader is definedThe fallback
const loader = factory.moduleLoader ?? factory.componentLoader;is correct for well‑typed factories, but if a consumer misconfigures a widget (e.g. JS usage oranycasts),loadercan beundefined, causing a hard runtime error when invoked instead of a clear message.You can cheaply harden this with a guard and slightly simplify error formatting:
- if (factory[componentName]) { - const loader = factory.moduleLoader ?? factory.componentLoader; - loader(factory[componentName]!).then( + if (factory[componentName]) { + const loader = factory.moduleLoader ?? factory.componentLoader; + if (!loader) { + result.error( + `Provided component factory has neither moduleLoader nor componentLoader configured` + ); + return result; + } + loader(factory[componentName]!).then( @@ - const msg = rejection - ? `${errorMsg} with ${JSON.stringify(rejection.toString())}` - : `${errorMsg}`; + const msg = rejection + ? `${errorMsg} with ${String(rejection)}` + : errorMsg;Functionality stays the same for valid configs but failure modes become clearer and less brittle.
Also applies to: 137-144
projects/dashboards-ng/README.md (1)
167-175: Widget development section matches implementation; consider adding a tiny exampleThe description that widgets “have to be provided by an Angular module or as standalone” and that you “must provide either a module loader function with module name or a component loader function for standalone components” is in line with the new
WidgetComponentFactory/loader behavior. For extra clarity, you could add a short module‑based vs standalonecomponentFactorysnippet so users see both concrete shapes immediately.projects/dashboards-ng/test/test-widget/test-widget.ts (1)
15-23: Standalone loader aligns with new componentLoader contract; type could be made more specific
loaderFunctionStandalonecorrectly returns the concrete component export and matches thecomponentLoader(componentName: string) => Promise<...>usage inwidget-loader.ts. You could optionally tighten the signature fromPromise<any>to the sameType<unknown>(or a more specific generic) used inStandaloneOptionsto keep type information consistent end‑to‑end.projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts (1)
35-160: Nice parametric tests over both widget variants; decouple config from TEST_WIDGET idLooping over
{ widget, name }to run the same suite forTEST_WIDGETandTEST_WIDGET_STANDALONEis a good way to guarantee behavior parity between module‑based and standalone widgets. The only small coupling is thatTEST_WIDGET_CONFIG_0.widgetIdis hard‑coded toTEST_WIDGET.id, so the standalone tests rely on both widgets sharing the same id string.You could make the tests more future‑proof by overriding
widgetIdper variant when setting up the input:- gridService.widgetCatalog.set([widget]); - fixture.componentRef.setInput('widgetConfig', TEST_WIDGET_CONFIG_0); + gridService.widgetCatalog.set([widget]); + fixture.componentRef.setInput('widgetConfig', { + ...TEST_WIDGET_CONFIG_0, + widgetId: widget.id + });That way, the tests stay correct even if you later give the standalone widget a distinct id.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
projects/dashboards-demo/src/app/widgets/charts/charts-widget.module.ts(0 hunks)projects/dashboards-demo/src/app/widgets/charts/index.ts(0 hunks)projects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts(9 hunks)projects/dashboards-demo/src/app/widgets/contact-widget/contact-descriptors.ts(2 hunks)projects/dashboards-demo/src/app/widgets/contact-widget/contact-widget.module.ts(0 hunks)projects/dashboards-demo/src/app/widgets/contact-widget/index.ts(0 hunks)projects/dashboards-demo/src/app/widgets/hello-widget/hello-widget.module.ts(0 hunks)projects/dashboards-demo/src/app/widgets/hello-widget/index.ts(0 hunks)projects/dashboards-demo/src/app/widgets/hello-widget/widget-descriptors.ts(2 hunks)projects/dashboards-ng/README.md(2 hunks)projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts(2 hunks)projects/dashboards-ng/src/model/widgets.model.ts(2 hunks)projects/dashboards-ng/src/widget-loader.ts(1 hunks)projects/dashboards-ng/test/test-widget/test-widget.ts(2 hunks)
💤 Files with no reviewable changes (6)
- projects/dashboards-demo/src/app/widgets/hello-widget/hello-widget.module.ts
- projects/dashboards-demo/src/app/widgets/charts/index.ts
- projects/dashboards-demo/src/app/widgets/hello-widget/index.ts
- projects/dashboards-demo/src/app/widgets/contact-widget/contact-widget.module.ts
- projects/dashboards-demo/src/app/widgets/contact-widget/index.ts
- projects/dashboards-demo/src/app/widgets/charts/charts-widget.module.ts
🧰 Additional context used
🧬 Code graph analysis (2)
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts (1)
projects/dashboards-ng/test/test-widget/test-widget.ts (3)
TEST_WIDGET(25-43)TEST_WIDGET_STANDALONE(45-62)TEST_WIDGET_CONFIG_0(64-74)
projects/dashboards-ng/test/test-widget/test-widget.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
Widget(16-44)
🔇 Additional comments (6)
projects/dashboards-demo/src/app/widgets/hello-widget/widget-descriptors.ts (1)
20-25: LGTM! ComponentFactory correctly migrated to standalone pattern.The switch from
moduleName/moduleLoadertocomponentLoaderaligns with the PR's objective to support widgets as standalone components.projects/dashboards-demo/src/app/widgets/contact-widget/contact-descriptors.ts (1)
20-24: ComponentFactory migration looks correct.The transition to
componentLoadermaintains consistency with the standalone component pattern.projects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts (1)
23-179: All widget descriptors correctly migrated to componentLoader.All seven widget descriptors (LINE_CHART_DESC, BAR_CHART_DESC, CIRCLE_CHART_DESC, GAUGE_CHART_DESC, VALUE_WIDGET, LIST_WIDGET, TIMELINE_WIDGET) have been consistently updated to use
componentLoader, maintaining the standalone component pattern across the board.projects/dashboards-ng/README.md (1)
19-19: Docs note about standalone and modules is accurate“The library supports standalone components and modules.” correctly reflects the current API surface; no change needed.
projects/dashboards-ng/test/test-widget/test-widget.ts (1)
45-62: TEST_WIDGET_STANDALONE definition mirrors TEST_WIDGET and looks correctThe standalone test widget reuses the same id, defaults, and payload as
TEST_WIDGETwhile switching tocomponentLoader: loaderFunctionStandalone, which is exactly what you need to exercise both loading paths without duplicating test data. No issues from a test perspective.projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts (1)
17-21: Import of TEST_WIDGET_STANDALONE is consistent with new test widgetAdding
TEST_WIDGET_STANDALONEalongsideTEST_WIDGETandTEST_WIDGET_CONFIG_0is consistent with the new standalone widget definition and keeps all test fixtures in one place.
df66eba to
4f86474
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
projects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts (1)
7-21: Consider simplifying the async function and addressing code duplication.The
asyncfunction uses.then()instead ofawait, which is less idiomatic. Additionally, this pattern is duplicated intest-widget.ts(loaderFunctionStandalone). Consider:
- Using
awaitfor cleaner async code- Creating a shared utility function to reduce duplication
Apply this diff to simplify the function:
-const loaderFunction = async (name: string): Promise<any> => { +const loaderFunction = async (name: string): Promise<any> => { if ( name === 'CircleComponent' || name === 'CartesianComponent' || name === 'GaugeComponent' || name === 'ValueWidgetComponent' || name === 'ValueWidgetEditorComponent' || name === 'ListWidgetComponent' || name === 'TimelineWidgetComponent' ) { - return import('../../widgets/charts/index').then(m => m[name as keyof typeof m]); + const m = await import('../../widgets/charts/index'); + return m[name as keyof typeof m]; } else { throw new Error(`Unknown component to be loaded ${name}`); } };projects/dashboards-ng/test/test-widget/test-widget.ts (1)
15-23: Code duplication with widget-descriptors.ts loaderFunction.The
loaderFunctionStandalonefollows the same pattern as theloaderFunctioninprojects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts. While the duplication is currently limited to test code, consider creating a shared utility function if this pattern is used in additional places to improve maintainability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
projects/dashboards-demo/src/app/widgets/charts/charts-widget.module.ts(0 hunks)projects/dashboards-demo/src/app/widgets/charts/index.ts(0 hunks)projects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts(9 hunks)projects/dashboards-demo/src/app/widgets/contact-widget/contact-descriptors.ts(2 hunks)projects/dashboards-demo/src/app/widgets/contact-widget/contact-widget.module.ts(0 hunks)projects/dashboards-demo/src/app/widgets/contact-widget/index.ts(0 hunks)projects/dashboards-demo/src/app/widgets/hello-widget/hello-widget.module.ts(0 hunks)projects/dashboards-demo/src/app/widgets/hello-widget/index.ts(0 hunks)projects/dashboards-demo/src/app/widgets/hello-widget/widget-descriptors.ts(2 hunks)projects/dashboards-ng/README.md(2 hunks)projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts(2 hunks)projects/dashboards-ng/src/model/widgets.model.ts(2 hunks)projects/dashboards-ng/src/widget-loader.ts(1 hunks)projects/dashboards-ng/test/test-widget/test-widget.ts(2 hunks)
💤 Files with no reviewable changes (6)
- projects/dashboards-demo/src/app/widgets/charts/index.ts
- projects/dashboards-demo/src/app/widgets/hello-widget/index.ts
- projects/dashboards-demo/src/app/widgets/hello-widget/hello-widget.module.ts
- projects/dashboards-demo/src/app/widgets/contact-widget/contact-widget.module.ts
- projects/dashboards-demo/src/app/widgets/contact-widget/index.ts
- projects/dashboards-demo/src/app/widgets/charts/charts-widget.module.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- projects/dashboards-demo/src/app/widgets/contact-widget/contact-descriptors.ts
- projects/dashboards-ng/src/widget-loader.ts
- projects/dashboards-demo/src/app/widgets/hello-widget/widget-descriptors.ts
- projects/dashboards-ng/src/model/widgets.model.ts
🧰 Additional context used
🧬 Code graph analysis (2)
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts (1)
projects/dashboards-ng/test/test-widget/test-widget.ts (3)
TEST_WIDGET(25-43)TEST_WIDGET_STANDALONE(45-62)TEST_WIDGET_CONFIG_0(64-74)
projects/dashboards-ng/test/test-widget/test-widget.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
Widget(16-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
projects/dashboards-ng/README.md (2)
19-19: LGTM!The updated documentation clearly states that the library supports both standalone components and modules, which accurately reflects the PR changes.
167-174: Documentation accurately reflects type definitions.The type system enforces the documented requirement perfectly.
WidgetComponentTypeFactoryuses a discriminated union ofModuleOptions | StandaloneOptions, where each option forbids the other's fields via?: never. This prevents providing both loaders simultaneously and guarantees compliance with the documented "either/or" requirement.projects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts (1)
23-179: LGTM!The widget descriptors have been consistently migrated from module-based loading (
moduleName+moduleLoader) to component-based loading (componentLoader). The pattern is uniform across all seven descriptors, maintaining backward compatibility with existing descriptor properties.projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts (1)
35-160: Excellent test coverage for both widget loading strategies!The test suite has been properly refactored to use a data-driven approach that validates both module-based (
TEST_WIDGET) and standalone (TEST_WIDGET_STANDALONE) widget loading. All test cases are executed for both variants, ensuring backward compatibility while validating the new standalone component support.projects/dashboards-ng/test/test-widget/test-widget.ts (1)
45-62: LGTM!The
TEST_WIDGET_STANDALONEprovides essential test infrastructure for validating standalone component loading. The descriptor correctly usescomponentLoaderinstead ofmoduleName/moduleLoader, enabling the test suite to verify both loading strategies work correctly.
4f86474 to
c2692ad
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
projects/dashboards-demo/src/app/widgets/hello-widget/widget-descriptors.ts (1)
7-13: Add runtime validation for the imported export.The dynamic export selection
m[name as keyof typeof m]will returnundefinedif the export doesn't exist in the module, bypassing the error handling on line 11. This could cause cryptic failures downstream when the widget loader tries to instantiateundefinedas a component.Apply this diff to add validation:
-const loaderFunction = async (name: string): Promise<any> => { +const loaderFunction = async (name: string): Promise<any> => { if (name === 'HelloWidgetComponent' || name === 'HelloWidgetEditorComponent') { - return import('./index').then(m => m[name as keyof typeof m]); + return import('./index').then(m => { + const component = m[name as keyof typeof m]; + if (!component) { + throw new Error(`Component ${name} not found in module exports`); + } + return component; + }); } else { throw new Error(`Unknown component to be loaded ${name}`); } };
🧹 Nitpick comments (6)
projects/dashboards-ng/README.md (1)
167-174: Add example code for standalone widget development.The documentation now correctly mentions that widgets can be provided as standalone components with a component loader function, but unlike the setup sections (which show both standalone and module examples with code), the widget development guidance lacks a concrete example of the standalone pattern.
Consider adding a code example demonstrating:
- The component loader function signature and how it imports/returns named components
- A sample widget descriptor using
componentFactory,componentName,editorComponentName, andcomponentLoaderThis would parallel the clarity of the existing setup examples and help developers understand the new pattern.
Example addition after line 174:
// Standalone widget loader example export const myWidgetLoader: WidgetComponentLoader = async () => ({ MyWidgetComponent: (await import('./my-widget.component')).MyWidgetComponent, MyWidgetEditorComponent: (await import('./my-widget-editor.component')).MyWidgetEditorComponent, }); // Widget descriptor const myWidget: Widget = { id: 'my-widget', title: 'My Widget', componentFactory: { componentName: 'MyWidgetComponent', editorComponentName: 'MyWidgetEditorComponent', componentLoader: myWidgetLoader, }, };projects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts (1)
7-21: Consider usingawaitfor idiomatic async/await pattern.The function is declared
asyncbut uses.then()for the dynamic import. While this works correctly, usingawaitwould be more consistent with modern async/await patterns.Apply this diff to use
await:-const loaderFunction = async (name: string): Promise<any> => { +const loaderFunction = async (name: string): Promise<any> => { if ( name === 'CircleComponent' || name === 'CartesianComponent' || name === 'GaugeComponent' || name === 'ValueWidgetComponent' || name === 'ValueWidgetEditorComponent' || name === 'ListWidgetComponent' || name === 'TimelineWidgetComponent' ) { - return import('../../widgets/charts/index').then(m => m[name as keyof typeof m]); + const m = await import('../../widgets/charts/index'); + return m[name as keyof typeof m]; } else { throw new Error(`Unknown component to be loaded ${name}`); } };Optional: Consider stronger return typing.
The return type
Promise<any>loses type safety. If the component types are available, consider using a union type of all component classes for better type checking.projects/dashboards-ng/src/model/widgets.model.ts (1)
59-62: Consider narrowing the index signature for better type safety.The
[index: string]: any;index signature permits arbitrary properties of any type, which weakens type safety. If additional properties are only needed for specific use cases, consider using a more restrictive type or requiring explicit extension.projects/dashboards-demo/src/app/widgets/contact-widget/contact-descriptors.ts (1)
7-13: Consider improving type safety for the loader function.The return type
Promise<any>is permissive and loses type information. Since the function loads specific component types, consider using a more specific return type (e.g.,Promise<Type<any>>if using Angular's Type, or a union of the expected component types) to improve type safety and IDE support.projects/dashboards-ng/src/widget-loader.ts (2)
128-133: Redundantresult.complete()afterresult.error().In RxJS, calling
error()on a Subject terminates it; the subsequentcomplete()call is a no-op. Additionally,JSON.stringify(rejection.toString())double-wraps the string in quotes.rejection => { const errorMsg = `Loading widget module ${factory.moduleName} failed`; - const msg = rejection ? `${errorMsg} with ${JSON.stringify(rejection.toString())}` : errorMsg; + const msg = rejection ? `${errorMsg} with ${rejection.toString()}` : errorMsg; result.error(msg); - result.complete(); }
153-158: Same issues asloadModuleBasedComponent: redundant completion and double-stringification.rejection => { const errorMsg = `Loading widget component ${componentKey} failed`; - const msg = rejection ? `${errorMsg} with ${JSON.stringify(rejection.toString())}` : errorMsg; + const msg = rejection ? `${errorMsg} with ${rejection.toString()}` : errorMsg; result.error(msg); - result.complete(); }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
projects/dashboards-demo/src/app/widgets/charts/charts-widget.module.ts(0 hunks)projects/dashboards-demo/src/app/widgets/charts/index.ts(0 hunks)projects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts(9 hunks)projects/dashboards-demo/src/app/widgets/contact-widget/contact-descriptors.ts(2 hunks)projects/dashboards-demo/src/app/widgets/contact-widget/contact-widget.module.ts(0 hunks)projects/dashboards-demo/src/app/widgets/contact-widget/index.ts(0 hunks)projects/dashboards-demo/src/app/widgets/hello-widget/hello-widget.module.ts(0 hunks)projects/dashboards-demo/src/app/widgets/hello-widget/index.ts(0 hunks)projects/dashboards-demo/src/app/widgets/hello-widget/widget-descriptors.ts(2 hunks)projects/dashboards-ng/README.md(2 hunks)projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts(2 hunks)projects/dashboards-ng/src/model/widgets.model.ts(2 hunks)projects/dashboards-ng/src/widget-loader.ts(2 hunks)projects/dashboards-ng/test/test-widget/test-widget.ts(2 hunks)
💤 Files with no reviewable changes (6)
- projects/dashboards-demo/src/app/widgets/hello-widget/index.ts
- projects/dashboards-demo/src/app/widgets/contact-widget/index.ts
- projects/dashboards-demo/src/app/widgets/contact-widget/contact-widget.module.ts
- projects/dashboards-demo/src/app/widgets/charts/charts-widget.module.ts
- projects/dashboards-demo/src/app/widgets/hello-widget/hello-widget.module.ts
- projects/dashboards-demo/src/app/widgets/charts/index.ts
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-12-17T04:34:55.597Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1173
File: src/app/examples/si-charts/interactive/interactive.ts:5-5
Timestamp: 2025-12-17T04:34:55.597Z
Learning: Guideline: In Angular projects using OnPush change detection (zoneless mode), you only need to call ChangeDetectorRef.markForCheck() for asynchronous operations (e.g., setTimeout, setInterval, Promises, Observables) where a change is not automatically detected. Event handlers triggered by Angular template bindings (click, input, etc.) will trigger change detection automatically and do not require explicit markForCheck() calls. Apply this broadly to TS files within Angular components/services that use OnPush; use markForCheck() selectively for async work where change detection wouldn’t run otherwise.
Applied to files:
projects/dashboards-ng/src/model/widgets.model.tsprojects/dashboards-demo/src/app/widgets/charts/widget-descriptors.tsprojects/dashboards-demo/src/app/widgets/hello-widget/widget-descriptors.tsprojects/dashboards-ng/test/test-widget/test-widget.tsprojects/dashboards-ng/src/widget-loader.tsprojects/dashboards-demo/src/app/widgets/contact-widget/contact-descriptors.tsprojects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-15T07:17:06.981Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts:37-49
Timestamp: 2025-12-15T07:17:06.981Z
Learning: In widget-instance-editor-dialog component tests (projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing component initialization. This is likely due to the component's lifecycle behavior or modal initialization timing that fixture.whenStable() cannot properly wait for in zoneless mode.
Applied to files:
projects/dashboards-ng/test/test-widget/test-widget.tsprojects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-15T07:16:32.082Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:69-80
Timestamp: 2025-12-15T07:16:32.082Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing grid item rendering and initialization. This is likely due to gridstack library's initialization timing or lifecycle behavior that fixture.whenStable() cannot properly wait for in zoneless mode.
Applied to files:
projects/dashboards-ng/test/test-widget/test-widget.tsprojects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-15T07:16:53.762Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:92-105
Timestamp: 2025-12-15T07:16:53.762Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits must be used instead of fixture.whenStable() to avoid injector destroyed errors during mount/unmount operations in zoneless mode. The GridStack timing-dependent operations require actual async delays.
Applied to files:
projects/dashboards-ng/test/test-widget/test-widget.tsprojects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-04T11:54:31.132Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1133
File: projects/element-ng/tour/si-tour.service.spec.ts:21-26
Timestamp: 2025-12-04T11:54:31.132Z
Learning: In the siemens/element repository, all components are standalone by default and do not require the explicit `standalone: true` flag. Components should be added to the `imports` array in TestBed configuration, not the `declarations` array.
Applied to files:
projects/dashboards-ng/test/test-widget/test-widget.tsprojects/dashboards-ng/README.mdprojects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-09T14:19:34.846Z
Learnt from: Killusions
Repo: siemens/element PR: 967
File: package.json:153-153
Timestamp: 2025-12-09T14:19:34.846Z
Learning: In library projects like element-ng, optional features that are provided through callback/function parameters (e.g., syntaxHighlighter in markdown renderer) should not have their implementation dependencies (e.g., highlight.js) listed in the library's dependencies. These should remain in devDependencies for demo/example purposes, and consuming applications should install them directly if they choose to use those features.
Applied to files:
projects/dashboards-ng/README.md
📚 Learning: 2025-12-11T10:09:01.564Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1151
File: projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts:83-104
Timestamp: 2025-12-11T10:09:01.564Z
Learning: In the auto-collapsable-list directive tests (projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts), jasmine.clock() successfully controls the directive's setTimeout-based layout logic in zoneless mode. Mock timers work fine here, unlike in select component overflow tests where real setTimeout waits are required.
Applied to files:
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-08T11:25:51.584Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1163
File: projects/element-ng/select/si-select.component.spec.ts:511-516
Timestamp: 2025-12-08T11:25:51.584Z
Learning: In the select component overflow tests (projects/element-ng/select/si-select.component.spec.ts), jasmine.clock() cannot be used to control the timing for overflow item detection. Real setTimeout waits must be used instead, likely due to ResizeObserver or debounced layout calculations that jasmine.clock() cannot control in zoneless mode.
Applied to files:
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-05T08:00:38.407Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1151
File: projects/element-ng/typeahead/si-typeahead.directive.spec.ts:114-121
Timestamp: 2025-12-05T08:00:38.407Z
Learning: In the typeahead test suite (projects/element-ng/typeahead/si-typeahead.directive.spec.ts), jasmine.clock() must be installed in beforeAll and uninstalled in afterAll (not beforeEach/afterEach) because using per-test clock installation causes schedulerFunction-related issues in these specific tests.
Applied to files:
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-01T04:01:27.365Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1099
File: projects/element-ng/accordion/si-accordion.component.spec.ts:39-43
Timestamp: 2025-12-01T04:01:27.365Z
Learning: With Angular 20 and esbuild, synchronous `TestBed.configureTestingModule(...).compileComponents()` calls work correctly even when components use external templates (templateUrl), because esbuild pre-compiles templates at build time. The async/await pattern is not required in this scenario.
Applied to files:
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-04T05:50:17.637Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:953-964
Timestamp: 2025-12-04T05:50:17.637Z
Learning: In the siemens/element repository, when making tree-view tests zoneless-ready, jasmine.clock() should be used to mimic setTimeouts even in zoneless mode, as the component implementation relies on setTimeout calls that need to be controlled in tests.
Applied to files:
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-04T05:50:38.735Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:1244-1245
Timestamp: 2025-12-04T05:50:38.735Z
Learning: In zoneless Angular tests with OnPush components, calling `await runOnPushChangeDetection(fixture)` followed by `await fixture.whenStable()` is NOT redundant. The `runOnPushChangeDetection` helper forces change detection to run for OnPush components, while `whenStable()` waits for all async operations to complete. Both are necessary and serve different purposes.
Applied to files:
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
📚 Learning: 2025-12-15T10:05:59.100Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1193
File: projects/element-ng/search-bar/si-search-bar.component.spec.ts:21-28
Timestamp: 2025-12-15T10:05:59.100Z
Learning: In tests that rely on Jasmine's fake clock to control time-based RxJS operators (e.g., debounceTime), use jasmine.clock().install() together with jasmine.clock().mockDate() to enable precise timing control. Without mockDate, scheduler-based operators may not respond to the fake clock. Apply this guidance to spec files (e.g., any file named *.spec.ts or *.spec.js) that test time-sensitive behavior, ensuring you install and mockDate before advancing time in tests.
Applied to files:
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts
🧬 Code graph analysis (3)
projects/dashboards-ng/test/test-widget/test-widget.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
Widget(16-44)
projects/dashboards-ng/src/widget-loader.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
WidgetComponentTypeFactory(78-78)
projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts (1)
projects/dashboards-ng/test/test-widget/test-widget.ts (3)
TEST_WIDGET(25-43)TEST_WIDGET_STANDALONE(45-62)TEST_WIDGET_CONFIG_0(64-74)
🔇 Additional comments (7)
projects/dashboards-demo/src/app/widgets/charts/widget-descriptors.ts (1)
23-179: LGTM! Widget descriptors successfully migrated to componentLoader pattern.All seven widget descriptors have been correctly updated to use
componentLoaderinstead of the previousmoduleName/moduleLoaderapproach. The component names referenced in each descriptor match those validated in theloaderFunction, and the VALUE_WIDGET properly specifies both the widget and editor components.This migration aligns perfectly with the PR objectives of supporting standalone components while maintaining backward compatibility.
projects/dashboards-ng/src/model/widgets.model.ts (1)
73-73: Verify componentLoader signature matches intended design.The current signature includes a
componentNameparameter:<T = unknown>(componentName: string) => Promise<Type<T>>. A past review comment suggested a simpler signature without parameters:() => Promise<Type<unknown>>. The current implementation in test-widget.ts (line 15-23) uses the componentName parameter, so this appears intentional. However, please confirm this signature change was deliberate.projects/dashboards-ng/test/test-widget/test-widget.ts (1)
45-62: Verify that duplicate widget IDs won't cause catalog conflicts.Both
TEST_WIDGET(line 27) andTEST_WIDGET_STANDALONEshare the sameidvalue'@siemens/dashboards-ng/TestWidgetComponent'. If both widgets are registered in the widget catalog simultaneously, this could cause key conflicts or unexpected behavior. The parameterized tests in si-widget-host.component.spec.ts appear to use one variant at a time, but verify this design is intentional and won't cause issues in other test scenarios.projects/dashboards-ng/src/components/widget-host/si-widget-host.component.spec.ts (1)
35-160: Excellent test refactoring for dual-variant coverage.The parameterized test structure effectively eliminates duplication while ensuring both module-based (
TEST_WIDGET) and standalone (TEST_WIDGET_STANDALONE) widgets are tested consistently. The per-variant setup maintains proper test isolation, and the shared test logic follows the DRY principle without sacrificing readability.projects/dashboards-demo/src/app/widgets/contact-widget/contact-descriptors.ts (1)
20-24: Migration to componentLoader pattern is correct and complete.The shift from module-based loading (
moduleName/moduleLoader) to component-based loading (componentLoader) properly supports standalone Angular components. The configuration correctly specifies both component names, and the./indexmodule exports bothContactWidgetComponentandContactWidgetEditorComponentas expected. TheloaderFunctioncorrectly handles dynamic imports using the component names as keys, with proper error handling for unknown components.projects/dashboards-demo/src/app/widgets/hello-widget/widget-descriptors.ts (1)
20-25: The implementation correctly supports dynamic loading of standalone components.The migration to
componentLoaderis properly implemented:
- The
./indeximport path is correct and resolves to the hello-widget directory's index file- Both
HelloWidgetComponentandHelloWidgetEditorComponentare correctly exported from index.ts- The
loaderFunctionproperly handles dynamic imports via the componentLoader pattern- The widget-loader service correctly consumes the componentLoader configuration
No changes needed.
projects/dashboards-ng/src/widget-loader.ts (1)
109-160: Good separation of loading strategies into dedicated functions.The extraction of
loadModuleBasedComponentandloadStandaloneComponentimproves readability and addresses the prior feedback about excessive conditionals. The pattern is consistent across both loaders.
33b2669 to
0bfd5a1
Compare
0bfd5a1 to
ea74260
Compare
Support widgets as standalone Angular components. This change maintains backward compatibility with existing module-based widgets while enabling modern standalone component architecture.
Usage:
For standalone components, use `componentLoader` in your widget descriptor:
```ts
const loaderFunction = async (name: string): Promise<any> => {
if (name === 'MyWidgetComponent' || name === 'MyWidgetEditorComponent') {
return import('./index').then(m => m[name as keyof typeof m]);
}
throw new Error(`Unknown component: ${name}`);
};
export const MY_WIDGET: Widget = {
name: 'My Widget',
id: '@myapp/my-widget',
componentFactory: {
componentName: 'MyWidgetComponent',
editorComponentName: 'MyWidgetEditorComponent',
componentLoader: loaderFunction
},
...
};
```
ea74260 to
0ec2f96
Compare
Support widgets as standalone Angular components. This change maintains backward compatibility with existing module-based widgets while enabling modern standalone component architecture.
Usage:
For standalone components, use
componentLoaderin your widget descriptor:Summary by CodeRabbit
New Features
Refactor
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.