Skip to content

Conversation

@chintankavathia
Copy link
Member

@chintankavathia chintankavathia commented Nov 18, 2025

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:

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
  },
  ...
};

Summary by CodeRabbit

  • New Features

    • Widgets can be provided as standalone components or via modules; loader logic now supports per-component loading.
  • Refactor

    • Removed several consolidated NgModule wrappers and reworked descriptors to use component-level loaders.
    • Core loader now falls back between module and component loading and differentiates error messages.
  • Documentation

    • README updated to reflect dual provisioning and mandatory widget interfaces.
  • Tests

    • Tests extended to exercise both module-based and standalone widget variants.

@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding support for widgets as standalone components, which is the central focus of all modifications across the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/dashboards/support-standalone-widgets

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chintankavathia chintankavathia force-pushed the feat/dashboards/support-standalone-widgets branch 4 times, most recently from 35c191a to df66eba Compare November 18, 2025 05:09
@github-actions
Copy link

@chintankavathia chintankavathia marked this pull request as ready for review November 18, 2025 05:47
@chintankavathia chintankavathia requested a review from a team as a code owner November 18, 2025 05:47
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Type safety: Replace Promise<any> with a more specific return type to preserve type information.

  2. Code reuse: This loader pattern is duplicated in contact-descriptors.ts and charts/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.ts would 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 typing

The new WidgetComponentTypeBaseConfig + ModuleOptions/StandaloneOptions union correctly enforces mutual exclusivity of moduleLoader and componentLoader and matches the loader logic. You could optionally:

  • Narrow componentLoader’s return type away from Promise<Type<unknown>>/any toward a more specific generic (e.g., Type<WidgetInstance | WidgetInstanceEditor>) to improve editor/compile‑time help.
  • Reconsider the [index: string]: any on 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 defined

The fallback const loader = factory.moduleLoader ?? factory.componentLoader; is correct for well‑typed factories, but if a consumer misconfigures a widget (e.g. JS usage or any casts), loader can be undefined, 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 example

The 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 standalone componentFactory snippet 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

loaderFunctionStandalone correctly returns the concrete component export and matches the componentLoader(componentName: string) => Promise<...> usage in widget-loader.ts. You could optionally tighten the signature from Promise<any> to the same Type<unknown> (or a more specific generic) used in StandaloneOptions to 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 id

Looping over { widget, name } to run the same suite for TEST_WIDGET and TEST_WIDGET_STANDALONE is a good way to guarantee behavior parity between module‑based and standalone widgets. The only small coupling is that TEST_WIDGET_CONFIG_0.widgetId is hard‑coded to TEST_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 widgetId per 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5231513 and df66eba.

📒 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/moduleLoader to componentLoader aligns 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 componentLoader maintains 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 correct

The standalone test widget reuses the same id, defaults, and payload as TEST_WIDGET while switching to componentLoader: 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 widget

Adding TEST_WIDGET_STANDALONE alongside TEST_WIDGET and TEST_WIDGET_CONFIG_0 is consistent with the new standalone widget definition and keeps all test fixtures in one place.

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
  },
  ...
};
```
@chintankavathia chintankavathia force-pushed the feat/dashboards/support-standalone-widgets branch from df66eba to 4f86474 Compare November 18, 2025 12:57
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 async function uses .then() instead of await, which is less idiomatic. Additionally, this pattern is duplicated in test-widget.ts (loaderFunctionStandalone). Consider:

  1. Using await for cleaner async code
  2. 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 loaderFunctionStandalone follows the same pattern as the loaderFunction in projects/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

📥 Commits

Reviewing files that changed from the base of the PR and between df66eba and 4f86474.

📒 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. WidgetComponentTypeFactory uses a discriminated union of ModuleOptions | 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_STANDALONE provides essential test infrastructure for validating standalone component loading. The descriptor correctly uses componentLoader instead of moduleName/moduleLoader, enabling the test suite to verify both loading strategies work correctly.

@github-actions
Copy link

Code Coverage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants