SignalR and Angular are new tools for me, so I decided to recreate the project in ASP.NET MVC 4 with SignalR 2.2 to better understand the issue at hand. It seems there may be more than one problem...
1) SignalR relies on jQuery. In jQuery (and most other JS frameworks), it's best practice to execute your code after the 'document ready' event. I wrapped the SignalR setup in a jQuery document ready handler to ensure this (see code below). Additionally, I eliminated the global variable hubConnection
because global variables can cause issues, and by wrapping it in a jQuery document ready handler, the variable becomes local and not accessible in controller code.
2) The main issue, in my opinion, lies in a specific part of the SignalR client API documentation:
Normally, you register event handlers before calling the start method to establish the connection. If you want to register event handlers after establishing the connection, you must still register at least one handler before calling the start method. This is crucial as it ensures that the OnConnected event in the Hub will trigger correctly. Without registering any event handlers prior to starting the connection, the OnConnected method in the Hub won't be called and no client methods will be invoked from the server.
Your event handler should be set up before the connection start()
method is called. Try adding a temporary handler like this:
$.connection.tasklistHub.client.getTask = function () { };
3) To confirm that the issue lies within SignalR and not Angular, I enabled tracing on both the server and client sides. After implementing the changes mentioned above, the event from the server was successfully received on the client side:
SignalR: Triggering client hub event 'getTask' on hub 'TasklistHub'
However, Angular failed to update the div with the received data. The solution was to use the $rootScope.$apply
function as suggested in an article about using SignalR and Angular together. With this change, everything worked as expected. See the full code below for reference.
Sidenote: While this is just sample code, in a real-world project, I would recommend encapsulating SignalR hubs on the client side in a service, as described in the article mentioned above or utilizing a library like this one for a cleaner implementation.
I also retract my previous statement regarding the case sensitivity of client-side handler names, as per the same documentation which states that method name matching is case-insensitive.
Index.cshtml
<html lang="en" ng-app="SignalR">
<head>
<meta charset="utf-8" />
<title>SignalR & AngularJS</title>
<script src="~/Scripts/jquery-1.6.4.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.js"></script>
<script src="~/signalr/hubs"></script>
<script src="~/Scripts/angular.js"></script>
<script type="text/javascript">
$(function() {
$.connection.hub.logging = true;
// temporary handler to force SignalR to subscribe to server events
$.connection.tasklistHub.client.getTask = function () { };
$.connection.hub.start().done(function () {
$('#connectionStatus').text('Connected');
console.log('Now connected, connection ID =' + $.connection.hub.id);
});
});
var SignalRControllers = angular.module('SignalRControllers', []);
var SignalRApp = angular.module('SignalR', ['SignalRControllers']);
</script>
<script src="~/Scripts/app/TodayController.js"></script>
</head>
<body>
<div id="body">
<div ng-view>
<div ng-controller="TodayController">
<div id="connectionStatus"></div>
<div>{{Task}}</div>
<input id="taskName" type="text"/>
<input type="button" name="Send Task" value="Send Task" data-ng-click="sendTask()" />
</div>
</div>
</div>
</body>
</html>
TodayController.js
SignalRControllers.controller('TodayController', [
'$scope', '$http', '$rootScope',
function ($scope, $http, $rootScope) {
$scope.sendTask = function() {
$.connection.tasklistHub.server.sendTask($("#taskName").val());
};
$scope.Task = "Empty";
$.connection.tasklistHub.client.getTask = function (task) {
console.log(task);
$rootScope.$apply(function () {
$scope.Task = task;
});
};
}
]
);