Dear sir,
I'm tring to create angular base application joined with .Net MVC & SignalR...
First Let Say i'm new to this world of angular, and even JS framework, and not totally familiar with even JS it self, so it may not be an issue in angular, in other hand it may.
What i'm tring to do is to Use this sample, change it's base structure from JQuery to Angularjs... and in the end change it's data structure, customize it, add and remove feature, and so on
I also create a model, not sure when should i do this, so i did it all on $scope, and create a variable Named ChatModel, and put all variable into that...
the incomplete code is in below, comment are places i didn't convert to angular...
Now lets talk about issues, and what came after another...
first, i needed to put my msg into collection, so i add this:
var MessageList=function(){ //Type Message this.values=[]; this.push=function(username, message){ this.values.push({UserName: username, Message: message}); return;//throw exception }; this.get=function(index) { return this.values[index]; } this.toArray = function () { return angular.copy(this.values); } }
So i can store collection of data, as i were unaware of how to create strongly typed, also one with constructor, so i leave it with dynamic variable... so it went like that, and this in my model:
$scope.ChatModel = { Logged: false, Message: '', Login: { UserName: '', UserId: '' }, Messages: new MessageList()//UserName, Message };
and like this in my UI:
<div class="message" data-ng-repeat="x in ChatModel.Messages.toArray()"> <span class="userName">{{x.userName}}</span>: {{x.message}} </div>
but the issue was that the UI wouldn't get update automatically after function call from server...
for example previously i had $scope.ChatModel.Logged as $scope.logged, but even on that conditon the UI needed to be clicked twice to update UI (Show One Panel And Hide Another One).
i read lot about it, and i used apply function, though it didn't worked. finally, today i understand that theapply is $apply. so it start working. So i then wonder if i can omit $apply, and let angular it self detect the issues, and that's when i came across the $watch, since it didn't worked i went to console, and see there are errors: [$rootScope:infdig], and soon i found out the $apply() it self doesn't work properly.
So can any body help?
I put all the code you may require in below of page. please Help Me:
var app = angular.module("chatApp", []); var advanceChatHub = $.connection.advanceChatHub; app.directive('ngEnter', function () { return function (scope, element, attrs) { element.bind("keydown keypress", function (event) { if (event.which === 13) { scope.$apply(function () { scope.$eval(attrs.ngEnter); }); event.preventDefault(); } }); }; }); app.controller("chatCtrl", ['$scope', function($scope) { //Chat Ctrl 1 / Full Function var MessageList=function(){ //Type Message this.values=[]; this.push=function(username, message){ this.values.push({UserName: username, Message: message}); return;//throw exception }; this.get=function(index) { return this.values[index]; } this.toArray = function () { return angular.copy(this.values); } } $scope.ChatModel = { Logged: false, Message: '', Login: { UserName: '', UserId: '' }, Messages: new MessageList()//UserName, Message }; //$scope.$watchCollection(function(scope) { // return scope.ChatModel.Messages.values; // }, // function(newVal, oldVal, scope) { // if (oldVal.UserName === newVal.UserName && oldVal.Message === newVal.Message) { // console.log("change"); // } // }, true //); $scope.RegisterClientMethod = function(advanceChatHub) { // Calls when user successfully logged in advanceChatHub.client.onConnected = function(id, userName, allUsers, messages) { $scope.ChatModel.Logged = true; $scope.ChatModel.Login.UserId = id; $scope.ChatModel.Login.UserName = userName; //Add All Users var i; for (i = 0; i < allUsers.length; i++) { $scope.AddUser(advanceChatHub, allUsers[i].ConnectionId, allUsers[i].UserName); } for (i = 0; i < messages.length; i++) { $scope.AddMessage(messages[i].UserName, messages[i].Message); } $scope.$apply(); } advanceChatHub.client.onNewUserConnected = function(id, name) { $scope.AddUser(advanceChatHub, id, name); } advanceChatHub.client.onUserDisconnected = function(id, userName) { //$('#' + id).remove(); //var ctrId = 'private_' + id; //$('#' + ctrId).remove(); //var disc = $('<div class="disconnect">"' + userName + '" logged off.</div>'); //$(disc).hide(); //$('#divusers').prepend(disc); //$(disc).fadeIn(200).delay(2000).fadeOut(200); } advanceChatHub.client.messageReceived = function(userName, message) { $scope.AddMessage(userName, message); } advanceChatHub.client.sendPrivateMessage = function(windowId, fromUserName, message) { //var ctrId = 'private_' + windowId; //if ($('#' + ctrId).length == 0) { // createPrivateChatWindow(chatHub, windowId, ctrId, fromUserName); //} //$('#' + ctrId).find('#divMessage').append('<div class="message"><span class="userName">' + fromUserName + '</span>: ' + message + '</div>'); //// set scrollbar //var height = $('#' + ctrId).find('#divMessage')[0].scrollHeight; //$('#' + ctrId).find('#divMessage').scrollTop(height); } } $scope.Events = { btnStartChat: function() { var name = $scope.ChatModel.Login.UserName; if (name.length > 0) { advanceChatHub.server.connect(name); } else { alert("Please enter name"); } }, btnSendMsg: function() { var msg = $scope.ChatModel.Message; if (msg.length > 0) { var userName = $scope.ChatModel.Login.UserName; advanceChatHub.server.sendMessageToAll(userName, msg); $scope.ChatModel.Message = ''; } }, txtNickName: function () { angular.element('#btnStartChat').trigger('click'); }, txtMessage: function () { angular.element('#btnSendMsg').trigger('click'); } }; $scope.AddUser = function(advanceChatHub, id, name) { //var userId = $scope.userId; //var code = ""; //if (userId == id) { // code = $('<div class="loginUser">' + name + "</div>"); //} //else { // code = $('<a id="' + id + '" class="user" >' + name + '<a>'); // $(code).dblclick(function () { // var id = $(this).attr('id'); // if (userId != id) // OpenPrivateChatWindow(chatHub, id, name); // }); //} //$("#divusers").append(code); } $scope.AddMessage = function (userName, message) { $scope.ChatModel.Messages.push(userName, message); var divChatWindows = angular.element('#divChatWindow'); $scope.$apply(); //var divNewMessage = '<div class="message"><span class="userName">' + userName + '</span>: ' + message + '</div>'; //angular.element(divNewMessage).appendTo(divChatWindows); var height = divChatWindows[0].scrollHeight; divChatWindows.scrollTop(height); } $scope.OpenPrivateChatWindow = function(chatHub, id, userName) { var ctrId = 'private_' + id; //if ($('#' + ctrId).length > 0) return; $scope.createPrivateChatWindow(chatHub, id, ctrId, userName); } $scope.createPrivateChatWindow = function(chatHub, userId, ctrId, userName) { //var div = '<div id="' + ctrId + '" class="ui-widget-content draggable" rel="0">' + // '<div class="header">' + // '<div style="float:right;">' + // '<img id="imgDelete" style="cursor:pointer;" src="/Images/delete.png"/>' + // '</div>' + // '<span class="selText" rel="0">' + userName + '</span>' + // '</div>' + // '<div id="divMessage" class="messageArea">' + // '</div>' + // '<div class="buttonBar">' + // '<input id="txtPrivateMessage" class="msgText" type="text" />' + // '<input id="btnSendMessage" class="submitButton button" type="button" value="Send" />' + // '</div>' + // '</div>'; //var $div = $(div); //// DELETE BUTTON IMAGE //$div.find('#imgDelete').click(function () { // $('#' + ctrId).remove(); //}); //// Send Button event //$div.find("#btnSendMessage").click(function () { // $textBox = $div.find("#txtPrivateMessage"); // var msg = $textBox.val(); // if (msg.length > 0) { // chatHub.server.sendPrivateMessage(userId, msg); // $textBox.val(''); // } //}); //// Text Box event //$div.find("#txtPrivateMessage").keypress(function (e) { // if (e.which == 13) { // $div.find("#btnSendMessage").click(); // } //}); //AddDivToContainer($div); } $scope.AddDivToContainer = function($div) { //$('#divContainer').prepend($div); //$div.draggable({ // handle: ".header", // stop: function () { // } //}); //////$div.resizable({ ////// stop: function () { ////// } //////}); } advanceChatHub.connection.start().done(function() { //$scope.registerEvents(hub); }); $scope.RegisterClientMethod(advanceChatHub); } ]);
@model dynamic
@{
ViewBag.Title = "title";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@section headers{
}
@section scripts{
<script src="~/Scripts/angular.min.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
@* ReSharper disable Html.PathError *@
<script src="~/SignalR/Hubs"></script>
@* ReSharper restore Html.PathError *@
<script src="~/Scripts/custom/advanceChatSurf.js"></script>
}
<h2>SignalR Chat Room</h2>
<br />
<br />
<br/>
<div id="divContainer" data-ng-app="chatApp" data-ng-controller="chatCtrl">
<div id="divLogin" class="login" data-ng-hide="ChatModel.Logged">
<div>
Your Name:<br />
<input id="txtNickName" data-ng-enter="Events.txtNickName()" type="text" class="textBox" data-ng-model="ChatModel.Login.UserName" />
</div>
<div id="divButton">
<input id="btnStartChat" data-ng-click="Events.btnStartChat()" type="button" class="submitButton" value="Start Chat" />
</div>
</div>
<div id="divChat" data-ng-show="ChatModel.Logged" class="chatRoom">
<div class="title">
Welcome to Chat Room [<span id='spanUser'></span>]
</div>
<div class="content">
<div id="divChatWindow" class="chatWindow">
<div class="message" data-ng-repeat="x in ChatModel.Messages.toArray()">
<span class="userName">{{x.userName}}</span>: {{x.message}}
</div>
</div>
<div id="divusers" class="users">
</div>
</div>
<div class="messageBar">
<input class="textbox" data-ng-enter="Events.txtMessage()" type="text" id="txtMessage" data-ng-model="ChatModel.Message" />
<input id="btnSendMsg" data-ng-click="Events.btnSendMsg()" type="button" value="Send" class="submitButton" />
</div>
</div>
<input data-ng-model="userId" type="hidden" />
<input data-ng-model="userName" type="hidden" />
</div>using System; using System.Collections.Generic; using System.Linq; using System.Web; using Microsoft.AspNet.SignalR; namespace ChatSignalR.Hubs { public class AdvanceChatHub : Hub { readonly List<MessageDetail> _currentMessage = new List<MessageDetail>(); readonly List<UserDetail> _connectedUsers = new List<UserDetail>(); public void Hello() { Clients.All.hello(); } #region Proxy Methods public void Connect(string userName) //-->Take User Name From Client Before Application Start // , Store In Hidden / OR / Set it from credentials { var id = Context.ConnectionId; //Get Id And Return It To User Somehow if (_connectedUsers.All(x => x.ConnectionId != id)) // But Only, If Nobody Has This Id { _connectedUsers.Add(new UserDetail //User Not Exist? Then Add Him To Collection { ConnectionId = id, UserName = userName }); //send self information / ConnectedUserLists / Old Messages to User // TODO: 1 --> Never Give Other Users UserId To Other Clients, Says Asp.net Forum, as other can take advantage of it // TODO: 2 --> Limit Messages To Send, As it may become large over time //Send To Caller Clients.Caller.onConnected(id, userName, _connectedUsers, _currentMessage); //Send To Other Clients.AllExcept(id).onNewUserConnected(id, userName); // --> Clients.Others.onNewUserConnected(id, userName); } } public void SendMessageToAll(string userName, string message) { //Get User Name By Connection Id //var id = Context.ConnectionId; //var userInfo = _connectedUsers.FirstOrDefault(w => w.ConnectionId == id); //var userName = (userInfo != null) ? userInfo.UserName : "Unknown Sender"; //Get User By Credential Name //Context.User.Identity.Name //Store Last 100 Messages in Cache AddMessageinCache(userName, message); //Broadcast Message Clients.All.messageReceived(userName, message); } public void SendPrivateMessage(string toUserId /*toUserName*/, string message) { string fromUserId = Context.ConnectionId; var toUser = _connectedUsers.FirstOrDefault(x => x.ConnectionId == toUserId); //Filter By UserName var fromUser = _connectedUsers.FirstOrDefault(x => x.ConnectionId == fromUserId); if (toUser != null && fromUser!=null) { //send to Clients.Client(toUserId).sendPrivateMessage(fromUserId /*UserName*/, fromUser.UserName, message); } } #endregion Proxy Methods #region Hub Events public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled = true) //Ver 2 SignalR { var userInfo = _connectedUsers.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId); { if (userInfo != null) { _connectedUsers.Remove(userInfo); var id = Context.ConnectionId; Clients.All.onDisconnected(id, userInfo.UserName); } } return base.OnDisconnected(stopCalled); } #endregion Hub Events #region Utility #region Cache Management private void AddMessageinCache(string userName, string message) { _currentMessage.Add(new MessageDetail { UserName = userName, Message = message }); if (_currentMessage.Count > 100) _currentMessage.RemoveAt(0); } #endregion Cache Management #endregion Utility } internal class MessageDetail { public string UserName { get; set; } public string Message { get; set; } } internal class UserDetail { public string ConnectionId { get; set; } public string UserName { get; set; } } }
And Just One Simple Controller
Hi Hassan,
Don’t ever use functions for the thing you are iterating on, in your case xxx.toArray(), as this will cause a new digest cycle, which then will cause an infinitive loop.
Regards
Sander
--
You received this message because you are subscribed to a topic in the Google Groups "AngularJS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/angular/U4ZV2RXAEMU/unsubscribe.
To unsubscribe from this group and all its topics, send an email to angular+u...@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular.
For more options, visit https://groups.google.com/d/optout.
Hi Sander,And, thanks for the reply, but how i'm suppose to not do that?
If i treat the Messages as predefined list, so i can't manage it like it's strongly typed, though i still hate that i can't strongly type it... And this way my MessageList Class is not sure to work, depend on how javascript handle array... if it pass reference maybe i can fix it.. i don't know, game me time for this...
--
--
what "mutate" mean? in second sentence?
i don't double click it accidentally, the first time like not working, second, do first action, third to second action, ... and so on, other kinda click or interupt do last action on the button.
I tried put some step along side your codes, but after long time wasting my company time, i'm still issued, and receive errors :(
--
