Angular infinite digest loop with ui-router - angularjs

The problem I was initially trying to solve was to redirect a user to the login page if they are not logged in and vice versa.
I did this with the following code
.run(function($rootScope, $http, AppService, $state) {
$rootScope.$on('application:refreshtoken', function(rootScope, token) {
if(token) {
$http.defaults.headers.common['X-Auth-Token'] = token;
AppService.setAuthToken(token);
AppService.resetLoginTimeout();
}
});
$rootScope.$on('$stateChangeSuccess', function() {
$http.get('/api/heartbeat');
});
// This is the really pertinent bit...
$rootScope.$on('$stateChangeStart', function(e, toState) {
if(toState.name === 'login') {
if(AppService.getIsLoggedIn()) {
e.preventDefault();
$state.go(AppService.getRedirectPage());
}
} else {
if(!AppService.getIsLoggedIn()) {
e.preventDefault();
$state.go('login');
}
}
});
});
AppService
.factory('AppService', ['$rootScope', 'locker', '$http', '$state',
function ($rootScope, locker, $http, $state) {
var _isLoggedIn = locker.get('loggedIn', false),
_authToken = locker.get('authtoken', null),
_roles = locker.get('roles', null),
_permissions = locker.get('permissions', null),
_user = locker.get('user', null),
_userid = locker.get('userid', null),
_userprefs = locker.get('userprefs', null),
_timedout,
_timeoutId,
service = {};
if (_authToken) {
$http.defaults.headers.common['X-Auth-Token'] = _authToken;
}
service.setIsLoggedIn = function (isLoggedIn) {
_isLoggedIn = isLoggedIn;
this.doLogin();
broadcastLogin();
};
service.doLogin = function () {
if (_isLoggedIn) {
locker.put({
loggedIn: _isLoggedIn,
authtoken: _authToken,
roles: _roles,
permissions: _permissions,
user: _user,
userprefs: _userprefs
});
}
};
service.doLogout = function (cb) {
_isLoggedIn = false;
_authToken = null;
_roles = null;
_permissions = null;
_user = null;
_userid = null;
_userprefs = null;
delete $http.defaults.headers.common['X-Auth-Token'];
locker.clean();
cb();
};
service.getIsLoggedIn = function () {
return _isLoggedIn;
};
service.setAuthToken = function (authToken) {
_authToken = authToken;
locker.put({
authtoken: _authToken
});
};
service.getAuthToken = function () {
return _authToken;
};
service.setUserid = function (userid) {
locker.put('userid', userid);
_userid = userid;
};
service.getUserid = function () {
return _userid;
};
service.setUser = function (user) {
_user = user;
};
service.getUser = function () {
return _user;
};
service.setRoles = function (roles) {
_roles = roles;
};
service.getRoles = function () {
return _roles;
};
service.setPermissions = function (permissions) {
_permissions = permissions;
};
service.getPermissions = function () {
return _permissions;
};
service.setUserPreferences = function (prefs) {
_userprefs = prefs;
};
service.getUserPreferences = function () {
return _userprefs;
};
service.resetLoginTimeout = function () {
if (_timeoutId) {
clearTimeout(_timeoutId);
}
_timeoutId = setTimeout(function () {
$rootScope.$broadcast('application:logintimeoutwarn');
}, 1000 * 60 * 4);
};
service.setTimedOut = function (timedout) {
_timedout = timedout;
};
service.getTimedOut = function () {
return _timedout;
};
service.extendSession = function () {
$http.get('/api/heartbeat');
};
service.goDefaultUserPage = function () {
var success = false;
if (_userprefs.landingPage) {
$state.go(_userprefs.landingPage);
success = true;
} else {
var permissionRoutes = {
'regimens': 'regimens.do',
'pathways': 'pathways',
'manage.users': 'manageusers.do',
'manage.practices': 'managepractices.do',
'patients': 'patients'
};
_.some(_permissions, function (el) {
var state = $state.get(permissionRoutes[el]);
if (!state.abstract) {
$state.go(state.name);
success = true;
return true;
}
});
}
return success;
};
service.getRedirectPage = function () {
var page = false;
if (_userprefs.landingPage) {
page = _userprefs.landingPage;
} else {
var permissionRoutes = {
'regimens': 'regimens.do',
'pathways': 'pathways',
'manage.users': 'manageusers.do',
'manage.practices': 'managepractices.do',
'patients': 'patients'
};
_.some(_permissions, function (el) {
var state = $state.get(permissionRoutes[el]);
if (!state.abstract) {
page = state.name;
return true;
}
});
}
return page;
};
function broadcastLogin() {
$rootScope.$broadcast('application:loggedinstatus');
}
broadcastLogin();
return service;
}
])
This code works great until I take a very specific set of actions:
Login
Close the open tab or window
Open a new tab and go to the application
Since I am still logged in to the application, I have a user object and a valid token, but I am getting error:infdig Infinite $digest Loop. It eventually resolves and goes to the correct state, but it takes a while and the path flickers (I can post a video if needed).
I tried using $location.path instead of $state.go in the $rootScope.$on('$stateChangeSuccess') callback, but the issue persists.
This doesn't really affect the functioning of the application, but it is annoying. I also don't really want to change my locker storage to session storage because I want the user to stay logged in if they close the tab and reopen.

I would say, that the issue is hidden in the improper if statements inside of the $rootScope.$on('$stateChangeStart'... Check this:
Ui-Router $state.go inside $on('$stateChangeStart') is cauzing an infinite loop
With a general suggestion:
let's redirect ($state.go()) only if needed - else get out of the event listener
$rootScope.$on('$stateChangeStart' ...
if (toState.name === 'login' ){
// going to login ... do not solve it at all
return;
}
Second check should be: is user authenticated (and NOT going to login)?
if(AppService.getIsLoggedIn()) {
// do not redirect, let him go... he is AUTHENTICATED
return;
}
Now we have state, which is not login, user is not authenticated, we can clearly call:
// this is a must - stop current flow
e.preventDefault();
$state.go('login'); // go to login
And all will work as we'd expected
Very detailed explanation and working example could be also found here...

this usally happens when the app gets stuck between a route rejection through a resolve clause and an automatic redirection on the previous route where the landing page will redirect to some page, say auth, and the auth page needs some conditions to let you in and if it fails or it will redirect back to some other page, hence the cycle, make sure you get your story straight and if needed use an intermediate state to clear all preferences and take the default path

Related

Delay loading data in Angular JS

I have code like this
(function (app) {
app.controller('productListController', productListController)
productListController.$inject = ['$scope', 'apiService', 'notificationService', '$ngBootbox', '$filter'];
function productListController($scope, apiService, notificationService, $ngBootbox, $filter) {
$scope.products = [];
$scope.page = 0;
$scope.pagesCount = 0;
$scope.getProducts = getProducts;
$scope.keyword = '';
$scope.search = search;
$scope.deleteProduct = deleteProduct;
$scope.selectAll = selectAll;
$scope.deleteMultiple = deleteMultiple;
function deleteMultiple() {
var listId = [];
$.each($scope.selected, function (i, item) {
listId.push(item.ID);
});
var config = {
params: {
checkedProducts: JSON.stringify(listId)
}
}
apiService.del('/api/product/deletemulti', config, function (result) {
notificationService.displaySuccess('Deleted successfully ' + result.data + 'record(s).');
search();
}, function (error) {
notificationService.displayError('Can not delete product.');
});
}
$scope.isAll = false;
function selectAll() {
if ($scope.isAll === false) {
angular.forEach($scope.products, function (item) {
item.checked = true;
});
$scope.isAll = true;
} else {
angular.forEach($scope.products, function (item) {
item.checked = false;
});
$scope.isAll = false;
}
}
$scope.$watch("products", function (n, o) {
var checked = $filter("filter")(n, { checked: true });
if (checked.length) {
$scope.selected = checked;
$('#btnDelete').removeAttr('disabled');
} else {
$('#btnDelete').attr('disabled', 'disabled');
}
}, true);
function deleteProduct(id) {
$ngBootbox.confirm('Are you sure to detele?').then(function () {
var config = {
params: {
id: id
}
}
apiService.del('/api/product/delete', config, function () {
notificationService.displaySuccess('The product hase been deleted successfully!');
search();
}, function () {
notificationService.displayError('Can not delete product');
})
});
}
function search() {
getProducts();
}
function getProducts(page) {
page = page || 0;
var config = {
params: {
keyword: $scope.keyword,
page: page,
pageSize: 20
}
}
apiService.get('/api/product/getall', config, function (result) {
if (result.data.TotalCount == 0) {
notificationService.displayWarning('Can not find any record.');
}
$scope.products = result.data.Items;
$scope.page = result.data.Page;
$scope.pagesCount = result.data.TotalPages;
$scope.totalCount = result.data.TotalCount;
}, function () {
console.log('Load product failed.');
});
}
$scope.getProducts();
}
})(angular.module('THTCMS.products'));
So my problem is when i loading data the application take me some time to load data.
I need load data as soon as
Is the any solution for this?
Since you are loading data via api call, there will be a delay. To handle this delay, you should display a loading screen. Once the data is loaded, the loading screen gets hidden and your main screen is visible. You can achieve this using $http interceptors.
See : Showing Spinner GIF during $http request in angular
The api-call is almost certainly causing the delay. Data may be received slowly via the api-call so you could display any sort of loading text/image to notify the use that the data is being loaded.
If u want the data ready at the time when controller inits, u can add a resolve param and pass the api call as a $promise in the route configuration for this route.

pageshow not working in apache cordova

Hello I am trying to implement the "pageshow" because I need to block the return if the page button that will take is the login.Porém the method "pageshow" is not called , so I can not apply logic:
// and then run "window.location.reload()" in the JavaScript Console.
(function () {
"use strict";
var pageHistoryCount = 0;
var goingBack = false;
document.addEventListener('deviceready', onDeviceReady.bind(this), false);
function onDeviceReady() {
document.addEventListener('pause', onPause.bind(this), false);
document.addEventListener('resume', onResume.bind(this), false);
document.addEventListener('backbutton', backButtonHandler, false);
$(document).bind("pageshow", function (e, data) {
console.log("AAAAAAAAAAAAAAA");
if (goingBack) {
goingBack = false;
} else {
pageHistoryCount++;
console.log("HERE");
}
});
// TODO: Cordova has been loaded. Perform any initialization that requires Cordova here.
};
function onPause() {
};
function onResume() {
};
function exitApp() {
navigator.app.exitApp();
};
function backButtonHandler(e) {
e.preventDefault();
console.log(pageHistoryCount);
if (pageHistoryCount > 0) pageHistoryCount--;
if (pageHistoryCount == 0) {
} else {
goingBack = true;
console.log("Going back to page #" + pageHistoryCount);
window.history.back();
}
//Here implement the back button handler
};
})();

Backbone Modal with Q.promise issue

We have a method (onOpenNotitiesClicked) for showing a Modal view for entering notes. We have implemented the Backbone Modal plugin for this (https://github.com/awkward/backbone.modal).
There are two situations:
There are not yet notes in the backend: initialize and render the
modal
There are already notes in the backend => first collect them and
then pass the notes to the modal (initialize) and then render
In the first situation it works fine! The modal is shown.
In the second situation, the modal is not shown.
I have debugged both situations and in both situations, alle methods are executed and in the elements, I see the HTML of the modal view.
I suspect this looses some data during the Q/promise data get, but I can't see what/where/how/why....
Anyone any idea what I am doing wrong? Or what I am missing?
The creation of the modal:
onOpenNotitieClicked: function (event) {
var $element, taak, taakId, id, options = {};
$element = this.$(event.currentTarget).closest("li");
id = $element.data("id");
taakId = $element.data("taak-id");
taak = this.getCurrentTask(event);
options.taakKey = id;
options.taakId = taakId;
options.heeftNotities = taak.heeftNotities;
options.datacontroller = this.datacontroller;
this.notitiesOptions = options;
// this.renderNotitieModal(options);
if (taak.heeftNotities) {
this.getActiviteitNotities(taakId).then(_.bind(this.onSuccessGetNotities, this), _.bind(this.onErrorGetNotities, this));
} else {
this.renderNotitieModal(this.notitiesOptions);
}
},
In case there are notes to be collected:
getActiviteitNotities: function (taakId) {
return this.datacontroller.getNotities(taakId);
},
onSuccessGetNotities: function (notities) {
this.notitiesOptions.notities = notities;
this.renderNotitieModal(this.notitiesOptions);
},
onErrorGetNotities: function () {
this.renderNotitieModal(this.notitiesOptions);
},
To get the notes from the backend, Q/promises is used.
getNotities: function (taakId, refresh, klantorderId) {
return Q.promise(function (resolve, reject) {
var options = {};
if (!this.notitiesCollection || this.taakId !== taakId || refresh) {
delete this.notitiesCollection;
this.notitiesCollection = this.createCollection("NotitiesCollection", {
id: this.taakId,
resource: this.NOTITIES_RESOURCE
});
if (taakId) {
this.taakId = taakId;
options = {
data: {
parentId: this.taakId
}
};
} else if (klantorderId) {
options = {
data: {
klantorderId: klantorderId
}
};
}
resolve(this.notitiesCollection.fetch(options));
} else if (this.notitiesCollection) {
resolve(this.notitiesCollection.toJSON());
} else {
reject("ERROR");
}
}.bind(this));
},
Notities.js (the modal view):
(function () {
"use strict";
App.Bewaking.modals.BewakingNotitieModal = Backbone.Modal.extend({
template: JST.bewaking_notitie_modal, //jshint ignore:line
title: "Notities",
formatter: new App.Main.helpers.Formatter(),
events: {
"click #save-notitie": "onSaveNotitieClicked"
},
initialize: function (options) {
this.taakId = options.taakId;
this.taakKey = options.taakKey;
this.datacontroller = options.datacontroller;
this.notities = options.notities;
},
afterRender: function () {
console.log("afterRender");
this.$notitieModal = this.$("#notitieModal");
this.$nieuweNotitie = this.$("#nieuwe-notitie");
this.$notitieErrorTekst = this.$("#notitie-error-tekst");
this.$notitieModal.on("shown.bs.modal", function () {
this.$nieuweNotitie.focus();
}.bind(this));
},
render: function () {
console.log(this.notities);
this.$el.html(this.template({
formatter: this.formatter,
notities: this.notities
}));
return this;
}
});
}());

$watch not updating scope variable

First I want to say that I am a complete beginner in AngularJS and just attempting to understand the basic concepts. I have a background in Java and PHP.
I am building a part of a website. Right now the angular app only consists of opening and closing 2 drop down menus registrationDropDown and loginDropDown. I want them to work so that only one can be open at a time ie. if I open one, and the other is already open, the older one is forced to close.
I have a service to manage the variables that determine whether the drop downs should be open or closed and 2 controllers, one for login and one for registration, both include $watch for the respective variables.
THE PROBLEM
I want the app to work so that only one of the drop downs can be open at one time.
JSFIDDLE: http://jsfiddle.net/F5p6m/3/
angular.module("ftApp", [])
.factory('dropDownService', function () {
var loginDropDownStatus = false;
var registrationDropDownStatus = false;
return {
getLoginDropDownStatus: function () {
return loginDropDownStatus;
},
showLoginDropDown: function () {
console.log("showing login drop down");
registrationDropDownStatus = false;
loginDropDownStatus = true;
console.log("loginDropDownStatus" + loginDropDownStatus + "registrationDropDownStatus" + registrationDropDownStatus);
},
hideLoginDropDown: function () {
console.log("hiding login drop down");
loginDropDownStatus = false;
console.log("loginDropDownStatus" + loginDropDownStatus);
},
getRegistrationDropDownStatus: function () {
return registrationDropDownStatus;
},
showRegistrationDropDown: function () {
console.log("showing registration drop down");
registrationDropDownStatus = true;
loginDropDownStatus = false;
console.log("registrationDropDownStatus" + registrationDropDownStatus);
},
hideRegistrationDropDown: function () {
console.log("hiding registration drop down");
registrationDropDownStatus = false;
console.log("registrationDropDownStatus" + registrationDropDownStatus);
}
};
}) .controller("LoginDropDownController", function ($scope, dropDownService) {
$scope.loginDropDownStatus = dropDownService.getLoginDropDownStatus();
$scope.$watchCollection('loginDropDownStatus', function(newValue, oldValue) {
console.log("watcher is working");
console.log("value is " + newValue + oldValue);
console.log("LOGIN new value is " + newValue);
$scope.loginDropDownStatus = newValue;
});
$scope.toggleDropDown = function () {
if ( $scope.loginDropDownStatus == false ) {
dropDownService.showLoginDropDown();
dropDownService.hideRegistrationDropDown();
$scope.loginDropDownStatus = true;
} else if ( $scope.loginDropDownStatus == true ) {
dropDownService.hideLoginDropDown();
$scope.loginDropDownStatus = false;
}
};
})
.controller("RegistrationDropDownController", function ($scope, dropDownService) {
$scope.registrationDropDownStatus = dropDownService.getRegistrationDropDownStatus();
$scope.$watch('registrationDropDownStatus', function(newValue, oldValue) {
console.log("watcher is working");
console.log("value is " + newValue + oldValue);
console.log("new value is " + newValue);
$scope.registrationDropDownStatus = newValue;
});
$scope.toggleDropDown = function () {
if ( $scope.registrationDropDownStatus == false ) {
dropDownService.showRegistrationDropDown();
dropDownService.hideLoginDropDown();
$scope.registrationDropDownStatus = true;
} else if ( $scope.registrationDropDownStatus == true ) {
dropDownService.hideRegistrationDropDown();
$scope.registrationDropDownStatus = false;
}
};
})
Edit:
Here is probably the shortest option:
angular.module("ftApp", [])
.controller("ctrl", function ($scope) {
$scope.toggle = function(menu){
$scope.active = $scope.active === menu ? null : menu;
}
})
FIDDLE
One controller, no service.
Previous Answer:
I think you have quite a bit of code to get something very simple done. Here is my solution:
angular.module("ftApp", [])
.service('dropDownService', function () {
this.active = null;
this.toggle = function(menu){
this.active = this.active === menu ? null : menu;
}
})
.controller("LoginDropDownController", function ($scope, dropDownService) {
$scope.status = dropDownService;
$scope.toggleDropDown = function () {
dropDownService.toggle("login");
};
})
.controller("RegistrationDropDownController", function ($scope, dropDownService) {
$scope.status = dropDownService;
$scope.toggleDropDown = function () {
dropDownService.toggle("reg");
};
})
FIDDLE
You can make it even shorter by only using one controller. You wouldn't even need the service then.
You are overcomplicating things. All you need your service to hold is a property indicating which dorpdown should be active.
Then you can change that property's value from the controller and check the value in the view to determine if a dropdown should be shown or hidden.
Something like this:
<!-- In the VIEW -->
<li ng-controller="XyzController">
<a ng-click="toggleDropdown()">Xyz</a>
<div ng-show="isActive()">Dropdown</div>
</li>
/* In the SERVICE */
.factory('DropdownService', function () {
return {
activeDropDown: null
};
})
/* In the CONTROLLER */
controller("XyzDropdownController", function ($scope, DropdownService) {
var dropdownName = 'xyz';
var dds = DropdownService;
$scope.isActive = function () {
return dropdownName === dds.activeDropdown;
};
$scope.toggleDropdown = function () {
dds.activeDropdown = (dds.activeDropdown === dropdownName) ?
null :
dropdownName;
};
})
See, also, this short demo.
Based on what exactly you are doing, there might be other approaches possible/preferrable:
E.g. you could use just on controller to control all dropdowns
or you could use two instances of the same controller to control each dropdown.
See my updated fiddle. I simplified the code and removed the service. Because you just used two variables to control visibility, you don't need a service nor $watch. You need to keep variables in the $rootScope, otherwise changes in a controller is not visible to another controller due to isolated scopes.
angular.module("ftApp", [])
.controller("LoginDropDownController", function ($scope, $rootScope) {
$rootScope.loginDropDownStatus = false;
$scope.toggleDropDown = function () {
if ($rootScope.loginDropDownStatus == false) {
$rootScope.registrationDropDownStatus = false;
$rootScope.loginDropDownStatus = true;
} else if ($rootScope.loginDropDownStatus == true) {
$rootScope.loginDropDownStatus = false;
}
};
}).controller("RegistrationDropDownController", function ($scope, $rootScope) {
$rootScope.registrationDropDownStatus = false;
$scope.toggleDropDown = function () {
if ($rootScope.registrationDropDownStatus === false) {
$rootScope.loginDropDownStatus = false;
$rootScope.registrationDropDownStatus = true;
} else if ($scope.registrationDropDownStatus === true) {
$rootScope.registrationDropDownStatus = false;
}
};
})
This code can be simplified further. I'll leave that to you.

AngularJS local storage - initialize app retrieving local-stored data

I'm pretty new to angular and I'm trying to avoid losing items added on a simple cart application when the user refreshes the page.
I'm using angularLocalStorage (https://github.com/agrublev/angularLocalStorage) but don't know how to retrieve it back the content.
My lines:
var myApp = angular.module('ionicApp', ['ionic','angularLocalStorage']);
myApp.factory('prodottiData', function($http) {
return {
getFooOldSchool: function(callback) {
$http.get('http://192.168.1.128/hongkongapp/?json=get_recent_posts&post_type=product&custom_fields=all').success(callback);
}
}
});
myApp.factory('DataService', function() {
var myCart = new shoppingCart("AngularStore");
return {
cart : myCart
};
});
myApp.controller('MyController', function MyController ($scope, storage, $ionicSideMenuDelegate, prodottiData, DataService, $sce) {
$scope.toggleLeft = function() {
$ionicSideMenuDelegate.$getByHandle('mainMenu').toggleLeft();
};
$scope.toggleMySecondMenuLeft = function() {
$ionicSideMenuDelegate.$getByHandle('mySecondMenu').toggleLeft();
};
//adding menu data to the scope object
prodottiData.getFooOldSchool(function(data) {
$scope.menu = data;
});
//adding the cart to the scope object
$scope.cart = DataService.cart;
$scope.to_trusted = function(html_code) {
return $sce.trustAsHtml(html_code);
}
images = $scope.menu;
$scope.showloader = function(){
$scope.shownImage = this.post.thumbnail_images.full.url;
$scope.itemDesc = this.post.content;
$scope.itemPrice = this.post.custom_fields._price[0];
$scope.productName = this.post.title;
$scope.skuProdotto = this.post.id;
}
});
Now, if I check local storage on the console I can see something is really stored, but I miss the way to re-populate the cart at startup.
Any help would be great!
why not just using browser local storage ?
you can add it to your services.js as a new service and just used that.
var storeService = myAppServices.factory('storeService', function() {
var service =
{
setClientData:function(client_details)
{
window.localStorage.setItem( "client_data", JSON.stringify(client_details) );
client_data = client_details;
},
getClientData:function()
{
if (client_data == null)
{
client_data = JSON.parse(window.localStorage.getItem("client_data"));
}
return client_data;
}
}
var client_data = null;
return service;
});
From the documentation, to retrieve, it's storage.get('key')
So, to check after refresh:
if (storage.get('someKey')){
$scope.retrieved_value = storage.get('someKey');
}else{
// whatever
}
You can use localStorage instead windows.localStorage.
if(typeof(Storage)!=="undefined")
{
// Code for localStorage/sessionStorage.
var hello = "Hello World!!";
localStorage.setItem("hello",hello);
// get string
console.log(localStorage.getItem("hello")); // will return 'Hello World!!'
var me = {name:'abel',age:26,gender:'male'};
localStorage.setItem("user", JSON.stringify(me));
//fetch object
console.log(localStorage.getItem("user")); // will return {"name":"myname","age":99,"gender":"myGender"}
var objetos = JSON.parse(localStorage.getItem("user"));
console.log(objetos.name);
}
else
{
// Sorry! No Web Storage support..
}

Resources