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.
I've built out an application and mostly everything is working as expected except I have these three states where when I refresh the page via reloading the url it causes a trailing slash on the end of the page causing my otherwise('/404') to trigger.
This happen nowhere else on the entire platform. The weird part is that if I change the state URL for one of the states with issues, the issue is fixed.
The three state url's that have issues are market, refer-business and invite-friends. These are all a child of a shell view named root. I have absolutely no idea what is causing this issue.
I also realized that if I take $urlRouterProvider.otherwise('/404'); out of my main modules config, the issue goes away as well.
Here are two of the routes that aren't working.
market.routes.js
function marketRoutes($stateProvider) {
$stateProvider.state('market', {
parent: 'root',
url: '/market?category', // If I change this to /blahblah, it works perfects
views: {
"#root": {
templateUrl: '/market/market.html',
controller: 'MarketController',
controllerAs: 'MarketCtrl'
}
},
resolve: {
Categories: ['MarketService', function (MarketService) {
return MarketService.getCategories();
}],
Products: ['MarketService', '$stateParams', function (MarketService, $stateParams) {
// By default the market should open on `deals of the moment` category
return MarketService.getProducts(0, 9, 1, $stateParams.category || 4);
}]
},
data: {
title: 'Market'
}
});
}
refer-business.routes.js
function ReferBusinessRoutes($stateProvider) {
$stateProvider.state('refer-business', {
parent: 'root',
url: "/refer",
templateUrl: "/refer-business/refer-business.html",
controller: "ReferBusinessController",
controllerAs: "rbCtrl",
onEnter: function () {
this.data.title = "Refer a business";
},
data: {
title: 'Refer a business'
},
authenticate: true
});
}
I've spent the last 3 days on this and I've come up with nothing. Hopefully somebody here has run into this issue or has some tips on how to further debug it.
You might find the answer here: Handling trailing slashes in angularUI router
Explains How to tell angular deal with trailing slashes as usual.
It also might be permission problem in your template files at server side
I have made yoman angular fullstack app setup.
I am making use of ui.router in application.
I have defined state parameters as
.state('tour', {
url: '/tour',
templateUrl: 'app/tour/tour.html',
controller: 'tourCtrl',
params : {tourName: null, },
})
I am able to pass parameter to this state within application.
when my application goes to external link.
I want to get back to one of the state of my angular application with some parameter. so how can i do that??
Please Help me to do that .. Thanks
you can pass parameters and maintain states using stateParams, and then maintaining them in the URL. For your understanding, I have added a plunk:
http://plnkr.co/edit/SDOcGS?p=preview
Observe the console whenever you navigate to Route1 tab, and then observe the code:
.state('route1', {
url: "/route1/:place",
params: {
place: null
},
views: {
"viewA": {
template: "route1.viewA"
},
"viewB": {
template: "route1.viewB"
}
},
resolve :{
place: function($stateParams){
console.log($stateParams);
console.log("url", window.location);
return $stateParams.place;
}
}
})
Try this on localhost. It would work the same way on localhost, or any place where its hosted.
I've been looking at these pages (1, 2, 3). I basically want to change my $state, but I don't want the page to reload.
I am currently in the page /schedules/2/4/2014, and I want to go into edit mode when I click a button and have the URL become /schedules/2/4/2014/edit.
My edit state is simply $scope.isEdit = true, so there is no point of reloading the whole page. However, I do want the $state and/or url to change so that if the user refreshses the page, it starts in the edit mode.
What can I do?
For this problem, you can just create a child state that has neither templateUrl nor controller, and advance between states normally:
// UPDATED
$stateProvider
.state('schedules', {
url: "/schedules/:day/:month/:year",
templateUrl: 'schedules.html',
abstract: true, // make this abstract
controller: function($scope, $state, $stateParams) {
$scope.schedDate = moment($stateParams.year + '-' +
$stateParams.month + '-' +
$stateParams.day);
$scope.isEdit = false;
$scope.gotoEdit = function() {
$scope.isEdit = true;
$state.go('schedules.edit');
};
$scope.gotoView = function() {
$scope.isEdit = false;
$state.go('schedules.view');
};
},
resolve: {...}
})
.state('schedules.view', { // added view mode
url: "/view"
})
.state('schedules.edit', { // both children share controller above
url: "/edit"
});
An important concept here is that, in ui-router, when the application is in a particular state—when a state is "active"—all of its ancestor states are implicitly active as well.
So, in this case,
when your application advances from view mode to edit mode, its parent state schedules (along with its templateUrl, controller and even resolve) will still be retained.
since ancestor states are implicitly activated, even if the child state is being refreshed (or loaded directly from a bookmark), the page will still render correctly.
REF: https://github.com/angular-ui/ui-router/wiki/Quick-Reference#statetransitiontoto-toparams--options
$state.transitionTo('yourState', params, {notify: false});
Adding my answer because I think it's different enough from the accepted answer and may be useful to others:
I had two states, begin and view, with a bunch of optional parameters being synced with the URL for view, like so:
$stateProvider
.state('begin',
{
url: '/',
template: '<app-element></app-element>'
})
.state('view',
{
url: '/View?param1¶m2&...¶mN',
template: '<app-element></app-element>'
params: {
param1: {
value: null,
squash: true
},
...
}
});
The link function for <app-element> would run any time I tried to sync the parameters using $state.go. Using {notify: false, reload: false} did not work for me. The link function still ran each time. I'm on 0.2 so dynamic isn't an available param option, either. I followed #b0nyb0y's suggestion and turned it into a parent/child relationship, which worked:
$stateProvider
.state('app',
{
url: '/',
template: '<app-element></app-element>'
})
.state('app.view',
{
url: 'View?param1¶m2&...¶mN',
params: {
param1: {
value: null,
squash: true
},
...
}
});
Currently our project is using default $routeProvider, and I am using this "hack", to change url without reloading page:
services.service('$locationEx', ['$location', '$route', '$rootScope', function($location, $route, $rootScope) {
$location.skipReload = function () {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
return $location;
};
return $location;
}]);
and in controller
$locationEx.skipReload().path("/category/" + $scope.model.id).replace();
I am thinking of replacing routeProvider with ui-router for nesting routes, but cant find this in ui-router.
Is it possible - do the same with angular-ui-router?
Why do I need this?
Let me explain with an example :
Route for creating new category is /category/new
after clicking on SAVE I show success-alert and I want to change route /category/new to /caterogy/23 (23 - is id of new item stored in db)
Simply 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 notify: false . For example:
$state.go('.detail', {id: newId})
can be replaced by
$state.transitionTo('.detail', {id: newId}, {
location: true,
inherit: true,
relative: $state.$current,
notify: false
})
Edit: As suggested by fracz it can simply be:
$state.go('.detail', {id: newId}, {notify: false})
Ok, solved :)
Angular UI Router has this new method, $urlRouterProvider.deferIntercept()
https://github.com/angular-ui/ui-router/issues/64
basically it comes down to this:
angular.module('myApp', [ui.router])
.config(['$urlRouterProvider', function ($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
}])
// then define the interception
.run(['$rootScope', '$urlRouter', '$location', '$state', function ($rootScope, $urlRouter, $location, $state) {
$rootScope.$on('$locationChangeSuccess', function(e, newUrl, oldUrl) {
// Prevent $urlRouter's default handler from firing
e.preventDefault();
/**
* provide conditions on when to
* sync change in $location.path() with state reload.
* I use $location and $state as examples, but
* You can do any logic
* before syncing OR stop syncing all together.
*/
if ($state.current.name !== 'main.exampleState' || newUrl === 'http://some.url' || oldUrl !=='https://another.url') {
// your stuff
$urlRouter.sync();
} else {
// don't sync
}
});
// Configures $urlRouter's listener *after* your custom listener
$urlRouter.listen();
}]);
I think this method is currently only included in the master version of angular ui router, the one with optional parameters (which are nice too, btw). It needs to be cloned and built from source with
grunt build
The docs are accessible from the source as well, through
grunt ngdocs
(they get built into the /site directory) // more info in README.MD
There seems to be another way to do this, by dynamic parameters (which I haven't used).
Many credits to nateabele.
As a sidenote, here are optional parameters in Angular UI Router's $stateProvider, which I used in combination with the above:
angular.module('myApp').config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('main.doorsList', {
url: 'doors',
controller: DoorsListCtrl,
resolve: DoorsListCtrl.resolve,
templateUrl: '/modules/doors/doors-list.html'
})
.state('main.doorsSingle', {
url: 'doors/:doorsSingle/:doorsDetail',
params: {
// as of today, it was unclear how to define a required parameter (more below)
doorsSingle: {value: null},
doorsDetail: {value: null}
},
controller: DoorsSingleCtrl,
resolve: DoorsSingleCtrl.resolve,
templateUrl: '/modules/doors/doors-single.html'
});
}]);
what that does is it allows to resolve a state, even if one of the params is missing.
SEO is one purpose, readability another.
In the example above, I wanted doorsSingle to be a required parameter. It is not clear how to define those. It works ok with multiple optional parameters though, so not really a problem. The discussion is here https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090
After spending a lot of time with this issue, Here is what I got working
$state.go('stateName',params,{
// prevent the events onStart and onSuccess from firing
notify:false,
// prevent reload of the current state
reload:false,
// replace the last record when changing the params so you don't hit the back button and get old params
location:'replace',
// inherit the current params on the url
inherit:true
});
Calling
$state.go($state.current, {myParam: newValue}, {notify: false});
will still reload the controller, meaning you will lose state data.
To avoid it, simply declare the parameter as dynamic:
$stateProvider.state({
name: 'myState',
url: '/my_state?myParam',
params: {
myParam: {
dynamic: true, // <----------
}
},
...
});
Then you don't even need the notify, just calling
$state.go($state.current, {myParam: newValue})
suffices. Neato!
From the documentation:
When dynamic is true, changes to the parameter value will
not cause the state to be entered/exited. The resolves will not be
re-fetched, nor will views be reloaded.
This can be useful to build
UI where the component updates itself when the param values change.
This setup solved following issues for me:
The training controller is not called twice when updating the url from .../ to .../123
The training controller is not getting invoked again when navigating to another state
State configuration
state('training', {
abstract: true,
url: '/training',
templateUrl: 'partials/training.html',
controller: 'TrainingController'
}).
state('training.edit', {
url: '/:trainingId'
}).
state('training.new', {
url: '/{trainingId}',
// Optional Parameter
params: {
trainingId: null
}
})
Invoking the states (from any other controller)
$scope.editTraining = function (training) {
$state.go('training.edit', { trainingId: training.id });
};
$scope.newTraining = function () {
$state.go('training.new', { });
};
Training Controller
var newTraining;
if (!!!$state.params.trainingId) {
// new
newTraining = // create new training ...
// Update the URL without reloading the controller
$state.go('training.edit',
{
trainingId : newTraining.id
},
{
location: 'replace', // update url and replace
inherit: false,
notify: false
});
} else {
// edit
// load existing training ...
}
If you need only change url but prevent change state:
Change location with (add .replace if you want to replace in history):
this.$location.path([Your path]).replace();
Prevent redirect to your state:
$transitions.onBefore({}, function($transition$) {
if ($transition$.$to().name === '[state name]') {
return false;
}
});
i did this but long ago in version: v0.2.10 of UI-router like something like this::
$stateProvider
.state(
'home', {
url: '/home',
views: {
'': {
templateUrl: Url.resolveTemplateUrl('shared/partial/main.html'),
controller: 'mainCtrl'
},
}
})
.state('home.login', {
url: '/login',
templateUrl: Url.resolveTemplateUrl('authentication/partial/login.html'),
controller: 'authenticationCtrl'
})
.state('home.logout', {
url: '/logout/:state',
controller: 'authenticationCtrl'
})
.state('home.reservationChart', {
url: '/reservations/?vw',
views: {
'': {
templateUrl: Url.resolveTemplateUrl('reservationChart/partial/reservationChartContainer.html'),
controller: 'reservationChartCtrl',
reloadOnSearch: false
},
'viewVoucher#home.reservationChart': {
templateUrl: Url.resolveTemplateUrl('voucher/partial/viewVoucherContainer.html'),
controller: 'viewVoucherCtrl',
reloadOnSearch: false
},
'addEditVoucher#home.reservationChart': {
templateUrl: Url.resolveTemplateUrl('voucher/partial/voucherContainer.html'),
controller: 'voucherCtrl',
reloadOnSearch: false
}
},
reloadOnSearch: false
})
Try something like this
$state.go($state.$current.name, {... $state.params, 'key': newValue}, {notify: false})
In Angular 2, the accepted answer from RezKesh translates to the following:
this.uiRouter.stateService.go(
"home.myRouteState",
{
"param1": this.myParam1,
"param2": this.myParam2
},
{ notify: false }
);
Assuming you have injected UIRouter into your component's constructor as follows:
constructor(
private uiRouter: UIRouter
) { }
I don't think you need ui-router at all for this. The documentation available for the $location service says in the first paragraph, "...changes to $location are reflected into the browser address bar." It continues on later to say, "What does it not do? It does not cause a full page reload when the browser URL is changed."
So, with that in mind, why not simply change the $location.path (as the method is both a getter and setter) with something like the following:
var newPath = IdFromService;
$location.path(newPath);
The documentation notes that the path should always begin with a forward slash, but this will add it if it's missing.