Cumulocity IoT Web Development Tutorial - Part 4: Convert component into a widget

Cumulocity IoT Web Development Tutorial - Part 1: Start your journey
Cumulocity IoT Web Development Tutorial - Part 2: Create your first component
Cumulocity IoT Web Development Tutorial - Part 3: Query data from Cumulocity IoT
Cumulocity IoT Web Development Tutorial - Part 4: Convert component into a widget
Cumulocity IoT Web Development Tutorial - Part 5: Provide widget as a UI Plugin

Welcome to the 4th round of the web development series.

minion-boxing

In the last part you have integrated the @c8y/client from the Cumulocity Web SDK to communicate with Cumulocity’s API. In particular, you have learned how to query data from the Inventory using the InventoryService. Among other, the Inventory stores your device data. To have some device data available in the Inventory of your Cumulocity instance, you have created a temperature simulator using the simulator self-service feature in the Device Management application. The temperature simulator sends continuously temperature measurements. Using the MeasurementService from the @c8y/client you have queried the current temperature measurement. To always get the latest measurement from the simulator and display it in the UI you have integrated the Realtime service into your component. With these changes you could remove all previously mocked data from the device-info module and achieve a more dynamic behavior of the component.

In the 4th part of the web development series, the device-info component will be converted into a Cumulocity widget, which can then be used dynamically on Cumulocity dashboards.

Add dashboarding to your application

Originally, dashboards are part of the Cockpit application. But you can integrate the dashboard feature in any of your custom applications, as the necessary component is part of the @c8y/ngx-components package. In this section, you will update your application and add a dashboard to it. You can continue with your project or use part-03 from github. If you are struggling with part 4, you can find the final application in the corresponding github project.

In the src directory create a new folder called named-dashboard. This folder will be home to your dashboard module. Create a new file for the template named named-dashboard.component.html.

named-dashboard.component.html

<c8y-title>Named dashboard</c8y-title>

<c8y-context-dashboard [name]="'My named dashboard'" [canDelete]="false"></c8y-context-dashboard>

The template consists of a c8y-title element to specify the title in the header. In addition, it uses the Cumulocity ContextDashboard component c8y-context-dashboard from @c8y/ngx-components to instantiate a new dashboard. In this case, the dashboard will be a named dashboard. This means the context of your dashboard is defined by its name and therefore will be identified by its name: My named dashboard. To learn more about context dashboards have a look at the tutorial application of Cumulocity. The tutorial application provides some examples for Cumulocity dashboards.

Important: When you want to see the possibilities and implementation details of the Web SDK you should try the tutorial application. You can install it by running c8ycli new <<your-app-name>> tutorial .

Create the corresponding component and name the file named-dashboard.component.ts. The purpose of the component is to serve the previously created template:

named-dashboard.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'named-dashboard-component',
  templateUrl: './named-dashboard.component.html',
})
export class NamedDashboardComponent implements OnInit {
  constructor() {}

  ngOnInit() {}
}

You will need a navigation factory to add a navigation entry to the left side menu to navigate to your dashboard. Create a new navigation factory called: named-dashboard.factory.ts.

named-dashboard.factory.ts

import { Injectable } from '@angular/core';
import { NavigatorNode, NavigatorNodeFactory } from '@c8y/ngx-components';

@Injectable()
export class NamedDashboardNavigationFactory implements NavigatorNodeFactory {
  private readonly DASHBOARD_NAVIGATOR_NODE = new NavigatorNode({
    label: 'Dashboard',
    icon: 'robot',
    path: 'named-dashboard',
    priority: 50,
  });

  get() {
    return this.DASHBOARD_NAVIGATOR_NODE;
  }
}

In the NamedDashboardNavigationFactory you will register a new NavigatorNode, which forwards the user to your dashboard component via the path named-dashboard.

Finally, you need to create the module for your new dashboard component. Create the additional file named-dashboard.module.ts in your named-dashboard folder:

named-dashboard.module.ts

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoreModule, hookNavigator } from '@c8y/ngx-components';
import { ContextDashboardModule } from '@c8y/ngx-components/context-dashboard';
import { NamedDashboardComponent } from './named-dashboard.component';
import { NamedDashboardNavigationFactory } from './named-dashboard.factory';

const routes: Routes = [
  {
    path: '',
    redirectTo: 'named-dashboard',
    pathMatch: 'full',
  },
  {
    path: 'named-dashboard',
    component: NamedDashboardComponent,
  },
];

@NgModule({
  imports: [
    ContextDashboardModule.config(),
    CommonModule,
    RouterModule.forChild(routes),
    CoreModule,
    ContextDashboardModule,
  ],
  exports: [],
  declarations: [NamedDashboardComponent],
  providers: [hookNavigator(NamedDashboardNavigationFactory)],
})
export class NamedDashboardModule {}

The module imports the ContextDashboardModule from @c8y/ngx-components to provide the dashboard feature for your application. Furthermore, the NamedDashboardNavigationFactory is registered and provided using the hookNavigator() function. The path named-dashboard defined in the NamedDashboardNavigationFactory will be connected to the NamedDashboardComponent by using the RouterModule.

Finally, update the AppModule and import your NamedDashboardModule:

import { NamedDashboardModule } from './src/named-dashboard/named-dashboard.module';

@NgModule({
  imports: [
    ...
    NamedDashboardModule,
  ],
  bootstrap: [BootstrapComponent],
})
export class AppModule {} 

If you start your application, you will see a new navigation entry in the left side navigation to access the named dashboard component

For now, you can’t add any widgets to the dashboard, because the list of widgets is empty. You are going to change this by converting the device-info component to a widget.

Convert the device-info component into a c8y widget

Let’s update the DeviceInfoComponent component in the device-info directory. First, remove the device-info.factory.ts file from the project. The DeviceInfoNavigationFactory isn’t needed anymore, as the component should be used on the dashboard in the future.

Update the device-info.module.ts. Remove the DeviceInfoNavigationFactory and the routes (including the RouterModule and the corresponding imports) from it. Now, add the widget configuration to the module. The widget configuration is provided using the hookComponent function from @c8y/ngx-components.

device-info.module.ts

...
import { CoreModule, hookComponent } from '@c8y/ngx-components';
import { ContextWidgetConfig } from '@c8y/ngx-components/context-dashboard';
...

@NgModule({
  imports: [CoreModule],
  exports: [],
  declarations: [DeviceInfoComponent],
  providers: [
    MeasurementRealtimeService,
    hookComponent({
      id: 'device-info.widget',
      label: 'Device Info Widget',
      description: 'This is a sample widget',
      component: DeviceInfoComponent,
      data: {
        settings: {
          noNewWidgets: false,
          ng1: {
            options: {
              noDeviceTarget: false,
              groupsSelectable: false,
            },
          },
        },
      } as ContextWidgetConfig,
    }),
  ],
})
export class DeviceInfoModule {}

In the configuration of the widget, you define following properties:

  • id - unique identifier for you widget
  • label - used as the title in the widget catalog
  • description - used as the description in the widget catalog
  • component - the component, which should be used for the widget. You define the DeviceInfoComponent as the relevant component.

In the data property, you can define some basic configuration for your widget

  • noDeviceTarget - set to false if a you want to enable the device selector for your widget
  • groupsSelectable - set to true if groups should be selectable as well

In case of the device-info widget, you will enable device selection, but disable group selection.

Next you need to update the DeviceInfoComponent to correctly receive the configuration, if the component is added as a widget to the dashboard. Remove the hardcoded device id from the component. Instead add a new variable called config for the configuration of your widget, which is marked as Input() in your component. This variable config is automatically set, when the configuration for your widget is completed and the widget has been added to the dashboard.

device-info.component.ts

import { Component, Input, OnInit } from '@angular/core';
...

export class DeviceInfoComponent implements OnInit {
  @Input() config: { device: { id: string; name: string } };
  
  ...
  
  private async initDeviceDetails() {
    this.deviceDetails = await this.deviceInfoService.getDeviceDetails(this.config.device.id);
  }

  private subscribeForTemperatureMeasurements() {
    this.deviceInfoService.temperatureMeasurement$.subscribe(
      (temperatureMeasurement) => (this.tempteratureMeasurement = temperatureMeasurement)
    );

    this.deviceInfoService.subscribeForTemperatureMeasurements(this.config.device.id);
  }

Instead of using the hardcoded device id for the method calls this.deviceInfoService.getDeviceDetails() and this.deviceInfoService.subscribeForTemperatureMeasurements(), you will use the id of the device, which has been selected as an input for your widget: this.config.device.id.

Lastly, you will update the template for your device-info widget. Simply remove the card class from the root <div> element:

device-info.component.html

<div class="p-l-16 p-r-16" *ngIf="deviceDetails">
  ...
</div>

The card class is removed because if your device-info widget is added to a dashboard, it will automatically be wrapped in a card element.

Run your application locally and add your newly created widget to the named dashboard. Select your temperature simulator as an input device for your widget.

device-info-dashboard

Conclusion

Great, you have successfully converted the device-info component into a widget, which now can be used on Cumulocity dashboards. You can view the full code for part 4 in the corresponding github project.

The Fresh Prince Of Bel Air Carlton Dance GIF by HBO Max

In part 5 we will have a look at the newly introduced Micro-Frontend framework and learn how the device-info widget can be provided as a UI plugin to be used in the Micro-Frontend framework. Check out this article to get a first introduction.

Until next time :wave:

5 Likes