Override back button to go to specific state using Angular UI Router - angularjs

I have an Angular app with various different states defined. I do not want the user to be able to go back to previous states using the back button, as they are part of a simulation and it disturbs the simulation flow.
Is there a way I can override the browser back button to take users to the starting state of the simulation?
angular.module('participant').config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('simulations', {
url: '/',
controller: 'SimulationIndexController',
templateUrl: '/angular_templates/simulations/index.html',
})
.state('simulation.content', {
// other routes
});
I want to go to the 'simulations' state when the user presses the back button on the browser but I am not sure how to accomplish this.

Use location: 'replace' when doing state transitions:
$state.go("simulation.content.state2",{location:'replace'});
By using the replace option, the individual simulation steps will not be stored in the browser history. When the user hits the back button, the user will be taken back to the beginning instead of the previous content.

You could add on top
.run(function($state) {
$(window).on('popstate', function(event) {
// This event fires on current history change
$state.go("simulations");
}, false);
})

Related

Running ui.router in modal and ui-view

So not certain if this is possible with ui.router, but thought I would add a nice little modal that launches and says welcome to the site, give the user a chance to enter some details and so forth.
Setup is that the base view have two ui-views (mainview and menuview), and I added formview in the modal. I can get it to work so that when the modal opens it loads formview
.state('form-welcome', {
views: {
formview:
{
templateUrl: "/modals/form-welcome",
},
},
//parent:"index"
})
Didn't actually think it would work that easy, but it did, the problem is that as soon as it has loaded, it resets mainview and menuview (and as it is a modal, that means the background goes grey).
I tried to make form-welcome a child of index (the initial view), however, that breaks the whole thing. (index looks as follows)
.state('index', {
url:"/int/index",
views: {
mainview: {
templateUrl: "/pages/index",
controller: "sketchMagController"
},
menuview: {templateUrl: "/pages/top-menu"},
},
})
I can reload all three views (mainview, menuview and formview), and other than a flickering screen its not to bad. But is there a way I can limit state, so that it only changes formview but leaves the other ones alone.
Reason is that I want to change formview through five different screens, and hate flickering pages:)
It seems to me like it should be possible, but I may have missunderstood how it works
UI-router is for changing the application state, not nesting views together. For that purpose you have angular#component.
var app = angular.module('app', []);
app.component('myModal', {
template: '<div ng-show="isShowing">Hello User</div>',
controller: 'modalCtrl'
});
app.controller('modalCtrl', ['$scope', 'modalService', function($scope, modalService) {
//Save showing state in a service (default to false) so you don't popup your modal everytime user visit homepage
$scope.isShowing = modalService.getShowStatus();
$scope.pressButton = function() {
$scope.isShowing = false;
modalService.setShowStatus(false);
}
});
Then using ui-router, declare your index state, with its template as follow
<-- INDEX.HTML -->
<my-modal></my-modal>
<div ui-view='mainview'></div>
<div ui-view='menuview'></div>
The power of ui-router is the ability to replace the ui-views by different template each different state
stateIndex: abstract
stateIndex.stateA: mainview: /home.html
stateIndex.stateB: mainview: /information.html
So ask yourself, will menuview gonna change to different templates in future states? If not, make it a component.
I would try a different approach, and not having this "welcome" modal part of UI router. It doesn't sounds it should be a "state" in an app, where you can navigate to etc.
I would just pop up this welcome modal after your app finished to bootstrap (e.g. in your run() method or after w/e logic you have to start your app), based on your business logic (e.g. show it only one time).
Why not try $uibModal, part of ui-bootstrap? https://angular-ui.github.io/bootstrap/
It sounds like its everything you need, It pretty much has its own state (includes its own view and controller) so piping data in is easy. You can even pass on data captured within the modal with a simple result.then(function(){} . We use it at work and it doesn't reload the state its on.
You'll probably just want to have it be a function that runs automatically in your controller. If you want to limit how often it pops up you can even have some logic for determining when it pops up in the resolve, pass it to your controller and open the modal based on the resolve.
I think the best way to accomplish what you want is to listen on state changed event and fire the modal. The idea is you fire the welcome modal when the first view is opened and then you set variable in localStorage or some service (depends what you want to achive). Here is an example
$rootScope.$on(['$stateChangeSuccess', function(){
var welcomeModalFired = localStorage.get('welcomeModalFired');
if(!welcomeModalFired) {
//fire modal
localStorage.set('welcomeModalFired', true);
}
})

How to distinguish link click vs transitionTo

I am trying to remember the scroll position of a page using that code:
$rootScope.$on('$locationChangeStart', function(angularEvent, nextRoute, currentRoute) {
$rootScope.scrollPosCache['dailyController'] = $window.pageYOffset;
});
When the page infinitely-scrolls I am using ui-router's transitionTo to modify the current url in the browser history like that:
$state.transitionTo('daily', { page: pageIndex }, { notify: false, location: 'replace' });
the transition changes the search string to e.g.: http://example.com/?page=1/2/3/4/etc
so, the $locationChangeStart code is running both when I make the transition and when the user leaves the list page to follow a link to a detail page.
How do I know the location change has come from a transition and not from a click through? (I only want to remember the scroll position when the page is left)
The solution is to use $stateChangeStart and not $locationChangeStart.

Ionic / UI router: Navigating to the same state with reload: true breaks the history

I'm trying to make my ionic app transition within the same state but with different URL. I have a the following state:
.state('app.profile.id', {
url: '/profile/:id',
data : {
id: -1
},
views: {
'menuContent': {
templateUrl: 'templates/pages/profile.html',
controller: 'ProfileCtrl'
}
}
})
This is the profile page of a user. What I'm trying to do is to have a link from the profile page to other users profile pages. I'm making the transition with the following code:
$state.go('app.profile.id', {id: id}, {reload: true});
If I don't add the reload: true part, then the data on the page won't refresh since I'm not changing the state. What I understand from reload: true is that it forces transition between states.
However my problem is, the back button acts weird after the transition. The back transition doesn't happen at first few levels: It changes the id (which is noticeable after a manual refresh), and after one or two level, $ionicHistory.goBack() breaks completely.
I think this is a bug in UI router, any idea how to fix this ?
Thanks in advance.

Issue with $state.go() transitions, only on second click

I have a issue with ui-router which I can't quite put my finger on. I can click on a link (on the login page of my app) and navigate to a second page (one where the user can register). There is a cancel button on the second page which simply navigates the user back to the first page. All good so far.
If I then click on the same link again, the user appears to stay on the same page (i.e. the login page), however I have added a 'state change' handler to my app (it just logs the transition to the console) and can see that what actually happens is that the app seems to transition to the second page and then back again. The following won't make much sense other than to demonstrate the flow. You can see the state change start, end and the view load before it starts again and transitions back to 'authentication.login'
Start state change : {authentication.login} to {authentication.registration}
View content loading
End state change success : {authentication.login} to {authentication.registration}
View content loaded
Start state change : {authentication.registration} to {authentication.login}
View content loading
End state change success : {authentication.registration} to {authentication.login}
View content loaded
NOTE: both pages are siblings of the same abstract node.
Any ideas why this might be happening?
NOTE: state config is as follows
.state("authentication", {
url : "/auth",
abstract : "true",
templateUrl : "app/main/common/html/templates/simple-template.html",
})
.state("authentication.login", {
url: "/login",
views: {
"main": {
templateUrl : "app/main/authentication/html/login.html",
controller: "LoginController"
}
}
})
.state("authentication.registration", {
url: "/registration",
views: {
"main": {
templateUrl : "app/main/authentication/html/registration.html",
controller: "RegistrationController"
}
}
})
do you have any
rootScope.$on('$stateChangeStart'
logic in your app that is causing the state transition.
Usually logic will be put in here to kick a user back to the login page if the user is not auth'd
a wild guess but the best guess i can make given the information provided.
thanks
another idea, if you remove the state change handler does everything work ok?

Ui-router: how to block url navigation but accept state navigation

I try to do some functionality that accepts a state navigation (using $state.go(otherState)), refreshing the associated url in the adress/url bar but it blocks (or redirects to not allowed page) if user directly puts this url in the adress/url bar.
Could it be done by ui-router rules or something inside ui-router module?
I put the example code:
$stateProvider.state("main", {
url: "/index.html",
templateUrl: "main.html"
}).state("notAccessibleScreenByBar", {
url: "/private/example.html",
templateUrl: "example.html"
});
From main view (index.html), the next angular code will be executed:
$state.go("notAccessibleScreenByBar");
This action changes the view, loading example.html and refreshing the url bar to /private/example.html.
If user puts /private/example.html in the adress/url bar, ui-router must block this request (or redirect to not-allowed page).
What you are trying to do seems very similar to any web authentication standard, but, if you don't want that, you could use $locationChangeSuccess (docs here)
A basic example inspired by this sample:
$rootScope.$on('$locationChangeSuccess', function(e, newUrl, oldUrl) {
// Prevent $urlRouter's default handler from firing
e.preventDefault();
if(isThisTransitionValid(newUrl, oldUrl)) {
// Ok, let's go
$urlRouter.sync();
});
});

Resources