Angular Ui Router :stop $state.go to reload controllers but change URL - angularjs

My app uses $state.go when switching between tabs and this cause to re-initializes the controllers and scope variable in those controllers get updated and causes memory leaks. Is there a way to stop re-initializing the controllers but change URL on state change?
Example below is routes.js in my app.
.state('home',
{
abstract : true,
url : '/home',
templateUrl : 'scripts/home.html'
})
.state('home.summary',
{
url : '/summary?userId',
controller : 'SummaryCtrl',
views :
{
summary:
{
templateUrl: 'scripts/home/summary.html'
}
}
})
.state('home.summary.detail',
{
url : '/detail/:id',
controller : 'DetailCtrl',
views :
{
detail:
{
templateUrl: 'scripts/home/detail.html'
}
}
})
How to stop reloading DetailCtrl but change URL when going to state home.summary.detail if the DetailCtrl is already loaded for unique id???
Also tried to $q.reject in resolve of child state, it stops reload of controllers but doesn't change url.
Also tried reloadOnSearch=false it stops reload of controllers but doesn't change url.

You can use $state.transitionTo instead of $state.go . $state.go calls $state.transitionTo internally but automatically sets options to { location: true, inherit: true, relative: $state.$current, notify: true } . You can call $state.transitionTo and set location: false . For example:
$state.go('.detail', {id: newId})
can be replaced by
$state.transitionTo ('.detail', {id: newId}, { location: false, inherit: true, relative: $state.$current, notify: true })

You can have a abstract controller that contain your memory leak code.
And a abstract controller will not update when url change.
Here is the doc from official github repo
https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views
look at the Combination part.
here is the plnkr, contacts controller will only call once in this demo
contacts.detail controller will update every time you change state but share the scope with contacts controller
http://plnkr.co/edit/gmtcE2?p=preview

Related

ui-router is reloading controller

Using ui-router 1.0.6.
Every time I return to an url (using ui-sref) it reloads the controller. I would like to avoid that and to load the controller only the first time it is accessed.
In this example Plunkr: every time I switch repeatedly between Hello and About it logs the console.
It can be wrapped in a parent controller to track who's already loaded
Here is a working example: Plnkr
Basically you create another controller that holds an object with an empty list:
myApp.controller('ModuleNumCtrl', function() {
loadedCtrl = {};
});
And set it to be parent by setting the abstract attribute to true:
var parentState = {
abstract: true,
name: 'parent',
controller: 'ModuleNumCtrl'
};
Then you set the the exiting controllers to be his children by prefixing their names with 'parent.'
var helloState = {
name: 'parent.hello',
url: '/hello',
template: '<h3>hello world!</h3>',
controller: 'ModuleTwoCtrl'
};
var aboutState = {
name: 'parent.about',
url: '/about',
template: '<h3>Its the UI-Router hello world app!</h3>',
controller: 'ModuleOneCtrl'
};
$stateProvider.state(parentState);
$stateProvider.state(helloState);
$stateProvider.state(aboutState);
Then on each controller you want to load only once, you can add it to the list the first time it's loaded and the code that you want to run only once put in an if statement:
myApp.controller('ModuleOneCtrl', function() {
if (!loadedCtrl.one) {
console.log("One");
}
loadedCtrl.one = true;
});
Last thing, don't forget to change the HTML with the new controllers names:
<a ui-sref="parent.hello" ui-sref-active="active">Hello</a>
<a ui-sref="parent.about" ui-sref-active="active">About</a>
There's a plugin for ui-router which can do that, named sticky-states: https://github.com/ui-router/sticky-states
I would build on top of your plunker, but i can't find a CDN that's hosting sticky states. I found a CDN for ui-router-extras which is the equivalent for sticky states in ui-router 0.x, but for 1.x that won't work.
What you'll need to do is
1) Add the plugin. The github page for sticky-states gives instructions on how to do this, which i'll replicate here:
import {StickyStatesPlugin} from "ui-router-sticky-states";
angular.module('myapp', ['ui.router']).config(function($uiRouterProvider) {
$uiRouterProvider.plugin(StickyStatesPlugin);
});
2) For the state definitions that you want to remain active, add the property sticky: true, as in:
var aboutState = {
name: 'about',
url: '/about',
template: '<h3>Its the UI-Router hello world app!</h3>',
controller : 'ModuleOneCtrl',
sticky: true
}
With this flag, moving from a state to a sibling state will not exit the old state, but rather will "inactivate" it. The controller remains loaded. If you try to enter that old state, it will be "reactivated". The state is now active, but the existing controller is reused.
Note that sticky states will still be exited if you do one of the following:
1) exit the parent of the sticky state
2) directly activate the parent of the sticky state
So you'll need to arrange your tree of states so that that either can't happen , or only happens when you want it to.

reload: true works only once

I am trying to pass parameters from one view to another in my anulgar/ionic app.
in the volCompute view ($stateParams is declared):
if ($stateParams.modelLine){
console.log('receiving', $stateParams.modelLine.id, $stateParams.modelLine.modelStrict)
$scope.data.chosenModel = $stateParams.modelLine.modelStrict;
}
in the compareview:
$state.go('app.volCompute', { modelLine: test[0] }, {reload: true});
but it works only one time, if I go back and forth between the two views the code in volCompute is not run.
It seems like the reload: true is only executed once.
Can you help please ?
There are few more details how to understand ionic caches
What is the difference between $ionicView.enter and cache:false
ui.router not reloading controller
And I would suggest to use the event hook here $ionicView.enter or $ionicView.afterEnter
$scope.$on('$ionicView.enter', function(){
// Coding
});
there is the link to doc
In your routes file:
$stateProvider.state('myState', {
cache: false,
url : '/myUrl',
templateUrl : 'my-template.html'
})
Or inidividually in an <ion-view> tag:
<ion-view cache-view="false">
...
</ion-view>

JavaScript | AngularJS | UI-Router: Force Controller Execution Always

My scenario entails a route with a controller and view for state app.foo. When a user lands on this route via a menu-link, I would like UI-Router to always rerun/execute the controller -- as I have an event being dispatched here to get data from another module. The problem is that if User switches to app.bar and comes back to app.foo, this event never fires.
How can I force UI-Router to run this controller again?
Example:
.state('app.foo', {
url: '/foo',
alwaysReload: true
});
Is this possible?
This is default behavior if you add a controller to the route definition, for ex:
$stateProvider.state('app.foo', {
url: '/foo',
views: {
"main": {
controller: function ($scope) {
console.log('foo controller loaded.');
},
controllerAs: 'foo',
templateUrl: 'foo/foo.tpl.html'
}
}
'foo controller loaded.' will always log when the 'app.foo' state is loaded.

AngularJs UI-Router - Page Refresh $state.current is now empty

I have an Angular application using ui-router and I am having issues whenever I refresh the page. I am using nested views, named views to build the application. Whenever I refresh the page, ui-router doesn't reload the current state and just leaves the page blank.
On page load $state.current is equal to
Object {name: "", url: "^", views: null, abstract: true}
I am reading my navigation from a .json file via $http and looping through the states. So this is what I can show:
stateProvider.state(navElement.stateName, {
url: navElement.regexUrl ? navElement.regexUrl : url,
searchPage: navElement.searchPage, //something custom i added
parent: navElement.parent ? navElement.parent : "",
redirectTo: navElement.redirectTo,
views: {
'subNav#index': {
templateUrl: defaults.secondaryNavigation,
controller: 'secondaryNavigationController as ctrl' //static
},
'pageContent#index': {
template: navElement.templateUrl == null
? '<div class="emptyContent"></div>'
: undefined,
templateUrl: navElement.templateUrl == null
? undefined
: navElement.templateUrl,
controller: navElement.controller == null
? undefined
: navElement.controller + ' as ctrl'
}
}
});
This code gets executed for each item in the nested json object. If there is anything else that would be helpful, let me know.
There is a question: AngularJS - UI-router - How to configure dynamic views with one answer, which shows how to do that.
What is happening? On refresh, the url is evaluated sooner, then states are registered. We have to postpone that. And solution is driven by UI-Router native feature deferIntercept(defer)
$urlRouterProvider.deferIntercept(defer)
As stated in the doc:
Disables (or enables) deferring location change interception.
If you wish to customize the behavior of syncing the URL (for example, if you wish to defer a transition but maintain the current URL), call this method at configuration time. Then, at run time, call $urlRouter.listen() after you have configured your own $locationChangeSuccess event handler.
In a nutshell, we will stop URL handling in config phase:
app.config(function ($urlRouterProvider) {
// Prevent $urlRouter from automatically intercepting URL changes;
// this allows you to configure custom behavior in between
// location changes and route synchronization:
$urlRouterProvider.deferIntercept();
})
And we will re-enable that in .run() phase, once we configured all dynamic states from JSON:
.run(function ($rootScope, $urlRouter, UserService) {
...
// Once the user has logged in, sync the current URL
// to the router:
$urlRouter.sync();
// Configures $urlRouter's listener *after* your custom listener
$urlRouter.listen();
});
There is a plunker from the linked Q & A
I don't know how are all your routes.. but if you refresh a page of a child state, you need to pass all parameters of the parents states to be resolved correctly.

How to reload the current state?

I'm using Angular UI Router and would like to reload the current state and refresh all data / re-run the controllers for the current state and it's parent.
I have 3 state levels: directory.organisations.details
directory.organisations contains a table with a list of organisations. Clicking on an item in the table loads directory.organisations.details with $StateParams passing the ID of the item. So in the details state I load the details for this item, edit them and then save data. All fine so far.
Now I need to reload this state and refresh all the data.
I have tried:
$state.transitionTo('directory.organisations');
Which goes to the parent state but doesn't reload the controller, I guess because the path hasn't changed. Ideally I just want to stay in the directory.organisations.details state and refresh all data in the parent too.
I have also tried:
$state.reload()
I have seen this on the API WIKI for $state.reload "(bug with controllers reinstantiating right now, fixing soon)."
Any help would be appreciated?
I found this to be the shortest working way to refresh with ui-router:
$state.go($state.current, {}, {reload: true}); //second parameter is for $stateParams
Update for newer versions:
$state.reload();
Which is an alias for:
$state.transitionTo($state.current, $stateParams, {
reload: true, inherit: false, notify: true
});
Documentation: https://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state#methods_reload
This solution works in AngularJS V.1.2.2:
$state.transitionTo($state.current, $stateParams, {
reload: true,
inherit: false,
notify: true
});
That would be the final solution. (inspired by #Hollan_Risley's post)
'use strict';
angular.module('app')
.config(function($provide) {
$provide.decorator('$state', function($delegate, $stateParams) {
$delegate.forceReload = function() {
return $delegate.go($delegate.current, $stateParams, {
reload: true,
inherit: false,
notify: true
});
};
return $delegate;
});
});
Now, whenever you need to reload, simply call:
$state.forceReload();
for ionic framework
$state.transitionTo($state.current, $state.$current.params, { reload: true, inherit: true, notify: true });//reload
$stateProvider.
state('home', {
url: '/',
cache: false, //required
https://github.com/angular-ui/ui-router/issues/582
Probably the cleaner approach would be the following :
<a data-ui-sref="directory.organisations.details" data-ui-sref-opts="{reload: true}">Details State</a>
We can reload the state from the HTML only.
#Holland Risley 's answer is now available as an api in latest ui-router.
$state.reload();
A method that force reloads the current state. All resolves are
re-resolved, controllers reinstantiated, and events re-fired.
http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state
$state.go($state.current, $stateParams, {reload: true, inherit: false});
$scope.reloadstat = function () { $state.go($state.current, {}, {reload: true}); };
if you want to reload your entire page, like it seems, just inject $window into your controller and then call
$window.location.href = '/';
but if you only want to reload your current view, inject $scope, $state and $stateParams (the latter just in case you need to have some parameters change in this upcoming reload, something like your page number), then call this within any controller method:
$stateParams.page = 1;
$state.reload();
AngularJS v1.3.15
angular-ui-router v0.2.15
Silly workaround that always works.
$state.go("otherState").then(function(){
$state.go("wantedState")
});
For angular v1.2.26, none of the above works. An ng-click that calls the above methods will have to be clicked twice in order to make the state reload.
So I ended up emulating 2 clicks using $timeout.
$provide.decorator('$state',
["$delegate", "$stateParams", '$timeout', function ($delegate, $stateParams, $timeout) {
$delegate.forceReload = function () {
var reload = function () {
$delegate.transitionTo($delegate.current, angular.copy($stateParams), {
reload: true,
inherit: true,
notify: true
})
};
reload();
$timeout(reload, 100);
};
return $delegate;
}]);
Everything failed for me. Only thing that worked...is adding cache-view="false" into the view which I want to reload when going to it.
from this issue https://github.com/angular-ui/ui-router/issues/582
Not sure why none of these seemed to work for me; the one that finally did it was:
$state.reload($state.current.name);
This was with Angular 1.4.0
I had multiple nested views and the goal was to reload only one with content.
I tried different approaches but the only thing that worked for me is:
//to reload
$stateParams.reload = !$stateParams.reload; //flip value of reload param
$state.go($state.current, $stateParams);
//state config
$stateProvider
.state('app.dashboard', {
url: '/',
templateUrl: 'app/components/dashboard/dashboard.tmpl.html',
controller: 'DashboardController',
controllerAs: 'vm',
params: {reload: false} //add reload param to a view you want to reload
});
In this case only needed view would be reloaded and caching would still work.
I know there have been a bunch of answers but the best way I have found to do this without causing a full page refresh is to create a dummy parameter on the route that I want to refresh and then when I call the route I pass in a random number to the dummy paramter.
.state("coverage.check.response", {
params: { coverageResponse: null, coverageResponseId: 0, updater: 1 },
views: {
"coverageResponse": {
templateUrl: "/Scripts/app/coverage/templates/coverageResponse.html",
controller: "coverageResponseController",
controllerAs: "vm"
}
}
})
and then the call to that route
$state.go("coverage.check.response", { coverageResponse: coverage, updater: Math.floor(Math.random() * 100000) + 1 });
Works like a charm and handles the resolves.
You can use #Rohan answer https://stackoverflow.com/a/23609343/3297761
But if you want to make each controller reaload after a view change you can use
myApp.config(function($ionicConfigProvider) { $ionicConfigProvider.views.maxCache(0); ... }
If you are using ionic v1, the above solution won't work since ionic has enabled template caching as part of $ionicConfigProvider.
Work around for that is a bit hacky - you have to set cache to 0 in ionic.angular.js file:
$ionicConfigProvider.views.maxCache(0);
I had this problem it was wrecking my head, my routing is read from a JSON file and then fed into a directive. I could click on a ui-state but I couldn't reload the page. So a collegue of mine showed me a nice little trick for it.
Get your Data
In your apps config create a loading state
Save the state you want to go to in the rootscope.
Set your location to "/loading" in your app.run
In the loading controller resolve the routing promise and then set the location to your intended target.
This worked for me.
Hope it helps

Resources