Using Socket.IO and AngularJS to make Chat Application - javascript

I'm trying to make a chat application which I completed on Code School using NodeJS, Express, and Socket.IO. except where they used jQuery, I want to use AngularJS.
The problem that i'm having is that my $scope.model gets updated by an 'join' event emitted from my server, the client listens to this event and updates the $scope.model... but this change doesn't effect the DOM automatically like it's supposed to.
Index.html
<body ng-app="chatApp" ng-controller="ChatCtrl">
<header class="container">
<h1 class="page-header">Chatt App</h1>
</header>
<div class="container">
<div class="col-xs-3">
<ul class="list-unstyled">
<li ng-repeat="user in model.onlineUsers">{{user}}</li>
</ul>
</div>
<div class="col-xs-9">
<button class="btn btn-primary" ng-click="add()">Tester</button>
</div>
</div>
<!-- Vendors -->
<script src="vendors/jquery-1.11.3.min.js"></script>
<script src="vendors/angular.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<!-- app.js -->
<script>
angular.module('chatApp', [])
.controller('ChatCtrl', ['$scope', '$http', function($scope, $http) {
$scope.model = {
onlineUsers: ['test'],
};
var socket = io.connect();
socket.on('connect', function(data) {
var nickname = prompt("What's your name?");
socket.emit('join', nickname);
});
socket.on('join', function(onlineUsers) {
$scope.model.onlineUsers = onlineUsers;
console.log('model changed!');
});
$scope.add = function() {
var newUser = 'tester1';
$scope.model.onlineUsers.push(newUser);
};
}]);
</script>
</body>
Node.JS
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var onlineUsers = [];
// sets root
app.use(express.static(__dirname + '/public/src'));
io.on('connect', function(client) {
client.on('join', function(data) {
if(data === null) {
data = 'Nameless';
}
onlineUsers.push(data);
console.log(onlineUsers);
client.broadcast.emit('join', onlineUsers);
client.emit('join', onlineUsers);
});
});
server.listen(8080, function() {
console.log('Running at 8080');
});
I created a test button to add another item to the $scope.model.onlineUsers, and then finally, the DOM updates.
There is probably a better place to put my socket.io listening event's besides in my controller but I did also using a .run(function() {}); but this doesn't let me inject $scope for some reason, so i'm not sure where to put the code for the server in an Angular Project.

Related

AngularJS: $emit method sending duplicate data

In my AngularJS app, I have three controllers. One is the main controller and the other two are siblings.
I have Sibling control 1 to emit data to Main control, which broadcasts the data, which sibling control 2 then picks up.
Sibling control 1
$scope.selectedPatentFx;
$scope.$watch('selectedPatentFx', function(newValue, oldValue){
if($scope.selectedPatentFx) {
$scope.$emit('calculateFx', {patentfx: newValue});
}
})
Main control
$scope.$on('calculateFx', function(event, obj){
$scope.$broadcast('calculateFxBroadcast', {fx: obj})
});
Sibling control 2
$scope.$on('calculateFxBroadcast', function(event, obj){
//handle obj
})
The issue is that the data is being sent twice. However it doesn't cause any errors (as of yet).
Question
Why is the data being emitted/broadcasted twice?
I would avoid using events ($broadcast) here. You can do it by using a service which shares the data. I created an abstract example which delivers you the basic handling.
> Share data via service between controllers - demo fiddle
View
<div ng-controller="MyCtrl">
<button ng-click="setData()">
Set data
</button>
<h1>
Controller1
</h1>
<hr>
<p>
{{data.getContactInfo()}}
</p>
</div>
<div ng-controller="MyOtherCtrl">
<br><br>
<h1>
Controller2
</h1>
<hr> {{data.getContactInfo()}}
</div>
AngularJS application
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope, myService) {
$scope.data = myService;
$scope.setData = function() {
myService.setContactInfo('Hello World');
}
});
myApp.controller('MyOtherCtrl', function($scope, myService) {
$scope.data = myService;
});
myApp.service('myService', function() {
this.contactInfo = '';
this.setContactInfo = function (data) {
this.contactInfo = data;
}
this.getContactInfo = function () {
return this.contactInfo;
}
});

How to implement Infinite Scrolling using Node.js, Angular.js and Firebase?

UPDATE 8:
CODE:
<% include ../partials/header %>
<script src="https://www.gstatic.com/firebasejs/3.5.2/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firebase-util/0.2.5/firebase-util.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.4/angularfire.min.js"></script>
<script>
var config = {
info
};
firebase.initializeApp(config);
var fb = firebase.database().ref("posts/fun");
var app = angular.module('app', ['firebase']);
app.controller('ctrl', function ($scope, $firebaseArray, $timeout) {
$scope.data = [];
var _start = 0;
var _end = 4;
var _n = 5;
$scope.getDataset = function() {
fb.orderByChild('id').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
$scope.data.push(dataSnapshot.val());
console.log("THE VALUE:"+$scope.data);
});
_start = _start + _n;
_end = _end + _n;
};
$scope.getDataset()
});
// Compile the whole <body> with the angular module named "app"
angular.bootstrap(document.body, ['app']);
</script>
<div class ="containerMarginsIndex">
<div ng-controller="ctrl">
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{d.id}}" target="_blank">
<img class= "imgIndex" ng-src="/images/uploads/{{d.image}}" >
</a>
</div>
<div class="postScore">{{d.upvotes - d.downvotes}} HP</div>
</div>
</div>
</div>
<% include ../partials/footer %>
SITUATION:
Ok, I have reworked my Firebase database architecture and changed the Firebase rules.
I am now certain the Firebase function returns a value (it is logged in the console).
But I still get the following error:
This HTML:
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{d.id}}" target="_blank">
<img class= "imgIndex" ng-src="/images/uploads/{{d.image}}" >
</a>
</div>
<div class="postScore">{{d.upvotes - d.downvotes}} HP</div>
</div>
gets REPLACED by this once RENDERED:
<!-- ngRepeat: d in data --> == $0
What have I done wrong ?
It's not displaying in your view because you have nothing on the $scope and you're not using {{}} to interpolate your data. See the following changes:
Assign data to a $scope variable to be used in the view:
$scope.data = [];
var _start = 0;
var _end = 4;
var _n = 5;
var getDataset = function() {
fb.orderByChild('time').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
$scope.data.push(dataSnapshot.val());
});
_start = _start + _n;
_end = _end + _n;
And your view, use ngRepeat and {{}} to interpolate:
<div class ="containerMarginsIndex">
<div class="fun" ng-repeat="d in data">
<h3 class="text-left">{{d.title}}</h3>
<div class = "postImgIndex">
<a href="details/{{post.id}}" target="_blank">
<img class= "imgIndex" src="/images/uploads/{{post.image}}" >
</a>
</div>
<div class="postScore">({{d.upvotes - d.downvotes}}) HP</div>
</div>
</div>
Add your scroll listener within your controller. The function more does not exist indeed, however you do have a $scope.more method.
app.controller('ctrl', function ($scope, $firebaseArray, $timeout) {
// ORDERED BY TIME:
var ref = firebase.database().ref("posts/fun");
var scrollRef = new Firebase.util.Scroll(ref, "time");
$scope.posts = $firebaseArray(scrollRef);
scrollRef.scroll.next(5);
// AS DATA APPEARS IN DATABASE ORDERED BY TIME:
ref.once('value', function(snap) {
$scope.rawdata = snap.val();
$scope.$apply();
});
$scope.more = function() {
scrollRef.scroll.next(5);
};
// Add scroll listener
window.addEventListener('scroll', function() {
if (window.scrollY === document.body.scrollHeight - window.innerHeight) {
$scope.$apply($scope.more);
}
});
});
Note that I am calling $scope.more within $scope.$apply so that the scope is digested at the end of the call. Indeed a JS listener on a window scroll event is out of the Angular lifecycle so we need to manually $digest the scope for Angular to update all its watchers and update the HTML. Search online about $scope.$apply if you want to learn more about it.
About your first problem
Your angular application is not started because angular is never initialized. For that you need either to load it synchronously and use the ng-app directive, or if you don't want to change anything with your code you can simply add these lines after your module and controller definition:
// Compile the whole <body> with the angular module named "app"
angular.bootstrap(document.body, ['app']);
You need to include $scope.$apply() because the the scroll event executes outside Angular's context.
Also the event listener should be inside your controller so that the scoped more function is accessible.
Here's an updated fiddle:
https://jsfiddle.net/xue8odfc/2/
I'd say the problem with Angular not resolving the {{post.image}} etc. is due to incompatibilities among the libraries you are referencing. I suggest testing using the versions from the working jsfiddle:
<script src="https://cdn.firebase.com/js/client/2.0.3/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firebase-util/0.2.5/firebase-util.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.min.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.4/angularfire.min.js"></script>

Serving static html files using express is not executing the javascript

I am serving a static html file using the app.use(express.static(__dirname+"views")). And when I go to localhost:3000/first.html I get the html file. But I don't know if the angularjs and other controller javascript files were executed. I think they were not executed because I use ng-model for a button's value and it's just an empty button with no text and the function is not executed for the ng-click.
I think maybe it's because only the html file is served by the server without my controller js and my downloaded angularjs which I have src'd to in the html. But I don't see a 404 or any error in the console.
And also, when I use a virtual path(like app.use(express.static('',__dirname+"/views"))), I don't get the html file when I go to localhost:3000/views/first.html.
This is my server.js:
(function(){
var http = require("http");
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var path = require('path');
// app.use(express.static(__dirname));
app.use(bodyParser.json());
app.use(express.static(__dirname+'/views'));
// app.use(express.static('/views',__dirname+'/views')); this is not working
var server = http.createServer(app);
server.listen('3000');
console.log("Server is listening");
})();
My first.html which is served by the server:
<!DOCTYPE html>
<html ng-app="myapp">
<head>
<title></title>
<base href="localhost:3000/">
</head>
<body>
<script type="text/javascript" src="./angular.js"></script>
<script type="text/javascript" src="./angular-route.js"></script>
<script type="text/javascript" src="./controller.js"></script>
<div ng-controller="firstController">
First name:<input type="text" ng-model="firstName"><br>
Last name:<input type="text" ng-model="lastName"><br>
<input type="button" ng-click="loadView()" ng-model="submit" name="">
</div>
</body>
</html>
This is the controller.js:
var app = angular.module('myapp', ['ngRoute']);
app.config(function($routeProvider,$locationProvider){
$routeProvider.when('/first',{
templateUrl:'/first.html',
controller: 'firstController'
})
.when('/second',{
templateUrl:'/second.html',
controller:'secondController'
})
.otherwise({
redirectTo:'/first'
})
$locationProvider.html5Mode(true);
})
console.log("controller");
app.controller('firstController',function($scope,$location){
$scope.firstName="";
$scope.lastName="";
$scope.loadView = function()
{
$location.path('views/second/'+$scope.firstName +"/" +$scope.lastName);
}
$scope.submit = "submit";
})
.controller('secondController',function($scope,$routeParams){
$scope.firstName = $routeParams.firstName;
$scope.lastName = $routeParams.lastName;
})

Implementing simple upvote downvote program

I am using express.js and jQuery to create a basic upvote downvote program. Basically the voting buttons increment/decrement by one whenever pressed and the result of each button click is displayed below. Nothing happens when the buttons are clicked, not even a Cannot POST /. I am not sure what is causing the error. This is what my main.js file looks like:
$(document).ready(function(){
$("#up").click(function(){
var up = $.post("/upvote", {changeBy: 1}, function(dataBack){
$("#upvote").text(dataBack);
});
});
$("#down").click(function(){
var down = $.post("/downvote", {changeBy: 1},
function(dataBack){
$("#downvote").text(dataBack);
});
});
});
and then on the server side server.js, I have this:
var express = require("express");
var app = express();
var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static("public"));
var inc = 0;
var dec = 0;
app.post("/upvote", function(req, res){
res.setHeader("Content-Type", "application/json");
inc += parseFloat(req.body.changeBy);
res.write(JSON.stringify(inc));
res.end();
});
app.post("/downvote", function(req, res){
res.setHeader("Content-Type", "application/json");
dec -= parseFloat(req.body.changeBy);
res.write(JSON.stringify(dec));
res.end();
});
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})
A bit of HTML:
<div class="buttons">
<button id="down">Downvote</button>
<p id="downvote"></p>
<button id="up">Upvote</button>
<p id="upvote"></p>
</div>
I used your code to create a jsfiddle and it seems to be working fine with click events. Of course the ajax calls error out but the click event handler itself seems to be called Chrome at least.
<div class="buttons">
<button id="down">Downvote</button>
<p id="downvote"></p>
<button id="up">Upvote</button>
<p id="upvote"></p>
</div>
$(document).ready(function(){
$("#up").click(function(){
var up = $.post("/upvote", {changeBy: 1}, function(dataBack){
$("#upvote").text(dataBack);
});
});
$("#down").click(function(){
var down = $.post("/downvote", {changeBy: 1},
function(dataBack){
$("#downvote").text(dataBack);
});
});
});
https://jsfiddle.net/j14qkyae/
i gave this a try and it works. Is this really the whole html you use? Then this is the error. You need to in include jquery and your main.js into the html. I copied a query.min.js into the public directory of the server.
<html>
<head>
<title>Up Down Vote</title>
<script src="jquery.min.js"></script>
<script src="main.js"></script>
</head>
<body>
<div class="buttons">
<button id="down">Downvote</button>
<p id="downvote"></p>
<button id="up">Upvote</button>
<p id="upvote"></p>
</div>
</body>
</html>

How to associate an input with a user in firebase using angularfire?

I've built an app with firebase that can login a user and attain their id, but I can't figure out how to incorporate this with a user making a submission of a string.
See Code pen here: http://codepen.io/chriscruz/pen/OPPeLg
HTML Below:
<html ng-app="fluttrApp">
<head>
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.1/jquery-ui.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<script src="https://cdn.firebase.com/js/client/2.0.2/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/0.9.0/angularfire.min.js"></script>
</head>
<body ng-controller="fluttrCtrl">
<button ng-click="auth.$authWithOAuthPopup('google')">Login with Google</button>
<li>Welcome, {{user.google.displayName }}</li>
<button ng-click="auth.$unauth()">Logout with Google</button>
<input ng-submit= "UpdateFirebaseWithString()" ng-model="string" ></input>
Javascript Below:
<script>
var app = angular.module("fluttrApp", ["firebase"]);
app.factory("Auth", ["$firebaseAuth", function($firebaseAuth) {
var ref = new Firebase("https://crowdfluttr.firebaseio.com/");
return $firebaseAuth(ref);
}]);
app.controller("fluttrCtrl", ["$scope", "Auth", function($scope, Auth) {
$scope.auth = Auth;
$scope.user = $scope.auth.$getAuth();
$scope.UpdateFirebaseWithString = function () {
url = "https://crowdfluttr.firebaseio.com/ideas"
var ref = new Firebase(url);
var sync = $firebaseAuth(ref);
$scope.ideas = sync.$asArray();
$scope.ideas.$add({
idea: $scope.string,
userId:$scope.user.google.id,
});
};
}])
</script>
</body>
</html>
Also assuming, the above dependencies, the below works to submit an idea, but the question still remains in how to associate this with a user. See codepen here on this: http://codepen.io/chriscruz/pen/raaENR
<body ng-controller="fluttrCtrl">
<form ng-submit="addIdea()">
<input ng-model="title">
</form>
<script>
var app = angular.module("fluttrApp", ["firebase"]);
app.controller("fluttrCtrl", function($scope, $firebase) {
var ref = new Firebase("https://crowdfluttr.firebaseio.com/ideas");
var sync = $firebase(ref);
$scope.ideas = sync.$asArray();
$scope.addIdea = function() {
$scope.ideas.$add(
{
"title": $scope.title,
}
);
$scope.title = '';
};
});
</script>
</body>
There a couple of things tripping you up.
Differences between $firebaseand $firebaseAuth
AngularFire 0.9 is made up of two primary bindings: $firebaseAuth and $firebase. The $firebaseAuth binding is for all things authentication. The $firebase binding is for synchronizing your data from Firebase as either an object or an array.
Inside of UpdateFirebaseWithString you are calling $asArray() on $firebaseAuth. This method belongs on a $firebase binding.
When to call $asArray()
When you call $asArray inside of the UpdateFirebaseWithString function you will create the binding and sync the array each time the function is called. Rather than do that you should create it outside of the function so it's only created one item.
Even better than that, you can abstract creation of the binding and the $asArray function into a factory.
Plunker Demo
app.factory("Ideas", ["$firebase", "Ref", function($firebase, Ref) {
var childRef = Ref.child('ideas');
return $firebase(childRef).$asArray();
}]);
Get the user before the controller invokes
You have the right idea by getting the user from $getAuth. This is a synchronous method, the app will block until the user is returned. Right now you'll need to get the user in each controller. You can make your life easier, by retrieving the user in the app's run function. Inside of the run function we can inject $rootScope and the custom Auth factory and attach the user to $rootScope. This way the user will available to all controllers (unless you override $scope.user inside of your controller).
app.run(["$rootScope", "Auth", function($rootScope, Auth) {
$rootScope.user = Auth.$getAuth();
}]);
This is a decent approach, but as mentioned before $scope.users can be overridden. An even better way would be to resolve to user from the route. There's a great section in AngularFire guide about this.
Associating a user with their data
Now that we have the user before the controller invokes, we can easily associate their id with their input.
app.controller("fluttrCtrl", ["$scope", "Ideas", function($scope, Ideas) {
$scope.ideas = Ideas;
$scope.idea = "";
$scope.UpdateFirebaseWithString = function () {
$scope.ideas.$add({
idea: $scope.idea,
userId: $scope.user.google.id,
}).then(function(ref) {
clearIdea();
});
};
function clearIdea() {
$scope.idea = "";
}
}]);

Categories

Resources