Check whether ui-state has forward ui-state - angularjs

I think it is a simple question but could not find on google. I am using angular 1.5 and ui-router 1.5. How do I check if current ui-view state had next ui-state? (This is required because I want to check whether current ui-view is loaded when back button is pressed or through direct link)
Case 1
--------
view1 -> view2 -> view3 // now I have pressed back button
view1 -> view2 ? // Here view2 actually had next state that was view3
Case 2
--------
view1 -> view4 -> view2 //Here view2 do not have next ui-state
I have tried using $ionicHistory.forwardView() but it always return null. Not sure why? Any help is greatly appreciated.

After googling for 4 hours I have come up with some custom solution. would like to share. Proper error handling has to be done yet.
// just listen to statechange success event
app.run(['$rootScope', function($rootScope){
$rootScope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState) {
// if fromState.name is empty, its refresh || first landing
var history = $window.localStorage.getItem('history');
var back = false;
if (history !== null) {
history = JSON.parse(history);
if (toState.name === history.from && fromState.name === history.to) {
back = true;
}
}
$window.localStorage.setItem('history', JSON.stringify({
to: toState.name,
from: fromState.name,
isBack: back
}));
});
});
Note I have used persistent storage technique. One must handle refresh condition also.

Related

Clear $stateParams for specific view

In my Ionic app I have defined a parameter as null for default in my home state. So when this parameter is defined as true, some actions are performed, in this case a modal appears.
Problem is, when I switch to another state, and go back, this param keeps it's value as true.
How can I clear a specific param for a specific view?
This is the piece of code:
if ($stateParams.watchTutorial === true) {
$rootScope.$broadcast('startTutorial');
$stateParams.watchTutorial = null;
}
Tried to set it to null but didn't work.
To resume, navigating:
Home -> View 1 -> Tap button -> Home (Param: {watchTutorial: true}). Great, goes home and modal appears. Keep navigating in home..
Home -> View 2 -> Go back Home (No params specificed), watchTutorial = true anyways and modal screen appears. And it shouldn't.
Any ideas? Thank you.
I would listen for the $stateChangeSuccess event and then react on the available information.
$rootScope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState, fromParams){
if(fromState == "[the_state_of_view_2]"){
toParams.watchTutorial = false;
//or toParams = {}
}else{
if ($stateParams.watchTutorial === true) {
$rootScope.$broadcast('startTutorial');
$stateParams.watchTutorial = null;
}
})
Ok, I solved it thanks to your answer #Andre Kreinbring.
This made the job:
$rootScope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState, fromParams) {
if(toState.name == 'app.home') {
if(toParams.watchTutorial === true) {
$rootScope.$broadcast('startTutorial');
toParams.watchTutorial = false;
}
}
});

Angular ui-router struggle

Well, I have this project, and ui-router is giving me hard times. I made a quick Plunker demo: http://plnkr.co/edit/imEErAtOdEfaMMjMXQfD?p=preview
So basically I have a main view, index.html, into which other top-level views get injected, like this operations.index.html. The pain in my brain starts when there are multiple named views in a top-level view, operations.detail.html and operations.list.html are injected into operations.index.html, which is in turn injected into index.html.
Basically, what I'm trying to achieve is the following behaviour:
When a user clicks Operations item in the navbar, a page with empty (new) operation is shown. The URL is /operations.
When they select an item in a list, the fields are updated with some data (the data is requested via a service, but for simplicity let's assume it's right there in the controller). The URL is /operations/:id.
If they decide that they want to create a new item, while editing a current one, they click New operation button on top of the list, the URL changes from /operations/:id to /operations.
No matter new or old item, the item Operations in the navbar stays active.
If the user is editing an item, it should be highlighted as active in the list, if they create a new item — New operation button should be highlighted, accordingly.
Now, check out the weird behaviour: go to Operations and then click Operations navbar item again. Everything disappears. The same happens if I do Operations -> select Operation 1 -> select New operation.
Besides, please, check out the part where I try to get the id parameter:
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
if (toParams) {
if (toParams.id) {
for (var i = 0; i < vm.operations.length; i++) {
if (vm.operations[i].id == toParams.id) {
vm.operation = vm.operations[i];
break;
}
}
}
}
});
I am no expert, but it seems too long and complex to be true, especially for such a simple task as getting a request parameter. If I try to check on state change $stateParams the object is empty, hence this workaround. If I try to tamper with states in app.js, things change slightly, but there are always bugs like Operations navbar item losing its active state or other weird stuff.
I know that asking such general questions is uncommon in SO, but I really can't grasp the concept of the ui-router, and I can feel that I'm doing things wrong here, and I would really appreciate any help in pointing me in the right direction of how to properly use ui-router for my purposes. Cheers.
There is the updated plunker
I just used technique from this Q & A: Redirect a state to default substate with UI-Router in AngularJS
I added the redirectTo setting (could be on any state)
.state('operations', {
url: '/operations',
templateUrl: 'operations.index.html',
controller: 'operationsController as op',
// here is redirect
redirectTo: 'operations.new',
})
and added this redirector:
app.run(['$rootScope', '$state', function($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function(evt, to, params) {
if (to.redirectTo) {
evt.preventDefault();
$state.go(to.redirectTo, params)
}
});
}]);
and also, I removed the redirection currently sitting in the operationsController.js:
angular.module('uiRouterApp')
.controller('operationsController', function($state, $stateParams, $rootScope) {
var vm = this;
//if ($state.current.name === 'operations') $state.go('operations.new');
And that all above is just to keep the new state - without url. Because the solution would become much more easier, if we would just introduce url: '/new':
.state('operations', {
url: '/operations',
..
})
.state('operations.new', {
//url: '',
url: '/new',
Check the plunker here
So, this way we gave life to our routing. Now is time to make the detail working. To make it happen we would need more - there is another updated plunker
Firstly, we will get brand new controller to both child state views:
.state('operations.new', {
url: '',
views: {
'detail': {
templateUrl: 'operations.detail.html',
controller: 'detailCtrl as dc', // here new controller
...
})
.state('operations.detail', {
url: '/:id',
views: {
'detail': {
templateUrl: 'operations.detail.html',
controller: 'detailCtrl as dc', // here new controller
...
It could be same controller for both, because we will keep decision new or existing on the content of the $stateParams.id. This would be its implementation:
.controller('detailCtrl', function($scope, $state, $stateParams) {
var op = $scope.op;
op.operation = {id:op.operations.length + 1};
if ($stateParams.id) {
for (var i = 0; i < op.operations.length; i++) {
if (op.operations[i].id == $stateParams.id) {
op.operation = op.operations[i];
break;
}
}
}
})
We keep the original approach, and set the op.operation just if $stateParams.id is selected. If not, we create new item, with id properly incremented.
Now we just adjust parent controller, to not save existing, just new:
.controller('operationsController', function($state, $stateParams, $rootScope) {
var vm = this;
//if ($state.current.name === 'operations') $state.go('operations.new');
vm.operation = {};
/*$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams) {
if (toParams) {
if (toParams.id) {
for (var i = 0; i < vm.operations.length; i++) {
if (vm.operations[i].id == toParams.id) {
vm.operation = vm.operations[i];
break;
}
}
}
}
});*/
vm.save = function() {
if(vm.operations.indexOf(vm.operation) >= 0){
return;
}
if (vm.operation.name
&& vm.operation.description
&& vm.operation.quantity) {
vm.operations.push(vm.operation);
vm.operation = {id: vm.operations.length + 1};
}
};
Check the complete version here

How to prevent default browser back button functionality in AngularJS?

I'm making a mobile app and it has a back button in the navbar of the app. The button is used for navigating one level up in the application. Not all levels of the applications are written in the url, some of them are skipped so i cannot rely on the browser back button because not everything is written in the history. So what i want to do is to prevent the default event that happens when you click the back button (even the url change because i have a function that's manually rewriting the url when you click on some parts of the app) and i want the browser's back button to be tied to my custom function.
I'm thinking of adding a false state in the history with history.pushstate so when i'm pressing back i'll be going to the false state, but unfortunately you can't have two states with a same name.
Do you guys know any solution for this?
Thanks
We want to prevent that users can use the back button when logged off or when the token is not valid anymore.
// Prevent to use the back button.
$scope.$on('$locationChangeStart', function(event) {
if (!$scope.isAuthenticated) {
event.preventDefault();
}
});
Where the if is you can implement your url rewrite function.
Hope this helps
try this:
$rootScope.$on("$routeChangeStart", function(event, next, current) {
if(current && current.params === "example") {
$location.path("/defaultPage");
}
});
The following code should do the trick:
var allowNav = false;
var checkNav = false;
$rootScope.$on('$stateChangeSuccess', function (event, toState, toStateParams, fromState, fromStateParams) {
allowNav = checkNav;
checkNav = true;
});
$rootScope.$on('$locationChangeStart', function (event, next, current) {
// Prevent the browser default action (Going back)
if (checkNav) {
if (!allowNav) {
event.preventDefault();
}
else {
allowNav = false;
}
}
});

angularjs template cache lazy loading with permissions

I am not using Angular views or router. simply ng-include . I was able to get lazy loading with ng-include (initializing the templates with dummy div) and setting and updating it after getting template from server and updating templateCache.
The problem was most of the time templates where not updated.
Also i want allow user to see the template if the user has permissions. Permissions will be checked on server.
What is the best way to achieve this ?
I found the following solution from the following link
// CacheTemplate buster.
//
// Clears on successful route change (see later) the previous page template
// from the cache, like that the application DOES SEND another request to
// the server which in turn has an opportunity to chech access-rights of
// the user/session
$scope.tC = $templateCache;
$rootScope.$on('$routeChangeSuccess', function(event, next, current) {
switch( $location.path() ) {
case '/login' :
$window.document.title = "LOGIN";
$scope.loggedIn = false;
break;
case '/home' :
$window.document.title = "HOME";
$scope.loggedIn = true;
break;
default:
$window.document.title = "";
$scope.loggedIn = false;
break;
};
// clear cache to trigger reloading
// (server decides if has permission or not)
if (current && current.$route) {
if ($scope.tC.get(current.$route.templateUrl)) {
$scope.tC.remove(current.$route.templateUrl);
};
};
});
...

How to cancel routeChange or browser reload when form is incomplete?

There are scenarios like:
Browser reload,
Closing tab
closing browser
Route change (e.g. clicking on links)
Browsers back button was clicked. or history.go(-1). 3 fingers swipe on Macbooks.
that we would want to prevent if the user has filled some sort of form or is in middle of the writing.
I have written this code which works fine but its absolutely not useful if I cant implement it on several textfields. Currently it only check if we are at #/write url. It doesnt check any inputs.
Whats the angular way to deal with this? Whats the best way to check the target textfield. Is a directive the solution?
something like:
<input type="text" warningOnLeave ng-model="title"/>
or
<form warningOnLeave name="myForm">...</form>
$rootScope.$on('$locationChangeStart', function(event, current, previous){
console.log(current);
console.log(previous);
// Prevent route change behaviour
if(previous == 'http://localhost/#/write' && current != previous){
var answer = confirm ("You have not saved your text yet. Are you sure you want to leave?");
if (!answer)
event.preventDefault();
}
});
/**
Prevent browser behaviour
*/
window.onbeforeunload = function (e) {
if(document.URL == 'http://localhost/#/write'){
e = e || window.event;
// For IE and Firefox prior to version 4
if (e) {
e.returnValue = 'You have not saved your text yet.';
}
// For Safari
return 'You have not saved your text yet.';
}
else
return;
}
Forms in Angular have the $dirty/$pristine properties that mark if the user has/hasn't interacted with the form controlls, and the accompanying method $setPristine(). I would base the desired functionality on this feature. Consider:
<form name="theForm" ng-controller="TheCtrl" ...>
This puts the form in the scope of the controller, under the given name. Then something like:
controller("TheCtrl", function($scope, $rootScope) {
$rootScope.$on('$locationChangeStart', function(event, current, previous) {
if( $scope.theForm.$dirty ) {
// here goes the warning logic
}
});
});
Do not forget to call $scope.theForm.$setPristine() where appropriate (i.e. after submitted or cleared).
For the window unload case, you will have to watch the $dirty flag. So in the previous controller:
$scope.$watch("theForm.$dirty", function(newval) {
window.myGlobalDirtyFlag = newval;
});
You have to do this because the window.onbeforeunload event does not have access to the scope of the form. Then, in the global section of your app:
window.onbeforeunload = function (e) {
if( window.myGlobalDirtyFlag === true ) {
// warning logic here
}
};
Again, you may want to clear the global dirty flag when the scope is destroyed, so in the controller:
$scope.$on("$destroy", function() {
window.myGlobalDirtyFlag = false;
});

Resources