Clear History and Reload Page on Login/Logout Using Ionic Framework - angularjs

I am new to mobile application development with Ionic. On login and logout I need to reload the page, in order to refresh the data, however, $state.go('mainPage') takes the user back to the view without reloading - the controller behind it is never invoked.
Is there a way to clear history and reload the state in Ionic?

Welcome to the framework! Actually the routing in Ionic is powered by ui-router. You should probably check out this previous SO question to find a couple of different ways to accomplish this.
If you just want to reload the state you can use:
$state.go($state.current, {}, {reload: true});
If you actually want to reload the page (as in, you want to re-bootstrap everything) then you can use:
$window.location.reload(true)
Good luck!

I found that JimTheDev's answer only worked when the state definition had cache:false set. With the view cached, you can do $ionicHistory.clearCache() and then $state.go('app.fooDestinationView') if you're navigating from one state to the one that is cached but needs refreshing.
See my answer here as it requires a simple change to Ionic and I created a pull request: https://stackoverflow.com/a/30224972/756177

The correct answer:
$window.location.reload(true);

I have found a solution which helped me to get it done. Setting cache-view="false" on ion-view tag resolved my problem.
<ion-view cache-view="false" view-title="My Title!">
....
</ion-view>

Reload the page isn't the best approach.
you can handle state change events for reload data without reload the view itself.
read about ionicView life-cycle here:
http://blog.ionic.io/navigating-the-changes/
and handle the event beforeEnter for data reload.
$scope.$on('$ionicView.beforeEnter', function(){
// Any thing you can think of
});

In my case I need to clear just the view and restart the controller. I could get my intention with this snippet:
$ionicHistory.clearCache([$state.current.name]).then(function() {
$state.reload();
});
The cache still working and seems that just the view is cleared.
ionic --version says 1.7.5.

None of the solutions mentioned above worked for a hostname that is different from localhost!
I had to add notify: false to the list of options that I pass to $state.go, to avoid calling Angular change listeners, before $window.location.reload call gets called. Final code looks like:
$state.go('home', {}, {reload: true, notify: false});
>>> EDIT - $timeout might be necessary depending on your browser >>>
$timeout(function () {
$window.location.reload(true);
}, 100);
<<< END OF EDIT <<<
More about this on ui-router reference.

I was trying to do refresh page using angularjs when i saw websites i got confused but no code was working for the code then i got solution for reloading page using
$state.go('path',null,{reload:true});
use this in a function this will work.

I needed to reload the state to make scrollbars work. They did not work when coming through another state - 'registration'. If the app was force closed after registration and opened again, i.e. it went directly to 'home' state, the scrollbars worked. None of the above solutions worked.
When after registration, I replaced:
$state.go("home");
with
window.location = "index.html";
The app reloaded, and the scrollbars worked.

The controller is called only once, and you SHOULD preserve this logic model, what I usually do is:
I create a method $scope.reload(params)
At the beginning of this method I call $ionicLoading.show({template:..}) to show my custom spinner
When may reload process is finished, I can call $ionicLoading.hide() as a callback
Finally, Inside the button REFRESH, I add ng-click = "reload(params)"
The only downside of this solution is that you lose the ionic navigation history system
Hope this helps!

If you want to reload after view change you need to
$state.reload('state',{reload:true});
If you want to make that view the new "root", you can tell ionic that the next view it's gonna be the root
$ionicHistory.nextViewOptions({ historyRoot: true });
$state.go('app.xxx');
return;
If you want to make your controllers reload after each view change
app.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$ionicConfigProvider.views.maxCache(0);

.state(url: '/url', controller: Ctl, templateUrl: 'template.html', cache: false)
cache: false ==> solved my problem !

As pointed out by #ezain reload controllers only when its necessary. Another cleaner way of updating data when changing states rather than reloading the controller is using broadcast events and listening to such events in controllers that need to update data on views.
Example: in your login/logout functions you can do something like so:
$scope.login = function(){
//After login logic then send a broadcast
$rootScope.$broadcast("user-logged-in");
$state.go("mainPage");
};
$scope.logout = function(){
//After logout logic then send a broadcast
$rootScope.$broadcast("user-logged-out");
$state.go("mainPage");
};
Now in your mainPage controller trigger the changes in the view by using the $on function to listen to broadcast within the mainPage Controller like so:
$scope.$on("user-logged-in", function(){
//update mainPage view data here eg. $scope.username = 'John';
});
$scope.$on("user-logged-out", function(){
//update mainPage view data here eg. $scope.username = '';
});

I tried many methods, but found this method is absolutely correct:
$window.location.reload();
Hope this help others stuck for days like me with version: angular 1.5.5, ionic 1.2.4, angular-ui-router 1.0.0

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');

The event $scope.$on('$destroy') doesn't work updating ionic & angular

I use $interval and need to detect when the controller is destroyed. Until now, I have used the $destroy event and it worked perfectly. For example with this basic code, it prints "destroy" in the console when I go to another page (with a simple <a href="#/myNewUrl"> in myView.html).
angular.module('myModule').controller('myController', ['$scope', function($scope) {
$scope.$on('$destroy', function() {
console.log('destroy');
});
}]);
But since I updated Ionic to the new version (v1.0.0-beta.14), that uses the new version to Angular too (v1.3.6), the $destroy event isn't detect when I go to another page.
Does anybody get the same problem? How can I be resolve it?
Thank you for your answer!
EDIT:
I have finally fixed the problem!!! Now, with the new Ionic version, the view is cached automatically. Adding cache-view="false" in the template disable it.
But I found a best way than the destroy event. Ionic added new events (on $ionicView) and now you can detect when you leave the page (and the page stays cached) with: $ionicView.leave.
To get more information: http://ionicframework.com/docs/nightly/api/directive/ionView/
Is your template cached? If you don't have cache: false in your state routes, then the controller is not destroyed.
http://forum.ionicframework.com/t/how-to-destroy-controllers-in-ion-tab-directive/16658
It's a hello from Ionic dev team. They like to leak memory, you see.
Just set
$ionicConfigProvider.views.maxCache(0);
That should do it

Updating URL in Angular JS without re-rendering view

I'm building a dashboard system in AngularJS and I'm running into an issue with setting the url via $location.path
In our dashboard, we have a bunch of widgets. Each shows a larger maximized view when you click on it. We are trying to setup deep linking to allow users to link to a dashboard with a widget maximized.
Currently, we have 2 routes that look like /dashboard/:dashboardId and /dashboard/:dashboardId/:maximizedWidgetId
When a user maximizes a widget, we update the url using $location.path, but this is causing the view to re-render. Since we have all of the data, we don't want to reload the whole view, we just want to update the URL. Is there a way to set the url without causing the view to re-render?
HTML5Mode is set to true.
In fact, a view will be rendered everytime you change a url. Thats how $routeProvider works in Angular but you can pass maximizeWidgetId as a querystring which does not re-render a view.
App.config(function($routeProvider) {
$routeProvider.when('/dashboard/:dashboardId', {reloadOnSearch: false});
});
When you click a widget to maximize:
Maximum This Widget
or
$location.search('maximizeWidgetId', 1);
The URL in addressbar would change to http://app.com/dashboard/1?maximizeWidgetId=1
You can even watch when search changes in the URL (from one widget to another)
$scope.$on('$routeUpdate', function(scope, next, current) {
// Minimize the current widget and maximize the new one
});
You can set the reloadOnSearch property of $routeProvider to false.
Possible duplicate question : Can you change a path without reloading the controller in AngularJS?
Regards
For those who need change full path() without controllers reload
Here is plugin: https://github.com/anglibs/angular-location-update
Usage:
$location.update_path('/notes/1');
I realize this is an old question, but since it took me a good day and a half to find the answer, so here goes.
You do not need to convert your path into query strings if you use angular-ui-router.
Currently, due to what may be considered as a bug, setting reloadOnSearch: false on a state will result in being able to change the route without reloading the view. The GitHub user lmessinger was even kind enough to provide a demo of it. You can find the link from his comment linked above.
Basically all you need to do is:
Use ui-router instead of ngRoute
In your states, declare the ones you wish with reloadOnSearch: false
In my app, I have an category listing view, from which you can get to another category using a state like this:
$stateProvider.state('articles.list', {
url: '{categorySlug}',
templateUrl: 'partials/article-list.html',
controller: 'ArticleListCtrl',
reloadOnSearch: false
});
That's it. Hope this helps!
We're using Angular UI Router instead of built-in routes for a similar scenario. It doesn't seem to re-instantiate the controller and re-render the entire view.
How I've implemented it:
(my solution mostly for cases when you need to change whole route, not sub-parts)
I have page with menu (menuPage) and data should not be cleaned on navigation (there is a lot of inputs on each page and user will be very very unhappy if data will disappear accidentally).
turn off $routeProvider
in mainPage controller add two divs with custom directive attribute - each directive contains only 'templateUrl' and 'scope: true'
<div ng-show="tab=='tab_name'" data-tab_name-page></div>
mainPage controller contains lines to simulate routing:
if (!$scope.tab && $location.path()) {
$scope.tab = $location.path().substr(1);
}
$scope.setTab = function(tab) {
$scope.tab = tab;
$location.path('/'+tab);
};
That's all. Little bit ugly to have separate directive for each page, but usage of dynamic templateUrl (as function) in directive provokes re-rendering of page (and loosing data of inputs).
If I understood your question right, you want to,
Maximize the widget when the user is on /dashboard/:dashboardId and he maximizes the widget.
You want the user to have the ability to come back to /dashboard/:dashboardId/:maximizedWidgetId and still see the widget maximized.
You can configure only the first route in the routerConfig and use RouteParams to identify if the maximized widget is passed in the params in the controller of this configured route and maximize the one passed as the param. If the user is maximizing it the first time, share the url to this maximized view with the maximizedWidgetId on the UI.
As long as you use $location(which is just a wrapper over native location object) to update the path it will refresh the view.
I have an idea to use
window.history.replaceState('Object', 'Title', '/new-url');
If you do this and a digest cycle happens it will completely mangle things up. However if you set it back to the correct url that angular expects it's ok. So in theory you could store the correct url that angular expects and reset it just before you know a digest fires.
I've not tested this though.
Below code will let you change url without redirection such as: http://localhost/#/691?foo?bar?blabla
for(var i=0;i<=1000;i++) $routeProvider.when('/'+i, {templateUrl: "tabPages/"+i+".html",reloadOnSearch: false});
But when you change to http://localhost/#/692, you will be redirected.

AngularJS + Safari: Force page to scroll to top when pages switch

I am having an odd, safari-only scrolling behavior using AngularJS.
Whenever the user flips between pages, the pages are being changed as if they are AJAX. I understand they are in AngualrJS, but the resulting behavior is that the browser does not scroll to top when the user switches pages.
I've tried to force the browser to scroll to top whenever a new controller is being used, but it does not seem to do anything.
I'm running the following JS at the top of every controller:
document.body.scrollTop = document.documentElement.scrollTop = 0;
This is also a Safari-only bug, every other browser will scroll to top when the page changes. Has anyone encountered a similar issue or think of a better way to resolve it?
$window.scrollTo(0,0) will scroll to the top of the page.
I just found a nice plugin (pure angularJS) that supports animations also:
https://github.com/durated/angular-scroll
you can use this:
.run(["$rootScope", "$window", '$location', function($rootScope, $window, $location) {
$rootScope.$on('$routeChangeStart', function(evt, absNewUrl, absOldUrl){
$window.scrollTo(0,0); //scroll to top of page after each route change
}}])
or for tab switches you can use the $window.scrollTo(0,0); in your controller
Have you tried using $anchorScroll()? it's documented here.
I got the same problem while using AngularJS in a Cordova App. In a normal Browser or on Android i have no trouble but on ios i get the same behavior as discribed by Neil.
The AngularJS documentation on $anchorScroll is not that great so i thought to post this link which helped me way more:
http://www.benlesh.com/2013/02/angular-js-scrolling-to-element-by-id.html
You can use $anchorScroll
$scope.gotoTop = function (){
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('top');
// call $anchorScroll()
$anchorScroll();
};
Like #nonstopbutton said, adding autoscroll="true" to my ngView element worked for me too. I mention this here because it was a comment to an answer and it was not easy to see his reply.
More information here: https://stackoverflow.com/a/24549173/1578861
I'd a similar issue with Chrome. However, I don't know if any specific external library is causing this issue or otherwise.
However I wrote this piece of code at app level and it works.
$rootScope.$on('$viewContentLoaded', function(){
$window.scrollTo(0, 0);
});
Call $window.scrollTo(0,0); after locationChangeSuccess event:
$rootScope.$on("$locationChangeSuccess",
function(event, current, previous, rejection) {
$window.scrollTo(0,0);
});
In the controller you can actually drop the $ from window and simply putwindow.scrollTo(0,0); without having to inject $window into the controller. It worked great for me.

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