I have a problem with nested views in ui-router.
My layout is :
site.search
-----------------
site.search.runs
-----------------
site.search.runs.scheme
-----------------
site.search.runs.scheme.payment
-----------------
I don't want to change url that's why I use $state.go('', {}, {location:false});
I also use $stage.go('^'...) for back to parent.
But when I click on Back button on site.search.runs.scheme.payment or site.search.runs.scheme - site.runs controller also reloads (I don't use {reload:true}).
How to prevent reloading top level parent controller ?
I have found a problem like mine https://github.com/angular-ui/ui-router/issues/2096
Can anybody help me ?
My configuration is
$stateProvider
.state('site', {
abstract: true,
url: '',
template: '<ui-view/>'
})
.state('site.search', {
url: '',
parent: 'site',
controller: 'SearchController',
controllerAs: 'search',
templateUrl: 'js/search/search.tpl.html'
})
.state('site.search.runs', {
url: '',
parent: 'site.search',
params: {back: false, timestamp: 0, search: {}},
controller: 'RunsController as runsCtrl',
templateUrl: 'js/runs/runs.tpl.html'
})
.state('site.search.runs.scheme', {
url: '',
parent: 'site.search.runs',
params: {back: false, run: ''},
controller: 'SchemeController as schemeCtrl',
templateUrl: 'js/scheme/scheme.tpl.html'
})
.state('site.search.runs.scheme.payment', {
url: '',
parent: 'site.search.runs.scheme',
params: {back: false},
controller: 'PaymentController as payCtrl',
templateUrl: 'js/payment/liqpay/payment.tpl.html'
})
});
Back method calls this $state.go('^', {back: true}, {location: false, inherit: false});
A dirty trick you could do is wrap the controller code in an if statement and do something like this (this is by no means the best way, but it will certainly work):
// make sure you inject $state
// and put the if statement inside the main function of your controller
function RunsController($state) {
// var will return something like: site.search.runs.scheme
var currentState = $state.current.name;
if (currentState === 'site.search.runs') {
// in here put the code that would normally be in this controller for site.search.runs
}
}
Depends on when you are calling $state.go().
You could try the notify: false flag on the back method $state.go('^', {back: true}, {location: false, inherit: false, notify: false});
If youre trying to call state.go in the middle of a state already in transition, you should try $state.transitionTo() for example:
$rootScope.$on('$stateChangeStart', function(event, toState) {
$state.transitionTo('login');
}
I've found that transitionTo will not reload my parent controllers
$state.transitionTo('^', $stateParams, {
reload: false, inherit: false, notify: false
});
The parent property on the state is redundant if youre using dot notation so that could be causing a problem as only one is required. I would check $state.$current when youre calling 'back' to make sure you really are part of the parent's children.
Related
I have one parent state and child states for that parent. Parent state have one optional parameter reviewId. Everything is working good, when I am sending parameter in $state.go state is opened correctly. What I need is to append (add that optional query parametar in url) when creating new review (id is populated after making back end call).
This is url for opening existing review with id:
http://localhost:3000/#/planning?reviewId=1000.
When creating new review url is:
http://localhost:3000/#/planning. So what I need is after review is saved (Id is populated) to append ?reviewId=newId without state.go.
$stateProvider
.state('root.planning', {
url: '/planning?reviewId=',
abstract: true,
params: {
reviewId: null,
readOnlyMode: null,
locked: null,
editButton: null
},
reloadOnSearch:false,
views: {
'#': {
templateUrl: 'src/app/modules/planning/views/planning.tpl.html',
controller: 'PlanningMainController as reviewvm'
}
}
})
.state('root.planning.planning', {
url: '',
params: {
reviewId: null
},
data: {
planningChild: true,
stateTypeCd : "PLA"
},
reloadOnSearch:false,
templateUrl: 'src/app/modules/planning/views/tabs/tab.planning.view.html',
controller: 'PlanningController as planningvm'
})
EDIT
Tried also to update reviewId in stateParams after Id is accessed and then call $state.reload(). With this approach stateParams.reviewId is null after reload. And I was wondering if I can do this without reload.
$stateProvider
.state('root.planning', {
url: '/planning?reviewId',
abstract: true,
controller: function($scope, $stateParams) {
$scope.reviewId = $stateParams.reviewId;
}
call the function if $statePrams.reviewId is available. hope this will help you
I have configured the following ui-router.
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('global.editor', {
url: '/posts/editor/{id}',
templateUrl: '/htmls/editor.html',
controller: 'EditorCtrl',
resolve: {
post: ['$stateParams', 'codeService', function ($stateParams, codeService) {
return codeService.getPost($stateParams.id)
}]
}
}
.state('global.new', {
url: '/new',
templateUrl: '/htmls/editor.html',
controller: 'EditorCtrl'
})
.state('global.newTRUE', {
url: '/newTRUE',
templateUrl: '/htmls/editor.html',
controller: 'EditorCtrl'
})
.state('global.editor.panels', {
controller: 'PanelsCtrl',
params: { layout: 'horizontal' },
templateUrl: function (params) { return "/htmls/" + params.layout + '.html' }
}
}])
app.controller('EditorCtrl', ['$scope', '$state', function ($scope, $state) {
$scope.layout = "horizontal";
$scope.$watch('layout', function () {
$state.go('global.editor.panels', { layout: $scope.layout });
});
}]);
As a result, https://localhost:3000/#/new in a browser leads to (the state global.editor, then to) the state global.editor.panels.
Now, I want to add a parameter connected:
I don't want it to be shown in the url
https://localhost:3000/#/new in a browser makes connected to be false, and https://localhost:3000/#/newTRUE in a browser makes connected to be true
connected can be past into the controller EditorCtrl and PanelsCtrl
connected can be available in the resolve of global.editor; ideally, we could resolve different objects according to the value of connected.
Does anyone know how to accomplish this?
You can add resolve for new and newTRUE:
.state('global.new', {
url: '/new',
templateUrl: '/htmls/editor.html',
resolve: {
isConnected: function() {
return false;
}
},
controller: 'EditorCtrl'
})
.state('global.newTRUE', {
url: '/newTRUE',
templateUrl: '/htmls/editor.html',
resolve: {
isConnected: function() {
return true;
}
},
controller: 'EditorCtrl'
})
And in EditorCtrl (or PanelsCtrl) you can use it like:
app.controller('EditorCtrl', ['$scope', '$state', 'isConnected', function($scope, $state, isConnected) {
console.log("connected : " + isConnected); // you can save this value in Service and use it later.
...
}]);
You can use classic approach - in resolve
Or you can use hidden parameters from angular ui router.
Define params : {isConnected' : null} in your parent global state.
In:
global.newTRUE - set value in $state config
global.new - set value in $state config
global.editor.panels - set parameters in transition/go or ui-sref
definition is like this:
$stateProvider
.state('global.newTRUE', {
url : '/:newTRUE',
params : {
'isConnected' : false
}
});
}
and in controller you get in from $stateParams.
Problem with this approach is hidden parameters are loses in refresh page, except if is set default value
You can surely use the params of UI-Router states' config to not show it in URL and achieve all mentioned points.
Also, as per #2, you need connected to be false for /new and true for /newTRUE. We can do so by passing true or false as default value for those states. Something like this:
$stateProvider
.state('global.editor', {
url: '/posts/editor/{id}',
templateUrl: '/htmls/editor.html',
params: { connected: null },
controller: 'EditorCtrl',
resolve: {
post: ['$stateParams', 'codeService', function ($stateParams, codeService) {
return codeService.getPost($stateParams.id)
}]
}
}
.state('global.new', {
url: '/new',
templateUrl: '/htmls/editor.html',
params: { connected: false }, // default false for /new
controller: 'EditorCtrl'
})
.state('global.newTRUE', {
url: '/newTRUE',
templateUrl: '/htmls/editor.html',
params: { connected: true }, // default true for /newTRUE
controller: 'EditorCtrl'
})
.state('global.editor.panels', {
controller: 'PanelsCtrl',
params: { layout: 'horizontal', connected: null },
templateUrl: function (params) { return "/htmls/" + params.layout + '.html' }
}
For #3, In order to access connected in your controllers (EditorCtrl and PanelsCtrl) you can inject $stateParams to controller and use $stateParams.connected to get it.
For #4, (This is more or less similar to achieveing #3)
Just like you get $stateParams.id, you can have $stateParams.connected as well, which you can use to resolve different objects according to the value of connected. Something like this:
.state('global.editor', {
url: '/posts/editor/{id}',
templateUrl: '/htmls/editor.html',
params: { connected: null },
controller: 'EditorCtrl',
resolve: {
post: ['$stateParams', 'codeService', function ($stateParams, codeService) {
return $stateParams.connected ?
codeService.getPost($stateParams.id) :
codeService.getSomethingElse($stateParams.id)
}]
}
}
But, for that to work, make sure that you are passing connected as params when you visit global.editor state (using $state.go or ui-sref)
Hope this helps!
I am passing a value from one state to another using UI-Router. In my controller when the url is updated I am trying to get access to the second parameter of my url using $stateParams, but for some reason I can get access to the first parameter but the second one is UNDEFINED. This is my code:
state 1, URL:
http://localhost:16009/#/5nqeAPqlv21/
state 2, URL:
http://localhost:16009/#/5nqeAPqlv21/details/PP163Ku3dOR
candidateContoller.js:
//go to state 2 (same controller for parent and child views)
$state.go('index.details', { "submissionIdd": publicSubmissionId });
//when located in the new state and new url:
console.log($stateParams.submissionIdd); //shows undefined
console.log($stateParams.token); //shows 5nqeAPqlv21
App.js:
$stateProvider
.state('index',
{
url: '/:token/',
views: {
'': {
templateUrl: 'AngularJS/Templates/indexView.html',
controller: 'candidateController as candCtrl'
},
'sectioncandidate#index': {
templateUrl: (_isNotMobile)
? 'AngularJS/Templates/candidatesView.html'
: 'AngularJS/Templates/candidatesMobileView.html'
}
}
})
.state('index.details', {
url: 'details/{submissionIdd}',
views: {
'sectioncandidate#index': {
templateUrl: (_isNotMobile)
? 'AngularJS/Templates/candidateView.html'
: 'AngularJS/Templates/candidateMobileView.html'
}
}
})
You do experience standard behavior of the UI-Router and Parent/Child state defintion:
parent state declares $stateParams (as url: '/:token/') - so there is just one declared - a token
child state gets all from parent(s), plus declares new parameter (as url: 'details/{submissionIdd}') - so it has both defined - token and submissionIdd,
So, while child has access to both params, parent state has just a token parameter
state 1 === PARENT, URL:
http://localhost:16009/#/5nqeAPqlv21/
here we will have submissionIdd undefined
state 2 === CHILD, URL:
http://localhost:16009/#/5nqeAPqlv21/details/PP163Ku3dOR
both are there submissionIdd=PP163Ku3dOR and token=5nqeAPqlv21
Extend - there is a working plunker
This states definition (in the plunker a bit adjusted)
.state('index',
{
url: '/:token/',
views: {
'': {
templateUrl: 'indexView.html',
controller: 'candidateController as candCtrl'
},
'sectioncandidate#index': {
templateUrl: 'parent.html'
}
}
})
.state('index.details', {
url: 'details/{submissionIdd}',
views: {
'sectioncandidate#index': {
templateUrl:'child.html',
}
})
will properly show state params for these links
href
<a href="#/5nqeAPqlv21/">
<a href="#/5nqeAPqlv21/details/PP163Ku3dOR">
<a href="#/5nqeAPqlv21/details/other">
ui-sref
<a ui-sref="index({token: '5nqeAPqlv21'})">
<a ui-sref="index.details({token: '5nqeAPqlv21', submissionIdd: 'another'})">
Check it here
You can prefer not to pass the the params in the URL unless there is an explicit requirement. I would prefer to use $stateParams in controller to fetch the data.
For this you need to define the default params in the state to
make it work.
$stateProvider .state('index.details', {
url: 'details',
params: {
submissionIdd: 'default',
},
views: {
'sectioncandidate#index': {
templateUrl: (_isNotMobile) ? 'AngularJS/Templates/candidateView.html' : 'AngularJS/Templates/candidateMobileView.html' ,
controller: function($stateParams){
console.log($stateParams.submissionIdd)
}
}
}
})
In my Angular app, I have the following route structure:
.state('mapping', {
url: '/mapping',
templateUrl: 'app/components/mapping/mapping.html',
controller: 'MapCtrl as map',
abstract: true,
authenticate: true
})
.state('mapping.all', {
url: '',
templateUrl: 'app/components/mapping/partials/all.html',
authenticate: true
})
.state('mapping.project', {
url: '/:projectName',
templateUrl: 'app/components/mapping/partials/project.html',
controller: 'ProjectCtrl as proj',
authenticate: true
})
When accessing the mapping state, mapping.all loads by default. It basically shows a list of projects which link to the mapping.project state, as such:
<a ui-sref="mapping.project({projectId: project.id, projectName: project.name})">...</a>
I want to call the ProjectCtrl when I access mapping.project in order to load the necessary data, but it never even gets loaded. In the snippet below which includes only relevant data, the alert message never pops up:
angular
.module('watera')
.controller('ProjectCtrl', Controller);
Controller.$inject = ['$stateParams', 'UserFactory', 'ProjectFactory'];
function Controller($stateParams, UserFactory, ProjectFactory) {
var proj = this;
activate();
function activate() {
alert('1');
}
}
The js file is correctly linked and the controller is named correctly. The <div ui-view></div> element is placed within mapping.html, as seen below:
<div id="ribbon" class="no-print">
<div class="container-fluid">
<div class="row">
<div class="col-xs-12">
<strong>Mapping</strong>
<p class="text-muted">In this section...</p>
</div>
</div>
</div>
</div>
<div ui-view></div>
Why is this controller not loading?
EDIT: It gets weirder, I switched the structure to:
.state('mapping', {
url: '/mapping',
templateUrl: 'app/components/mapping/mapping.html',
abstract: true,
authenticate: true
})
.state('mapping.all', {
url: '',
templateUrl: 'app/components/mapping/partials/all.html',
controller: 'MapCtrl as map',
authenticate: true
})
.state('mapping.project', {
url: '/:projectName',
templateUrl: 'app/components/mapping/partials/project.html',
controller: 'ProjectCtrl as proj',
authenticate: true
})
And while MapCtrl keeps working correctly, ProjectCtrl still doesn't.
Found the issue... There were two instances of ProjectCtrl in this project:
Apparently, this causes some sort of silent conflict because there was absolutely no console error. Since the second instance was also linked on index.html, after the one that I wanted to load, it was probably simply replacing the correct version. Debugging nightmare.
Fixed now.
An abstract state cannot exist by definition so having a template URL for the parent and the children (which won't inherit the parent's templateUrl because the children define their own) is highly dubious IMO.
Im also wondering if there is trouble distinguising between ...
url: ''
AND
url: '/:projectName'
(Although it should handle the distinction).
However, to make sure I suggest a step-by-step approach. Kick off with the following mapping I suggest and then enhance it from there ...
.state('mapping', {
abstract: true,
authenticate: true
})
.state('mapping.all', {
url: '/',
templateUrl: 'app/components/mapping/partials/all.html',
controller: 'MapCtrl as map',
})
.state('mapping.project', {
url: '/project/:projectName',
templateUrl: 'app/components/mapping/partials/project.html',
controller: 'ProjectCtrl as proj',
})
I have a state that looks like something like this
.state('home.foo.bar', {
url: 'view?one&two',
views: {
'base': {
templateUrl: 'blah',
controller: 'blahCtrl'
}
},
reloadOnSearch: false
})
if I navigate to this state in my template with
ui-sref="home.foo.bar({one: valueOne, two: valueTwo})"
all is well and the url is
.com/view?one=valueOne&two=valueTwo
however if I refresh the page the url changes to just .com/view and neither $state.params or $stateParams contains the values.
if I add a resolve step like so
.state('home.foo.bar', {
url: 'view?one&two',
views: {
'base': {
templateUrl: 'blah',
controller: 'blahCtrl'
}
},
resolve: {
p: function($location, $stateParams, $state) {
console.log($location.search(), $stateParams, $state);
if(!$stateParams.one && !$stateParams.two {
$stateParams.one = $location.search().one;
$stateParams.two = $location.search().two;
} else {
return $location.search().one;
}
}
},
reloadOnSearch: false
})
then when I initially navigation to the state in my template the log statement prints nothing for $location.search() but shows oneValue and twoValue in the $stateParams and $state.params. When I refresh its opposite $location.search() shows the parameters and their values correctly but $stateParams and $state.params are empty.
adding the if statement and setting the values explicitly resolves my bug but this cant be working as intended can it?
am I missing something obvious?
I think it might be because you are missing a slash at the beginning or your URL:
.state('home.foo.bar', {
url: '/view?one&two',
views: {
'base': {
templateUrl: 'blah',
controller: 'blahCtrl'
}
},
reloadOnSearch: false
})
I had the same problem. However it turned out that the problem is not angular. Check your routing on the server, there may change url.
In my case, I interfered the regular statechange/statestart/statesuccess
so in my resolving in statesuccess I have added:
//$state.go(toState,toParams);
//when I manupulated the regular ui-router params
$state.go($rootScope.toState,$rootScope.toParams);
So ve aware of passing toParams when you change the regular ui-router parameters control.