Angular navigate to the previous state on page reload - angularjs

I wanted to know if there is a way in Angular to refresh a single page application and properly go back to the same state as before? I am using ui-router (version 0.2.15) for defining different states.
Currently, I am storing the state name using ngStorage and have placed $state.go(last_stored_state_name) in the controller of the state that loads first. This way I get to the state that i was on before refresh but the problem is i can see the first state's view just for a second before $state.go(last_stored_state_name) runs.
Can anyone please tell me how I can bypass the first state's view directly to the desired state?

Try this:
$state.go($state.current, {}, {reload: true});

Create a controller and add it with ng-controller at any top level element like on <body> tag so that its scope is available throughout the application and in that controller, do like this:
// Event listener for state change.
$scope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
if (last_stored_state_name && isPageRefresh) {
// prevent execution of current state and navigate to last saved state
event.preventDefault();
$state.go(last_stored_state_name);
return;
}
});
So you don't have to do $state.go(last_stored_state_name); in your controller. This way, you will not see the first state for a second.
You can figure out a variable isPageRefresh with any type of checks like checking if fromState is null or setting a local storage variable or setting a global javascript variable .

Related

Angular UI Router reload data on state change

I am using Angular ui router (https://github.com/angular-ui/ui-router) and coming across an unexpected behavior.
I have a recordView and recordEdit states set up and using sref/$state.transitionTo to switch between them.
When in recordEdit state, update is being done via ajax and upon success, I am programatically chaning the state into recordView.
Problem is that the recordView state does not show the update data and will only show it if I refresh the page.
I tried using the reload option but with no success.
App.saveRecord($scope.formData).then(function (response) {
$state.transitionTo('recordView', $stateParams, {
reload: true
});
}
I also tried using $state.go(...) but getting the same result.
I also tried using the cache = false on the state property but with no success.
.state('recordView', {
url: '/folder/:hash/:recordId',
resolve: {},
cache: false,
templateUrl: function (urlattr) {
//return the url
}
})
I then tried explicitly changing the window.location to the view url but it will still show the previous data.
The only time it will actually work is if I call location.reload(); after changing the state but this is not good for the user experience.
Does anyone know why this is happening? all the posts I've seen about it mention setting the reload to true or the cache to false.
UPDATE
Per the comments I understand that the problem is that I am using ng-init and server side rendering to inject the data from php to angular and when reloading the view, this data is not reloading.
My questions then are:
Can I "inject" the edited data from the recordEdit state into the recordView state after the user edited the data?
Is there a way to simply force a reload of the page and ignore the caching? Basically simulate as if the route was hit for the first time.
Thanks.
Here is an idea.
On your routes file define a parent abstract state and initialize that with the data
.state('parent.state'
{
abstract: true,
data:{
init://server rendered data here
}
}
)
.state('child.state.view',
{
// your route definition here
}
)
.state('child.state.edit',
{
// your route definition here
}
)
then in your controllers inject the $state service in yout view controller use
//assuming the data variable holds the needed data. change that to what ever
$scope.data = $state.current.data.init; //if using $scope
this.state = $state.current.data.init; //if controller As syntax
Remove the ng-init sentence as the data will be initialized on the controller. Then on the Update function in the edit view make sure you update the $state.current.data.init with the new data. This way next time you go there the controller will pick the data from the $state object and get the updated one.
Answer:
$templateCache.remove('http://urlthemplate');

Fire an event when user moves out of speciifc route in AngularJS

I am using AngularJS 1.3. Assume I have created several routes in my application. But when user hits a specifc route/url & then tries to move to another route/url, I want to fire some event. I do not want to fire this event on every URL change.
So only when user comes out of this url http://localhost:9000/data/55677c/edit, I want to fire one function available in XYZ controller.
Here is my scenario:
I have a page which looks like this:
<div class="well">
<button id='edit-btn' type="button" ng-click='saveContent()'>
<div ng-include="'components/grid/comOne.html'"></div>
</div>
components/grid/comOne.html page contains one grid and it has its own controller which takes care of data management of the grid.
This grid is shown in two pages. One in editable mode and one is non-ediatble mode. While user is in editable mode and try to move out of the page without saving the info, I need to fire an event in order to discard ant changes user has made to the grid data.
Please suggest
If the listening controller is a parent controller you could $emit the event.
Or you could have a common service like this:
angular.module('x').factory('CommonLogic', function(){
var pageChangeListeners = [];
return {
listenToPageChange: listenToPageChange
};
function listenToPageChange(callback){
pageChangeListeners.push(callback);
}
function pageChanged(){
for(var i = 0; i < pageChangeListeners.length; i++){
pageChangeListeners[i]();
}
}
});
then when leaving that url (track that via $routeChangeStart) you can call: commonLogic.pageChanged()
In the controller where you want to take action just:
commonLogic.listenToPageChange(function(){..}).
Obviously this should be improved to avoid duplicate registration of the listener ... etc.
I hope I'm not overcomplicating this. Could you describe your use case in more detail ?
I guess you want to use $routeChangeStart:
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
});
You can put this in the scope of your current controller which might be edit as your url says.
From the docs:
$routeChangeStart
Broadcasted before a route change. At this point the route services starts resolving all of the dependencies needed for the route change to occur. Typically this involves fetching the view template as well as any dependencies defined in resolve route property. Once all of the dependencies are resolved $routeChangeSuccess is fired.
The route change (and the $location change that triggered it) can be prevented by calling preventDefault method of the event. See $rootScope.Scope for more details about event object.
Type:broadcast
Target:root scope

Prevent a stateChange with angular ui router without using $rootScope

My user can leave a state but before I want to show a modal dialog "Do you want to save?"
ONLY if the user data is dirty that means changed.
What I do NOT want is to stick a isDirty property in my EditController to the $rootScope go to the stateChangeStart event and check there for isDirty and then show/not the save dialog.
Prevent global variables says every javascript beginner book...
1.) What is then the pro way to prevent a state change without hacking the $rootscope?.
2.) Are there any helper libraries for ui-router which enhance the ui-router offering function hooks inside the controller to encapsulate the ui logic?
(1) According to the docs under State Change Events
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){
event.preventDefault();
// transitionTo() promise will be rejected with
// a 'transition prevented' error
})
You could change $rootScope to $scope wherever appropriate and works.
Under Attach Custom Data to State Objects, you can pass on custom data.
(2) I'm not sure what you're asking but factories/services/providers would really help.
Using $transitions.onStart (angular-ui-router 1.0.0-rc) you can return a boolean. If false the transition will be cancelled.
$transitions.onStart({}, function (trans) {
var answer = confirm("Want to leave this page?")
if (!answer) {
return false;
}
});
Here is the documentation: https://ui-router.github.io/ng1/docs/latest/modules/transition.html#hookresult
Though at the time of writing it is not a part of the stable release, the 1.0 release of the UI-router will use the return value of onEnter/onExit to prevent navigation.
See GitHub issue 2219

Receive an event on direct page visit from address bar, Angular JS - ui-router

guys I am working on an angular js app which uses ui-router for deep linking and routing. I want to set up a variable on the $rootScope every time a state is visited/changed. I am able to accomplish that on subsequent state transitions via listening to $stateChangeSuccess but since the app is deep linked, I want to set the same variable when a state is directly hit from the address bar.
When I hit a specific URL from address bar e.g. Home/Summary/Change, $stateChangeSuccess doesn't fire hence I am not able to set the value.
Any suggestions on how I can listen to EACH state visit on my $rootScope.
You may use combination of $locationChangeStart and $route.current:
$rootScope.$on('$locationChangeStart', function (event, next, current) {
if(!$route.current) {
$rootScope.myVar = ...
}
});
I found a work around for this. I was not able to get $stateChangeSuccess on the first visit to a page so on initialization i called the function explicitly.
example code
var handle = function(){
//do something ...
}
$scope.$on('$stateChangeSuccess', handle); // call handle on each state change
handle(); // will only be called on initialization
might not be the cleanest way but it works
In the meantime they have implemented a $urlRouterProvider where you can add a when:
app.config(function($urlRouterProvider){
// when there is an empty route, redirect to /index
$urlRouterProvider.when('', '/index');
// You can also use regex for the match parameter
$urlRouterProvider.when(/aspx/i, '/index');
})
So if you like to have Home/Summary/Change matching a state, you would write something like $urlRouterProvider.when('Home/Summary/Change','myhomestate') then the $stateChangeSuccess should fire correct.
For more details about that provider look at https://github.com/angular-ui/ui-router/wiki/URL-Routing#urlrouterprovider

AngularJS Paging with $location.path but no ngView reload

My single page application loads a home page and I want to display a series of ideas. Each of the ideas is displayed in an animated flash container, with animations displayed to cycle between the ideas.
Ideas are loaded using $http:
$scope.flash = new FlashInterface scope:$scope,location:$location
$http.get("/competition.json")
.success (data) ->
$scope.flash._init data
However, to benefit from history navigation and UX I wish to update the address bar to display the correct url for each idea using $location:
$location.path "/i/#{idea.code}"
$scope.$apply()
I am calling $apply here because this event comes from outwith the AngularJS context ie Flash. I would like for the current controller/view to remain and for the view to not reload. This is very bad because reloading the view results in the whole flash object being thrown away and the preloader cycle beginning again.
I've tried listening for $routeChangeStart to do a preventDefault:
$scope.$on "$routeChangeStart", (ev,next,current) ->
ev.preventDefault()
$scope.$on "$routeChangeSuccess", (ev,current) ->
ev.preventDefault()
but to no avail. The whole thing would be hunky dory if I could figure out a way of overriding the view reload when I change the $location.path.
I'm still very much feeling my way around AngularJS so I'd be glad of any pointers on how to structure the app to achieve my goal!
Instead of updating the path, just update query param with a page number.
set your route to ignore query param changes:
....
$routeProvider.when('/foo', {..., reloadOnSearch: false})
....
and in your app update $location with:
...
$location.search('page', pageNumber);
...
From this blog post:
by default all location changes go through the routing process, which
updates the angular view.
There’s a simple way to short-circuit this, however. Angular watches
for a location change (whether it’s accomplished through typing in the
location bar, clicking a link or setting the location through
$location.path()). When it senses this change, it broadcasts an
event, $locationChangeSuccess, and begins the routing process. What
we do is capture the event and reset the route to what it was
previously.
function MyCtrl($route, $scope) {
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function(event) {
$route.current = lastRoute;
});
}
My solution was to use the $routeChangeStart because that gives you the "next" and "last" routes, you can compare them without the need of an extra variable like on $locationChangeSuccess.
The benefit is being able to access the "params" property on both "next" and "last" routes like next.params.yourproperty when you are using the "/property/value" URL style and of course use $location.url or $location.path to change the URL instead of $location.search() that depends on "?property=value" URL style.
In my case I used it not only for that but also to prevent the route to change is the controller did not change:
$scope.$on('$routeChangeStart',function(e,next,last){
if(next.$$route.controller === last.$$route.controller){
e.preventDefault();
$route.current = last.$$route;
//do whatever you want in here!
}
});
Personally I feel like AngularJS should provide a way to control it, right now they assume that whenever you change the browser's location you want to change the route.
You should be loading $location via Dependency Injection and using the following:
$scope.apply(function () {
$location.path("yourPath");
}
Keep in mind that you should not use hashtags(#) while using $location.path. This is for compability for HTML5 mode.
The $locationChangeSuccess event is a bit of a brute force approach, but I found that checking the path allows us to avoid page reloads when the route path template is unchanged, but reloads the page when switching to a different route template:
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function (event) {
if (lastRoute.$$route.originalPath === $route.current.$$route.originalPath) {
$route.current = lastRoute;
}
});
Adding that code to a particular controller makes the reloading more intelligent.
Edit: While this makes it a bit easier, I ultimately didn't like the complexity of the code I was writing to keep friendly looking URL's. In the end, I just switched to a search parameter and angular handles it much better.
I needed to do this but after fussing around trying to get the $locationChange~ events to get it to work I learned that you can actually do this on the route using resolve.
$routeProvider.when(
'/page',
{
templateUrl : 'partial.html',
controller : 'PageCtrl',
resolve : {
load : ['$q', function($q) {
var defer = $q.defer();
if (/*you only changed the idea thingo*/)
//dont reload the view
defer.reject('');
//otherwise, load the view
else
defer.resolve();
return defer.promise;
}]
}
}
);
With AngularJS V1.7.1, $route adds support for the reloadOnUrl configuration option.
If route /foo/:id has reloadOnUrl = false set, then moving from /foo/id1 to /foo/id2 only broadcasts a $routeUpdate event, and does not reload the view and re-instantiate the controller.

Resources