I'm developing an application where I have the use for a child state. The purpose of making a child state is
To inherit the resolve
To inherit the url base
When I navigate to the child state, the parent state's view and controller is initialized. The child controller isn't initialized at all, and the view isn't showing at all. One thing that I think is weird is that the child view is actually loaded over XHR, but then it never seems to appear.
How can I make the child state's view appear, and the child state's controller initialize with resolves from the parent state?
.state('restaurant', {
url: "/{city:[a-zA-ZåäöÅÖÄ]{2,}}/{restaurantUrl:[a-zA_ZåäöÅÄÖ\-]{2,}}",
views: {
"main": {
templateUrl: "/views/restaurant.html",
controller: "RestaurantCtrl",
resolve: {
restaurant: function($q, $stateParams, RestaurantsSrvc) {
/*Some stuff going on that returns a promise*/
}
}
}
}
})
.state('restaurant.checkout', {
url: "/checkout",
views: {
"main": {
templateUrl: "/views/checkout.html",
controller: "CheckoutCtrl"
}
}
})
Add the <div ui-view="main"></div> to restaurant.html. Populating ui-view elements outside of the parent template is apparently not possible.
Also make sure that there is one ui-view per template with child states. When you have only one place to insert child templates, don't use named views, they are for cases where multiple child views need to be shown at the same time. The sketch in the documentation illustrates this use case nicely.
Also note that by default the parent view is shown when a child state is active, because the ui-view for the child is within the parent template. If you need to hide the parent stuff (or just parts of it) use ng-hide with the $state service as indicated by this answer.
Related
How to configure in ui-router a parent/children relationship where siblings coexist in parent page? I can't make child state multiple named views working.
This is my parent page:parent.html:
<div> I have two child pages:
child page1 detail: <ui-view /> and
child page2 detail:<ui-view />.
I need both pages
</div>
I don't know how to or if I should use multiple-named views since multiple named views seem parallel and separable rather than wrapped around by other text like in the code above.
My ui router config:
$stateProvider
.state('parent', {
url: '/parent',
templateUrl: 'parent.html'
})
.state('parent.children', {
url: '/children',
views: {
'child1': {
templateUrl: 'child1.html'
},
'child2': {
templateUrl: 'child2.html'
}
}
});
The unnamed ui-view only allows one child to be plugged in.
See code in
Plunker
The <ui-view>s in your example are incorrect. It's not a self-closing tag and when ui-view is used as the element instead of an attribute directive you need a name attribute to use named views. If you change your parent.html to the following it should work.
<div> I have two child pages:
child page1 detail: <ui-view name="child1"></ui-view> and
child page2 detail:<ui-view name="child2"></ui-view>.
I need both pages
</div>
Edit: Here is the complete code at Plunker. Though I can not c anything in execution but same code working at local. However gives a console error though
It all works perfect. But due to :id in /news/:id/, i am getting jquery/angular errors in console which can not be tracked anywhere in my code
I can not c What i am doing wrong.
Edit: Solved plunker https://plnkr.co/edit/FWcuBgGpVdMj3CroFrYJ
First of all you are trying to use ui-router but you're including ngRoute script in your plunker. Change it to
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.1/angular-ui-router.min.js"></script>
Then everything should work fine!
I suggest you a few changes...
1. Use ui-sref instead of href because it's much easier to define
ui-sref="post({id:1})" which turns into href="#/news/1"
If you would like to change url some day, then you will have to just change your route file, not each href.
$stateProvider
.state('post', {
url: "news/:id"
or
$stateProvider
.state('post', {
url: "archive/:id"
or
$stateProvider
.state('post', {
url: "whatever/:id"
2. Use abstract state
In your example it's a way better to define abstract state which holds header, content and footer - it's a typical use case.
ui-router
Abstract States
An abstract state can have child states but can not get activated
itself. An 'abstract' state is simply a state that can't be
transitioned to. It is activated implicitly when one of its
descendants are activated.
Some examples of how you might use an abstract state are:
To prepend a url to all child state urls. To insert a template with
its own ui-view(s) that its child states will populate. Optionally
assign a controller to the template. The controller must pair to a
template. Additionally, inherit $scope objects down to children, just
understand that this happens via the view hierarchy, not the state
hierarchy. To provide resolved dependencies via resolve for use by
child states. To provide inherited custom data via data for use by
child states or an event listener. To run an onEnter or onExit
function that may modify the application in someway. Any combination
of the above. 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:
"".
Here's a plunker which shows how I would do it.
https://plnkr.co/edit/5FvJaelyxdl5MuALt5VY?p=preview
Hope it helps.
Look at the documentation for ui router named views,
You can use following syntax for using multiple views
$stateProvider
.state('state',{
url: '',
views: {
'header': {
templateUrl: 'views/header.html',
controller: 'headerCtrl'
},
'content': {
template: '<div ui-view=" "></div>', //<-- child templates loaded to here
},
'footer': {
templateUrl: 'views/footer.html',
controller: 'footerCtrl'
}
}
})
.state('state.post', {
url: 'news/:id/:KeyWords'
templateUrl: 'views/post.html' //<-- This goes into content's ui-view
});
I'm guessing you want to keep the header and footer and change content views.
You can achieve this by making this state as parent to all other states
suppose
.state('main',{
abstract: true,
views: {
'header': ... ,
'content': {
template: '<ui-view></ui-view>',
}
'footer': ...
}
})
then all the child views will load their views in the ,
ex: in main.child etc, your template will load in the content's <ui-view></ui-view> tag
If you need to use a custom template depending on keywords you can do the following:
.config(['$routeProvider',
function($routeProvider, $routeParams) {
$routeProvider
.when('/news/:id/:keyWords', {
template: '<div ng-include="url"></div>',
controller: "exampleController"
})
then in the exampleController
function($routeParams, $scope) {
$scope.url = $routeParams.keyWords;
}
I have a simple form that can be used to initiate a forecast request. I created this as a parent state requests (Initiate Forecast).
Desired behavior
When a request is submitted, that immediate request is shown in a child state (View Status) as most recent request. This View Status state will also hold a grid of all past requests, meaning I will be refreshing the grid with data every time this state is invoked.
Both parent and child states are navigable from a sidebar menu.
So, if a user clicks on parent (Initiate Forecast), he should be able to see only the form to submit a request. If a user directly clicks on the 'View Status'(child), then he should be able to see both the form and the grid of requests.
app.js
function statesCallback($stateProvider) {
$stateProvider
.state('requests', {
url: '',
views: {
'header': {
templateUrl: 'initiateforecasting.html',
controller: 'requestsInitiateController'
},
'content': {
template: '<div ui-view></div>'
}
},
params: {
fcId: null,
fcIndex: null
}
})
.state('requests.viewStatus', {
url: '/ViewStatus',
templateUrl: 'viewstatus.html',
controller: 'requestsStatusController'
});
}
var requestsApp = angular.module('requestsApp', ['ui.router']);
requestsApp.config(['$stateProvider', statesCallback]);
requestsApp.run(['$state', function($state) {
$state.go('requests');
}]);
Plunker of my attempts so far.
This is all working for me as shown in the plunker, but I am doing it by not setting a URL to the parent. Having no URL for the parent state is allowing me to see both states. If I add a URL to parent state, then clicking on View Status link is not going anywhere (it stays on Initiate).
How do I change this so that I can have a URL for parent state and still retain the behaviour I need from the parent and child states as described above?
Note: I am fine without the URL for parent state in standalone sample code, but when I integrate this piece with backend code, having no URL fragment on the parent state is making an unnecessary request to the server. This is visible when I navigate to the child state and then go to the parent state. It effectively gives the impression of reloading the page which I think is unnecessary and can be avoided if a URL can be set to the parent state.
You shall not directly write url when using ui.router, try like this:
<a ui-sref="requests.viewStatus">View Status</a>
You are writing state name in ui-sref directive and it automatically resolves url. It's very comfortable because you can change urls any time and it will not break navigation.
I am confused about when controllers get instantiated. Also, how do controllers gets instantiated when nesting states. I might be confused how scope gets attached to view and controller, that is, if every view gets its own controller and scope or do they share the same scope.
Can someone please explain when controllers get instantiated? Under nested routes do all the views share one controller and scope? What happens when I switch states and go back to a state does another controller get instantiated?
Below are my routes(config file ):
.config (googleAnalyticsCordovaProvider, $stateProvider, $urlRouterProvider, IdleProvider, KeepaliveProvider) ->
$stateProvider
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'templates/menu.html',
controller: 'AppController'
})
.state('app.pincode', {
url: '/pincode',
views: {
menuContent: {
templateUrl: 'templates/pincode-yield.html',
controller: 'PincodeController'
}
}
})
.state('app.pincode.create', {
url: '/create',
views: {
pincode: {
templateUrl: 'templates/pincode-create.html',
controller: 'PincodeController'
}
}
})
.state('app.pincode.pincodeLogin', {
url: '/login',
views: {
pincode: {
templateUrl: 'templates/pincode-login.html',
controller: 'PincodeController'
}
}
})
.state('app.pincode.settings', {
url: '/settings',
views: {
pincode: {
templateUrl: 'templates/settings.html',
controller: 'PincodeController'
}
}
})
To get even more detailed answers, we can/should observe the source code and check the documentation. Let me try to explain all three questions (and also cite from code and doc).
1. When do controllers get instantiated?
Here we can observe the code of the ui-view directive:
[$ViewDirective.$inject = \['$state', '$injector', '$uiViewScroll', '$interpolate'\];][1]
Controllers are related to views. Those views, which are defined inside of a .state() as the views object:
.state('...', {
// The view definition
views : {
'' : {
template: ...
controller: ...
resolve: ..
}
},
resolve: ...
}
So, whenever is view (the ui-view) filled with settings defined inside of a state view, it acts almost as a standard, but enhanced directive.
1) Template is found,
2) Resolves are resolved
...
x) Controller is instantiated...
View targets (ui-view directives) could use names, and could be filled by different states in the hierarchy.
It could mean, that there could be a content inside of one view (e.g. title), defined by parent as well as replaced by child
// parent
.state('parent', {
views : {
'' : {...} // the main parent view, with ui-view="title"
'title#parent' : { ...} // here we go and fill parent's ui-view="title"
},
...
}
// child
.state('parent.child', {
views : {
'title' : { ...} // here we change the parent's target ui-view="title"
},
...
}
The above state definition will (whenever we transition among these two states) do:
The $state.go('parent') - the view (template, controller...) defined in 'title#parent' : { ...} will be injected into target ui-view="title" and instantiated as described above
The $state.go('parent.child') - almost the same, just the view will be taken from child state/view defintion 'title' : { ...}. That will replace the content of the ui-view="title" and will be instantiated as described above
This will be happening every time we do go from parent to child and from child to parent.
2. Under nested routes do all the views share one controller and scope?
A simple answer is NO, there is no common sharing.
In fact, each controller has its own scope, the one which is created from parent view scope. Firstly the documentation:
What Do Child States Inherit From Parent States?
...
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
So, whenever is our controller (well the view with template, controller...) injected into parent's target ui-view="..." it gets inherited scope:
newScope = scope.$new();
That in a nutshell means that JS objects (e.g. scope.Model = {}) can be shared among child and parent.
$scope.Model.id = 1; // will refer to the same id in both parent & child
However, basic Javascript types are not passed by reference, and so their values are not automatically synchronised between scopes:
// set in parent
$scope.id = 1;
// in child after inherted still === 1
$scope.id = 2; // now 2 for a child, different value in parent - still === 1
It's worth reading more about prototypical inheritance here:
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
3. What happens when I switch states and go back to a state - does another controller get instantiated?
It depends.
If the parent sub view (remember ui-view="title" above) is replaced by child view, and then it is re-created (transitioning from child to parent) - such controller wil be re-initialized (discussed above).
But when we speak about the main parent view (usually unnamed), which represents the parent (For example the unnamed view below with controller 'ParentMainCtrl')
.state('parent', {
views : {
'' : { // // the main parent view
controller: 'ParentMainCtrl',
}
'title#parent'
'tooltip#parent'
},
Then we can be sure that such controller is NOT re-instantiated. It lives during the lifetime of all its children, plus a parent's one (no child state selected).
To re-load this view/controller, we have to use an option reload
$state.go(to, params, options)
...
options Options object. The options are:
...
reload - {boolean=false}, If true will force transition even if the state or params have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd use this when you want to force a reload when everything is the same, including search params.
Hope that helps a bit. For further information, check out these resources:
Nested States & Nested Views
Multiple Named Views
API Reference
State.js of the sample application - I would say the best documented piece of code ever
Controllers get instantiated whenever you visit the specific state. For example, while visiting app.pincode.pincodeLogin for the first time one AppController and two PincodeControllers are constructed, each with its own view assuming you got the templates right. Switching to 'app.pincode.settings' would destroy the innermost controller and replace it with a new one, althought the two controllers higher at the hierarchy will not be touched. Scopes follow the standard AngularJS' inheritance pattern, they are not isolated.
You probably would want to remove the controllers in the sub states (and handle the business logic in the parent controller) or have a distinct controller for each state - the same controller for different templates and views is usually a sign of bad design.
Controllers get instantiated when the corresponding views are loaded for the first time.
For example if you have 3 tabs associated with 3 controllers - then the controller associated with the default view instantiates First. Next when you load the other views the associated controllers also get instantiated.
But interestingly, once a view is loaded in the DOM - it is cached by-default. When a view is navigated away from, its element is left in the DOM, and its scope is disconnected from the $watch cycle. When navigating to a view that is already cached, its scope is then reconnected, and the existing element that was left in the DOM becomes the active view.
I'm trying to decide on an Angular Structure for my app. I started with the Angular Full Stack Generator.
In UI-Router, you can nest states. It seems logical for me to nest related states. ex: Registration, Registration.form, Registration.confirmation.... but those nested states seem to rely on each child view and parent view being viewed on the same page.
Meaning the parent will have an <ui-view> tag that will insert the child view when called.
Is it possible to have a nested state replace the parent?
Or do I have just make unnested states?
Or is this a case for an abstract state?
There is a working example
Child replacing parent is almost "native" UI-Router solution. Let's have this state definitions:
.state('parent', {
url: "/parent",
templateUrl: 'tpl.html',
})
.state('parent.child', {
url: "/child",
templateUrl: 'tpl.html',
controller: 'ChildCtrl',
})
And this would be the tpl.html, in our case used for both states:
<div ui-view>
<h3>current state name: <var>{{$state.current.name}}</var></h3>
...
</div>
So, what we can see, parent root HTML element, does have attribute ui-view. That means, that this element also plays a role of a target for a child.
Check it in action here