Angular binding not updating with socket.io broadcast - angularjs

I have a server variable named currentPlayer that binds properly in the emit 'init' call, but won't bind in my broadcast.emit. The variable turnNumber which is passed through from the client first updates properly. When the pass event is fired I can see currentPlayer switching values in the terminal, but I'm stumped as to why it doesn't update in the view.
I had some trouble binding to primitive types before, so I tried going an extra step and assigning currentPlayer to an object array: $scope.globalVars = [{ currentPlayer: data.currentPlayer }], but that didn't help.
//server
var currentPlayer = 1;
socket.emit('init', {
currentPlayer: currentPlayer
});
socket.on('pass', function (data) {
currentPlayer = currentPlayer == 1 ? 2 : 1;
console.log(currentPlayer);
socket.broadcast.emit('pass', {
number: data.turnNumber,
currentPlayer: currentPlayer
});
});
//client
socket.on('init', function (data) {
$scope.globalVar.currentPlayer = data.currentPlayer;
});
socket.on('pass', function (data) {
$scope.globalVar.turnNumber = data.number;
$scope.globalVar.currentPlayer = data.currentPlayer;
});
//click event
$scope.globalVar.turnNumber++;
socket.emit('pass', {
turnNumber: $scope.globalVar.turnNumber
});
<!-- html -->
{{globalVar.currentPlayer}} {{globalVar.turnNumber}}
UPDATE:
I realized I could handle the current turn logic in my angular controller. I'm no longer changing the variable on the server and trying to pass it back to the client. Maybe as Chandermani says it's simply out of angular's context.
//server
socket.on('pass', function (data) {
socket.broadcast.emit('pass', {
number: data.turnNumber,
currentPlayer: data.currentPlayer
});
});
//client
socket.on('pass', function (data) {
$scope.globalVar.turnNumber = data.number;
$scope.globalVar.currentPlayer = data.currentPlayer;
});
//click event
$scope.globalVar.currentPlayer = $scope.globalVar.currentPlayer == 1 ? 2 : 1;
$scope.globalVar.turnNumber++;
socket.emit('pass', {
turnNumber: $scope.globalVar.turnNumber,
currentPlayer: $scope.globalVar.currentPlayer
});

These calls seem to be execution outside the anuglar context. Try to use $scope.$apply in the socket.on handlers, like
socket.on('pass', function (data) {
$scope.$apply(function() {
$scope.globalVar.turnNumber = data.number;
$scope.globalVar.currentPlayer = data.currentPlayer;
});
});

Related

why variables keep being empty on factory angular?

services.factory('profilFactory',['$q','$http',function($q,$http){
var factory2 =
{
profils : {},
getProfils : function(){
$dfd = $q.defer();
$http.get('data.json')
.success(function(data,status){
this.profils = data.profil;
$dfd.resolve(this.profils);
})
.error(function(data,status) {
$dfd.reject('erreur recuperation des profils');
});
return $dfd.promise;
},
getProfil : function(idProfil){
var profil={};
var profils = {};
factory2.getProfils().then(function(data){
profils= data;
console.log(profils);//all right until here profils has values
});
console.log(profils);// now profils is empty :\ and the foreach will not execute
angular.forEach(profils, function(value, key){
if(value.id == idProfil){
profil= value;
}
});
return profil;
}
};
return factory2;
}]);
This is a screenshot of the problem : method "getProfil"
To answer your question, "Why are the variables empty in the factory", it's because you are using a console.log statement in a location where the data has not yet been loaded from the server. To learn more, Google this: "angularjs http get promises"
services.factory('profilFactory',['$q','$http',function($q,$http){
var factory2 =
{
profils : {},
getProfils : function(){
$dfd = $q.defer();
$http.get('data.json')
.success(function(data,status){
this.profils = data.profil;
$dfd.resolve(this.profils);
})
.error(function(data,status) {
$dfd.reject('erreur recuperation des profils');
});
return $dfd.promise;
},
getProfil : function(idProfil){
var profil={};
var profils = {};
// Run a function to get data, "THEN" we run a function to process the data:
factory2.getProfils().then(function(data){
// Data has now been loaded so we can process it and return it.
profils = data;
angular.forEach(profils, function(value, key){
if(value.id == idProfil){
profil= value;
}
});
return profil;
});
console.log(profils); // This is EMPTY because it runs immediately after
// the factory2.getProfils() function which may need several seconds to
// load data. That's why "profils" is empty. The data hasn't loaded at
// this point.
//
// No data will be available at this level of the code. Don't try to access
// "profils" here! Only in your .then() function above.
}
};
return factory2;
}]);
Your console.log statement is outside of the callback. That is the issue. You need to console.log in the callback or use a watcher to update it when it gets changed. For future reference you should always copy and paste your code here.

How to refresh the view when angular $http promise call wrapped in rxjs Observable

I have a project that uses angular's $http service to load data from a remote location. I want to use rxjs Observables so the call in my service looks like this:
userInfo() : Rx.Observable<IUserInfo> {
var url : string = someUrl + this._accessToken;
return Rx.Observable.fromPromise<IUserInfo>( this.$http.get<IUserInfo>( url ) );
}
and this is subscribed to by my controller like this:
getUserInfo() : void {
this._googleService.userInfo().subscribe(
( result ) => { this.handleUserInfo( result ) },
( fault : string ) => this.handleError( fault )
)
}
private handleUserInfo( result : IHttpPromiseCallbackArg<IUserInfo> ) : void {
console.log( "User info received at " + new Date() );
this._name = result.data.given_name + " " + result.data.family_name;
this._email = result.data.email;
this._profilePicUrl = result.data.picture;
}
the problem is that despite the name, email and profile pic being updated these changes are not visible. As soon as anything else triggers an angular $apply the changes appear but because of the Observable these changes in the controller happen after the angular digest loop that is triggered by the $http call.
This does work correctly if my service just returns a promise to the controller.
How do I update my view in this case? I do not want to manually have to wire up each observable to trigger a digest cycle. I want all Observables to trigger a digest cycle when they receive a new value or error.
We can use the ScopeScheduler from rx.angular.js for this. We only have to create a new one where we create our angular module and pass the $rootScope to it:
const module : ng.IModule = angular.module( 'moduleName', [] );
module.run( ["$rootScope", ( $rootScope ) => {
new Rx.ScopeScheduler( $rootScope );
}]);
That's all you have to do. Now all Rx.Observables trigger an $apply when they get a new value.
For some reason the ScopeScheduler was deleted when the rx.angular.js library was upgraded to rxjs version 4. We have to use rx.angular.js version 0.0.14 to use the ScopeScheduler.
I do not know what the suggested solution to this is in version 4.
A project using this fix can be viewed here:
https://github.com/Roaders/Typescript-OAuth-SPA/tree/observable_apply_issues
I couldn't get the Rx.ScopeScheduler method to work, so I just overwrote the rx observable subscribe method itself instead, and wrapped the callbacks in $rootScope.$apply :)
module.run(['$rootScope', 'rx', function ($rootScope, rx) {
rx.Observable.prototype.subscribe = function (n, e, c) {
if(typeof n === 'object') {
return this._subscribe(n);
}
var onNext = function(){};
if(n) {
onNext = function(value) {
if($rootScope.$$phase) {
n(value);
}
else {
$rootScope.$apply(function(){ n(value); });
}
};
}
var onError = function(err) { throw err; };
if(e) {
onError = function(error) {
if($rootScope.$$phase) {
e(error);
}
else {
$rootScope.$apply(function(){ e(error); });
}
};
}
var onCompleted = function(){};
if(c) {
onCompleted = function() {
if($rootScope.$$phase) {
c();
}
else {
$rootScope.$apply(function(){ c(); });
}
};
}
return this._subscribe(
new rx.AnonymousObserver(onNext, onError, onCompleted)
);
};
}]);

View not updating - angular, firebase

This function fires once at page load, but then never again. I've tried $watch, $apply, and firebase's ref.on('value', ... but no dice. The model changes for the value of scope.job.getApplicants(), but scope.applicants_ is not refreshing when this happens.
function go() {
var p = [];
// scope.job.getApplicants() returns a $firebaseArray of user IDs
scope.job.getApplicants().$loaded().then(function(list) {
list.forEach(function(app) {
// User_(app.$id) returns a $firebaseObject
var user = User_(app.$id);
p.push(user);
})
});
return p;
}
scope.applicants_ = go();
This is how I resolved the problem:
ref.on('value', function(apps) {
var applicantIDs = Object.keys(apps.val());
scope.applicants_ = applicantIDs.map(function(app) {
return User_(app);
});
});

Assign value to the Factory variable through http

I have two controllers and one Factory. I am using angular-devise module for authentication.
UserFactory.js
myApp.factory('Userfactory', function(Auth,$location){
var Userfactory = {}
Userfactory.user = []
Userfactory.active = false
Userfactory.isLogged = function(){
if(!Userfactory.active[0]){
return Auth.currentUser().then(function(user) {
Userfactory.user.push(user)
Userfactory.active = angular.copy(true)
}, function(error) {
});
}
}
return Userfactory;
})
UserController.js
myApp.controller("userController",function($scope,Userfactory){
Userfactory.isLogged().then(function(){
$scope.active = Userfactory.active;
})
}
NavController.js
myApp.controller("navController", function navController(Userfactory){
this.user = Userfactory.user;
this.active = Userfactory.active;
})
UserFactory.active in nav controller is not updating when isLogged updates the value , since the value is getting resetted (which breaks binding) , where as in user controller the value is assigned through promise , so its getting the latest value. My doubt is how to manage to get it working in both places, meaning how to assign value without breaking the binding.
One workaround is declaring Userfactory.active as an array and pushing the value to it as true or false, but looking for better way of doing it, any help will be useful.
I would recommend you to use a model Object to wrap all the variables/properties you need to be bindable.
myApp.factory('Userfactory', function(Auth,$location){
var Userfactory = {}
Userfactory.model = {
user: undefined,
active: false
};
Userfactory.isLogged = function(){
if(!Userfactory.active[0]){
return Auth.currentUser().then(function(user) {
Userfactory.model.user = user;
Userfactory.model.active = true;
}, function(error) {
});
}
}
return Userfactory;
})
And then on your controllers:
myApp.controller("userController",function($scope,Userfactory){
Userfactory.isLogged().then(function(){
$scope.userModel = Userfactory.model;
// and on the UI use: userModel.active
})
}
myApp.controller("navController", function navController(Userfactory){
this.userModel = Userfactory.model;
// then access it like: this.userModel.active
})

Can't get waiting for function to return (with promises?) working in angular controller

I'm trying to get the following findTimelineEntries function inside an Angular controller executing after saveInterview finishes:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId}, function() {
$scope.findTimelineEntries();
});
};
The save action adds or edits data that also is part of the timeline entries and therefore I want the updated timeline entries to be shown.
First I tried changing it to this:
$scope.saveInterview = function() {
var functionReturned = $scope.interviewForm.$save({employeeId: $scope.employeeId});
if (functionReturned) {
$scope.findTimelineEntries();
}
};
Later to this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
$scope.saveInterview.done(function(result) {
$scope.findTimelineEntries();
});
And finaly I found some info about promises so I tried this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
var promise = $scope.saveInterview();
promise.done(function() {
$scope.findTimelineEntries();
});
But somehow the fact that it does work this way according to http://nurkiewicz.blogspot.nl/2013/03/promises-and-deferred-objects-in-jquery.html, doesn't mean that I can use the same method on those $scope.someFuntcion = function() functions :-S
Here is a sample using promises. First you'll need to include $q to your controller.
$scope.saveInterview = function() {
var d = $q.defer();
// do something that probably has a callback.
$scope.interviewForm.$save({employeeId: $scope.employeeId}).then(function(data) {
d.resolve(data); // assuming data is something you want to return. It could be true or anything you want.
});
return d.promise;
}

Resources