
Syncfusion Angular Dashboard Layout
- 150 installs
- Updated August 4, 2026
- syncfusion/angular-ui-components-skills
Use syncfusion-angular-dashboard-layout for development tasks
About
syncfusion-angular-dashboard-layout: A skill for development. This provides functionality for development workflows.
- syncfusion-angular-dashboard-layout
Syncfusion Angular Dashboard Layout by the numbers
- 150 all-time installs (skills.sh)
- +5 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,478 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/angular-ui-components-skills --skill syncfusion-angular-dashboard-layoutAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 150 |
|---|---|
| Last updated | August 4, 2026 |
| Repository | syncfusion/angular-ui-components-skills ↗ |
What it does
Use syncfusion-angular-dashboard-layout for development tasks
Files
Implementing Syncfusion Angular Dashboard Layout
The Dashboard Layout component is a powerful grid-structured layout control that helps you create responsive, interactive dashboards with dynamic panels. Panels can be dragged, resized, added, removed, and their state can be persisted. This skill guides you through all aspects of implementing, configuring, and customizing dashboard layouts in Angular.
When to Use This Skill
- Creating customizable dashboard layouts with multiple panels
- Implementing drag-and-drop panel repositioning
- Enabling panel resizing in multiple directions
- Dynamically adding or removing dashboard panels
- Making dashboards responsive across device sizes
- Saving and restoring dashboard layout configurations
- Styling dashboard components with CSS customization
- Integrating Syncfusion charts, grids, or other components as panel content
Component Overview
The Dashboard Layout component provides:
- Grid-based layout system with configurable columns and cell dimensions
- Drag-and-drop support for reordering panels with visual feedback
- Resizing in multiple directions (east, west, north, south, etc.)
- Floating panels that automatically fill empty spaces
- Responsive behavior with custom media query breakpoints
- State persistence through serialize/deserialize methods
- Dynamic panel management with add, remove, and move operations
- Event-driven architecture for tracking user interactions
Documentation and Navigation Guide
Getting Started
📄 Read: references/getting-started.md
- Angular CLI setup and project initialization
- Installing Dashboard Layout package (Ivy and ngcc variants)
- CSS imports and theme configuration
- Two panel definition approaches (HTML attributes vs property binding)
- Basic component rendering and first dashboard creation
- Cell spacing and layout initialization
Adding and Removing Panels
📄 Read: references/adding-removing-panels.md
- Dynamic panel management with addPanel() method
- Removing individual panels with removePanel(id)
- Bulk removal with removeAll() method
- Panel configuration properties for dynamic generation
- Event handling and callbacks for lifecycle management
Setting Panel Headers
📄 Read: references/panel-headers.md
- Configuring panel headers with header property
- Adding titles, labels, and HTML content to headers
- Styling headers with CSS customization
- Embedding Syncfusion components in panel headers
- Header interaction and user experience patterns
Dragging and Dropping Panels
📄 Read: references/dragging-and-dropping.md
- Default drag-drop behavior and panel collision handling
- Drag events: dragStart, drag, dragStop
- Customizing drag handles with draggableHandle property
- Programmatic panel movement with movePanel(id, row, col)
- Disabling drag functionality with allowDragging
- Touch support and user interaction patterns
Moving Panels Programmatically
📄 Read: references/moving-panels.md
- Using movePanel(id, row, col) to reposition panels
- Moving panels without user interaction
- Tracking panel movements with change events
- Swapping panel positions programmatically
- Panel rearrangement patterns and use cases
Resizing and Floating Panels
📄 Read: references/resizing-and-floating.md
- Enabling panel resizing with allowResizing
- Resize handles in different directions (e-south-east, e-east, e-west, e-north, e-south)
- Resize events: resizeStart, resize, resizeStop
- Programmatic resizing with resizePanel(id, sizeX, sizeY)
- Min and max size constraints (minSizeX, minSizeY, maxSizeX, maxSizeY)
- Floating behavior and automatic panel repositioning
- Disabling floating for fixed grid layouts
Saving and Restoring State
📄 Read: references/save-restore-state.md
- Serializing dashboard layout with serialize()
- Saving layout state to localStorage, sessionStorage, or backend
- Restoring layouts from persisted configuration
- Implementing layout templates and presets
- State persistence patterns and best practices
Responsive and Adaptive Design
📄 Read: references/responsive-and-adaptive.md
- Built-in responsive behavior and auto-adaptation
- Customizing responsive breakpoints with mediaQuery
- Stacked layout on mobile devices (vertical columns)
- Cell aspect ratio configuration with cellAspectRatio
- Testing responsive layouts at different resolutions
- Parent element sizing (percentage vs static dimensions)
Configuring Cell Spacing
📄 Read: references/cell-spacing-configuration.md
- Setting [horizontal, vertical] spacing with cellSpacing property
- Adjusting spacing for compact or spacious layouts
- Dynamic spacing adjustment for responsive designs
- Asymmetric spacing patterns for visual hierarchy
- Practical examples: compact dashboards, executive layouts
Graphical Representation and Grid Lines
📄 Read: references/grid-lines-and-visualization.md
- Visualizing grid structure with showGridLines property
- Understanding grid cells, rows, columns, and spacing
- Design-time grid visualization for layout planning
- Debugging panel positioning and sizing
- Best practices for grid line usage
Right-to-Left Language Support
📄 Read: references/rtl-support.md
- Enabling RTL rendering with enableRtl property
- Supporting Arabic, Hebrew, Farsi, and Urdu languages
- Dynamic RTL toggling for language switching
- HTML dir attribute integration
- CSS logical properties for RTL-aware styling
- RTL considerations with drag-drop interactions
Styling and Customization
📄 Read: references/styling-and-customization.md
- CSS selectors for customizing panels, headers, and content
- Panel header and content styling (.e-panel-header, .e-panel-content)
- Resize handle customization (.e-resize)
- Dashboard background styling
- Panel positioning and sizing with row, col, sizeX, sizeY properties
- Min/max size constraints and grid structure
- Integrating Syncfusion components (Charts, Grids, Gauges) as panel content
- SystemJS configuration for legacy Angular setups
For detailed examples and code samples, see:
- getting-started.md - Quick start and basic setup examples
- Common patterns and implementation examples in specific reference files
API Reference
Component Properties (14 total)
Layout Configuration
| Property | Type | Default | Description |
|---|---|---|---|
columns | number | 5 | Number of columns in the grid layout. Panels are positioned within this column-based grid. |
cellSpacing | number[] | [10, 10] | Spacing between panels: [horizontal, vertical] in pixels. Creates visual separation and padding around panels. |
cellAspectRatio | number | Auto (1:1) | Width/height ratio of cells. Example: 100/50 creates cells that are twice as wide as tall. |
mediaQuery | string | 'max-width: 600px' | CSS media query breakpoint for responsive stacked layout. Panels stack vertically below this breakpoint. |
showGridLines | boolean | false | When true, visualizes grid cells and structure for design-time debugging and layout planning. |
Interaction Features
| Property | Type | Default | Description |
|---|---|---|---|
allowDragging | boolean | true | Enables/disables panel drag-drop functionality. When true, users can reorder panels by dragging. |
allowResizing | boolean | false | Enables/disables panel resizing. When true, resize handles appear on panel edges. |
allowFloating | boolean | true | Enables/disables floating behavior. When true, panels automatically move upward to fill empty spaces left by moved/resized panels. |
resizableHandles | string[] | ['e-south-east'] | Array of resize handle directions: 'e-south-east', 'e-east', 'e-west', 'e-north', 'e-south', 'e-south-west'. Controls which directions users can resize panels. |
draggableHandle | string | null | CSS selector string for drag handle element. If set, dragging only works when user grabs this specific element (e.g., '.e-panel-header'). |
Content and Styling
| Property | Type | Default | Description |
|---|---|---|---|
panels | PanelModel[] | [] | Array of panel configurations defining the dashboard layout structure and content. |
enableRtl | boolean | false | Enables right-to-left rendering for RTL languages (Arabic, Hebrew, Farsi, Urdu). |
enableHtmlSanitizer | boolean | true | Sanitizes HTML content in panels to prevent XSS attacks. Set to false only with trusted content. |
enablePersistence | boolean | false | When true, automatically saves/restores dashboard state in browser storage between page reloads. |
Panel Properties (13 total)
| Property | Type | Default | Description |
|---|---|---|---|
| Position & Size | |||
row | number | 0 | Starting row position (0-based indexing) in the grid. Determines vertical placement. |
col | number | 0 | Starting column position (0-based indexing) in the grid. Determines horizontal placement. |
sizeX | number | 1 | Width of panel in cells. Example: sizeX=2 spans 2 grid columns. |
sizeY | number | 1 | Height of panel in cells. Example: sizeY=2 spans 2 grid rows. |
| Size Constraints | |||
minSizeX | number | 1 | Minimum width in cells. Prevents panel from being resized smaller than this value. |
minSizeY | number | 1 | Minimum height in cells. Prevents panel from being resized smaller than this value. |
maxSizeX | number | null | Maximum width in cells. Null means no limit. Prevents panel from being resized larger than this value. |
maxSizeY | number | null | Maximum height in cells. Null means no limit. Prevents panel from being resized larger than this value. |
| Content | |||
id | string | '' | Unique identifier for the panel. Required for methods like movePanel(), resizePanel(), removePanel(). |
header | string \ | HTMLElement \ | Function |
content | string \ | HTMLElement \ | Function |
| Styling & State | |||
cssClass | string | '' | Custom CSS class names to apply to the panel for styling and theming. |
enabled | boolean | true | When false, disables the panel and makes it non-interactive. |
zIndex | number | 1000 | CSS z-index for layer ordering. Higher values appear on top. |
Events (9 total)
Drag Events
| Event | Arguments | Description |
|---|---|---|
dragStart | DragStartArgs | Fires when user begins dragging a panel. Arguments: cancel (bool to prevent drag), element (HTMLElement), event (MouseEvent\ |
drag | DraggedEventArgs | Fires continuously while dragging. Arguments: element (HTMLElement being dragged), event (MouseEvent\ |
dragStop | DragStopArgs | Fires when drag completes/releases. Arguments: cancel (bool to revert position), element, event, panels (PanelModel[] of affected panels), target. Use to save final positions or validate drop location. |
Resize Events
| Event | Arguments | Description |
|---|---|---|
resizeStart | ResizeArgs | Fires when resize begins. Arguments: element (HTMLElement), event (MouseEvent\ |
resize | ResizeArgs | Fires continuously during resize. Same arguments as resizeStart. Use for real-time validation or display of new dimensions. Fires frequently—throttle expensive operations. |
resizeStop | ResizeArgs | Fires when resize completes. Same arguments as resize. Use to finalize panel sizes or save state. |
Layout Change Events
| Event | Arguments | Description |
|---|---|---|
change | ChangeEventArgs | Fires when layout changes (panel added, removed, moved, resized). Arguments: addedPanels (PanelModel[]), changedPanels (PanelModel[]), isInteracted (bool), removedPanels (PanelModel[]). Use to detect and react to all layout modifications. |
created | Object | Fires when dashboard component is fully initialized and rendered. Use for post-initialization setup. |
destroyed | Object | Fires when dashboard component is destroyed. Use for cleanup operations. |
Methods
Panel Management
| Method | Signature | Returns | Description |
|---|---|---|---|
addPanel | addPanel(panel: PanelModel) | void | Dynamically add a new panel to the dashboard. Panel will be positioned according to its row/col properties. Triggers change event. |
removePanel | removePanel(panelId: string) | void | Remove a specific panel by its ID. Triggers change event. Other panels may float up if allowFloating=true. |
removeAll | removeAll() | void | Remove all panels from the dashboard. Clears the entire layout. |
movePanel | movePanel(panelId: string, row: number, col: number) | void | Reposition a panel to a new grid location. Triggers drag-like behavior with floating if enabled. Triggers change event. |
resizePanel | resizePanel(panelId: string, sizeX: number, sizeY: number) | void | Resize a panel to new dimensions (in cells). Respects minSize/maxSize constraints. Triggers resize events with isInteracted=false. Triggers change event. |
State Management
| Method | Signature | Returns | Description |
|---|---|---|---|
serialize | serialize() | PanelModel[] | Captures and returns the current dashboard layout state as an array of PanelModel objects. Use with localStorage or database to persist user configurations. Can be used to create layout templates/presets. |
Component Lifecycle
| Method | Signature | Returns | Description |
|---|---|---|---|
updatePanel | updatePanel(panelId: string, panelModel: PanelModel) | void | Update properties of an existing panel. Useful for changing header, content, or other panel properties without removing and re-adding the panel. Triggers change event with changedPanels. |
refreshDraggableHandle | refreshDraggableHandle() | void | Refreshes the draggable handle selector. Call this when DOM structure changes or draggableHandle CSS selector needs to be re-evaluated. Useful after external DOM modifications. |
destroy | destroy() | void | Destroys the Dashboard Layout component and cleans up all resources. Triggers destroyed event. Call before component unmounting to prevent memory leaks. |
Event Arguments Reference
DragStartArgs
{
cancel: boolean; // Set to true to prevent the drag operation
element: HTMLElement; // The panel element being dragged
event: MouseEvent | TouchEvent; // The original browser event
}DraggedEventArgs
{
element: HTMLElement; // The panel element being dragged
event: MouseEvent | TouchEvent; // The original browser event (fires 60+ times/sec)
target: HTMLElement; // The element currently under the cursor
}DragStopArgs
{
cancel: boolean; // Set to true to cancel/revert the drag
element: HTMLElement; // The panel element that was dragged
event: MouseEvent | TouchEvent; // The drop/release event
panels: PanelModel[]; // Array of panels affected by the drag (position changed)
target: HTMLElement; // The element at the drop location
}ResizeArgs
{
element: HTMLElement; // The panel element being resized
event: MouseEvent | TouchEvent; // The original browser event
isInteracted: boolean; // true = user resize, false = programmatic resize
panels: PanelModel[]; // Array of panels affected by the resize
}ChangeEventArgs
{
addedPanels: PanelModel[]; // Panels added in this change
changedPanels: PanelModel[]; // Panels whose position/size changed
isInteracted: boolean; // true = user action, false = programmatic
removedPanels: PanelModel[]; // Panels removed in this change
}For detailed method examples and security configuration, see:
- adding-removing-panels.md - Method examples for panel management
- styling-and-customization.md - Security and configuration examples
Common Use Cases
1. Analytics Dashboard: Display charts and metrics in resizable panels for data monitoring 2. Admin Panel: Dynamic widget system with add/remove and drag-drop functionality 3. Real-time Monitoring: Update panel content while maintaining layout state 4. Mobile-responsive Portal: Stacked panels on mobile, grid layout on desktop 5. User Customizable Layout: Allow users to save their preferred dashboard arrangement 6. Widget Store: Add new panels dynamically from a widget library
Related Topics
Component Setup & Configuration
- Angular Module Setup: Importing DashboardLayoutModule in standalone components
- CSS Themes: Available themes (material3, bootstrap5, fabric, tailwind)
- Responsive Parent Sizing: Percentage vs static dimensions for fluid layouts
- Persistence & Storage: enablePersistence vs manual serialize() approach
Event Handling & Interaction
- Drag Events: dragStart (cancel drag), drag (visual feedback), dragStop (finalize)
- Resize Events: resizeStart, resize (continuous), resizeStop with constraint validation
- Change Detection: Using (change) event to track ALL layout modifications (add/remove/move/resize)
- Event Throttling: Performance optimization for drag (60+/sec) and resize events
- isInteracted Property: Distinguishing user actions from programmatic changes
Advanced Features
- Dynamic Panel Management: addPanel(), removePanel(), removeAll() methods
- Programmatic Movement: movePanel(id, row, col) for automatic rearrangement
- Programmatic Resizing: resizePanel(id, sizeX, sizeY) with constraint enforcement
- State Serialization: serialize() method for saving/restoring layouts
- Size Constraints: minSizeX, minSizeY, maxSizeX, maxSizeY for panel boundaries
- Floating Behavior: allowFloating for automatic gap filling
Component Integration
- Embedding Charts: Syncfusion Charts (Column, Line, Pie, etc.) as panel content
- Embedding Grids: Syncfusion Grids with data, sorting, filtering in panels
- Embedding Gauges: Linear and Radial Gauges for metrics and KPIs
- Custom HTML Content: Using Function type for dynamic content generation
- Content Function Type: content/header as (Function) for lazy-loaded or reactive content
Styling & Customization
- CSS Classes: .e-panel-header, .e-panel-content, .e-resize, .e-dashboardlayout
- Panel Styling: cssClass property for custom theming per panel
- Header Customization: Function/HTMLElement types for dynamic headers
- Drag Handle Customization: draggableHandle CSS selector for restricted drag areas
- RTL Support: enableRtl + resizableHandles behavior with right-to-left layouts
Performance & Optimization
- Large Dashboard Rendering: Techniques for 100+ panels
- Virtual Scrolling: When to consider alternative layouts for massive dashboards
- Event Throttling: Managing drag (60+/sec) and resize event frequency
- Memory Management: Panel cleanup on destroy event
- Change Event Filtering: Tracking only relevant changes (addedPanels, changedPanels, removedPanels)
Adding and Removing Panels Dynamically
Table of Contents
Overview
Dashboard layouts often need dynamic panel management. Users may want to add new widgets, remove old ones, or manage panels programmatically. The Dashboard Layout provides public methods to handle these operations efficiently.
Key Methods
addPanel(panel)- Add single panel to dashboardremovePanel(id)- Remove panel by IDremoveAll()- Remove all panels at once
Adding Panels
Basic Usage
Use the addPanel() method to add a new panel to the dashboard at runtime:
import { Component, ViewChild } from '@angular/core';
import { DashboardLayoutComponent, DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-dynamic-dashboard',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<button (click)="onAddPanel()">Add Panel</button>
<ejs-dashboardlayout #dashboard [columns]="5" [panels]="panels"></ejs-dashboardlayout>
`
})
export class DynamicDashboardComponent {
@ViewChild('dashboard') dashboardRef?: DashboardLayoutComponent;
public panelCount = 0;
public panels: any = [];
onAddPanel() {
const newPanel = {
id: `panel-${this.panelCount}`,
sizeX: 1,
sizeY: 1,
row: 0,
col: this.panelCount % 5,
header: `<div>Panel ${this.panelCount}</div>`,
content: `<div class="content">New Panel ${this.panelCount}</div>`
};
this.dashboardRef?.addPanel(newPanel);
this.panelCount++;
}
}Panel Configuration
When adding a panel, provide all required properties:
{
id: 'unique-id', // REQUIRED: Must be unique across all panels
sizeX: 1, // REQUIRED: Width in cells
sizeY: 1, // REQUIRED: Height in cells
row: 0, // REQUIRED: Starting row position
col: 0, // REQUIRED: Starting column position
header: '<div>Title</div>', // Optional: Panel header
content: '<div>...</div>' // Optional: Panel content
}Auto-Positioning New Panels
Add panels with automatic position calculation:
addPanelAtNextPosition() {
const nextCol = this.panels.length % 5;
const nextRow = Math.floor(this.panels.length / 5);
const newPanel = {
id: `panel-${this.panels.length}`,
sizeX: 1,
sizeY: 1,
row: nextRow,
col: nextCol,
content: `<div>Panel ${this.panels.length}</div>`
};
this.dashboardRef?.addPanel(newPanel);
this.panels.push(newPanel);
}Removing Panels
Remove Single Panel
Use removePanel() with the panel ID:
removePanelById(panelId: string) {
this.dashboardRef?.removePanel(panelId);
// Also update local array if tracking panels
this.panels = this.panels.filter(p => p.id !== panelId);
}Remove Panel with UI Confirmation
Add user confirmation before removal:
@Component({
template: `
<button (click)="onRemovePanel('panel-1')">Remove Panel 1</button>
<ejs-dashboardlayout #dashboard [panels]="panels"></ejs-dashboardlayout>
`
})
export class RemovePanelComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
onRemovePanel(panelId: string) {
const confirmed = confirm(`Remove panel ${panelId}?`);
if (confirmed) {
this.dashboard?.removePanel(panelId);
}
}
}Dynamic Removal with Dropdown
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
import { ButtonModule } from '@syncfusion/ej2-angular-buttons';
import { DropDownListModule } from '@syncfusion/ej2-angular-dropdowns';
@Component({
standalone: true,
imports: [DashboardLayoutModule, ButtonModule, DropDownListModule],
template: `
<div class="controls">
<label>Select Panel to Remove:</label>
<ejs-dropdownlist
#dropdown
[dataSource]="panelIds"
placeholder="Choose panel">
</ejs-dropdownlist>
<button ejs-button (click)="removeSelected()">Remove</button>
</div>
<ejs-dashboardlayout #dashboard [panels]="panels"></ejs-dashboardlayout>
`
})
export class RemoveWithDropdownComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
@ViewChild('dropdown') dropdown?: any;
public panels: any = [
{ id: 'sales', sizeX: 1, sizeY: 1, row: 0, col: 0, header: 'Sales' },
{ id: 'revenue', sizeX: 1, sizeY: 1, row: 0, col: 1, header: 'Revenue' },
{ id: 'growth', sizeX: 1, sizeY: 1, row: 0, col: 2, header: 'Growth' }
];
get panelIds(): string[] {
return this.panels.map(p => p.id);
}
removeSelected() {
const selectedId = this.dropdown?.value;
if (selectedId) {
this.dashboard?.removePanel(selectedId);
this.panels = this.panels.filter(p => p.id !== selectedId);
}
}
}Bulk Operations
Remove All Panels
Clear the entire dashboard:
clearDashboard() {
this.dashboardRef?.removeAll();
this.panels = [];
}Reset to Default Panels
Clear and reinitialize with default layout:
resetToDefault() {
this.dashboardRef?.removeAll();
this.panels = this.getDefaultPanels();
this.panels.forEach(panel => {
this.dashboardRef?.addPanel(panel);
});
}
getDefaultPanels() {
return [
{ id: 'default-1', sizeX: 2, sizeY: 1, row: 0, col: 0, header: 'Dashboard' },
{ id: 'default-2', sizeX: 1, sizeY: 2, row: 0, col: 2, header: 'Metrics' },
{ id: 'default-3', sizeX: 2, sizeY: 1, row: 1, col: 0, header: 'Reports' }
];
}Batch Add Multiple Panels
addMultiplePanels(panelConfigs: any[]) {
panelConfigs.forEach((config, index) => {
const panel = {
id: `batch-panel-${index}`,
sizeX: 1,
sizeY: 1,
row: Math.floor(index / 5),
col: index % 5,
...config // Spread custom properties
};
this.dashboardRef?.addPanel(panel);
});
}
// Usage
const configs = [
{ header: 'Panel A', content: '<div>A</div>' },
{ header: 'Panel B', content: '<div>B</div>' },
{ header: 'Panel C', content: '<div>C</div>' }
];
this.addMultiplePanels(configs);Best Practices
1. Unique Panel IDs
Always ensure panel IDs are unique across all panels:
addPanel(config: any) {
const panel = { id: `panel-${Date.now()}`, ...config };
this.dashboardRef?.addPanel(panel);
}2. Track Panels in Component
Maintain a local array to track panels for easier management:
public panels: any = [];
addPanel(config: any) {
const panel = { id: `panel-${this.panels.length}`, ...config };
this.dashboardRef?.addPanel(panel);
this.panels.push(panel); // Keep in sync
}
removePanel(panelId: string) {
this.dashboardRef?.removePanel(panelId);
this.panels = this.panels.filter(p => p.id !== panelId);
}3. Update Local Array After Removal
Always keep your component's panel array in sync with dashboard state:
onRemovePanel(panelId: string) {
this.dashboardRef?.removePanel(panelId);
this.panels = this.panels.filter(p => p.id !== panelId);
}Advanced Method Examples
updatePanel - Update Panel Properties Dynamically
Update an existing panel without removing and re-adding it:
// Update panel header and content dynamically
updatePanelContent() {
const updatedPanel = {
id: 'panel1',
row: 0,
col: 0,
sizeX: 2,
sizeY: 1,
header: 'Updated Panel Title',
content: '<div class="updated-content">New content here</div>'
};
this.dashboardRef?.updatePanel('panel1', updatedPanel);
}Use Cases:
- Update panel content dynamically based on user actions
- Change panel headers based on data updates
- Modify panel styling or configuration without full removal
refreshDraggableHandle - Refresh Drag Selector
Refresh the draggable handle selector after DOM modifications:
// Refresh draggable handle after external DOM changes
onDOMModified() {
// Your DOM modifications here (e.g., dynamically added elements)
this.dashboardRef?.refreshDraggableHandle();
}When to Use:
- After external DOM modifications
- When draggableHandle CSS selector changes dynamically
- After adding/removing panel elements externally
destroy - Component Cleanup
Properly destroy the component and cleanup resources:
ngOnDestroy() {
if (this.dashboardRef) {
this.dashboardRef.destroy();
}
}Important: Always call destroy in your component's ngOnDestroy lifecycle to prevent memory leaks and properly clean up resources.
Configuring Cell Spacing
Overview
Cell spacing controls the distance between panels in the Dashboard Layout. The cellSpacing property defines the spacing between each panel in both horizontal and vertical directions. Proper spacing improves visual clarity and makes dashboards easier to read and interact with.
Cell Spacing Property
Syntax
cellSpacing: [number, number] // [horizontal, vertical]- First value: Horizontal spacing in pixels (spacing between columns)
- Second value: Vertical spacing in pixels (spacing between rows)
Basic Configuration
Default Spacing
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-cell-spacing',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[columns]="5"
[cellSpacing]="[10, 10]"
[panels]="panels">
</ejs-dashboardlayout>
`,
styles: [`
:host { display: block; width: 100%; height: 100vh; }
`]
})
export class CellSpacingComponent {
public panels: any = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: '<div class="content">0</div>' },
{ sizeX: 3, sizeY: 2, row: 0, col: 1, content: '<div class="content">1</div>' },
{ sizeX: 1, sizeY: 3, row: 0, col: 4, content: '<div class="content">2</div>' },
{ sizeX: 1, sizeY: 1, row: 1, col: 0, content: '<div class="content">3</div>' },
{ sizeX: 2, sizeY: 1, row: 2, col: 0, content: '<div class="content">4</div>' },
{ sizeX: 1, sizeY: 1, row: 2, col: 2, content: '<div class="content">5</div>' },
{ sizeX: 1, sizeY: 1, row: 2, col: 3, content: '<div class="content">6</div>' }
];
}Spacing Variations
No Spacing (Compact Layout)
Panels placed directly adjacent to each other:
public cellSpacing: number[] = [0, 0];Small Spacing (Tight Layout)
Minimal spacing for compact dashboards:
public cellSpacing: number[] = [5, 5];Default Spacing (Recommended)
Balanced spacing for most dashboards:
public cellSpacing: number[] = [10, 10]; // Most commonLarge Spacing (Spacious Layout)
Generous spacing for clean, readable dashboards:
public cellSpacing: number[] = [20, 20];Asymmetric Spacing
Different horizontal and vertical spacing:
public cellSpacing: number[] = [15, 5]; // More horizontal spacingDynamic Spacing Adjustment
Change Spacing at Runtime
@Component({
selector: 'app-dynamic-spacing',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="controls">
<button (click)="setSpacing(0, 0)">No Spacing</button>
<button (click)="setSpacing(10, 10)">Default (10px)</button>
<button (click)="setSpacing(20, 20)">Large (20px)</button>
</div>
<ejs-dashboardlayout
[columns]="5"
[cellSpacing]="currentSpacing"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class DynamicSpacingComponent {
public currentSpacing: number[] = [10, 10];
public panels: any = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Panel 1' },
{ sizeX: 1, sizeY: 1, row: 0, col: 1, content: 'Panel 2' },
{ sizeX: 1, sizeY: 1, row: 0, col: 2, content: 'Panel 3' }
];
setSpacing(horizontal: number, vertical: number) {
this.currentSpacing = [horizontal, vertical];
}
}Responsive Spacing
Adjust Spacing Based on Screen Size
import { HostListener } from '@angular/core';
@Component({
selector: 'app-responsive-spacing',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[columns]="columns"
[cellSpacing]="cellSpacing"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class ResponsiveSpacingComponent {
public cellSpacing: number[] = [10, 10];
public columns = 5;
public panels: any = [];
@HostListener('window:resize')
onResize() {
this.updateSpacing();
}
private updateSpacing() {
const width = window.innerWidth;
if (width < 600) {
// Mobile: reduced spacing
this.cellSpacing = [5, 5];
this.columns = 2;
} else if (width < 1024) {
// Tablet: medium spacing
this.cellSpacing = [10, 10];
this.columns = 3;
} else {
// Desktop: larger spacing
this.cellSpacing = [20, 20];
this.columns = 5;
}
}
}Spacing with Different Panel Sizes
Large Panels with Small Spacing
@Component({
template: `
<ejs-dashboardlayout
[columns]="3"
[cellSpacing]="[5, 5]"
[panels]="largePanels">
</ejs-dashboardlayout>
`
})
export class LargePanelComponent {
public largePanels: any = [
{ sizeX: 3, sizeY: 2, row: 0, col: 0, content: 'Wide Panel' },
{ sizeX: 3, sizeY: 1, row: 2, col: 0, content: 'Full Width Panel' }
];
}Small Panels with Large Spacing
@Component({
template: `
<ejs-dashboardlayout
[columns]="5"
[cellSpacing]="[20, 20]"
[panels]="smallPanels">
</ejs-dashboardlayout>
`
})
export class SmallPanelComponent {
public smallPanels: any = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Small 1' },
{ sizeX: 1, sizeY: 1, row: 0, col: 1, content: 'Small 2' },
{ sizeX: 1, sizeY: 1, row: 0, col: 2, content: 'Small 3' }
];
}Practical Examples
Example: Compact Dashboard
Minimize spacing for information-dense layouts:
@Component({
template: `
<ejs-dashboardlayout
[columns]="6"
[cellSpacing]="[3, 3]"
[cellAspectRatio]="100/80"
[panels]="manyPanels">
</ejs-dashboardlayout>
`
})
export class CompactDashboardComponent {
public manyPanels: any = Array.from({ length: 12 }, (_, i) => ({
id: `p${i}`,
sizeX: 1,
sizeY: 1,
row: Math.floor(i / 6),
col: i % 6,
content: `<div>Panel ${i}</div>`
}));
}Example: Executive Dashboard
Use larger spacing for executive summaries:
@Component({
template: `
<ejs-dashboardlayout
[columns]="3"
[cellSpacing]="[25, 25]"
[panels]="executivePanels">
</ejs-dashboardlayout>
`
})
export class ExecutiveDashboardComponent {
public executivePanels: any = [
{
id: 'kpis',
sizeX: 3,
sizeY: 1,
row: 0,
col: 0,
header: 'Key Metrics',
content: '<div>KPI Data</div>'
},
{
id: 'revenue',
sizeX: 2,
sizeY: 2,
row: 1,
col: 0,
header: 'Revenue',
content: '<div>Revenue Chart</div>'
},
{
id: 'growth',
sizeX: 1,
sizeY: 2,
row: 1,
col: 2,
header: 'Growth',
content: '<div>Growth Gauge</div>'
}
];
}Best Practices for Cell Spacing
1. Maintain Consistency
Use consistent spacing throughout the dashboard for visual harmony:
const STANDARD_SPACING = [12, 12];
const COMPACT_SPACING = [8, 8];
const SPACIOUS_SPACING = [16, 16];
// Choose one and stick with it
public cellSpacing = STANDARD_SPACING;2. Consider Panel Content
Adjust spacing based on the content type:
// Text-heavy content: larger spacing
public textDashboardSpacing = [15, 15];
// Charts and visualizations: smaller spacing
public chartDashboardSpacing = [10, 10];
// Metrics only: minimal spacing
public metricsDashboardSpacing = [5, 5];3. Test for Readability
Ensure spacing doesn't make panels too far apart:
// Too small (≤ 2px): panels feel cramped
// Optimal (8-15px): good balance
// Too large (≥ 30px): panels feel disconnected
public cellSpacing = [12, 12]; // Recommended4. Account for Drag-and-Drop
Use adequate spacing for better drag-and-drop interaction:
if (this.allowDragging) {
this.cellSpacing = [12, 12]; // At least 10px for drag targets
} else {
this.cellSpacing = [8, 8]; // Can use smaller spacing
}Dragging and Dropping Panels
Table of Contents
- Overview
- Enabling Drag-Drop
- Drag Events
- Event Arguments
- Customizing Drag Handles
- Programmatic Panel Movement
- Performance Considerations
Overview
The Dashboard Layout provides intuitive drag-and-drop functionality that allows users to reorder panels within the dashboard. Dragging is enabled by default, but can be controlled with the allowDragging property. When dragging, visual feedback shows where the panel will be placed, and colliding panels automatically adjust their positions based on the allowFloating setting.
Key Features
- Enabled by Default:
allowDraggingis true by default - Visual Feedback: Placeholder shows where panel will land
- Collision Handling: Panels automatically push out of the way
- Touch Support: Works on mobile devices (touch events)
- Customizable Handles: Restrict dragging to specific elements via CSS selector
- Event Tracking: Listen to drag lifecycle events with full event arguments
- Floating Support: Panels auto-reposition to fill gaps when
allowFloatingis true
Enabling Drag-Drop
Basic Drag-Drop (Enabled by Default)
Drag-drop is enabled by default. No configuration needed:
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-draggable',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class DraggableDashboardComponent {
public panels = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: '<div>Drag me</div>' },
{ sizeX: 1, sizeY: 1, row: 0, col: 1, content: '<div>Or me</div>' }
];
}Explicitly Enable Drag-Drop
@Component({
template: `
<ejs-dashboardlayout
[allowDragging]="true"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class ExplicitlyDraggableComponent {
public panels = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: '<div>Draggable</div>' }
];
}Disable Drag-Drop
Prevent users from dragging panels:
@Component({
template: `
<ejs-dashboardlayout
[allowDragging]="false"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class StaticDashboardComponent {
public panels = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: '<div>Cannot drag</div>' }
];
}Drag Events
The Dashboard Layout triggers three events during drag operations, plus a change event after drag completes:
- dragStart - Before drag begins (cancellable)
- drag - During drag (fires frequently)
- dragStop - When drag ends (with final panel positions)
- change - After layout modification from any source
Event Arguments
dragStart Event
Triggered when a panel is about to be dragged.
DragStartArgs Properties:
cancel: boolean- Set to true to prevent the drag operationelement: HTMLElement- The cell element being draggedevent: MouseEvent | TouchEvent- The original mouse or touch event
@Component({
template: `
<div class="status">{{ status }}</div>
<ejs-dashboardlayout
[panels]="panels"
(dragStart)="onDragStart($event)">
</ejs-dashboardlayout>
`
})
export class DragStartExampleComponent {
public status = 'Ready';
public panels = [
{ id: 'panel-1', sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Panel 1', header: 'P1' },
{ id: 'panel-2', sizeX: 1, sizeY: 1, row: 0, col: 1, content: 'Panel 2', header: 'P2' }
];
onDragStart(args: any) {
console.log('Panel drag started');
console.log('Element:', args.element);
console.log('Event type:', args.event.type); // 'mousedown' or 'touchstart'
// Prevent dragging specific panels
if (args.element.classList.contains('locked')) {
args.cancel = true;
this.status = 'This panel cannot be dragged';
} else {
this.status = `Dragging panel from column ${args.element.dataset.col}`;
}
}
}drag Event
Triggered continuously while the user is dragging a panel.
DraggedEventArgs Properties:
element: HTMLElement- The cell element being draggedevent: MouseEvent | TouchEvent- The original mouse or touch eventtarget: HTMLElement- The element below the cell element being dragged (potential drop target)
Note: This event fires frequently (60+ times per second on modern browsers). Use throttling for expensive operations.
@Component({
template: `
<div class="position">{{ dragPosition }}</div>
<ejs-dashboardlayout
[panels]="panels"
(drag)="onDrag($event)">
</ejs-dashboardlayout>
`
})
export class DragExampleComponent {
public dragPosition = 'Not dragging';
private dragThrottle: any;
public panels = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Panel' }
];
onDrag(args: any) {
// Throttle updates to avoid performance issues
clearTimeout(this.dragThrottle);
this.dragThrottle = setTimeout(() => {
const rect = args.element.getBoundingClientRect();
this.dragPosition = `X: ${args.event.clientX}, Y: ${args.event.clientY}`;
if (args.target) {
console.log('Over target:', args.target.className);
}
}, 50); // Update every 50ms instead of 60+ times per second
}
}Performance Guidelines:
// ❌ AVOID: Heavy computation on every drag event
onDrag(args: any) {
this.expensiveCalculation(); // Called 60+ times per second!
this.updateDatabase(); // Will cause performance issues
}
// ✅ BETTER: Use throttling with reasonable intervals
private dragThrottle: any;
onDrag(args: any) {
clearTimeout(this.dragThrottle);
this.dragThrottle = setTimeout(() => {
this.lightweightUpdate(args);
}, 100); // Update at most every 100ms
}dragStop Event
Triggered when the dragged panel is dropped.
DragStopArgs Properties:
cancel: boolean- Set to true to prevent the drop operation (reverts to original position)element: HTMLElement- The current drag element being droppedevent: MouseEvent | TouchEvent- The actual event (typically mouseup or touchend)panels: PanelModel[]- Model values of panels that have been modified (their positions changed during drag)target: HTMLElement- The current target element (where the panel is being dropped)
@Component({
template: `
<div class="history">{{ history }}</div>
<ejs-dashboardlayout
[panels]="panels"
(dragStop)="onDragStop($event)"
(change)="onChange($event)">
</ejs-dashboardlayout>
`
})
export class DragStopExampleComponent {
public history = '';
public panels: any = [
{ id: 'panel-1', sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'P1', header: 'Panel 1' },
{ id: 'panel-2', sizeX: 1, sizeY: 1, row: 0, col: 1, content: 'P2', header: 'Panel 2' },
{ id: 'panel-3', sizeX: 1, sizeY: 1, row: 1, col: 0, content: 'P3', header: 'Panel 3' }
];
onDragStop(args: any) {
console.log('Drop event triggered');
console.log('Dropped element:', args.element);
console.log('Drop target:', args.target);
// Check which panels were affected
console.log(`${args.panels.length} panels were repositioned`);
args.panels.forEach(panel => {
console.log(`Panel ${panel.id}: row ${panel.row}, col ${panel.col}`);
});
// Optionally prevent the drop
if (args.target && args.target.id === 'protected-zone') {
args.cancel = true;
this.history = 'Drop cancelled - target is protected';
} else {
this.history = `Dropped panel. ${args.panels.length} panels repositioned`;
}
}
onChange(args: any) {
console.log('Change event triggered after drag');
console.log('Added panels:', args.addedPanels?.length || 0);
console.log('Changed panels:', args.changedPanels?.length || 0);
console.log('Removed panels:', args.removedPanels?.length || 0);
}
}Complete Drag Event Handler Example
import { Component, ViewChild } from '@angular/core';
import { DashboardLayoutComponent, DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-drag-events-full',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="container">
<div class="event-log">
<h4>Drag Event Timeline</h4>
<div *ngFor="let event of events" class="log-entry" [class.drag-start]="event.includes('START')" [class.drag-stop]="event.includes('STOP')">
{{ event }}
</div>
</div>
<ejs-dashboardlayout
#dashboard
[columns]="5"
[panels]="panels"
[allowFloating]="true"
(dragStart)="onDragStart($event)"
(drag)="onDrag($event)"
(dragStop)="onDragStop($event)"
(change)="onChange($event)">
</ejs-dashboardlayout>
</div>
`,
styles: [`
.container { display: flex; gap: 20px; }
.event-log {
width: 250px;
max-height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
border-radius: 4px;
}
.log-entry {
font-size: 12px;
padding: 4px 8px;
margin: 2px 0;
border-radius: 2px;
}
.drag-start { background: #c8e6c9; }
.drag-stop { background: #ffccbc; }
`]
})
export class DragEventsFullComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
public events: string[] = [];
public dragStartTime: number = 0;
private dragEventThrottle: any;
public panels: any = [
{ id: 'p1', sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Panel 1', header: 'P1' },
{ id: 'p2', sizeX: 2, sizeY: 1, row: 0, col: 1, content: 'Panel 2', header: 'P2' },
{ id: 'p3', sizeX: 1, sizeY: 1, row: 0, col: 3, content: 'Panel 3', header: 'P3' },
{ id: 'p4', sizeX: 1, sizeY: 2, row: 1, col: 0, content: 'Panel 4', header: 'P4' }
];
onDragStart(args: any) {
const time = new Date().toLocaleTimeString();
this.dragStartTime = Date.now();
this.events.unshift(`[${time}] START drag`);
if (this.events.length > 20) {
this.events.pop();
}
}
onDrag(args: any) {
// Throttle to every 5 events to avoid spamming log
clearTimeout(this.dragEventThrottle);
this.dragEventThrottle = setTimeout(() => {
const elapsed = Date.now() - this.dragStartTime;
this.events[0] = `[${elapsed}ms] Dragging...`;
}, 100);
}
onDragStop(args: any) {
const time = new Date().toLocaleTimeString();
const duration = Date.now() - this.dragStartTime;
const movedCount = args.panels?.length || 0;
this.events.unshift(`[${time}] STOP drag - ${movedCount} panels moved (${duration}ms)`);
if (this.events.length > 20) {
this.events.pop();
}
}
onChange(args: any) {
const time = new Date().toLocaleTimeString();
const details = `add:${args.addedPanels?.length || 0} chg:${args.changedPanels?.length || 0} rm:${args.removedPanels?.length || 0}`;
this.events.unshift(`[${time}] CHANGE event (${details})`);
if (this.events.length > 20) {
this.events.pop();
}
}
}Customizing Drag Handles
Restrict Dragging to Header Only
By default, dragging works from anywhere on the panel. Restrict it to the header using the draggableHandle property with a CSS selector:
@Component({
template: `
<ejs-dashboardlayout
[draggableHandle]="'.e-panel-header'"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class DragHeaderOnlyComponent {
public panels = [
{
sizeX: 2,
sizeY: 1,
row: 0,
col: 0,
header: 'Click here to drag',
content: '<div>Content area cannot drag</div>'
}
];
}Supported CSS Selectors:
.e-panel-header- Syncfusion's default panel header class#custom-handle- ID-based selector.custom-class- Custom class selector[data-draggable]- Attribute selector
// ✅ Examples of valid draggableHandle values
'#drag-button'
'.drag-handle'
'.panel-toolbar .btn-drag'
'[data-handle="true"]'
'.header-icon'Custom Drag Handle Element
@Component({
selector: 'app-custom-drag',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[draggableHandle]="'.drag-icon'"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`,
styles: [`
.e-panel-header {
display: flex;
align-items: center;
gap: 8px;
}
.drag-icon {
cursor: grab;
font-size: 18px;
}
.drag-icon:active {
cursor: grabbing;
}
`]
})
export class CustomDragHandleComponent {
public panels = [
{
sizeX: 1,
sizeY: 1,
row: 0,
col: 0,
header: '<span class="drag-icon">⋮⋮</span> Panel Title',
content: 'Only the ⋮⋮ icon can drag this panel'
}
];
}Programmatic Panel Movement
Move panels programmatically without user interaction using the movePanel() method or the change event.
Moving Panels with movePanel()
export class ProgrammaticMoveComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
// Move panel 'panel-1' to row 1, column 2
movePanel() {
this.dashboard?.movePanel('panel-1', 1, 2);
}
}Note: The movePanel() method triggers the layout recalculation and floating behavior (if allowFloating is enabled). Modified panels are reported in the dragStop or change events.
Performance Considerations
Drag Event Frequency
The drag event fires approximately 60+ times per second on modern browsers during active dragging. Avoid expensive operations in the drag handler:
// Performance DO's and DON'Ts
// ❌ DON'T: Database updates
onDrag(args: any) {
this.saveToDatabase(args); // Terrible performance
}
// ❌ DON'T: Complex calculations
onDrag(args: any) {
for (let i = 0; i < 1000000; i++) {
Math.sqrt(i);
}
}
// ✅ DO: Use throttling/debouncing
private dragThrottle: any;
onDrag(args: any) {
clearTimeout(this.dragThrottle);
this.dragThrottle = setTimeout(() => {
this.lightweightUpdate();
}, 100);
}
// ✅ DO: Cache values
private cachedPositions: any = {};
onDrag(args: any) {
const key = `${args.element.id}`;
this.cachedPositions[key] = {
x: args.event.clientX,
y: args.event.clientY
};
}Event Order During Drag
1. dragStart - User begins dragging → Fires once 2. drag - User moving mouse/finger → Fires 60+ times per second 3. dragStop - User releases → Fires once 4. change - Layout updated → Fires once with complete panel changes
Getting Started with Dashboard Layout
Table of Contents
- Angular Environment Setup
- Installing the Package
- Adding CSS Stylesheets
- Creating Your First Dashboard
- Understanding Panel Properties
- Next Steps
Angular Environment Setup
The Dashboard Layout component requires an Angular environment with proper tooling. Begin by installing the Angular CLI, which streamlines project creation and development.
Install Angular CLI
npm install -g @angular/cliCreate a New Angular Project
ng new dashboard-layout-app
cd dashboard-layout-appDuring setup, you'll be prompted to choose a stylesheet format (CSS, SCSS, LESS). The default CSS is sufficient for this guide.
Angular 21 Standalone Architecture
Dashboard Layout works with both module-based and standalone component architectures. The examples here use standalone components (default in Angular 21+), which is the recommended modern approach.
Installing the Package
Step 1: Add Syncfusion Dashboard Layout Package
Install the @syncfusion/ej2-angular-layouts package via npm:
npm install @syncfusion/ej2-angular-layouts --saveThis package includes:
- Core layout library (@syncfusion/ej2-layouts)
- Base utilities (@syncfusion/ej2-base)
- Angular bindings (@syncfusion/ej2-angular-base)
Step 2: Verify Package Installation
Check your package.json file to confirm the package is listed:
{
"dependencies": {
"@syncfusion/ej2-angular-layouts": "^20.2.38"
}
}Note: For Angular versions below 12, use the ngcc (Angular Compatibility Compiler) package:
npm install @syncfusion/ej2-angular-layouts@ngcc --saveAdding CSS Stylesheets
Dashboard Layout requires CSS imports to display properly. Import both the base and layout component styles.
Add to Your Global Styles
Edit src/styles.css and add these imports at the top:
@import "../node_modules/@syncfusion/ej2-base/styles/material3.css";
@import "../node_modules/@syncfusion/ej2-angular-layouts/styles/material3.css";Available Themes:
material3.css- Modern Material Design 3bootstrap5.css- Bootstrap 5 stylingfabric.css- Microsoft Fabric designtailwind.css- Tailwind CSS theme
Choose the theme that matches your project's design system.
Alternative: Component-Level Styles
You can also import styles within your component:
import { Component } from '@angular/core';
import '@syncfusion/ej2-base/styles/material3.css';
import '@syncfusion/ej2-angular-layouts/styles/material3.css';
@Component({
selector: 'app-root',
template: `...`
})
export class AppComponent { }Creating Your First Dashboard
Approach 1: HTML Attributes (Simple)
Define panels directly in the template using data attributes:
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-root',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="control-section">
<ejs-dashboardlayout id="defaultLayout" [columns]="5" [cellSpacing]="[10, 10]">
<div id="panel1" class="e-panel" data-row="0" data-col="0" data-sizeX="1" data-sizeY="1">
<span id="close" class="e-template-icon e-clear-icon"></span>
<div class="e-panel-container">
<div class="content">Panel 1</div>
</div>
</div>
<div id="panel2" class="e-panel" data-row="0" data-col="1" data-sizeX="2" data-sizeY="1">
<span id="close" class="e-template-icon e-clear-icon"></span>
<div class="e-panel-container">
<div class="content">Panel 2</div>
</div>
</div>
</ejs-dashboardlayout>
</div>
`,
styles: [`
.control-section { width: 100%; height: 100vh; }
.e-panel { background: #f5f5f5; }
.content { padding: 20px; text-align: center; font-weight: bold; }
`]
})
export class AppComponent { }Approach 2: Property Binding (Recommended)
Define panels in the component class for better control and dynamic updates:
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-root',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="control-section">
<ejs-dashboardlayout
id="defaultLayout"
[columns]="5"
[cellSpacing]="[10, 10]"
[panels]="panels">
</ejs-dashboardlayout>
</div>
`,
styles: [`
.control-section { width: 100%; height: 100vh; }
.e-panel { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
.content { padding: 30px; text-align: center; color: white; font-weight: bold; }
`]
})
export class AppComponent {
public panels: any = [
{
id: 'panel1',
sizeX: 1,
sizeY: 1,
row: 0,
col: 0,
header: '<div class="header">Sales</div>',
content: '<div class="content">$45,000</div>'
},
{
id: 'panel2',
sizeX: 2,
sizeY: 1,
row: 0,
col: 1,
header: '<div class="header">Revenue</div>',
content: '<div class="content">$120,000</div>'
},
{
id: 'panel3',
sizeX: 1,
sizeY: 2,
row: 0,
col: 3,
header: '<div class="header">Growth</div>',
content: '<div class="content">+25%</div>'
}
];
}Understanding Panel Properties
Basic Properties
{
id: 'unique-panel-id', // Unique identifier for this panel
row: 0, // Starting grid row (0-based)
col: 0, // Starting grid column (0-based)
sizeX: 1, // Width in grid cells
sizeY: 1, // Height in grid cells
header: '<div>Title</div>', // Optional header content
content: '<div>Body</div>' // Panel content (HTML string)
}Running Your Application
Start the development server:
npm startThis runs ng serve by default. Open your browser to http://localhost:4200 to see your dashboard.
Hot Reloading
Angular CLI watches for file changes and automatically reloads the browser, making development fast and interactive.
Key Concepts
Grid System
The Dashboard Layout uses a grid-based coordinate system:
- Columns: Total grid columns (e.g., 5 columns divides width into 5 equal parts)
- Rows: Auto-calculated based on panel positions
- Cells: Individual grid units where panels snap to positions
Example: With 5 columns, each cell takes 20% of the dashboard width.
Cell Spacing
Defined as [horizontal, vertical] spacing in pixels:
[cellSpacing]="[10, 10]" // 10px horizontal, 10px vertical spacingPanel Positioning
Panels are positioned using grid coordinates:
row: 0, col: 0= Top-leftrow: 0, col: 1= Next to it horizontallyrow: 1, col: 0= Below first panel
Panel Sizing
Size is measured in grid cells, not pixels:
sizeX: 1= 1 cell width (20% with 5 columns)sizeX: 2= 2 cells width (40% with 5 columns)sizeY: 1= 1 cell height
Quick Start Example
Here's a complete working example to get started immediately:
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-root',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="control-section">
<ejs-dashboardlayout
id="defaultLayout"
[columns]="5"
[cellSpacing]="[10, 10]"
[panels]="panels">
</ejs-dashboardlayout>
</div>
`,
styles: [`
.control-section { width: 100%; height: 100vh; }
.content { padding: 20px; text-align: center; }
`]
})
export class AppComponent {
public panels = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: '<div class="content">Panel 1</div>' },
{ sizeX: 2, sizeY: 1, row: 0, col: 1, content: '<div class="content">Panel 2</div>' },
{ sizeX: 1, sizeY: 2, row: 0, col: 3, content: '<div class="content">Panel 3</div>' }
];
}Common Patterns
Pattern 1: Basic Dashboard with 5 Columns
Start with a 5-column grid layout. Each column represents one unit. Set cellSpacing to create separation between panels for better visual organization.
<ejs-dashboardlayout
[columns]="5"
[cellSpacing]="[10, 10]"
[panels]="panels">
</ejs-dashboardlayout>Pattern 2: Draggable and Resizable Dashboard
Enable user interaction with drag-drop and resize capabilities for full dashboard customization. See dragging-and-dropping.md and resizing-and-floating.md for detailed configuration.
<ejs-dashboardlayout
[allowDragging]="true"
[allowResizing]="true"
[resizableHandles]="['e-south-east', 'e-east', 'e-west']"
[panels]="panels">
</ejs-dashboardlayout>Pattern 3: Responsive Mobile Dashboard
Automatically stack panels into a single column on mobile devices using media queries. See responsive-and-adaptive.md for more responsive strategies.
<ejs-dashboardlayout
[mediaQuery]="'max-width: 768px'"
[panels]="panels">
</ejs-dashboardlayout>Pattern 4: Persistent Dashboard State
Save and restore dashboard configurations using serialization. See save-restore-state.md for full state management examples.
// Save
const layoutState = this.dashboard.serialize();
localStorage.setItem('dashboardLayout', JSON.stringify(layoutState));
// Restore
const saved = JSON.parse(localStorage.getItem('dashboardLayout'));
this.dashboard.panels = saved;Next Steps
Now that you've created a basic dashboard:
1. Add Interaction: Enable drag-drop and resizing (see dragging-and-dropping.md) 2. Dynamic Panels: Add/remove panels at runtime (see adding-removing-panels.md) 3. Responsive Design: Handle mobile screens (see responsive-and-adaptive.md) 4. Persist State: Save user layouts (see save-restore-state.md) 5. Add Charts: Embed Syncfusion Charts as panel content (see styling-and-customization.md)
Troubleshooting
Issue: Styles not applied
- Solution: Ensure CSS imports are in
styles.css, not in component CSS - Check: Browser DevTools → Inspect dashboard element → Verify
.e-dashboardlayoutclass is present
Issue: Panels appearing stacked vertically
- Solution: Check parent container height. Dashboard expands to fill parent.
- Fix: Set
height: 100vhon parent container
Issue: Module import errors
- Solution: Ensure
DashboardLayoutModuleis imported in theimportsarray - Modern: Use standalone components (shown above)
- Legacy: Import in
NgModuledeclarations
Grid Lines and Visual Representation
Overview
The Dashboard Layout component provides a visual grid system that shows the cell divisions within the layout. By enabling grid lines, you can see how the dashboard is divided into cells, making it easier to understand panel positioning and sizing during the initial design phase. Grid lines are especially helpful when building and debugging dashboard layouts.
Enabling Grid Lines
Grid lines are controlled using the showGridLines property. When enabled, grid lines display the cell structure of the dashboard layout, making it easy to visualize panel positions and dimensions.
Basic Grid Lines Configuration
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-grid-lines',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="controls">
<button (click)="toggleGridLines()">
{{ showGridLines ? 'Hide' : 'Show' }} Grid Lines
</button>
</div>
<ejs-dashboardlayout
[columns]="5"
[cellSpacing]="[10, 10]"
[showGridLines]="showGridLines"
[panels]="panels">
</ejs-dashboardlayout>
`,
styles: [`
:host { display: block; width: 100%; height: 100vh; }
`]
})
export class GridLinesComponent {
public showGridLines = true;
public panels: any = [
{ sizeX: 3, sizeY: 2, row: 0, col: 1, content: '<div class="content">Panel 1</div>' },
{ sizeX: 1, sizeY: 3, row: 0, col: 4, content: '<div class="content">Panel 2</div>' },
{ sizeX: 2, sizeY: 1, row: 2, col: 2, content: '<div class="content">Panel 3</div>' }
];
toggleGridLines() {
this.showGridLines = !this.showGridLines;
}
}Understanding the Grid System
Grid Structure
The Dashboard Layout divides the available space into a grid:
- Columns: Set by the
columnsproperty (e.g., 5 columns) - Rows: Automatically calculated based on panel positions
- Cells: Individual units in the grid (width = total width / columns)
- Spacing: Defined by
cellSpacingproperty [horizontal, vertical]
Grid Visualization
When grid lines are enabled, you can see:
- Vertical lines separating columns
- Horizontal lines separating rows
- The exact boundaries of each cell
- How panels span across cells
Example with Grid Lines
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-visual-grid',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[columns]="5"
[cellSpacing]="[10, 10]"
[showGridLines]="true"
[panels]="panels">
</ejs-dashboardlayout>
`,
styles: [`
:host { display: block; width: 100%; height: 100vh; }
`]
})
export class VisualGridComponent {
public panels: any = [
// Panel spanning 1 column, 1 row
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: '<div>1x1</div>' },
// Panel spanning 3 columns, 2 rows
{ sizeX: 3, sizeY: 2, row: 0, col: 1, content: '<div>3x2</div>' },
// Panel spanning 1 column, 3 rows
{ sizeX: 1, sizeY: 3, row: 0, col: 4, content: '<div>1x3</div>' },
// Panel spanning 2 columns, 1 row
{ sizeX: 2, sizeY: 1, row: 2, col: 0, content: '<div>2x1</div>' },
// Panel spanning 2 columns, 1 row
{ sizeX: 2, sizeY: 1, row: 2, col: 2, content: '<div>2x1</div>' }
];
}Use Cases for Grid Lines
1. Dashboard Design Phase
Enable grid lines during initial design to visualize layout structure:
public showGridLines = !this.isProduction; // Show only in development2. Panel Positioning Help
Display grid lines when users are adding or configuring panels:
enableDesignMode() {
this.showGridLines = true;
this.editMode = true;
}
exitDesignMode() {
this.showGridLines = false;
this.editMode = false;
}3. Responsive Debugging
Show grid lines to verify responsive layout changes:
@HostListener('window:resize')
onResize() {
const isMobile = window.innerWidth < 600;
this.showGridLines = isMobile; // Show grid on mobile for debugging
}Grid Line Styling
While grid lines are built-in, you can customize the overall dashboard appearance:
/* Dashboard background */
.e-dashboardlayout.e-control {
background-color: #f5f5f5;
}
/* Panel styling to make grid visible */
.e-dashboardlayout.e-control .e-panel {
background-color: white;
border: 1px solid #e0e0e0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}Best Practices
1. Use Grid Lines for Layout Debugging
Enable grid lines when troubleshooting panel positioning issues:
if (panelNotDisplaying) {
this.showGridLines = true; // Enable to see cell structure
console.log('Grid lines enabled for debugging');
}2. Disable in Production
Typically hide grid lines in production environments:
public showGridLines = !environment.production;3. Provide User Toggle
Allow power users to toggle grid lines:
toggleGridVisibility(show: boolean) {
this.showGridLines = show;
// Optionally save preference
localStorage.setItem('gridLinesVisible', show.toString());
}4. Document Grid System
Include grid line information in dashboards you design:
// Column layout: 5 columns
// Each column is 20% of dashboard width
// With 10px spacing between columns
public gridInfo = {
columns: 5,
spacing: [10, 10],
cellWidth: '20%'
};Moving Panels Programmatically
Overview
The Dashboard Layout component provides the ability to move panels to different positions programmatically using the movePanel() method. This allows you to change panel positions without user interaction, enabling dynamic dashboard reorganization based on user preferences, data changes, or application logic.
movePanel() Method
Method Signature
movePanel(id: string, row: number, col: number): voidParameters
- id - The unique identifier of the panel to move
- row - The target row position (0-based index)
- col - The target column position (0-based index)
Basic Usage
Moving a Panel to a New Position
import { Component, ViewChild } from '@angular/core';
import { DashboardLayoutComponent, DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-move-panel',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="controls">
<button (click)="moveTopLeft()">Move to Top-Left</button>
<button (click)="moveBottomRight()">Move to Bottom-Right</button>
</div>
<ejs-dashboardlayout #dashboard [columns]="5" [panels]="panels">
</ejs-dashboardlayout>
`
})
export class MovePanelComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
public panels: any = [
{ id: 'panel-1', sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Panel 1' },
{ id: 'panel-2', sizeX: 1, sizeY: 1, row: 0, col: 1, content: 'Panel 2' },
{ id: 'panel-3', sizeX: 1, sizeY: 1, row: 0, col: 2, content: 'Panel 3' }
];
moveTopLeft() {
this.dashboard?.movePanel('panel-3', 0, 0);
}
moveBottomRight() {
this.dashboard?.movePanel('panel-1', 2, 4);
}
}Movement Tracking with Events
Listen to Layout Changes
When a panel is moved, the change event is triggered:
@Component({
template: `
<div class="change-log">{{ lastChange }}</div>
<div class="controls">
<button (click)="rearrangePanels()">Rearrange</button>
</div>
<ejs-dashboardlayout
#dashboard
[panels]="panels"
(change)="onLayoutChanged($event)">
</ejs-dashboardlayout>
`
})
export class ChangeEventComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
public lastChange = 'Awaiting changes...';
public panels: any = [
{ id: 'p1', sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Panel 1' },
{ id: 'p2', sizeX: 1, sizeY: 1, row: 0, col: 1, content: 'Panel 2' }
];
rearrangePanels() {
this.dashboard?.movePanel('p1', 1, 1);
}
onLayoutChanged(args: any) {
this.lastChange = `Layout changed at ${new Date().toLocaleTimeString()}`;
}
}Programmatic Reordering
Swap Panel Positions
swapPanels(panelId1: string, panelId2: string) {
const p1 = this.panels.find(p => p.id === panelId1);
const p2 = this.panels.find(p => p.id === panelId2);
if (p1 && p2) {
// Store original positions
const temp = { row: p1.row, col: p1.col };
// Move panels
this.dashboard?.movePanel(panelId1, p2.row, p2.col);
this.dashboard?.movePanel(panelId2, temp.row, temp.col);
// Update local state
p1.row = p2.row;
p1.col = p2.col;
p2.row = temp.row;
p2.col = temp.col;
}
}Reset to Default Layout
resetLayout() {
const defaultLayout = [
{ id: 'p1', row: 0, col: 0 },
{ id: 'p2', row: 0, col: 1 },
{ id: 'p3', row: 0, col: 2 }
];
defaultLayout.forEach(layout => {
this.dashboard?.movePanel(layout.id, layout.row, layout.col);
});
}Practical Examples
Example: Circular Panel Rotation
Move panels in a circular pattern:
circulatePanels() {
const positions = [
{ row: 0, col: 0 },
{ row: 0, col: 1 },
{ row: 0, col: 2 },
{ row: 1, col: 0 }
];
this.panels.forEach((panel, index) => {
const nextIndex = (index + 1) % positions.length;
const nextPos = positions[nextIndex];
this.dashboard?.movePanel(panel.id, nextPos.row, nextPos.col);
});
}Example: Sort Panels by Priority
sortByPriority(priority: 'high' | 'medium' | 'low') {
let row = 0;
let col = 0;
// Define priority order
const panelPriority: { [key: string]: number } = {
'critical': 0,
'important': 1,
'optional': 2
};
// Sort and position panels
const sorted = this.panels.sort((a, b) =>
(panelPriority[a.priority] || 999) - (panelPriority[b.priority] || 999)
);
sorted.forEach(panel => {
this.dashboard?.movePanel(panel.id, row, col);
col++;
if (col >= 5) {
col = 0;
row++;
}
});
}Best Practices
1. Validate Target Positions
Always ensure target positions are valid before moving:
isValidPosition(row: number, col: number, columns: number = 5): boolean {
return row >= 0 && col >= 0 && col < columns;
}
safeMovePanel(panelId: string, row: number, col: number) {
if (this.isValidPosition(row, col)) {
this.dashboard?.movePanel(panelId, row, col);
}
}2. Track Panel Positions
Maintain an updated record of panel positions:
private panelPositions: Map<string, { row: number; col: number }> = new Map();
moveAndTrack(panelId: string, row: number, col: number) {
this.dashboard?.movePanel(panelId, row, col);
this.panelPositions.set(panelId, { row, col });
}3. Save State After Moving
Persist the new layout configuration:
moveAndPersist(panelId: string, row: number, col: number) {
this.dashboard?.movePanel(panelId, row, col);
const state = this.dashboard?.serialize();
localStorage.setItem('dashboardLayout', JSON.stringify(state));
}Setting Panel Headers
Overview
Panel headers provide a way to display titles and labels at the top of each panel, helping users identify and organize the content within a dashboard. Headers are configured using the header property in the panel definition and can contain HTML content for rich customization.
Adding Panel Headers
Basic Header Configuration
Headers are added to panels using the header property in the panel object:
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-panel-headers',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[columns]="6"
[cellSpacing]="[10, 10]"
[panels]="panels">
</ejs-dashboardlayout>
`,
styles: [`
:host { display: block; width: 100%; height: 100vh; }
`]
})
export class PanelHeadersComponent {
public panels: any = [
{
id: 'Panel0',
sizeX: 1,
sizeY: 1,
row: 0,
col: 0,
header: '<div>Panel 0</div>',
content: '<div class="content">Panel Content</div>'
},
{
id: 'Panel1',
sizeX: 3,
sizeY: 2,
row: 0,
col: 1,
header: '<div>Panel 1</div>',
content: '<div class="content">Panel Content</div>'
},
{
id: 'Panel2',
sizeX: 1,
sizeY: 3,
row: 0,
col: 4,
header: '<div>Panel 2</div>',
content: '<div class="content">Panel Content</div>'
}
];
}Header Properties
Headers support HTML content and can be styled using CSS. The header property accepts any HTML string:
{
id: 'panel-1',
sizeX: 2,
sizeY: 1,
row: 0,
col: 0,
header: '<div class="panel-header">Sales Dashboard</div>',
content: '<div>Content here</div>'
}Styling Panel Headers
Headers can be customized using CSS classes:
.e-dashboardlayout.e-control .e-panel .e-panel-container .e-panel-header {
background-color: #667eea;
color: white;
padding: 12px;
font-weight: bold;
border-bottom: 2px solid #5568d3;
}Integrating Syncfusion Components with Headers
Panels can display Syncfusion components (Charts, Grids, Gauges) as their content. When integrated, headers help identify the component type:
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
import { ChartAllModule } from '@syncfusion/ej2-angular-charts';
@Component({
selector: 'app-component-dashboard',
standalone: true,
imports: [DashboardLayoutModule, ChartAllModule],
template: `
<ejs-dashboardlayout [columns]="6" [cellSpacing]="[10, 10]">
<e-panels>
<e-panel [sizeX]="3" [sizeY]="2" [row]="0" [col]="0">
<ng-template #header>
<div>Sales Chart</div>
</ng-template>
<ng-template #content>
<ejs-chart height="200px">
<e-series-collection>
<e-series [dataSource]="chartData" type='Column' xName='month' yName='sales'>
</e-series>
</e-series-collection>
</ejs-chart>
</ng-template>
</e-panel>
</e-panels>
</ejs-dashboardlayout>
`
})
export class ComponentDashboardComponent {
public chartData: any[] = [
{ month: 'Jan', sales: 35 },
{ month: 'Feb', sales: 28 },
{ month: 'Mar', sales: 34 }
];
}Dynamic Headers
Headers can be updated dynamically by modifying the panel configuration:
updatePanelHeader(panelId: string, newHeader: string) {
const panel = this.panels.find(p => p.id === panelId);
if (panel) {
panel.header = newHeader;
}
}Best Practices for Headers
1. Keep Headers Concise
Use short, descriptive titles that clearly identify panel content:
header: '<div>Q1 Revenue</div>' // Good
header: '<div>This is the quarterly revenue information for the first quarter of the year</div>' // Too long2. Use Consistent Styling
Apply consistent header styling across all panels:
.e-panel .e-panel-container .e-panel-header {
background: var(--primary-color);
color: white;
padding: 12px 16px;
font-size: 14px;
font-weight: 600;
}3. Include Visual Indicators
Add icons or badges to headers for better content identification:
header: '<div><span class="icon">📊</span> Sales Data</div>'Resizing and Floating Panels
Table of Contents
- Overview
- Enabling Resizing
- Resize Events
- Event Arguments Reference
- Programmatic Resizing
- Size Constraints
- Floating Behavior
- Advanced Examples
Overview
The Dashboard Layout component supports resizing panels to help users customize their dashboard layout. Resizing is disabled by default and must be explicitly enabled with the allowResizing property. When enabled, users can drag resize handles at panel edges to change panel dimensions.
Key Features
- Disabled by Default:
allowResizingis false by default (enable explicitly) - Customizable Handles: Six directional resize handles (e-south-east, e-east, e-west, e-north, e-south, e-south-west)
- Size Constraints: Enforce minimum and maximum panel sizes with
minSizeX,maxSizeX,minSizeY,maxSizeY - Event Tracking: Three lifecycle events:
resizeStart,resize,resizeStop - Floating Integration: Panels auto-reposition when
allowFloatingis true - Touch Support: Works on mobile with touch events
- Floating Panels: Panels automatically move upward to fill empty spaces
Enabling Resizing
Basic Resizing
Enable resizing with all default resize handles:
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-resizable',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[columns]="5"
[allowResizing]="true"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class ResizableDashboardComponent {
public panels = [
{ sizeX: 2, sizeY: 1, row: 0, col: 0, content: '<div>Resize me</div>' },
{ sizeX: 1, sizeY: 2, row: 0, col: 2, content: '<div>Or me</div>' }
];
}Disable Resizing
Prevent users from resizing panels:
@Component({
template: `
<ejs-dashboardlayout
[allowResizing]="false"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class StaticSizeDashboardComponent {
public panels = [
{ sizeX: 2, sizeY: 1, row: 0, col: 0, content: '<div>Cannot resize</div>' }
];
}Resize Handles
Available Resize Directions
The resizableHandles property accepts an array of handle positions:
| Handle | Position | Direction |
|---|---|---|
e-south-east | Bottom-right | Resize width and height |
e-east | Right edge | Resize width only |
e-west | Left edge | Resize width only (from left) |
e-north | Top edge | Resize height only (from top) |
e-south | Bottom edge | Resize height only |
e-south-west | Bottom-left | Resize width (from left) and height |
Custom Resize Handles
Restrict resizing to specific directions:
@Component({
template: `
<ejs-dashboardlayout
[allowResizing]="true"
[resizableHandles]="['e-south-east', 'e-east']"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class CustomHandlesComponent {
// Only bottom-right and right edge resize handles visible
public panels = [
{ sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'Resize from bottom-right or right' }
];
}All Resize Handles Example
@Component({
template: `
<ejs-dashboardlayout
[allowResizing]="true"
[resizableHandles]="['e-south-east', 'e-east', 'e-west', 'e-north', 'e-south', 'e-south-west']"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class AllHandlesComponent {
public panels = [
{ sizeX: 2, sizeY: 2, row: 0, col: 0, content: 'Fully resizable from all sides' }
];
}Resize Events
The Dashboard Layout provides three lifecycle events for resize operations:
- resizeStart - Fires when user begins resizing
- resize - Fires continuously while resizing
- resizeStop - Fires when resize ends
resizeStart Event
Triggered when a panel is about to be resized.
ResizeArgs Properties:
element: HTMLElement- The cell element being resizedevent: MouseEvent | TouchEvent- The original mouse or touch eventisInteracted: boolean- Whether triggered by user interaction (true) or programmatically (false)panels: PanelModel[]- Current panels that may be affected by the resize
@Component({
template: `
<div class="status">{{ status }}</div>
<ejs-dashboardlayout
[allowResizing]="true"
[panels]="panels"
(resizeStart)="onResizeStart($event)">
</ejs-dashboardlayout>
`
})
export class ResizeStartExampleComponent {
public status = 'Ready';
public panels = [
{ id: 'p1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'Panel 1', header: 'P1' }
];
onResizeStart(args: any) {
console.log('Resize started');
console.log('Element:', args.element);
console.log('Is user interaction:', args.isInteracted);
this.status = `Starting resize on panel`;
}
}resize Event
Triggered continuously while the user is resizing a panel.
ResizeArgs Properties: (same as resizeStart)
element: HTMLElement- The cell element being resizedevent: MouseEvent | TouchEvent- The original mouse or touch eventisInteracted: boolean- Whether triggered by user interactionpanels: PanelModel[]- Updated panels array showing current resize state
Note: This event fires frequently during resizing. Use throttling for expensive operations.
@Component({
template: `
<div class="size-display">{{ sizeInfo }}</div>
<ejs-dashboardlayout
[allowResizing]="true"
[columns]="5"
[panels]="panels"
(resize)="onResize($event)">
</ejs-dashboardlayout>
`
})
export class ResizeExampleComponent {
public sizeInfo = 'Not resizing';
private resizeThrottle: any;
public panels = [
{ id: 'p1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'Panel', header: 'Panel 1' }
];
onResize(args: any) {
// Throttle updates to avoid performance issues
clearTimeout(this.resizeThrottle);
this.resizeThrottle = setTimeout(() => {
if (args.panels && args.panels.length > 0) {
const panel = args.panels[0];
this.sizeInfo = `Panel size: ${panel.sizeX} x ${panel.sizeY} cells`;
}
}, 100);
}
}resizeStop Event
Triggered when the resize operation ends.
ResizeArgs Properties: (same as resize)
element: HTMLElement- The cell element that was resizedevent: MouseEvent | TouchEvent- The actual event (typically mouseup or touchend)isInteracted: boolean- Whether triggered by user interactionpanels: PanelModel[]- Final panels array after resize completes
@Component({
template: `
<div class="history">{{ history }}</div>
<ejs-dashboardlayout
[allowResizing]="true"
[columns]="5"
[panels]="panels"
(resizeStop)="onResizeStop($event)"
(change)="onChange($event)">
</ejs-dashboardlayout>
`
})
export class ResizeStopExampleComponent {
public history = '';
public panels: any = [
{ id: 'p1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'P1', header: 'Panel 1' },
{ id: 'p2', sizeX: 1, sizeY: 1, row: 0, col: 2, content: 'P2', header: 'Panel 2' }
];
onResizeStop(args: any) {
console.log('Resize completed');
console.log(`${args.panels.length} panels affected`);
args.panels.forEach(panel => {
console.log(`Panel ${panel.id}: ${panel.sizeX} x ${panel.sizeY} cells`);
});
this.history = `Resize complete. ${args.panels.length} panels updated`;
}
onChange(args: any) {
console.log('Layout change event after resize');
}
}Complete Resize Event Handler Example
import { Component, ViewChild } from '@angular/core';
import { DashboardLayoutComponent, DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-resize-events-full',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="container">
<div class="event-log">
<h4>Resize Events</h4>
<div *ngFor="let event of events" class="log-entry" [class.start]="event.includes('START')" [class.stop]="event.includes('STOP')">
{{ event }}
</div>
</div>
<ejs-dashboardlayout
#dashboard
[columns]="5"
[allowResizing]="true"
[resizableHandles]="['e-south-east', 'e-east']"
[panels]="panels"
(resizeStart)="onResizeStart($event)"
(resize)="onResize($event)"
(resizeStop)="onResizeStop($event)">
</ejs-dashboardlayout>
</div>
`,
styles: [`
.container { display: flex; gap: 20px; }
.event-log {
width: 250px;
max-height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
border-radius: 4px;
}
.log-entry {
font-size: 12px;
padding: 4px 8px;
margin: 2px 0;
border-radius: 2px;
}
.start { background: #c8e6c9; }
.stop { background: #ffccbc; }
`]
})
export class ResizeEventsFullComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
public events: string[] = [];
public panels: any = [
{ id: 'p1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'Panel 1', header: 'P1' },
{ id: 'p2', sizeX: 1, sizeY: 2, row: 0, col: 2, content: 'Panel 2', header: 'P2' }
];
onResizeStart(args: any) {
const time = new Date().toLocaleTimeString();
this.events.unshift(`[${time}] START resize`);
if (this.events.length > 20) this.events.pop();
}
onResize(args: any) {
if (this.events[0]?.includes('Resizing')) {
const elapsed = (Date.now() - this.resizeStartTime) / 1000;
this.events[0] = `Resizing... (${elapsed.toFixed(1)}s)`;
}
}
onResizeStop(args: any) {
const time = new Date().toLocaleTimeString();
this.events.unshift(`[${time}] STOP resize - ${args.panels.length} panels affected`);
if (this.events.length > 20) this.events.pop();
}
private resizeStartTime: number = Date.now();
}Event Arguments Reference
ResizeArgs Complete Structure
All resize events (resizeStart, resize, resizeStop) use the ResizeArgs interface:
interface ResizeArgs {
element: HTMLElement; // The panel element being resized
event: MouseEvent | TouchEvent; // The native browser event
isInteracted: boolean; // true if user-triggered, false if programmatic
panels: PanelModel[]; // Array of panels affected by resize
}Usage Example:
onResizeEvent(args: any) {
// Check source of resize
if (args.isInteracted) {
console.log('User is resizing');
} else {
console.log('Resize triggered programmatically');
}
// Get affected panels
args.panels.forEach(panel => {
console.log(`Panel ${panel.id}: ${panel.sizeX}x${panel.sizeY}`);
});
// Access the resized element
const element = args.element;
const rect = element.getBoundingClientRect();
console.log(`Element position: ${rect.width}x${rect.height}`);
}Programmatic Resizing
Using resizePanel() Method
Resize panels programmatically without user interaction:
export class ProgrammaticResizeComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
// Resize panel 'panel-1' to 3 cells wide and 2 cells tall
resizePanel() {
this.dashboard?.resizePanel('panel-1', 3, 2);
}
// Resize to minimum size
minimizePanel() {
this.dashboard?.resizePanel('panel-1', 1, 1);
}
// Resize to maximum allowed by constraints
maximizePanel() {
// Panel must have maxSizeX and maxSizeY set
this.dashboard?.resizePanel('panel-1', 5, 4);
}
}resizePanel() Signature:
resizePanel(panelId: string, newSizeX: number, newSizeY: number): voidParameters:
panelId(string) - The ID of the panel to resizenewSizeX(number) - New width in cellsnewSizeY(number) - New height in cells- Returns: void (no return value)
Notes:
- Respects minimum and maximum size constraints
- Triggers
resizeStart,resize, andresizeStopevents withisInteracted: false - Activates floating if
allowFloatingis true - Raises errors if panel ID doesn't exist
Size Constraints
Understanding Panel Size Limits
Each panel can have minimum and maximum size limits enforced during resizing:
| Property | Type | Default | Purpose |
|---|---|---|---|
minSizeX | number | 1 | Minimum width in cells (minimum 1) |
minSizeY | number | 1 | Minimum height in cells (minimum 1) |
maxSizeX | number | null | Maximum width in cells (null = no limit) |
maxSizeY | number | null | Maximum height in cells (null = no limit) |
Applying Size Constraints
@Component({
template: `
<ejs-dashboardlayout
[allowResizing]="true"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class SizeConstraintsComponent {
public panels = [
{
id: 'panel-1',
sizeX: 2,
sizeY: 1,
row: 0,
col: 0,
content: 'Panel 1',
minSizeX: 1,
maxSizeX: 4,
minSizeY: 1,
maxSizeY: 3
},
{
id: 'panel-2',
sizeX: 1,
sizeY: 2,
row: 0,
col: 2,
content: 'Panel 2',
minSizeX: 1,
minSizeY: 1
// No maxSizeX/maxSizeY = can grow to grid edges
}
];
}Enforcing Constraints During Resize
When a user resizes a panel: 1. New size is validated against minSizeX, minSizeY, maxSizeX, maxSizeY 2. If new size violates constraints, resize stops at the boundary 3. Resized panel may push other panels if allowFloating is false 4. Panels reposition upward if allowFloating is true
onResize(args: any) {
args.panels.forEach(panel => {
// Check if size is at constraint boundary
if (panel.sizeX === panel.maxSizeX) {
console.log(`Panel ${panel.id} at maximum width`);
}
if (panel.sizeX === panel.minSizeX) {
console.log(`Panel ${panel.id} at minimum width`);
}
});
}Floating Behavior
Understanding Floating
When allowFloating is true, panels automatically move upward to fill empty spaces created by resizing or dragging.
Floating with Resizing
@Component({
template: `
<ejs-dashboardlayout
[allowResizing]="true"
[allowFloating]="true"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class FloatingResizeComponent {
public panels = [
{ id: 'p1', sizeX: 2, sizeY: 2, row: 0, col: 0, content: 'Panel 1' },
{ id: 'p2', sizeX: 1, sizeY: 1, row: 0, col: 2, content: 'Panel 2' },
{ id: 'p3', sizeX: 1, sizeY: 1, row: 0, col: 3, content: 'Panel 3' },
{ id: 'p4', sizeX: 2, sizeY: 1, row: 1, col: 0, content: 'Panel 4' }
];
// If user shrinks Panel 1 from 2x2 to 1x1:
// - Panel 2 and 3 will float left to fill the gap
// - Panel 4 will float up to row 0, column 2
}Fixed Layout (No Floating)
@Component({
template: `
<ejs-dashboardlayout
[allowResizing]="true"
[allowFloating]="false"
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class FixedLayoutComponent {
// Panels maintain their row/col positions even with gaps
// Gaps remain empty until panels are moved
public panels = [
{ id: 'p1', sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'P1' },
{ id: 'p2', sizeX: 1, sizeY: 1, row: 0, col: 2, content: 'P2' } // Gap at col 1
];
}Advanced Examples
Example 1: Constrained Dashboard
Create a dashboard where panels have specific size limits:
@Component({
selector: 'app-constrained-dashboard',
standalone: true,
imports: [DashboardLayoutModule, CommonModule],
template: `
<div class="dashboard-container">
<h3>Constrained Resize Example</h3>
<div class="constraints-info">
<p>Panel 1: Width 1-4, Height 1-2</p>
<p>Panel 2: Width 1-3, Height 1-3</p>
</div>
<ejs-dashboardlayout
[columns]="6"
[allowResizing]="true"
[cellSpacing]="[10, 10]"
[panels]="constrainedPanels"
(resizeStart)="onConstraintedResizeStart($event)"
(resizeStop)="onConstraintedResizeStop($event)">
</ejs-dashboardlayout>
</div>
`,
styles: [`
.constraints-info {
background: #e3f2fd;
padding: 10px;
margin-bottom: 10px;
border-radius: 4px;
}
`]
})
export class ConstrainedDashboardComponent {
public constrainedPanels = [
{
id: 'constrained-p1',
sizeX: 2,
sizeY: 1,
row: 0,
col: 0,
header: 'Limited Panel',
content: 'Width: 1-4 cells, Height: 1-2 cells',
minSizeX: 1,
maxSizeX: 4,
minSizeY: 1,
maxSizeY: 2
},
{
id: 'constrained-p2',
sizeX: 2,
sizeY: 1,
row: 0,
col: 2,
header: 'Another Limited Panel',
content: 'Width: 1-3 cells, Height: 1-3 cells',
minSizeX: 1,
maxSizeX: 3,
minSizeY: 1,
maxSizeY: 3
}
];
onConstraintedResizeStart(args: any) {
console.log('Starting resize with constraints');
}
onConstraintedResizeStop(args: any) {
console.log('Resize stopped - constraints enforced');
}
}Responsive and Adaptive Design
Table of Contents
- Overview
- Built-in Responsive Behavior
- Customizing Breakpoints
- Cell Aspect Ratio
- Advanced Patterns
- Testing Responsive Layouts
Overview
Dashboard Layout automatically adapts to different screen sizes. On mobile devices, the layout transforms into a stacked single-column view. Customize this behavior using the mediaQuery property and responsive design patterns.
Key Responsive Features
- Auto-stacking: Panels stack vertically on small screens
- Custom Breakpoints: Define when stacking occurs
- Aspect Ratio: Control cell height/width ratio
- Parent Sizing: Responsive parent containers
- Touch-Friendly: Works on mobile devices
Built-in Responsive Behavior
Default Responsive Behavior
By default, Dashboard Layout stacks panels into a single column when screen width is 600px or less:
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-responsive',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[columns]="5"
[cellSpacing]="[10, 10]"
[panels]="panels">
<!-- Automatically stacks below 600px -->
</ejs-dashboardlayout>
`,
styles: [`
:host { display: block; width: 100%; height: 100vh; }
`]
})
export class ResponsiveComponent {
public panels: any = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Panel 1' },
{ sizeX: 2, sizeY: 1, row: 0, col: 1, content: 'Panel 2' },
{ sizeX: 1, sizeY: 2, row: 0, col: 3, content: 'Panel 3' }
];
}Desktop (600px+): Panels arranged in 5-column grid Mobile (<600px): All panels stacked in single column
Customizing Breakpoints
Change Breakpoint to 768px (Tablet)
@Component({
template: `
<ejs-dashboardlayout
[mediaQuery]="'max-width: 768px'"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class TabletBreakpointComponent {
public panels: any = [
{ sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'Panel 1' },
{ sizeX: 2, sizeY: 1, row: 0, col: 2, content: 'Panel 2' }
];
}Responsive Breakpoints for Device Types
@Component({
template: `
<ejs-dashboardlayout
[mediaQuery]="activeMediaQuery"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class DeviceResponsiveComponent implements OnInit {
public activeMediaQuery = '(max-width: 600px)';
public panels: any = [];
ngOnInit() {
this.updateBreakpoint();
window.addEventListener('resize', () => this.updateBreakpoint());
}
private updateBreakpoint() {
const width = window.innerWidth;
if (width < 480) {
this.activeMediaQuery = '(max-width: 480px)'; // Extra small
} else if (width < 768) {
this.activeMediaQuery = '(max-width: 767px)'; // Small
} else if (width < 1024) {
this.activeMediaQuery = '(max-width: 1023px)'; // Medium
} else {
this.activeMediaQuery = '(max-width: 1439px)'; // Large
}
}
}Media Query Patterns
Common CSS media query patterns:
// Mobile first (less than)
'(max-width: 600px)'
// Desktop only (greater than)
'(min-width: 1024px)'
// Range
'(min-width: 768px) and (max-width: 1023px)'
// Pixel ratios (high DPI screens)
'(min-width: 320px) and (-webkit-min-device-pixel-ratio: 2)'
// Landscape/Portrait
'(orientation: portrait) and (max-width: 600px)'
// Complex
'(max-width: 600px), (max-width: 1024px) and (orientation: landscape)'Cell Aspect Ratio
The cellAspectRatio property controls cell height relative to width.
Default Cell Aspect Ratio
Default ratio is Auto (cells are square):
@Component({
template: `
<ejs-dashboardlayout
[columns]="5"
[panels]="panels">
<!-- Cells are square (1:1 ratio) -->
</ejs-dashboardlayout>
`
})
export class DefaultAspectComponent {
public panels: any = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Square cell' }
];
}Custom Aspect Ratios
@Component({
template: `
<ejs-dashboardlayout
[columns]="5"
[cellAspectRatio]="aspectRatio"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class CustomAspectComponent {
// Wider, shorter cells (100px width, 50px height)
public aspectRatio = 100 / 50; // 2:1 ratio
public panels: any = [
{ sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Wide cell' }
];
}Common Aspect Ratios
| Ratio | Value | Use Case |
|---|---|---|
| Square | 1 | General dashboards |
| Wide | 16/9 | Video/media content |
| Ultra-wide | 21/9 | Widescreen displays |
| Tall | 9/16 | Mobile portraits |
| Golden | 1.618 | Aesthetic layouts |
| Short | 100/50 | Compact dashboards |
// Video aspect ratio
cellAspectRatio: 16/9
// Compact/dense
cellAspectRatio: 100/40
// Spacious
cellAspectRatio: 100/100Right-to-Left (RTL) Support
Overview
The Dashboard Layout component supports right-to-left (RTL) rendering, enabling you to build dashboards for languages like Arabic, Hebrew, and Persian. When RTL is enabled, the entire dashboard layout, including panels, navigation, and controls, automatically adapts to render from right to left.
Enabling RTL
RTL support is enabled using the enableRtl property:
Basic RTL Configuration
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-rtl-dashboard',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[columns]="5"
[cellSpacing]="[10, 10]"
[enableRtl]="true"
[panels]="panels">
</ejs-dashboardlayout>
`,
styles: [`
:host { display: block; width: 100%; height: 100vh; }
`]
})
export class RtlDashboardComponent {
public panels: any = [
{
id: 'Panel0',
sizeX: 1,
sizeY: 1,
row: 0,
col: 0,
header: '<div>لوحة 0</div>', // Arabic text
content: '<div class="content">محتوى</div>'
},
{
id: 'Panel1',
sizeX: 3,
sizeY: 2,
row: 0,
col: 1,
header: '<div>لوحة 1</div>',
content: '<div class="content">محتوى</div>'
},
{
id: 'Panel2',
sizeX: 1,
sizeY: 3,
row: 0,
col: 4,
header: '<div>لوحة 2</div>',
content: '<div class="content">محتوى</div>'
}
];
}Dynamic RTL Toggling
Toggle RTL at Runtime
@Component({
selector: 'app-rtl-toggle',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="controls">
<button (click)="toggleRtl()">
{{ isRtl ? 'Switch to LTR' : 'Switch to RTL' }}
</button>
</div>
<ejs-dashboardlayout
#dashboard
[columns]="5"
[enableRtl]="isRtl"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class RtlToggleComponent {
public isRtl = false;
public panels: any = [
{ id: 'p1', sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Panel 1' },
{ id: 'p2', sizeX: 1, sizeY: 1, row: 0, col: 1, content: 'Panel 2' },
{ id: 'p3', sizeX: 1, sizeY: 1, row: 0, col: 2, content: 'Panel 3' }
];
toggleRtl() {
this.isRtl = !this.isRtl;
// Optionally save preference
localStorage.setItem('rtlEnabled', this.isRtl.toString());
}
}Language-Based RTL
Auto-detect RTL Based on Language
import { Component, OnInit } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-language-rtl',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<div class="language-selector">
<button (click)="setLanguage('en')">English</button>
<button (click)="setLanguage('ar')">العربية</button>
<button (click)="setLanguage('he')">עברית</button>
<button (click)="setLanguage('fa')">فارسی</button>
</div>
<ejs-dashboardlayout
[columns]="5"
[enableRtl]="isRtlLanguage"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class LanguageRtlComponent implements OnInit {
public currentLanguage = 'en';
public isRtlLanguage = false;
private rtlLanguages = ['ar', 'he', 'fa', 'ur']; // RTL language codes
public panels: any = [];
ngOnInit() {
this.setLanguage('en');
}
setLanguage(lang: string) {
this.currentLanguage = lang;
this.isRtlLanguage = this.rtlLanguages.includes(lang);
this.updatePanelContent();
}
private updatePanelContent() {
// Update panel headers and content based on language
this.panels = this.getPanelsForLanguage(this.currentLanguage);
}
private getPanelsForLanguage(lang: string): any[] {
const content: { [key: string]: any } = {
'en': [
{ id: 'p1', header: 'Sales', content: 'Sales Data' },
{ id: 'p2', header: 'Revenue', content: 'Revenue Chart' }
],
'ar': [
{ id: 'p1', header: 'المبيعات', content: 'بيانات المبيعات' },
{ id: 'p2', header: 'الإيرادات', content: 'مخطط الإيرادات' }
],
'he': [
{ id: 'p1', header: 'מכירות', content: 'נתוני מכירות' },
{ id: 'p2', header: 'הכנסות', content: 'תרשים הכנסות' }
],
'fa': [
{ id: 'p1', header: 'فروش', content: 'داده های فروش' },
{ id: 'p2', header: 'درآمد', content: 'نمودار درآمد' }
]
};
return (content[lang] || content['en']).map((item, index) => ({
...item,
sizeX: 2,
sizeY: 1,
row: 0,
col: index * 2
}));
}
}RTL with HTML dir Attribute
Global RTL Configuration
For application-wide RTL support, set the HTML dir attribute:
<!-- In index.html -->
<html dir="rtl" lang="ar">
<head>
<title>Dashboard Layout - RTL</title>
</head>
<body>
<app-root></app-root>
</body>
</html>Then in your component:
@Component({
selector: 'app-root',
template: `
<ejs-dashboardlayout [columns]="5" [panels]="panels">
</ejs-dashboardlayout>
`
})
export class AppComponent {
public panels: any = [];
}RTL with CSS
CSS Adjustments for RTL
When using RTL, you may need to adjust CSS for proper alignment:
/* LTR Styles */
.dashboard-header {
text-align: left;
padding-left: 20px;
}
/* RTL Styles */
.e-rtl .dashboard-header {
text-align: right;
padding-right: 20px;
padding-left: 0;
}
/* Panel Spacing */
.e-rtl .e-panel {
margin-left: 0;
margin-right: 10px;
}RTL with Drag and Drop
RTL fully supports drag-and-drop functionality:
@Component({
template: `
<ejs-dashboardlayout
[columns]="5"
[enableRtl]="true"
[allowDragging]="true"
[panels]="panels"
(dragStart)="onDragStart($event)">
</ejs-dashboardlayout>
`
})
export class RtlDragDropComponent {
public panels: any = [
{ id: 'p1', sizeX: 1, sizeY: 1, row: 0, col: 0, content: 'Panel 1' },
{ id: 'p2', sizeX: 1, sizeY: 1, row: 0, col: 1, content: 'Panel 2' }
];
onDragStart(args: any) {
console.log('Drag started in RTL mode');
}
}RTL with Responsive Design
RTL works seamlessly with responsive breakpoints:
@Component({
template: `
<ejs-dashboardlayout
[columns]="columns"
[enableRtl]="isRtl"
[mediaQuery]="mediaQuery"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class RtlResponsiveComponent implements OnInit {
public columns = 5;
public isRtl = true;
public mediaQuery = '(max-width: 600px)';
public panels: any = [];
ngOnInit() {
// Responsive RTL dashboard automatically
// adjusts on mobile devices while maintaining RTL
}
}Best Practices for RTL
1. Test Content in RTL
Always test panel content with RTL enabled to ensure proper display:
// Use localized content
const arabicContent = '<div dir="rtl">المحتوى العربي</div>';2. Use Logical CSS Properties
Use CSS logical properties for better RTL support:
/* Logical properties work for both LTR and RTL */
.panel-content {
padding-inline: 16px; /* Works for both directions */
margin-inline-start: 10px; /* Automatic left/right based on direction */
text-align: start; /* Automatic left/right based on direction */
}3. Store RTL Preference
Save user's RTL preference:
saveRtlPreference(enabled: boolean) {
localStorage.setItem('rtlEnabled', enabled.toString());
}
loadRtlPreference(): boolean {
return localStorage.getItem('rtlEnabled') === 'true';
}4. Provide Language Switcher
Allow users to easily switch between LTR and RTL languages:
languages = [
{ code: 'en', name: 'English', rtl: false },
{ code: 'ar', name: 'العربية', rtl: true },
{ code: 'he', name: 'עברית', rtl: true }
];Saving and Restoring Dashboard State
Table of Contents
- Overview
- Persistence Configuration
- Serializing Panel State
- Restoring Saved State
- localStorage Integration
- Advanced Scenarios
Overview
The Dashboard Layout can save and restore panel arrangements, enabling users to customize their layout and have it remembered across sessions. Two approaches are available:
1. Automatic Persistence - Enable enablePersistence for automatic state management 2. Manual Serialization - Use serialize() method for custom state management
Key Features
- Automatic Persistence: Save/restore state automatically with
enablePersistenceproperty - Manual Control: Use
serialize()to explicitly capture layout state - State Structure: Returns array of
PanelModelobjects with complete panel configuration - Storage Flexibility: Save to localStorage, database, or server
- Complete State: Captures all panel positions, sizes, constraints, and properties
Persistence Configuration
Enable Automatic Persistence
The enablePersistence property automatically saves and restores component state:
import { Component } from '@angular/core';
import { DashboardLayoutModule } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-persistent',
standalone: true,
imports: [DashboardLayoutModule],
template: `
<ejs-dashboardlayout
[columns]="5"
[enablePersistence]="true"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class PersistentDashboardComponent {
public panels = [
{ id: 'p1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'Panel 1' },
{ id: 'p2', sizeX: 1, sizeY: 1, row: 0, col: 2, content: 'Panel 2' }
];
}Behavior:
- First load: Uses initial panels array
- Subsequent loads: Restores previously saved state from browser storage
- State updates automatically when user moves/resizes panels
- State format: Stored as JSON in browser's session/persistent storage
Serializing Panel State
Understanding serialize() Method
The serialize() method captures the current layout state and returns it as an array of PanelModel objects:
// Returns: PanelModel[]
const state = dashboard.serialize();serialize() Return Type and Structure
The method returns an array where each element is a PanelModel with current state:
interface PanelModel {
col: number; // Current column position
content: string | HTMLElement | Function; // Panel content
cssClass: string; // CSS classes (default '')
enabled: boolean; // Whether panel is enabled (default true)
header: string | HTMLElement | Function; // Panel header
id: string; // Unique identifier (default '')
maxSizeX: number | null; // Maximum width constraint (default null)
maxSizeY: number | null; // Maximum height constraint (default null)
minSizeX: number; // Minimum width constraint (default 1)
minSizeY: number; // Minimum height constraint (default 1)
row: number; // Current row position
sizeX: number; // Current width in cells
sizeY: number; // Current height in cells
zIndex: number; // Layer order (default 1000)
}Capturing Current State Example
import { Component, ViewChild } from '@angular/core';
import { DashboardLayoutComponent } from '@syncfusion/ej2-angular-layouts';
@Component({
selector: 'app-save-state',
template: `
<button (click)="saveState()">Save Layout</button>
<button (click)="loadState()">Load Layout</button>
<ejs-dashboardlayout
#dashboard
[columns]="5"
[allowDragging]="true"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class SaveStateComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
public panels: any = [
{ id: 'p1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'Panel 1' }
];
saveState() {
// Capture current layout state
const state = this.dashboard?.serialize();
if (state) {
localStorage.setItem('dashboard-state', JSON.stringify(state));
}
}
loadState() {
const savedState = localStorage.getItem('dashboard-state');
if (savedState) {
this.dashboard!.panels = JSON.parse(savedState);
}
}
}Restoring Saved State
Setting Panels from Serialized State
Restore a previously saved layout by setting the panels array:
@Component({
selector: 'app-restore-state',
template: `
<button (click)="restoreSavedLayout()">Restore Previous Layout</button>
<ejs-dashboardlayout
#dashboard
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class RestoreStateComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
public panels: any = [
{ id: 'p1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'Panel 1' }
];
restoreSavedLayout() {
const savedState = localStorage.getItem('dashboard-state');
if (savedState && this.dashboard) {
this.dashboard.panels = JSON.parse(savedState);
}
}
}localStorage Integration
Automatic Save with enablePersistence
The simplest approach uses the built-in enablePersistence:
@Component({
selector: 'app-auto-persist',
template: `
<p>Rearrange panels or refresh the page - your layout will be restored automatically</p>
<ejs-dashboardlayout
id="dashboardPersist"
[columns]="5"
[enablePersistence]="true"
[allowDragging]="true"
[allowResizing]="true"
[panels]="initialPanels">
</ejs-dashboardlayout>
`
})
export class AutoPersistComponent {
public initialPanels = [
{ id: 'p1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'Panel 1' },
{ id: 'p2', sizeX: 1, sizeY: 2, row: 0, col: 2, content: 'Panel 2' }
];
}What happens: 1. User loads page - initial panels shown 2. User moves/resizes panels 3. Automatic save triggered by change event 4. State stored in browser storage 5. User refreshes page - saved state restored automatically
Advanced Scenarios
Scenario 1: Multiple Dashboard Instances
Each dashboard maintains separate state:
@Component({
selector: 'app-multi-dashboard',
template: `
<div>
<h3>Dashboard A</h3>
<button (click)="saveDashboardA()">Save A</button>
<ejs-dashboardlayout
#dashA
[columns]="5"
[panels]="panelsA">
</ejs-dashboardlayout>
</div>
<div>
<h3>Dashboard B</h3>
<button (click)="saveDashboardB()">Save B</button>
<ejs-dashboardlayout
#dashB
[columns]="5"
[panels]="panelsB">
</ejs-dashboardlayout>
</div>
`
})
export class MultiDashboardComponent {
@ViewChild('dashA') dashboardA?: DashboardLayoutComponent;
@ViewChild('dashB') dashboardB?: DashboardLayoutComponent;
public panelsA = [
{ id: 'a1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'A-Panel-1' }
];
public panelsB = [
{ id: 'b1', sizeX: 2, sizeY: 1, row: 0, col: 0, content: 'B-Panel-1' }
];
saveDashboardA() {
const stateA = this.dashboardA?.serialize();
localStorage.setItem('dashboard-a-state', JSON.stringify(stateA));
}
saveDashboardB() {
const stateB = this.dashboardB?.serialize();
localStorage.setItem('dashboard-b-state', JSON.stringify(stateB));
}
}Scenario 2: User-Specific Layout Preferences
Store layouts per user:
@Component({
selector: 'app-user-layout',
template: `
<button (click)="saveUserLayout()">Save My Layout</button>
<button (click)="loadUserLayout()">Load My Layout</button>
<ejs-dashboardlayout
#dashboard
[columns]="5"
[panels]="panels">
</ejs-dashboardlayout>
`
})
export class UserLayoutComponent {
@ViewChild('dashboard') dashboard?: DashboardLayoutComponent;
private userId = 'user-123';
public panels: any = [];
saveUserLayout() {
if (this.dashboard) {
const state = this.dashboard.serialize();
// Save to backend API
console.log(`Saved layout for user ${this.userId}`, state);
}
}
loadUserLayout() {
// Load from backend API
console.log(`Loaded layout for user ${this.userId}`);
}
}