├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── signalr-hub.js └── signalr-hub.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #Changelog 2 | 3 | ##1.6.2 - Apr 19, 2016 4 | 5 | - Added `queryParams` to connect method [#70](https://github.com/JustMaier/angular-signalr-hub/pull/70) *Thanks @tribicito* 6 | 7 | ##1.6.1 - Mar 26, 2016 8 | 9 | - Added `withCredentials` option [#68](https://github.com/JustMaier/angular-signalr-hub/pull/68) *Thanks @redwyre* 10 | 11 | ##1.6.0 - Feb 23, 2016 12 | 13 | - Added `jsonp` option [#65](https://github.com/JustMaier/angular-signalr-hub/pull/65) *Thanks @sumant86* 14 | - Removed `hubDisconnected` use `stateChange` event hook instead 15 | 16 | ##1.5.0 - May 14, 2015 17 | 18 | - Added `stateChanged` event hook. [#45](https://github.com/JustMaier/angular-signalr-hub/pull/45) *Thanks @floo51* 19 | - Deprecated `hubDisconnected` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-signalr-hub 2 | ======================= 3 | ![Bower version][bower-image] 4 | [![Nuget version][nuget-image]][nuget-url] 5 | [![NPM version][npm-image]][npm-url] 6 | [![TypeScript definitions on DefinitelyTyped][typescript-image]][typescript-url] 7 | 8 | A handy wrapper for SignalR Hubs. Just specify the hub name, listening functions, and methods that you're going to use. 9 | 10 | ## Installation 11 | 12 | #### Bower 13 | `bower install angular-signalr-hub` 14 | 15 | #### Nuget 16 | `install-package AngularJs.SignalR.Hub` 17 | 18 | #### NPM 19 | `npm install angular-signalr-hub` 20 | 21 | #### Manually 22 | 23 | `` 24 | 25 | ## Usage 26 | 27 | 1. Include the `signalr-hub.js` script provided by this component into your app 28 | 2. add `SignalR` as a module dependency to your app 29 | 3. Call new Hub with two parameters 30 | 31 | ```javascript 32 | var hub = new Hub('hubname',options); 33 | ``` 34 | 35 | #### Javascript 36 | 37 | ```javascript 38 | angular.module('app',['SignalR']) 39 | .factory('Employees',['$rootScope','Hub', '$timeout', function($rootScope, Hub, $timeout){ 40 | 41 | //declaring the hub connection 42 | var hub = new Hub('employee', { 43 | 44 | //client side methods 45 | listeners:{ 46 | 'lockEmployee': function (id) { 47 | var employee = find(id); 48 | employee.Locked = true; 49 | $rootScope.$apply(); 50 | }, 51 | 'unlockEmployee': function (id) { 52 | var employee = find(id); 53 | employee.Locked = false; 54 | $rootScope.$apply(); 55 | } 56 | }, 57 | 58 | //server side methods 59 | methods: ['lock','unlock'], 60 | 61 | //query params sent on initial connection 62 | queryParams:{ 63 | 'token': 'exampletoken' 64 | }, 65 | 66 | //handle connection error 67 | errorHandler: function(error){ 68 | console.error(error); 69 | }, 70 | 71 | //specify a non default root 72 | //rootPath: '/api 73 | 74 | stateChanged: function(state){ 75 | switch (state.newState) { 76 | case $.signalR.connectionState.connecting: 77 | //your code here 78 | break; 79 | case $.signalR.connectionState.connected: 80 | //your code here 81 | break; 82 | case $.signalR.connectionState.reconnecting: 83 | //your code here 84 | break; 85 | case $.signalR.connectionState.disconnected: 86 | //your code here 87 | break; 88 | } 89 | } 90 | }); 91 | 92 | var edit = function (employee) { 93 | hub.lock(employee.Id); //Calling a server method 94 | }; 95 | var done = function (employee) { 96 | hub.unlock(employee.Id); //Calling a server method 97 | } 98 | 99 | return { 100 | editEmployee: edit, 101 | doneWithEmployee: done 102 | }; 103 | }]); 104 | ``` 105 | ## Options 106 | 107 | * `listeners` client side callbacks* 108 | * `withCredentials` whether or not cross-site Access-Control requests should be made using credentials such as cookies, authorization headers or TLS client certificates, defaults to `true` 109 | * `methods` a string array of server side methods which the client can call 110 | * `rootPath` sets the root path for the signalR web service 111 | * `queryParams` object representing additional query params to be sent on connection, can also be specified in the connect method 112 | * `errorHandler` function(error) to handle hub connection errors 113 | * `logging` enable/disable logging 114 | * `useSharedConnection` use a shared global connection or create a new one just for this hub, defaults to `true` 115 | * `transport` sets transport method (e.g `'longPolling'` or `['webSockets', 'longPolling']`) 116 | * `jsonp` toggle JSONP for cross-domain support on older browsers or when you can't setup CORS 117 | * `stateChanged` function() to handle hub connection state changed event `{0: 'connecting', 1: 'connected', 2: 'reconnecting', 4: 'disconnected'}` 118 | * `autoConnect` prevents from connecting automatically. useful for authenticating and then connecting. 119 | 120 | **Note** `hubDisconnected` has been removed, instead use the following: 121 | ``` 122 | 'stateChanged': function(state){ 123 | var stateNames = {0: 'connecting', 1: 'connected', 2: 'reconnecting', 4: 'disconnected'}; 124 | if(stateNames[state.newState] == 'disconnected'){ 125 | //Hub Disconnect logic here... 126 | } 127 | } 128 | ``` 129 | 130 | ## Demo 131 | 132 | [A simple demo using OData, Signalr, and Angular](https://github.com/JustMaier/signalrgrid) 133 | 134 | It's an adaption of [turanuk's great SignalR demo with Knockout](https://github.com/turanuk/signalrgrid). 135 | 136 | ## Simple Chat Demo 137 | 138 | This sample starts off with the [MVC-SignalR chat](http://www.asp.net/signalr/overview/getting-started/tutorial-getting-started-with-signalr-and-mvc) sample by Tim Teebken and Patrick Fletcher. 139 | 140 | This sample is then reworked (in a quick and dirty way) to show how to go about using the chathub from angular by using the angular-signalr-hub. 141 | 142 | Some extra NuGet packages are added to the project. (check out the packages.config file) 143 | An app folder was added for the angular app, in which the following was added: 144 | * a module (signalRChatApp) 145 | * a factory (ChatService) 146 | * a controller (ChatController) 147 | * an html page 148 | 149 | Modifications were made to the following files: 150 | * BundleConfig.cs 151 | * RouteConfig.cs 152 | * HomeController.cs 153 | * Global.asax.cs 154 | * Startup.cs 155 | * Index.cshtml 156 | 157 | In the app folder for the angular app, there is a ChatService which uses the angular-signalr-hub. 158 | The hub in this case is the ChatHub in this project. 159 | 160 | Download the full sample [here](https://onedrive.live.com/redir?resid=A384F349DF30AC50!34027&authkey=!ABcHiikiie50aCM&ithint=file%2czip). 161 | 162 | The sample is provided as is. 163 | There are soms issues with the way it is set up, but it does the trick in showing in showing how to use the angular-signalr-hub in an easy to reproduce app. 164 | 165 | ## Multiple hubs 166 | 167 | There is something you have to take care about when using multiple hubs in an angular app : 168 | 169 | Angular services are singletons, so they won't be instantiated before you need it. 170 | 171 | If you use shared connection between your hubs (`useSharedConnection`), and if you have two services containing hubs, you can have a problem : 172 | 173 | The first service loaded will start the connection. Then when the second service will load, its hub won't be registered to the server SignalR (`OnConnected` method) if this service is instantiated after that the shared connection is `connected`. 174 | (SignalR trace : SignalR: Client subscribed to hub 'hubname'.) 175 | The hub of the second service will be able to invoke server methods, but the server won't be able to invoke the client methods for this hub. 176 | 177 | To avoid that, you can put `useSharedConnection` to `false`. 178 | 179 | ## Notes 180 | 181 | * I would recommend creating a factory or service around the Hub so that you have an easy to use "model handler" that can include SignalR and Web API calls and be easily pulled into any controller 182 | * For an example of Web API, SignalR, and Angular working together check out this [small demo](https://github.com/JustMaier/signalrgrid) I adapted from [turanuk's SignalR demo with Knockout](https://github.com/turanuk/signalrgrid) 183 | 184 | 185 | [npm-image]: https://img.shields.io/npm/v/angular-signalr-hub.svg?style=flat-square 186 | [npm-url]: https://www.npmjs.com/package/angular-signalr-hub 187 | [bower-image]: https://img.shields.io/bower/v/angular-signalr-hub.svg?style=flat-square 188 | [nuget-image]: https://img.shields.io/nuget/v/AngularJs.SignalR.Hub.svg?style=flat-square 189 | [nuget-url]: https://www.nuget.org/packages/AngularJs.SignalR.Hub/ 190 | [typescript-image]: http://definitelytyped.org/badges/standard-flat.svg 191 | [typescript-url]: http://definitelytyped.org 192 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-signalr-hub", 3 | "version": "1.6.3", 4 | "repository":{ 5 | "type": "git", 6 | "url": "git@github.com:JustMaier/angular-signalr-hub.git" 7 | }, 8 | "main": "signalr-hub.js", 9 | "dependencies": { 10 | "angular": "*", 11 | "jquery": "*", 12 | "signalr": "*" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-signalr-hub", 3 | "version": "1.6.3", 4 | "description": "A handy wrapper for SignalR Hubs. Just specify the hub name, listening functions, and methods that you're going to use.", 5 | "main": "signalr-hub.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/justmaier/angular-signalr-hub.git" 9 | }, 10 | "keywords": [ 11 | "angular", 12 | "angularjs", 13 | "signalr", 14 | "hub", 15 | "service" 16 | ], 17 | "author": "Justin Maier (http://justinmaier.com)", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/justmaier/angular-signalr-hub/issues" 21 | }, 22 | "homepage": "https://github.com/justmaier/angular-signalr-hub", 23 | "devDependencies": {}, 24 | "dependencies": {} 25 | } 26 | -------------------------------------------------------------------------------- /signalr-hub.js: -------------------------------------------------------------------------------- 1 | angular.module('SignalR', []) 2 | .constant('$', window.jQuery) 3 | .factory('Hub', ['$', function ($) { 4 | //This will allow same connection to be used for all Hubs 5 | //It also keeps connection as singleton. 6 | var globalConnections = []; 7 | 8 | function initNewConnection(options) { 9 | var connection = null; 10 | if (options && options.rootPath) { 11 | connection = $.hubConnection(options.rootPath, { useDefaultPath: false }); 12 | } else { 13 | connection = $.hubConnection(); 14 | } 15 | 16 | connection.logging = (options && options.logging ? true : false); 17 | return connection; 18 | } 19 | 20 | function getConnection(options) { 21 | var useSharedConnection = !(options && options.useSharedConnection === false); 22 | if (useSharedConnection) { 23 | return typeof globalConnections[options.rootPath] === 'undefined' ? 24 | globalConnections[options.rootPath] = initNewConnection(options) : 25 | globalConnections[options.rootPath]; 26 | } 27 | else { 28 | return initNewConnection(options); 29 | } 30 | } 31 | 32 | return function (hubName, options) { 33 | var Hub = this; 34 | 35 | Hub.connection = getConnection(options); 36 | Hub.proxy = Hub.connection.createHubProxy(hubName); 37 | 38 | Hub.on = function (event, fn) { 39 | Hub.proxy.on(event, fn); 40 | }; 41 | Hub.invoke = function (method, args) { 42 | return Hub.proxy.invoke.apply(Hub.proxy, arguments) 43 | }; 44 | Hub.disconnect = function () { 45 | Hub.connection.stop(); 46 | }; 47 | Hub.connect = function (queryParams) { 48 | var startOptions = {}; 49 | if (options.transport) startOptions.transport = options.transport; 50 | if (options.jsonp) startOptions.jsonp = options.jsonp; 51 | if (options.pingInterval !== undefined) startOptions.pingInterval = options.pingInterval; 52 | 53 | if (angular.isDefined(options.withCredentials)) startOptions.withCredentials = options.withCredentials; 54 | if(queryParams) Hub.connection.qs = queryParams; 55 | return Hub.connection.start(startOptions); 56 | }; 57 | 58 | if (options && options.listeners) { 59 | Object.getOwnPropertyNames(options.listeners) 60 | .filter(function (propName) { 61 | return typeof options.listeners[propName] === 'function';}) 62 | .forEach(function (propName) { 63 | Hub.on(propName, options.listeners[propName]); 64 | }); 65 | } 66 | if (options && options.methods) { 67 | angular.forEach(options.methods, function (method) { 68 | Hub[method] = function () { 69 | var args = $.makeArray(arguments); 70 | args.unshift(method); 71 | return Hub.invoke.apply(Hub, args); 72 | }; 73 | }); 74 | } 75 | if (options && options.queryParams) { 76 | Hub.connection.qs = options.queryParams; 77 | } 78 | if (options && options.errorHandler) { 79 | Hub.connection.error(options.errorHandler); 80 | } 81 | if (options && options.stateChanged) { 82 | Hub.connection.stateChanged(options.stateChanged); 83 | } 84 | 85 | //Adding additional property of promise allows to access it in rest of the application. 86 | if(options.autoConnect === undefined || options.autoConnect){ 87 | Hub.promise = Hub.connect(); 88 | } 89 | 90 | return Hub; 91 | }; 92 | }]); 93 | 94 | // Common.js package manager support (e.g. ComponentJS, WebPack) 95 | if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports) { 96 | module.exports = 'SignalR'; 97 | } 98 | -------------------------------------------------------------------------------- /signalr-hub.min.js: -------------------------------------------------------------------------------- 1 | angular.module("SignalR",[]).constant("$",window.jQuery).factory("Hub",["$",function($){var globalConnections=[];function initNewConnection(options){var connection=null;if(options&&options.rootPath){connection=$.hubConnection(options.rootPath,{useDefaultPath:false})}else{connection=$.hubConnection()}connection.logging=options&&options.logging?true:false;return connection}function getConnection(options){var useSharedConnection=!(options&&options.useSharedConnection===false);if(useSharedConnection){return typeof globalConnections[options.rootPath]==="undefined"?globalConnections[options.rootPath]=initNewConnection(options):globalConnections[options.rootPath]}else{return initNewConnection(options)}}return function(hubName,options){var Hub=this;Hub.connection=getConnection(options);Hub.proxy=Hub.connection.createHubProxy(hubName);Hub.on=function(event,fn){Hub.proxy.on(event,fn)};Hub.invoke=function(method,args){return Hub.proxy.invoke.apply(Hub.proxy,arguments)};Hub.disconnect=function(){Hub.connection.stop()};Hub.connect=function(queryParams){var startOptions={};if(options.transport)startOptions.transport=options.transport;if(options.jsonp)startOptions.jsonp=options.jsonp;if(options.pingInterval!==undefined)startOptions.pingInterval=options.pingInterval;if(angular.isDefined(options.withCredentials))startOptions.withCredentials=options.withCredentials;if(queryParams)Hub.connection.qs=queryParams;return Hub.connection.start(startOptions)};if(options&&options.listeners){Object.getOwnPropertyNames(options.listeners).filter(function(propName){return typeof options.listeners[propName]==="function"}).forEach(function(propName){Hub.on(propName,options.listeners[propName])})}if(options&&options.methods){angular.forEach(options.methods,function(method){Hub[method]=function(){var args=$.makeArray(arguments);args.unshift(method);return Hub.invoke.apply(Hub,args)}})}if(options&&options.queryParams){Hub.connection.qs=options.queryParams}if(options&&options.errorHandler){Hub.connection.error(options.errorHandler)}if(options&&options.stateChanged){Hub.connection.stateChanged(options.stateChanged)}if(options.autoConnect===undefined||options.autoConnect){Hub.promise=Hub.connect()}return Hub}}]);if(typeof module!=="undefined"&&typeof exports!=="undefined"&&module.exports===exports){module.exports="SignalR"} --------------------------------------------------------------------------------