I have nested states, with the parent and child state having a separate controller. But only the parent state is getting executed.
I have the url structure: #/restaurant/2/our-food
So, I want it to load the restaurant with the ID 2 and then the child controller load the 'our-food' content and take care of some other functions.
My code is:
var app = angular.module("app", ['ui.router']);
app.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('home', {
url: '/',
controller: function($scope, $stateParams) {
$scope.setRestaurant(0);
}
})
.state('restaurant', {
url: '/restaurant/:restaurantId',
controller: function($scope, $stateParams, $state) {
console.log('first');
if($stateParams.restaurantId > 0) {
$scope.setRestaurant($stateParams.restaurantId);
}
else {
$state.go('home');
}
}
})
.state('restaurant.our-food', {
url: '/our-food',
templateUrl: 'templates/our-food.html',
controller: function() {
console.log('second');
}
});
});
The controller for your 'restaurant.our-food' state is not being executed because its parent state has no template. This means there is no ui-view directive for it to attach its own template and controller. Even if your parent directive doesn't do anything other than setup some state, it needs to provide at the very least a minimal template.
Try adding the following to your 'restaurant' state and see if that makes it work for you:
template: "<div ui-view />"
This is documented in the ui-router docs:
Remember: Abstract states still need their own for their children to plug into. So if you are using an abstract state just to
prepend a url, set resolves/data, or run an onEnter/Exit function,
then you'll additionally need to set template: <ui-view />'.
https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views
Related
If I have a parent route with a controller, and a child route with no controller, shouldn't the children see the $scope data from the parent.
.state('parent', {
url: '/parent/{idParentRecord}?extraInfo',
templateUrl: 'app/parent/parent.html',
controller: 'ParentController',
controllerAs: 'aparent'
})
.state('parent.child', {
url: '/child/{idChild}',
templateUrl: 'app/parent/child.html'
})
Can I then just access properties in the parent $scope, as if they were in the child scope, so in the child view could I say:
<h1>{{name}}</h1>
Assuming $scope.name has a value in ParentController's $scope?
If that's the case, and the controller is loading some slow data, when the parent $scope.data.subdata.mydata finally loads, if I have
<p>{{data.subdata.mydata}}</p>
in my Controller-less parent.child state view, it should just show up when that data loads... right? Even if maybe I'm asking for something based on my childstate like calling a $scope.myfunc(idChild) that is filtering an array for me like
<div ng-repeat="myparticle in getMyParticles($stateParams.idChild)">
<p>{{myparticle.aproperty}}</p>
Thanks for the help!
Yes your thinking is correct as long as your child view is nested within your parent view. Scope properties will only inherit down the state chain if the views of your states are nested.
See the ui-router wiki section Scope Inheritance by View Hierarchy Only for more information.
I made a quick demo in plunker so you can see this:
http://plnkr.co/edit/0EOdaXIiJhhoKElQnHfo?p=preview
States:
angular
.module('app', ['ui.router'])
.config(function ($stateProvider) {
$stateProvider
.state('parent', {
url: '/parent',
templateUrl: 'parent.html',
controller: 'ParentController',
controllerAs: 'aparent'
})
.state('parent.child', {
url: '/child',
templateUrl: 'child.html'
});
})
.controller('ParentController', function () {
var vm = this;
vm.name = 'Test Name';
});
parent.html:
<!-- Parent View -->
<h2>Parent View</h2>
<!-- This is where the child view will show -->
<ui-view></ui-view>
child.html
<!-- Child View -->
<h2>Child View</h2>
{{aparent.name}}
Here is an example to check http://embed.plnkr.co/uVMlkk/preview
When we navigate to 'page2' route there is a 'hey, I'm a subroute' note.
But once we navigate anywhere else that note will disappear forever.
The goal is to make some nested states to be shown right away (as a default ones).
I assume there should be some cases using $state.go(), but can't figure it out so far. Any help is highly appreciated.
State definition snippet:
.state('root.page2.tab', {
url: '/:tabId',
templateUrl: 'tpl.page2.tab.html',
controller: 'Page2TabController'
})
.state('root.page2.tab.subroute', {
url: '',
templateUrl: 'tpl.page2.tab.subroute.html'
})
the content of the 'tpl.page2.tab.subroute.html':
hey, I'm a subroute
related controller:
.controller('Page2TabController', ['$scope', '$state', function($scope, $state) {
$scope.tabId = $state.params.tabId;
$state.go('root.page2.tab.subroute');
}])
There is a fixed version.
I removed the url from the 'root.page2.tab.subroute'
.state('root.page2.tab.subroute', {
//url: '',
templateUrl: 'tpl.page2.tab.subroute.html'
})
And because the parent has defined paramater tabId:
.state('root.page2.tab', {
url: '/:tabId',
templateUrl: 'tpl.page2.tab.html',
controller: 'Page2TabController'
})
We have to pass that param inside of the redicrection:
.controller('Page2TabController', ['$scope', '$state', function($scope, $state) {
$scope.tabId = $state.params.tabId;
// instead of this
// $state.go('root.page2.tab.subroute');
// we need this
$state.go('root.page2.tab.subroute', $state.params);
}])
Check the working, fixed version here
ANOTHER approach - using redirectTo - there is a working plunker
One way, inspired by this:
Redirect a state to default substate with UI-Router in AngularJS
could be to add a very smart but small redirect code snippet:
.run(['$rootScope', '$state', function($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function(evt, to, params) {
if (to.redirectTo) {
evt.preventDefault();
$state.go(to.redirectTo, params)
}
});
}])
And adjust our state like this:
.state('root.page2.tab', {
url: '/:tabId',
templateUrl: 'tpl.page2.tab.html',
controller: 'Page2TabController',
redirectTo: 'root.page2.tab.subroute',
})
Check it here
There is a trick how to handle scenarios:
Parent should trigger some action in case that
it is accessed, or
its reached again, when navigating back from child in a parent state
In that case, we can use the "target (ui-view) for a child" as a place where sits the special view, with special controller. This will be
injected into that position once parent is created and
re-injected into that position again, once child is left. In that case, it will be re-init.
Enough explanation. There is a working plunker. There is adjusted state:
.state('root.page2', {
url: '/page2',
views: {
'content#root': {
templateUrl: './tpl.page2.html',
controller: 'Page2Controller'
},
'#root.page2': {
template: '<div></div>',
controller: 'RedirectorController'
}
}
})
So, now we can do some magic inside of our 'RedirectorController'
.controller('RedirectorController', ['$scope', '$state',
function($scope, $state) {
$state.go('root.page2.tab', { tabId: $scope.activeTabId });
}])
Check it in action here
Read more about what that new view/controller get from the other (Scope Inheritance by View Hierarchy Only) one here
Nested states or views for layout with leftbar in ui-router?
How do I share $scope data between states in angularjs ui-router?
I am trying to build an app which on login changes the state to a view with $state.go but when calling $state.go the controller is not instantiated as defined for that state.
Here is my state change logic (removing other code for brevity):
config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('login', {
url: "/",
templateUrl: "templates/login.html",
controller: 'LoginController'
})
// setup an abstract state for the tabs directive
.state('tab', {
url: "/tab",
abstract: true,
templateUrl: "templates/tabs.html"
})
// Each tab has its own nav history stack:
.state('tab.dash', {
url: '/dash',
views: {
'tab-dash': {
templateUrl: 'templates/tab-dash.html',
controller: 'DashboardController'
}
},
resolve: {
authenticated: ['RestService', function(restService) {
return restService.authenticationStatus();
}],
sessionService: ['SessionService', function(sessionService) {
return sessionService;
}]
}
})
My LoginController is something like:
// Perform the login action when the user submits the login form
$scope.doLogin = function(loginForm) {
if (loginForm.$invalid) {
$log.debug('Invalid Form...', $scope.loginData);
return;
}
$log.debug('Doing login', $scope.loginData);
RestService.login($scope.loginData)
.then(function(data) {
$log.debug("Inside loginController...");
$log.debug(data);
$scope.$state.go("tab.dash");
}, function(data) {
$log.debug(data);
$scope.formErrors = data.errors;
});
};
And my DashboardController is something like:
angular.module('starter.controller.dashboard', [])
.controller('DashboardController', ['$scope', '$log', '$http',
function($scope, $log, $http) {
$log.debug("reaching here..................");
$log.debug($scope.authenticated);
}]);
Now when the login succeeds, the state is transitioned to /tab/dash but the controller is not instantiated i.e. the debug logs in DashboardController are not printed. If I directly navigate to /tab/dash then the controller does get instantiated and I do see the logs getting printed.
Moreover the value of "authenticated" passed via resolve in state definition is not available via scope in templates.
Well turns out that the controller is getting instantiated but it gets instantiated only once. If I don't refresh the page while testing and just change the path then since the controller is already instantiated, it is not instantiated again. Only if I refresh the page (LoginPage) and then navigate to Dashboard page (via $state.go on logging in) the controller gets instantiated again.
And the issue with resolve data not available in the controller is because my assumption was that it is auto injected in $scope but actually it is not so. The resolve params get injected explicitly via the passed params to constructor function of controller and then one needs to assign the values manually in the scope. Something like:
.controller('DashboardController', ['$rootScope', '$scope', '$log', '$ionicLoading', '$http', 'authenticated',
function($rootScope, $scope, $log, $ionicLoading, $http, authenticated) {
$scope.authenticated = authenticated;
}]);
I have a cenario where I have a list and want to reorder it. I'm still new on ui-router and I couldnt figure out how to do it.
My states is something like this (with resolve to pass the data):
$stateProvider
.state('shoppings-view', {
url: '/shoppings/:id',
templateUrl: 'shoppings/partial/shoppings-view/shoppings-view.html'
}).state('shoppings-view.order', {
url: '/:order',
templateUrl: 'shoppings/partial/shoppings-view/shoppings-view.html'
});
And here my controller:
angular.module('shoppings').controller('ShoppingsViewCtrl',function($scope, $stateParams){
$scope.order = $stateParams.order != undefined ? $stateParams.order : 'id';
}
And a simple view with use a ng-repeat to show them.
The problem is: if I'm in the url: /shoppings/1 and change the link to /shoppings/1/date the controller is not recalled and I can't change the order. So, how can I do something like that?
This scenario with ui-router could have two (if not even more) solutions. Check both of them here in this working example. We can firstly continue with your Paren-Child scenario, we just have to do few changes
// Parent - Child Scenario
.config(['$stateProvider',
function($stateProvider) {
// parent child
$stateProvider
.state('shoppings-view', {
url: '/shoppings/:id',
templateUrl: 'tpl.shoppings-view.html',
controller: 'ParentCtrl',
})
.state('shoppings-view.order', {
url: '/:order',
template: '<div>some child content if needed</div>',
controller: 'ChildCtrl',
});
}])
.controller('ParentCtrl', function($scope, $state, $stateParams, DataSvc) {
$scope.data = [];
// parent declares method on a $scope
$scope.load = function(params){
$scope.data = DataSvc.getAll(params)
}
$scope.load($stateParams);
})
.controller('ChildCtrl', function($scope, $state, $stateParams) {
// child scope has inherit that function
// and calls the parent to relaod with new params
$scope.load($stateParams);
})
What we can see here, is that child gets the $scope inherited (see Understanding Scopes) and therefore has access to a parent method $scope.load($stateParams);. Whenever there is new child state with new param invoked, it calls parent to relaod the data.
Maybe not the best here, but the concept of published methods on parent $scope, available for a child(ren) is a feature I do use a lot...
Second approach could be to move all that stuff into one simple state, with more params:
// scenario with Single state
.config(['$stateProvider',
function($stateProvider) {
// single state
$stateProvider
.state('shoppings', {
url: '/shoppings-single/:id/:order',
templateUrl: 'tpl.shoppings-single.html',
controller: 'SingleCtrl',
resolve : {
data : function($stateParams, DataSvc){
return DataSvc.getAll($stateParams)
}
}
});
}])
.controller('SingleCtrl', function($scope, $state, $stateParams, data) {
$scope.data = data;
$scope.orderBy = $stateParams.order;
})
There is nothing special, just we can see that one state can have more params (see URL Parameters)
All that together check here
You have the same template for the parent and child state, which doesn't make much sense. Are you nesting an identical template inside itself?
A child state includes its parent state. So anything in the parent state template is included in the child state.
I have the following setup in my code
.config(function config($stateProvider)
$stateProvider
.state('home', {
url : '/home',
views : {
'main' : {
controller : 'HomeCtrl',
templateUrl : 'home/home.tpl.html'
}
}
})
.state('home.details', {
url : '/details',
views : {
" " : {
template : "<h1>hello</h1>",
controller : function ($scope, $http, $state) {
//do some stuff here
//does not seem to reach code in here
}
}
}
});
})
.controller('HomeCtrl', function ($scope, $http, $state) {
//on a button click do $state.go('.details');
});
When I do this , the button click on my HomeCtrl seems to take me to /home/details but it does not seems to go inside the controller in that particular route at that point. (I checked by putting a break point inside the controller for the details.) Is there something wrong with my setup? I'm trying to do something similar to this sample app shown in the ui-router webpage.
The solution here would in a named-view (not) matching. Here is the working plunker.
We have to place the named ui-view inside of the parent view (or use more precise naming, see for example here)
So, the parent, home template should contain the named ui-view, e.g. nameOtherThanSpace
<div ui-view="nameOtherThanSpace" ></div>
And the child defintion must target that view, the complete snippet is:
$stateProvider
.state('home', {
url: '/home',
views: {
'main': {
controller: 'HomeCtrl',
template: '<div>' +
'<h1>hello from parent</h1>' +
'<hr />' +
'<div ui-view="nameOtherThanSpace" ></div>' +
'<div>',
}
}
})
.state('home.details', {
url: '/details',
views: {
"nameOtherThanSpace": {
template: "<h2>hello from a child</h3>",
controller: function($scope, $http, $state) {},
}
}
});
How to use more specific view names:
View Names - Relative vs. Absolute Names
UI-Router isnt rendering childs correctly with templateurl in Asp.net Mvc
The working plunker using the name nameOtherThanSpace, instead of " " (space)
Try registering your controller on the app instead of on your $stateProvider. e.g.
var app = angular.module('myApp', []);
app.controller('HomeCtrl', function ($scope, $http, $state) {
//on a button click do $state.go('.details');
});
Update 1:
You should only need to specify a view if you have multiple views in which case the view probably needs to have a name. But you only have one view for that state so I would just do this.
.state('home.details', {
url : '/details'
template : "<h1>hello</h1>",
controller : function ($scope, $http, $state) {
//do some stuff here
//does not seem to reach code in here
}
}