In this tutorial, I show you how I created this sample application focusing on pluggable mechanism.
I've already made a simple version of this dashboard using Angular dynamic components that you can see it here. But I decided to use Webpack Module federation and convert it to pluggable dashboards.
What do i mean, pluggable?
Dashboard widgets can be loaded from separated compiled and deployed artifacts, and adding new widget or changing widgets can be done without any changes to shell application.(Designer & Viewer)
Any dashboard widget is a Angular application.
use Angular cli to add an application
ng g application --minimal new-widget
After that change some application settings on Angular.json.
-
change port to 4210
-
change builder to @angular-builders/custom-webpack we need add some webpack config to application. For this we use Angular custom builder.
-
add customWebpackConfig
"customWebpackConfig": { "path": "plugins/new-widget/extra-webpack.config.js", "mergeStrategies": { "module": "prepend", "module.rules": "prepend", "plugins": "prepend" } }
-
add extra-webpack.config.js with this content to 'new-widget\src'
const WebpackConfigHelper = require('./../../build-tools/webpack-config.helper'); module.exports = (config) => { WebpackConfigHelper.applyLayoutConfig(config, '/../../'); WebpackConfigHelper.applyFederationConfig(config, { uniqueName: 'kpi', filename: 'remoteEntry.js', exposes: { './Viewer': './plugins/newWidget/src/app/viewer/viewer.component.ts', // path to viewer component './Designer': './plugins/newWidget/src/app/designer/designer.component.ts', // path to designer component }, }); return config; };
In this file, we configure webpack module federation. I put all configuration into webpack-config.helper.js to use from all plugins.
Find more information about Angular cli builder here and about @angular-builders/custom-webpack here
Add a component to new-widget app. It should extends WidgetDesign.
import { Component, Injector } from '@angular/core';
import { WidgetDesign } from 'dashboard-lib';
@Component({
templateUrl: './designer.component.html',
})
export class NewWidgetDesignComponent extends WidgetDesign {
constructor(injector: Injector) {
super(injector);
}
}Add NewWidgetDesignComponent to AppModule declarations.
Add a component to new-widget app. It should extends WidgetView.
import { Component, Injector } from '@angular/core';
import { WidgetView } from 'dashboard-lib';
@Component({
templateUrl: './viewer.component.html',
})
export class NewWidgetViewComponent extends WidgetView {
constructor(injector: Injector) {
super(injector);
}
}Add NewWidgetViewComponent to AppModule declarations.
In part 2, add widget metadata to system. These metadata describe dashboard widgets.(dashboard.json)
for example:
{
"key": "newWidget",
"value": "New Widget",
"defaultModel": {
"links": []
},
"remote": {
"mode": "development",
"entry": {
"development": "http://localhost:4210/remoteEntry.js",
"production": "assets/plugins/newWidget/remoteEntry.js"
},
"viewer": {
"module": "./Viewer",
"component": "NewWidgetViewComponent"
},
"designer": {
"module": "./Designer",
"component": "NewWidgetDesignComponent"
}
}
},- key: Unique key for each widget
- value: Label of widget
- defaultModel: The default model that designer should create when creating cell of this widget type
- remote: From where and how this widget should be loaded
-
mode: For easy switching between 'development' and 'production' mode I use this field. If it is 'production', system uses entry.production for loading widget other wise system uses remote.development.
-
entry: Path to widget files
-
viewer: Widget viewer information contains module and component. module determines which module should be loaded and component determines the name of Angular component class should be created.
-
designer: Widget designer information contains module and component. module determines which module should be loaded and component determines the name of Angular component class should be created.
In some situation 'viewer' or 'designer' might need additional field, componentModule. componentModule determines the name of Angular modules should be instantiated before creation of Angular component. componentModule is necessary when Angular components uses some services that provides by componentModule.
-
Widget is ready!
Run ng serve new-widget and test it.
Find more information about Webpack Module Federation here
This tutorial was very helpful to me.
In this project I used some features of Narik