View is not refreshed after $scope is updated in Firebase promise - angularjs

I am using Ionic Framework and Firebase is my BaaS.
Controller:
.controller('ProfileCtrl', function($scope, AuthService, DatabaseService) {
console.info('** ProfileCtrl **');
var user = firebase.auth().currentUser;
$scope.public = {};
DatabaseService.getUserPublicInfo(user.uid)
.then(function(infoSnap) {
infoSnap.forEach(function(item) {
$scope.public[item.key] = item.val();
});
});
})
Service:
.service('DatabaseService', function($q) {
this.getUserPublicInfo = function(uid) {
return firebase.database().ref('/users/'+uid+'/public/').once('value');
}
}
In my HTML view I have the following:
<div><h3>{{public.firstname}} {{public.lastname}}</h3></div>
No error and when debugging, $scope.public.firstname as the correct value in it but nothing is displayed.
I have a button in my HTML view ; when I click on it, it changes page but just before page switches, I see the firstname appearing. When I go back to my view, the firstname is well displayed.
I tried to wrap getUserPublicInfo in $scope.$apply() in my controller but I get the "$digest already in progress" error...
Please, help, it's driving me crazy !
Thanks in advance

To resove, "$digest already in progress" error... put $scope .$appy inside timeout service.
DatabaseService.getUserPublicInfo(user.uid)
.then(function(infoSnap) {
infoSnap.forEach(function(item) {
$scope.public[item.key] = item.val();
$timeout(function(){
$scope.$apply()
},1)
});
});
Edit 1: Try this to avoid using $scope.$apply(). I haven't tested it. But it should work.
DatabaseService.getUserPublicInfo(user.uid)
.then(function(infoSnap) {
$scope.updatePublic(infoSnap)
});
});
$scope.updatePublic = function (infoSnap) {
infoSnap.forEach(function(item) {
$scope.public[item.key] = item.val();
})
}

I used $q to create a promise. By doing the initial Firebase promise is resolved within the Angular scope:
this.getUserPublicInfo = function(uid) {
console.info('getUserPublicInfo - get user public information for uid: '+uid);
var deferred = $q.defer();
firebase.database().ref('/users/'+uid+'/public/').once('value')
.then(function(snap) {
deferred.resolve(snap);
})
.catch(function(error) {
deferred.reject(error);
});
return deferred.promise;
}

Related

Variable does not pass to the view from the controller

I am new in AngularJs and can not figure out with such problem.
I try to change the variable in the scope that unassuming way.
.controller('AuthController', function($scope,Auth) {
$scope.submit = function() {
Auth.login($scope.username,$scope.password, function(result){
$scope.result = result
});
}
})
Where Auth is a service which makes GET request to server and gets response like this:
{ 'status': 1, 'message': "User does not found!"}
But my variable does not refresh in my template. When I put this
$scope.result = { 'status': 1, 'message': "User does not found!"}
outside the function $scope.submit. It works fine.
This is my routing.
$stateProvider
.state('index', {
url: "/",
templateUrl: "/static/templates/_index.html",
})
My template is.
<div class="alert" >{{ result.message }}</div>
Can someone explaine me what do I do wrong?
Thanks.
This is my service.
function login(username, password, callback) {
return $http.post('/api/login/', {
username: username, password: password
}).success(callback);
}
After inserting
Auth.login($scope.username,$scope.password, function(result){
$scope.result = result
console.log($scope.result);
I see correct data in my firebug.
Object { status=1, message="User does not found!"}
$scope.$apply(); - gives me Error: [$rootScope:inprog] http://errors.angularjs.org/1.4.0/$rootScope/inprog?p0=%24digest
Are you sure Auth.login() doesn't return a promise. It looks like it wants to, in which case try...
Auth.login($scope.username, $scope.password)
.then(function(result){
$scope.result = result
});
Hard to say without seeing your Auth service.
If your Auth.login() returns a promise you can use its then function to define success and error handler e.g:
Auth.login($scope.username, $scope.password)
.then(function(result){// success handler
$scope.result = result
$scope.$apply();
},function(error){// error handler
console.log(error);
});
However if it makes an ajax call and does not return a promise, you can use $q service to return a promise by yourself. Here's how you can do it:
var deferred = $.defer();
deferred.resolve($.ajax({
// Your ajax call
}));
You can return promise using return deferred.promise.
Note : Make sure you inject $q service in your Auth service.
The problem was decided by changing
$scope.result = result.data;
on
$scope.blabla = result.data;
And after that magic my template finally show me {{ blabla }} variable.
Angular dose not live up my expectation :-(

Why do i need to refresh page to view new value of ng-show

Hey guys so i am using firebase and angular to build a sample application.
This is the registration function
$scope.register = function (){
Authentication.register($scope.user)
.then(function(user){
Authentication.login($scope.user);
})
.then(function(user){
$timeout(function(){
$location.path('/meetings');
}, 5);
})
.catch(function(error){
$scope.message = error.toString();
});
} //register
this function calls to methods in this factory
myApp.factory('Authentication', function($firebase,
$firebaseAuth,$location, FIREBASE_URL){
var ref = new Firebase (FIREBASE_URL);
var auth = $firebaseAuth(ref);
var myObject = {
login : function (user){
return auth.$authWithPassword({
email: user.email,
password: user.password
});
}, // login
logout: function (){
return auth.$unauth();
},
register : function (user){
return auth.$createUser(user.email,user.password);
} //register
} //myObject
return myObject;
});
here is the status.js controller which changes my index based on whether the user is logged in or not
auth.$onAuth(function(authData){
if (authData){
console.log("i entered authData");
$scope.userStatus = authData.password.email;
} else {
$scope.userStatus = false;
}
});
part of index.html file
<div class="userinfo"
ng-controller="StatusController" ng-show="userStatus">
<span class="user">Hi {{userStatus}}</span>
Log Out
</div>
my problem is that the ng-view needs a page refresh to show the new value. its not showing it automatically but my code works. if i refresh the page manually i can see that the user got registered and logged in.
Search for about 2 hours now and $scope.$apply here does nt seem to be the case.
Thank you in advance
I wanna thank you guys for the possible solutions but it seems that the solution lies in the $timeout variable.
Because the registration function below talks with an API and i am working from a local environment using GulpJS there is a delay in talking between my script and the Firebase API.
$scope.register = function (){
Authentication.register($scope.user)
.then(function(user){
Authentication.login($scope.user);
})
.then(function(user){
$timeout(function(){
$location.path('/meetings');
}, 5);
})
.catch(function(error){
$scope.message = error.toString();
});
} //register
Thats why i put a $timeout angular function in there in the first place but i forgot that the number 5 in input in the $timeout function is in milliseconds and it seems because of my setup(using a local environment while talking to the Firebase API online) i found that if i use a 2 second delay in the redirection now ng-show changes automatically without manually refreshing the page.
correct code would be the one below:
$scope.register = function (){
Authentication.register($scope.user)
.then(function(user){
Authentication.login($scope.user);
})
.then(function(user){
$timeout(function(){
$location.path('/meetings');
}, 2000);
})
.catch(function(error){
$scope.message = error.toString();
});
} //register
I suspect that if i use my angular app on the web i could possibly lower the delay or even remove the $timeout function completely.

angular controller needing refresh to pickup new data in a factory promise

I have a pretty standard app which will display news items from a remote JSON feed. So basically I have decided to poll the remote server and store the JSON in localStorage (to enable offline usage). For the moment, I have a manual page/view I must click on to update the localStorage , this works fine.
The problem is that after I use my temporary manual update page, I then go to the news page/view and it is not updated. To view the current JSON contents I must hit refresh (while still developing in the browser.)
I'm totally new to Angular and have tried to find solutions to this myself - $watch or reload: true seem to be suggested as fixes, but I cannot get them to work in my case.
Route
.state('tab.news', {
url: '/news',
reload: true,
views: {
'news-tab': {
templateUrl: 'templates/news_home.html',
controller: 'newsCtrl'
}
}
})
factory
angular.module('schoolApp.services', [])
.factory('newsService', function($q) {
var newsHeadlines =localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail status
var newsHeadlinesObj = JSON.parse(newsHeadlines);// convert to an object
console.log("factory newsService ran");
return {
findAll: function() {
var deferred = $q.defer();
deferred.resolve(newsHeadlinesObj);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var deferred = $q.defer();
var newsItem = newsHeadlinesObj[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
Controller
schoolApp.controller('newsCtrl', function($scope, newsService) {
console.log ( 'newsCtrl ran' );
newsService.findAll().then(function (newsHeadlinesObj) {
$scope.newsHeadlinesObj = newsHeadlinesObj;
}, function(error){
console.log(error)
});
})
Looking at my console, the first time I read the news, the factory then controller run, but if I go to pull more data down, then go hack to news, only the controller runs, unless I refresh, then both run again.
I do not need the news view to update 'live' while still on it (but if that can be easilly done all the better) - just to pick up new data when you go back to news after being elsewhere in the app.
Thank you.
Factories return singletons and only run once. The object newsService is cached by angular. The var declarations for newsHeadlines and newsHeadlinesObj will only ever run once; meaning your promise returning methods will always resolve the promise with the same data that was retrieved when your factory was first instantiated. You should put them in a function and call it from your find methods on the singleton object.
.factory('newsService', function($q) {
function getHeadlines() {
var newsHeadlines = localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail
return JSON.parse(newsHeadlines);// convert to an object
}
return {
findAll: function() {
var headlines = getHeadlines();
var deferred = $q.defer();
deferred.resolve(headlines);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var headlines = getHeadlines();
var deferred = $q.defer();
var newsItem = headlines[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
PS - I'm sure you know and are planning to do things differently later or something, but just in case you don't: Using promises here is pointless and you have no need for $q here. You could simply return the data instead of returning the promises.
I solved this withouut promises, I just used $rootScope in the factory and $scope.$on in the controller; when I change the factory, i use $rootScope.$broadcast to tell the controller that I change it.
.factory('dataFactory', ['$http', '$rootScope', function ($http, $rootScope) {
var dataFactory = {
stock: null,
getStock: getStock
}
function getStock() {
$http.get("/api/itemfarmacia/").then(function success(res) {
dataFactory.stock = res.data;
$rootScope.$broadcast('changingStock'); //Ones who listen this will see it
}, function error(err) {
console.log("Bad request");
})
}
return dataFactory;
}])
and in the controller
.controller('atencion', ["$scope", "$state", "dataFactory", function ($scope, $state, dataFactory) {
$scope.stock = dataFactory.stock; //At first is null
dataFactory.getStock(); //wherever you execute this, $scope.stock will change
$scope.$on('changingStock', function () {//Listening
$scope.stock = dataFactory.stock; //Updating $scope
})
}])

How to update the $scope manually in angularjs

After i have updated a user in angularjs I will update the $scope.user with the new userdata. One way is to do an new api request to get all users. But i think, its better to update the $scope. I found $apply, but i'am not sure how to use it.
app.controller('UserCtrl', function( $scope, APIService ) {
$scope.users = APIService.query({ route:'users' });
$scope.activate = function( user ) {
var updateUser = {};
if(user.activated) {
updateUser.activated = false;
} else {
updateUser.activated = true;
}
APIService.update({ route:'users', id: user._id }, updateUser, function(updatedUser) {
//update $scope.users with new data from updatedUser;
});
}
});
The updatedUser looks like this:
{"__v":0,"_id":"535aa89d8b2766d012e14c21","activated":true,"role":"user","local":{"surname":"Carter","prename":"Rob","password":"459DS","email":"test"},"$promise":{},"$resolved":true}
the service:
app.service("APIService", function( $resource ) {
return $resource('http://localhost:3000/api/:route/:id', { route: "#route", id: "#id" }, {
'update': { method: 'PUT' }
});
});
If you're using ngRoute use:
$route.reload()
Causes $route service to reload the current route even if $location hasn't changed.
As a result of that, ngView creates new scope, reinstantiates the controller.
and if you're using ui-router:
$state.reload();
However I think a better way would be to recall the $resource call again:
var getUsers=function(){
$scope.users = APIService.query({ route:'users' });
}
You can call it initially like this:
getUsers();
and in the callback of your update:
APIService.update({ route:'users', id: user._id }, updateUser, function(updatedUser) {
getUsers();
});
EDIT
I'm not sure what you mean by "updating the scope"? You mean rerunning the controller again? In that case you're making a fresh API call again, the same thing as just calling the getUsers() method I suggested. If you mean you want to update an array that sits on your $scope with the new data of the user, rather than calling the server to get the entire users again, then in the callback of your update method do something like this:
angular.forEach($scope.users,function(user,key){
if(user._id == updatedUser._id){
$scope.users[key]=updatedUser;
}
})

Cordova/Phonegap + Angular: promise not resolving until next digest cycle

I'm using Angular + JQMobile inside a Cordova mobile app, and I'm having some difficulty with promises. I have a cordovaReady service that returns a promise and I'm looking to hide a splashscreen (no lectures please) when the promise is resolved. The trick comes when I try to use an if-then condition to resolve the promises at different times. My code:
app.factory('cordovaReady', function($q) {
var deferred = $q.defer();
return {
ready: function() {
document.addEventListener(
"deviceready",
function () {
deferred.resolve();
},
false
);
return deferred.promise;
}
}
});
app.run(['$rootScope', '$location', 'TagTemplates', 'cordovaReady', function($scope, $location, TagTemplates, cordovaReady) {
// If there is a user logged in, --fire camera-- and go to tag template
if ($scope.currentUser) {
// fire camera here, make sure cordova is ready first.
// alert('found user');
var promise = cordovaReady.ready();
promise.then(function() {
// alert('about to capture');
console.log('firing capture');
$scope.captureImage();
navigator.splashscreen.hide(); // this one fires.
});
console.log($scope.currentUser);
$scope.templates = $scope.currentUser.attributes.templates[0];
$scope.$apply();
$location.url("/#main");
} else {
var promise = cordovaReady.ready();
promise.then(function() {
alert('no user logged in, hide splashscreen');
navigator.splashscreen.hide(); // this one does not.
$scope.$apply(); // even with this.
});
}
}
In the case of an existing user, the splashscreen is hidden as desired, I thought because of scope updates that happen within this if statement. However, in the event of no existing user, the splashscreen does not fire until the next digest cycle (clicking on a button or link in my app). I verified this with the alert.
I've tried all manner of scope apply, include variants of setTimeout with 0 delay, etc.
Any thoughts?
Thanks.

Resources