A pluggable Angular dashboard viewer for rendering and editing MemberJunction dashboards with configurable panels, multiple part types (Web URLs, Entity Views, Queries, Artifacts), and a dynamic plugin architecture.
The @memberjunction/ng-dashboard-viewer package provides a complete dashboard rendering and editing system. Dashboards consist of panels containing parts, where each part type has a corresponding runtime renderer and configuration panel -- both loaded dynamically via MemberJunction's ClassFactory plugin system. The package includes a dashboard browser for navigating available dashboards, breadcrumb navigation, and built-in support for Web URL, Entity View, Query, and Artifact part types.
graph TD
A[DashboardViewerModule] --> B[Core Components]
A --> C[Config Panels]
A --> D[Runtime Parts]
A --> E[Dialogs]
B --> B1[DashboardViewerComponent]
B --> B2[DashboardBrowserComponent]
B --> B3[DashboardBreadcrumbComponent]
B --> B4[DashboardCardComponent]
C --> C1["WebURLConfigPanel
(@RegisterClass)"]
C --> C2["ViewConfigPanel
(@RegisterClass)"]
C --> C3["QueryConfigPanel
(@RegisterClass)"]
C --> C4["ArtifactConfigPanel
(@RegisterClass)"]
D --> D1["WebURLPartComponent
(@RegisterClass)"]
D --> D2["ViewPartComponent
(@RegisterClass)"]
D --> D3["QueryPartComponent
(@RegisterClass)"]
D --> D4["ArtifactPartComponent
(@RegisterClass)"]
E --> E0[DashboardPartDialogComponent]
E --> E4[DashboardNameDialogComponent]
E --> E1["AddPanelDialogComponent (deprecated)"]
E --> E2["EditPartDialogComponent (deprecated)"]
E --> E3["ConfirmDialogComponent (deprecated)"]
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
style B fill:#7c5295,stroke:#563a6b,color:#fff
style C fill:#2d8659,stroke:#1a5c3a,color:#fff
style D fill:#b8762f,stroke:#8a5722,color:#fff
style E fill:#7c5295,stroke:#563a6b,color:#fff
npm install @memberjunction/ng-dashboard-viewer
import { DashboardViewerModule } from '@memberjunction/ng-dashboard-viewer';
@NgModule({
imports: [DashboardViewerModule]
})
export class YourModule { }
Render a full dashboard by ID:
<mj-dashboard-viewer
[DashboardId]="selectedDashboardId"
[IsEditing]="isEditing"
[CanEdit]="canEdit"
(DashboardSaved)="onDashboardSaved($event)"
(PanelInteraction)="onPanelInteraction($event)">
</mj-dashboard-viewer>
The viewer asks the host to add, edit or remove a part through PanelInteraction; see DashboardViewerComponent.
Browse dashboards as cards or as a list, by category folder:
<mj-dashboard-browser
[Dashboards]="dashboards"
[Categories]="categories"
[SelectedCategoryId]="selectedCategoryId"
[DashboardPermissions]="permissions"
(DashboardOpen)="onDashboardOpen($event)"
(DashboardEdit)="onDashboardEdit($event)"
(DashboardDelete)="onDashboardDelete($event)">
</mj-dashboard-browser>
Set [FlatMode]="true" to show every dashboard you pass in one flat list, with no folder scoping and no folder cards (for example, a list of favorites or of dashboards shared with the user). Flat mode ignores SelectedCategoryId, so new dashboards and categories are created at the root.
Each dashboard in the card view is an mj-dashboard-card (see Dashboard Card). These inputs fill the cards and the empty state:
| Input | Default | Description |
|---|---|---|
FavoriteIds |
[] |
IDs of the user's favorite dashboards. Their cards show a filled star. IDs match in any letter case. |
ShowFavorites |
false |
Shows the star on each card. Turn it on when the host handles DashboardFavoriteToggle. |
OwnerLabels |
empty | Map of dashboard ID to the owner text its card shows, for example "You" or "Ana Ruiz". A dashboard that is not in the map shows no owner. |
FlatEmptyIcon, FlatEmptyTitle, FlatEmptyMessage |
"No dashboards to show" | The empty state of a flat list with no dashboards and no search. |
FlatEmptyWelcome |
false |
An empty flat list with no search shows the first-run welcome instead. |
DashboardFavoriteToggle gives a DashboardFavoriteToggleEvent ({ Dashboard }) when the user clicks a card's star. The browser does not change FavoriteIds itself; the host updates it.DashboardPermissions).EffectiveCategoryMap, else the dashboard's category).DashboardOpen), and a Shift, Ctrl or Cmd click sets OpenInNewTab. In selection mode, a Ctrl or Cmd click toggles the card, a Shift-click selects a range, and the card's checkbox toggles it. A double-click emits DashboardEdit.A miniature of a dashboard's saved panel layout, for dashboard cards: each panel is a box with its icon and title, sized like the saved layout. It fills its container, renders only phrasing elements (so it can sit inside a button), and is aria-hidden, so the host must name the dashboard. It draws nothing when UIConfigDetails has no panel layout. BuildDashboardLayoutPreview(uiConfigDetails) returns the tree it draws, or null, so a host can choose another picture first. A host that already has the tree passes it in [Preview], and the configuration is not parsed again.
<mj-dashboard-layout-preview [UIConfigDetails]="dashboard.UIConfigDetails"></mj-dashboard-layout-preview>
<mj-dashboard-layout-preview [Preview]="tree"></mj-dashboard-layout-preview>
One dashboard as a card: a picture, the name, the description, and a meta row with the owner, a Shared marker, the category path and the last update. The picture is the dashboard's screenshot (Thumbnail), else a miniature of its saved panel layout, else an icon. A Config dashboard with no parts shows the icon and "Not set up yet", also when it has an old screenshot. The card is presentational: the host gives it its state and decides what each event does.
<mj-dashboard-card
[Dashboard]="dashboard"
[IsFavorite]="isFavorite"
[IsShared]="isShared"
[OwnerLabel]="ownerLabel"
[CategoryPath]="categoryPath"
(CardClick)="open($event.Dashboard, $event.MouseEvent)"
(ToggleFavorite)="toggleFavorite($event)">
</mj-dashboard-card>
Dashboard (required), IsFavorite, ShowFavorite (default true), IsShared, OwnerLabel and CategoryPath (null shows none), HighlightQuery (search text to mark in the name and description), Selectable and Selected (the selection checkbox), CanEdit and CanDelete (the Edit and Delete buttons).CardClick and CardDoubleClick give a DashboardCardClickEvent ({ Dashboard, MouseEvent }), so the host can read the Shift, Ctrl and Cmd keys. A click on a card button counts only as that button: ToggleFavorite, Edit, Delete and SelectionToggle each emit the dashboard.User field (the owner's user name, often an e-mail). Pass the text to show in OwnerLabel.IsDashboardNotSetUp(dashboard), FormatDashboardDate(date) ("Today", "Yesterday", "N days ago" under a week, else the local date) and DashboardCategoryPath(categoryId, categories) (for example "Sales › Pipeline").DashboardLayoutPreviewComponent and DashboardLayoutPreviewNodeComponent.One dialog, on mj-dialog, adds a part or edits one. It shows the part type tiles, the part type's settings panel as the Source section, the title and, for a query, the parameter-controls and automatic refresh options. Create it with @if each time it opens:
@if (PartDialogMode) {
<mj-dashboard-part-dialog
[Visible]="true"
[Mode]="PartDialogMode"
[PartTypes]="viewer.PartTypes"
[Panel]="EditPartPanel"
[Provider]="Provider"
(Saved)="OnPartDialogSaved($event)"
(Cancelled)="PartDialogMode = null">
</mj-dashboard-part-dialog>
}
Mode is 'add' or 'edit'. In edit mode, Panel is the part. Its part type cannot change, so the other type tiles are disabled.Saved gives a DashboardPartDialogResult: { PartType, Config, Title, Icon? }. Pass it to the viewer's AddPanel(r.PartType.ID, r.Config, r.Title, r.Icon) or UpdatePanelConfig(panel.id, r.Config, r.Title, r.Icon), then close the dialog. The dialog does not close itself.SortOrder, with the part type's metadata Icon.QUERY_REFRESH_SECONDS (30, 60, 300 or 600 seconds).ConfigDialogClass. It passes the panel its Provider and sets the panel's ShowCommonFields to false, so the panel leaves out the title and query options that the dialog shows itself. A part type without a settings panel gets a config that names only its type.Asks for the name of a new dashboard before the host creates it. The component is standalone, so the host imports DashboardNameDialogComponent itself:
<mj-dashboard-name-dialog
[Visible]="ShowNewDashboardDialog"
[MaxLength]="NewDashboardNameMaxLength"
[Processing]="IsCreatingDashboard"
(Confirmed)="OnNewDashboardNamed($event)"
(Cancelled)="OnNewDashboardCancelled()">
</mj-dashboard-name-dialog>
MaxLength characters. The default is DASHBOARD_NAME_MAX_LENGTH (255, the length of MJ: Dashboards.Name).Confirmed with the trimmed name, and the dialog stays open. The host creates the dashboard, sets Processing while it saves, and closes the dialog through Visible.Cancelled. The host closes the dialog and creates nothing.Title ("New dashboard"), ConfirmText ("Create") and Placeholder ("Name this dashboard") change the texts. The name field is empty each time the dialog opens.The path from the root to a category, and optionally the current dashboard:
<mj-dashboard-breadcrumb
[Categories]="categories"
[CurrentCategoryId]="selectedCategoryId"
[CurrentDashboard]="dashboard"
[ShowDashboardName]="true"
RootLabel="Dashboards"
RootIcon="fa-solid fa-gauge-high"
(Navigate)="onBreadcrumbNavigate($event)"
(DashboardDrop)="onDashboardDrop($event)">
</mj-dashboard-breadcrumb>
Navigate gives a BreadcrumbNavigateEvent ({ CategoryId, Category }; null for the root) when the user clicks a crumb. The last category crumb emits it only when ShowDashboardName is true.DashboardDrop gives a BreadcrumbDropEvent ({ TargetCategoryId, DashboardIds }) when the user drops dashboards on a crumb. Set AllowDragDrop (default true) to false to turn this off.Visible (default true) to false to hide it. Size is 'normal' or 'large'.Both config panels and runtime parts are registered with @RegisterClass and loaded dynamically via ClassFactory. This allows custom part types to be added without modifying the dashboard viewer itself.
import { RegisterClass } from '@memberjunction/global';
import { BaseDashboardConfigPanel } from '@memberjunction/ng-dashboard-viewer';
@RegisterClass(BaseDashboardConfigPanel, 'CustomChart')
@Component({
selector: 'mj-custom-chart-config',
template: `<!-- chart configuration form -->`
})
export class CustomChartConfigComponent extends BaseDashboardConfigPanel {
// Configuration logic
}
The part dialog shows the title field itself and sets the panel's ShowCommonFields input to false. If the panel has its own title field, wrap it in @if (ShowCommonFields) { … }.
import { RegisterClass } from '@memberjunction/global';
import { BaseDashboardPart } from '@memberjunction/ng-dashboard-viewer';
@RegisterClass(BaseDashboardPart, 'CustomChart')
@Component({
selector: 'mj-custom-chart-part',
template: `<!-- chart rendering -->`
})
export class CustomChartPartComponent extends BaseDashboardPart {
// Rendering logic
}
| Part Type | Config Panel | Runtime Part | Description |
|---|---|---|---|
| Web URL | WebURLConfigPanelComponent |
WebURLPartComponent |
Embedded web page via iframe |
| View | ViewConfigPanelComponent |
ViewPartComponent |
MJ Entity View grid/cards |
| Query | QueryConfigPanelComponent |
QueryPartComponent |
MJ Query results display |
| Artifact | ArtifactConfigPanelComponent |
ArtifactPartComponent |
Conversation artifact viewer |
Main viewer component that renders a dashboard's panels and parts.
The viewer reads the dashboard's saved layout (UIConfigDetails) when it gets the dashboard. Two methods keep a host in step with saves made elsewhere:
HasNewerSavedLayout(dashboard?) returns true when the saved layout differs from the one the viewer last loaded or saved, for example after another tab saved the dashboard. Pass another copy of the dashboard to compare that copy.ReloadFromSaved(dashboard?) shows the saved layout again (of the given copy, if any) and drops unsaved layout changes. An edit-mode change made right after the call applies to the new layout. A hidden viewer builds the layout when its container gets a size.UseSavedCopy(dashboard) takes another copy of the shown dashboard whose saved layout is the one shown (for example after DashboardEngine reloaded its dashboards), without rebuilding the layout. Later saves write that copy. It returns false for a copy of another dashboard or with another saved layout.Editing:
IsEditing puts the dashboard in edit mode. CanEdit (default false) tells the viewer that the user may edit the dashboard: an empty dashboard then offers Add part in view mode too, and the host decides how to enter edit mode.PanelInteraction, with interactionType: 'custom' and one of these payloads:payload.action |
When | Also in the event |
|---|---|---|
add-panel-requested |
Add part (the toolbar, or the empty state) | payload.partTypes |
configure-part-requested |
Edit part | panelId |
remove-part-requested |
Remove | panelId, payload.panelTitle, payload.partTypeName (the MJ: Dashboard Part Types name, or null) |
RemovePartConfirmOptions(panelTitle, partTypeName) gives the texts of the Remove part confirm, for MJConfirmService.ConfirmDelete: for example Remove "Revenue" from this dashboard?, and for the four built-in part types a detail line that says the view, query, artifact or page itself is not deleted.The viewer's Golden Layout wrapper. In edit mode, OnStackAction emits a StackActionEvent ({ Action, PanelId }) when the user clicks Edit part (Action: 'edit') or Remove ('remove') in a tab group's header; PanelId is the group's active part at the time of the click. DASHBOARD_STACK_HEADER_HEIGHT (44) and DASHBOARD_TAB_CONTROL_OFFSET (78) are the header height and the room kept beside the tabs, in pixels. The wrapper writes both into each layout it loads, and the viewer CSS uses the same values.
Grid/list browser for navigating available dashboards with category tree sidebar. Each dashboard in the grid is an mj-dashboard-card; see Dashboard Browser for the inputs that fill the cards.
Delete asks the user to confirm through MJConfirmService (from @memberjunction/ng-ui-components), then emits DashboardDelete. The host deletes the dashboards and does not ask again.
One dashboard as a card (<mj-dashboard-card>); see Dashboard Card.
Breadcrumb navigation showing the current dashboard path.
| Component | Description |
|---|---|
DashboardPartDialogComponent |
Adds a part or edits one (<mj-dashboard-part-dialog>); see Part Dialog |
DashboardNameDialogComponent |
Asks for a new dashboard's name (<mj-dashboard-name-dialog>, standalone); see Name Dialog |
AddPanelDialogComponent |
Deprecated. Use DashboardPartDialogComponent with Mode="add" |
EditPartDialogComponent |
Deprecated. Use DashboardPartDialogComponent with Mode="edit" |
ConfirmDialogComponent |
Deprecated. Use MJConfirmService (or MJConfirmDialogComponent) from @memberjunction/ng-ui-components. MJ Explorer's dashboard tab still renders it for its two Dashboard Studio confirms |
| Package | Description |
|---|---|
@memberjunction/core |
Core framework |
@memberjunction/core-entities |
Entity type definitions |
@memberjunction/global |
Global utilities and ClassFactory |
@memberjunction/ng-artifacts |
Artifact viewer components |
@memberjunction/ng-entity-viewer |
Entity data grids |
@memberjunction/ng-query-viewer |
Query result display |
@memberjunction/ng-shared-generic |
Shared generic components |
@memberjunction/ng-trees |
Tree view components |
@memberjunction/ng-ui-components |
Dialogs, buttons, empty state, and MJConfirmService |
@angular/common ^21.x@angular/core ^21.x@angular/forms ^21.xcd packages/Angular/Generic/dashboard-viewer
npm run build
Business Source License 1.1 — see LICENSE for details.