SignalR Hub Configuration in EJ2 JavaScript Diagram

7 Aug 202616 minutes to read

Overview

This guide explains how to configure SignalR hub in a EJ2 JavaScript application for real-time collaborative diagram editing.

How to create EJ2 JavaScript sample

To create a EJ2 JavaScript web application, set up a basic HTML file with the required Diagram scripts and references. Refer to the EJ2 JavaScript Diagram Getting Started documentation.

How to add packages in the EJ2 JavaScript application

Include the required libraries via CDN in your HTML file:

<script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@latest/dist/browser/signalr.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@syncfusion/ej2-diagrams/dist/ej2-diagrams.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/@syncfusion/ej2-base/styles/material.css" rel="stylesheet" />

Configure SignalR service in EJ2 JavaScript application

To enable real-time collaboration, configure SignalR HubConnection in your EJ2 JavaScript code as follows:

  • Initialize the HubConnection when the page loads and start it by calling start().
  • Connect to the /diagramHub endpoint using WebSocket transport and enable automatic reconnect to handle transient network issues.
  • Join a SignalR group by calling invoke('JoinDiagram', roomName) after the connection is established. This ensures updates are shared only with users in the same diagram session.
  • Refer to the EJ2 JavaScript Diagram Getting Started guide.
// Initialize SignalR connection
let connection = null;
let roomName = "Syncfusion";
let connectionId = null;

function initializeSignalRConnection() {
    if (connection === null) {
        // Create connection
        connection = new signalR.HubConnectionBuilder()
            .withUrl("<<Place your SignalR Hub URL>>", {
                skipNegotiation: true,
                transport: signalR.HttpTransportType.WebSockets
            })
            .withAutomaticReconnect()
            .build();

        // Triggered when the connection to the SignalR Hub is successfully established
        connection.on("OnConnectedAsync", onConnectedAsync);

        // Start the connection
        connection.start()
            .then(() => {
                console.log("Connected to SignalR Hub");
            })
            .catch((error) => {
                console.error("Connection failed:", error);
            });
    }
}

function onConnectedAsync(id) {
    if (id && id.length > 0) {
        connectionId = id;
        console.log("Connection ID:", connectionId);
        // Join the room after connection is established
        connection.invoke("JoinDiagram", roomName)
            .catch((error) => {
                console.error("JoinDiagram failed:", error);
            });
    }
}

// Initialize connection when document is ready
document.addEventListener("DOMContentLoaded", function() {
    initializeSignalRConnection();
});
<!DOCTYPE html>
<html lang="en">
<head>
    <title>EJ2 Diagram</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Javascript UI Controls">
    <meta name="author" content="Syncfusion">
    <link href="index.css" rel="stylesheet">
    
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-base/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-buttons/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-popups/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-splitbuttons/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-diagrams/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-navigations/styles/fabric.css" rel="stylesheet">
    
    <script src="https://cdn.syncfusion.com/ej2/34.2.2/dist/ej2.min.js" type="text/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@latest/dist/browser/signalr.min.js"></script>
</head>

<body>
    
    <div id="container">
        <div id="diagram"></div>
    </div>

    <script>
        var ele = document.getElementById('container');
        if(ele) {
            ele.style.visibility = "visible";
        }   
    </script>
    <script src="index.js" type="text/javascript"></script>
</body>
</html>

Notes:

  • Use a unique roomName per diagram (for example, a diagram ID) to isolate collaboration sessions.
  • If WebSockets may be unavailable, remove skipNegotiation so SignalR can fall back to Server-Sent Events (SSE) or Long Polling.
  • Consider handling connection state changes and securing the connection with authentication, if required.

Sending and applying real-time diagram changes

  • The EJ2 JavaScript Diagram component triggers the historyChange event whenever the diagram is modified, such as when nodes or connectors are added, deleted, moved, resized, or edited.
  • Use the getDiagramUpdates method to generate a compact set of incremental updates (JSON-formatted changes) that represent only the changes, rather than the entire diagram.
  • Send these changes to the hub method BroadcastToOtherUsers, which relays them to all users joined to the same SignalR group (room).
  • Each remote user listens for the ReceiveData and applies the incoming changes with setDiagramUpdates, keeping their view synchronized without reloading the full diagram.
  • Enable the enableCollaborativeEditing property on the diagram to treat multi-step edits (like drag/resize sequences or batch changes) as a single operation. This property works in conjunction with the DiagramCollaboration and UndoRedo module to batch related changes efficiently.
// Initialize the diagram with enableCollaborativeEditing enabled
let diagram = new ej.diagrams.Diagram({
    width: '100%',
    height: '700px',
    nodes: [],
    connectors: [],
    enableCollaborativeEditing: true,
    
    // Listen to history changes
    historyChange: function(args) {
        if (args) {
            // Get diagram updates (incremental changes) and send to hub
            let diagramChanges = diagram.getDiagramUpdates(args);
            // When enableCollaborativeEditing is enabled, retrieve diagramChanges only after the group action completes (startGroup/endGroup).
            if (diagramChanges && diagramChanges.length > 0) {
                connection.invoke("BroadcastToOtherUsers", diagramChanges, roomName)
                    .catch(err => console.error("Send failed:", err));
            }
        }
    }
});

diagram.appendTo('#diagram');

// Listen for remote changes from other users
connection.on("ReceiveData", (diagramChanges) => {
    if (diagramChanges && diagramChanges.length > 0) {
        diagram.setDiagramUpdates(diagramChanges);
    }
});
<!DOCTYPE html>
<html lang="en">
<head>
    <title>EJ2 Diagram</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Javascript UI Controls">
    <meta name="author" content="Syncfusion">
    <link href="index.css" rel="stylesheet">
    
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-base/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-buttons/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-popups/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-splitbuttons/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-diagrams/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-navigations/styles/fabric.css" rel="stylesheet">
    
    <script src="https://cdn.syncfusion.com/ej2/34.2.2/dist/ej2.min.js" type="text/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@latest/dist/browser/signalr.min.js"></script>
</head>

<body>
    
    <div id="container">
        <div id="diagram"></div>
    </div>

    <script>
        var ele = document.getElementById('container');
        if(ele) {
            ele.style.visibility = "visible";
        }   
    </script>
    <script src="index.js" type="text/javascript"></script>
</body>
</html>

Conflict policy (optimistic concurrency) in EJ2 JavaScript application

To maintain consistency during collaborative editing, each user applies incoming changes using setDiagramUpdates. The EJ2 JavaScript application tracks a userVersion that is synchronized with the serverVersion through version-tracking events. This version-based approach ensures conflicts are resolved without locking, allowing real-time responsiveness while preserving data integrity.

Add the following code in your EJ2 JavaScript application:

// Version tracking for optimistic concurrency
let userVersion = 0;

// Initialize diagram with enableCollaborativeEditing enabled
let diagram = new ej.diagrams.Diagram({
    width: '100%',
    height: '700px',
    enableCollaborativeEditing: true,
    
    historyChange: function(args) {
        if (args) {
            let diagramChanges = diagram.getDiagramUpdates(args);
            if (diagramChanges && diagramChanges.length > 0) {
                let editedElements = getEditedElements(args);
                // Send changes with version and edited element IDs
                connection.invoke("BroadcastToOtherUsers", diagramChanges, userVersion, editedElements, roomName)
                    .catch(err => console.error("Send failed:", err));
            }
        }
    }
});

diagram.appendTo('#diagram');

// Listen for remote changes with version tracking
connection.on("ReceiveData", (diagramChanges, serverVersion) => {
    if (diagramChanges && diagramChanges.length > 0) {
        applyRemoteDiagramChanges(diagramChanges);
        // Update user version to server version after applying changes
        userVersion = serverVersion;
    }
});

// Listen for conflict notifications
connection.on("ShowConflict", () => {
    // Show notification to inform user their update was rejected due to conflict
    alert("Your changes conflicted with another user's updates and were not applied. Please refresh to see the latest version.");
});

// Listen for explicit version updates
connection.on("UpdateVersion", (serverVersion) => {
    userVersion = serverVersion;
});

// Apply changes received from other users
function applyRemoteDiagramChanges(diagramChanges) {
    // Sets diagram updates to current diagram
    diagram.setDiagramUpdates(diagramChanges);
}

// Extract edited element IDs from history args
function getEditedElements(args) {
    let editedElements = [];
    // Extract and return IDs of affected nodes/connectors from args
    // TODO: implement extraction logic based on HistoryChangedEventArgs
    return editedElements;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>EJ2 Diagram</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Javascript UI Controls">
    <meta name="author" content="Syncfusion">
    <link href="index.css" rel="stylesheet">
    
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-base/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-buttons/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-popups/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-splitbuttons/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-diagrams/styles/material.css" rel="stylesheet">
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-navigations/styles/fabric.css" rel="stylesheet">
    
    <script src="https://cdn.syncfusion.com/ej2/34.2.2/dist/ej2.min.js" type="text/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@latest/dist/browser/signalr.min.js"></script>
</head>

<body>
    
    <div id="container">
        <div id="diagram"></div>
    </div>

    <script>
        var ele = document.getElementById('container');
        if(ele) {
            ele.style.visibility = "visible";
        }   
    </script>
    <script src="index.js" type="text/javascript"></script>
</body>
</html>