disable'$scope.$on('$locationChangeStart' )' if user click cancel? - angularjs

I have problem when the user click cancel and it is true it will redirtect i it will trigger $scope.$on('$locationChangeStart') becouse of that I will have 'You have unsaved changes. If you continue your changes will be lost.' appear twice. just want to ask how can I disable'$scope.$on('$locationChangeStart' )' if user click cancel
code
$scope.cancel = function() {
var validator = npFormValidator($scope);
if ($scope.npForm.$dirty) {
var answer = confirm('You have unsaved changes. If you continue your changes will be lost.');
if (answer) {
$location.path('/home');
} else {
validator.addWatches();
event.preventDefault();
}
}
};
$scope.$on('$locationChangeStart', function (event) {
var validator = npFormValidator($scope);
if (validator.valid() && $scope.model.clickedSave) {
window.onbeforeunload = undefined;
} else {
if ($scope.npForm.$dirty) {
var answer = confirm('You have unsaved changes. If you continue your changes will be lost.');
if (!answer) {
validator.addWatches();
event.preventDefault();
}
}
}
})
;

You can set a flag and check it in your event receiver:
var canceled = false;
$scope.cancel = function() {
var validator = npFormValidator($scope);
if ($scope.npForm.$dirty) {
var answer = confirm('You have unsaved changes. If you continue your changes will be lost.');
if (answer) {
canceled = true;
$location.path('/home');
} else {
canceled = false;
validator.addWatches();
event.preventDefault();
}
}
};
$scope.$on('$locationChangeStart', function (event) {
if (canceled) return;
var validator = npFormValidator($scope);
if (validator.valid() && $scope.model.clickedSave) {
window.onbeforeunload = undefined;
} else {
if ($scope.npForm.$dirty) {
var answer = confirm('You have unsaved changes. If you continue your changes will be lost.');
if (!answer) {
validator.addWatches();
event.preventDefault();
}
}
}
});

Related

Execute an Angular function only if the current tab is active in the browser

I have an angular js function which should be called for every 2 seconds only when the current tab is open in the browser. Is there any way to check whether the current page is active in the browser.
$scope.callAtInterval = function() {
var url = "http://127.0.0.1/test";
$http.get(url).success( function(response) {
$scope.initial = response;
},
function errorCallback(response) {
$scope.result=response;
});
}
$interval( function(){ $scope.callAtInterval(); }, 5000);
}
I think below piece of code is self-explanatory
import { HostListener} from "#angular/core";
#HostListener("window:visibilitychange", ["$event"])
onVisibilityChange($event) {
const isVisible = $event.target.visibilityState === 'visible';
this.logger.info(isVisible);
if (isVisible) {
// tab is visible
} else {
// tab is not-visible
}
}
You would use the focus and blur events of the window:
$(window).on("blur focus", function(e) {
var prevType = $(this).data("prevType");
if (prevType != e.type) { // reduce double fire issues
switch (e.type) {
case "blur":
// cancel your interval function
break;
case "focus":
// emit your interval function
break;
}
}
$(this).data("prevType", e.type);
})

Change back button (android) behaviour when Popup is active, ionic

I'm trying to achieve this:
Scenario 1
User press back.
Popup appears asking if the user want to exit *
User press back.
App exits*
and
Scenario 2
User press back.
Popup appears asking if the user want to exit *
User press cancel
Popup closes
User press back.
Popup appears asking if the user want to exit *
User press back
App exits.
I tried using registerBackButtonAction and onHardwareBackButtoncombination but I can not get the exit popup to shows the second time (second scenario), it just exit.
This is the code I have now:
var exitPopupControl = function(event) {
//if I press back again, just go out
$ionicPlatform.onHardwareBackButton(function(event2){
navigator.app.exitApp();
});
if($state.current.name === "app.startseite"){
$ionicPopup.confirm({
title: 'Exit',
template: 'Do you want to exit? <br /><small>Press Back again to exit.</small>'
}).then(function(res) {
if(res) {
navigator.app.exitApp();
$rootScope.exitPopupShowed = false;
} else {
console.log('I choose not to left the app');
$ionicPlatform.registerBackButtonAction(exitPopupControl, 100);
}
});
}
else {
window.history.back();
}
};
$ionicPlatform.registerBackButtonAction(exitPopupControl, 100);
i did handel with following wa - just change confirmation popup code,
var exitPopupControl = function(event) {
//if I press back again, just go out
$ionicPlatform.onHardwareBackButton(function(event2){
navigator.app.exitApp();
});
if($state.current.name === "app.startseite"){
$ionicPopup.confirm({
title: 'Exit',
template: 'Do you want to exit? <br /><small>Press Back again to exit.</small>'
}).then(function(res) {
if(res) {
$rootScope.exitPopupShowed = false;
navigator.app.exitApp();
} else {
return;
}
});
}
else {
window.history.back();
}
};
$ionicPlatform.registerBackButtonAction(exitPopupControl, 100);
OR you can try something following:
document.addEventListener("deviceready", function() {
document.addEventListener("backbutton", function(e) {
$ionicPopup.confirm({
title: 'Exit',
template: 'Do you want to exit? <br /><small>Press Back again to exit.</small>'
}).then(function(res) {
if(res) {
$rootScope.exitPopupShowed = false;
navigator.app.exitApp();
} else {
return;
}
});
}, false);
}, false);
OR you can try something with toast, i prefer to use this one,
var countTimerForCloseApp = false;
$ionicPlatform.registerBackButtonAction(function(e) {
e.preventDefault();
function showConfirm() {
if (countTimerForCloseApp) {
ionic.Platform.exitApp();
} else {
countTimerForCloseApp = true;
showToastMsg($cordovaToast, 'Press again to exit.');
$timeout(function() {
countTimerForCloseApp = false;
}, 2000);
}
};
// Is there a page to go back to?
if ($ionicHistory.backView()) {
// Go back in history
$ionicHistory.backView().go();
} else {
// This is the last page: Show confirmation popup
showConfirm();
}
return false;
}, 101);
Thank you

Angular infinite digest loop with ui-router

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

$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.

Detect when user leaves window

I want to detect when user exit window with AngularJS. My app doesn't use ngRoute. So, I can't use $destroy or $locationChangeStart.
Can you help me ?
You can use $locationChangeStart:
$scope.$on('$locationChangeStart', function( event ) {
var answer = confirm("Are you sure you want to leave this page?")
if (!answer) {
event.preventDefault();
}
});
Or use onbeforeunload:
window.onbeforeunload = function (event) {
var message = 'Are you sure you want to leave this page?';
if (typeof event == 'undefined') {
event = window.event;
}
if (event) {
event.returnValue = message;
}
return message;
}

Resources