Hi I'm trying to create a nested route inside an already nested route. In synthesis, I have a portal which contains my sidebar and some other fancy stuff and there I can navigate to clusters. From clusters I should be able to select one cluster and view its details.
My "grandparent" route is called "portal" and the parent "clusters". Here's the code for clusters-route
(function () {
'use strict';
angular.module('ap.clusters.RouteConfig', ['blocks.router','ncy-angular-breadcrumb', 'ap.clusters.ClusterDetailsCtrl'])
.run(onRun);
/* #ngInject */
function onRun(routerHelper) {
routerHelper.configureStates(getStates());
}
function getStates() {
return [
{
state: 'portal.clusters',
config: {
url: '/clusters',
templateUrl: '/views/clusters/clusters.html',
controller: 'clustersCtrl',
controllerAs: 'vm',
title: 'Clusters',
ncyBreadcrumb: {
label: 'Clusters'
},
settings: {
nav: 4,
content: '<i class="fa fa-cubes nav-icon"></i>a<span>Clusters</span>'
}
}
}
];
}
})();
Here's the nested route I'm trying to include:
(function () {
'use strict';
angular.module('ap.clusters.clusterDetailsRouteConfig', ['blocks.router', 'ncy-angular-breadcrumb', 'ap.clusters.ClusterDetailsCtrl'])
.run(onRun);
/* #ngInject */
function onRun(routerHelper) {
routerHelper.configureStates(getStates());
}
function getStates() {
return [
{
state: 'portal.clusters.cluster-details',
config: {
url: '/cluster/:id',
templateUrl: '/views/clusters-details/clusters-details.html',
controller: 'clusterDetailsCtrl',
controllerAs: 'vm',
title: 'Cluster',
ncyBreadcrumb: {
label: '{{vm.cluster.name}}'
}
}
}
];
}
})();
The routing seems to be working fine, since the url is showing ....portal/clusters/cluster/0 or whatever index I select, but the html is not rendering. When I click the link just the url changes. Here's how i call the routing from clusters view (using jade)
a(ui-sref="portal.clusters.cluster-details({id: $index})")
Don't really know whats wrong with it, why isn't the html showing
I recently inherited this project that anram posted the question about. The issue he describes actually arose due to the nature of the site. The site actually has some fairly complex routing and breadcrumb needs (complex from a development standpoint, rather simple and strait forward from a usability standpoint). For more details on that, and a breadcrumb generation issue I am having with this same project, please read my question here.
In the specific case that anram was asking about, we have a portal layout with a banner bar, sidebar menu, table listing views, and detail views. Every one of the listing views can be navigated to directly from the sidebar menu. The detail views can all be navigated to via two means, either from the listing views reachable via the sidebar menu, or by child listing views from some virtual "parent" detail view. There are a number of different levels in this virtual hierarchy, so the navigation paths to get down to the lowest leaf level can be quite short, or quite long, depending on exactly how you navigate.
The problem that anram encountered was due to the fact that all detail views were originally configured to be child states of the listing states in ui-router, when in actuality they were not. They were siblings in the literal sense. A detail view was not a child view embedded in a <div ui-view /> somewhere on the listing views...they were entirely separate views. A such, the name portal.clusters.cluster-detail was incorrect. It implied that the clusters state should be loaded first, and that cluster-detail would be loaded into a ui-view somewhere in that view. There was no ui-view anywhere in the clusters view, so cluster-details did not load. The navigation via ui-router was working, and working as described...the problem wasn't ui-router, the problem was the simple fact that there was no second level of nested state.
I recently refactored the ui-router states, making all detail views direct children of the portal root view, so we now have portal.clusterDetails instead of portal.clusters.cluster-detail. That solved one problem, the detail views now load properly, and we can route to them from any other view (since pretty much every view is now a sibling, and all are children of portal.) This has given rise to another issue, however, regarding breadcrumb generation (see my linked question above if you have more interest in reading about that.)
I created working plunker here.
I would say, that your setting seems to be ok. The only (hidden here) place, which could be wrong is the content of the
templateUrl: '/views/clusters/clusters.html',
Inside of the state: 'portal.clusters'. What could be wrong there?
This is in fact a parent view for state: 'portal.clusters.cluster-details'. And that means, that we need to insert an anchor (ui-view) there:
...
<div ui-view=""></div>
That allows our child to be rendered
Check it in action here
Related
We have recently move our application to Ui-Router from ng-route. And we are trying to refactor some points in our code regarding routing.
We have a list view and a detail view in our application. The detail view will be shown as a pop-up on top of the list view, so in order not to initialize all list controller logic again and again, we have defined our detail view as a child view-state. It looks like this:
$stateProvider
.state('list',
{
url: "/",
template: '<list-directive></list-directive>'
})
.state('list.detail',
{
url: "/detail/{item_id}",
template: '<detail-directive></detail-directive>'
})
It actually works as expected. When i open a detail view from a list view, the list view (i mean the controller) does not run again, and when i close the detail view, the list view remains.
But now we would also like to call the detail view DIRECTLY, without revoking the parent. Currently when i directly call the detail state, the parent controller runs also.. How can i achieve this?
Is a parent - child relationship not a appropriate one for our scenario?
According ui-router doc :
Child states can be used to drill down from a more general feature to
more specific one, or to implement a master/detail pattern. (...) The child state’s view usually renders inside a viewport that the parent state created. This is referred to as Nested Views (...) These substates share the common parent state contacts and inherit data and behavior from the parent.
So I think you should use it if you don't want these behaviour .
It did work like this:
$stateProvider
.state('list',
{
url: "/",
template: '<list-directive></list-directive>'
})
.state('detail',
{
url: "/detail/{item_id}",
template: '<detail-directive></detail-directive>'
})
.state('list.detail',
{
url: "detail/{item_id}",
template: '<detail-directive></detail-directive>'
})
But the angular is very fragile.. and behaves very different with very small changes.. the slashes are the problem.
Child state list.detail does not have a leading slash in its URL. But our new state detail (which has exctly the same content as the list.detail) should have a leading slash so it can be called directly. So We have the same state both as child and as parent.
Now when we call directly the URL "host/detail/5" we go to detail view without revoking the parent. And we we call ui-sref=".detail(5)" we go to child state (list.detail) within the parent
So I want to have two big pages. I cannot use ui-view because many widgets/ sections of the views are shared (they remain on the screen without loading). The moment a user clicks on a button, the app should move to a different state with merely other sections changing with some animations.
Now, what I tried till now is nested views. But these are only one directional top down architecture. I could not find a way to share views.
I am not using mere ng-include or a directive because I want it to follow a state by state approach keeping intact the history with a URL mapped to it's own template.
The chances are many views are going to reuse/ share the inner views like widgets.
Config
$stateProvider.state('dashboard',{
url : "/dashboard",
views : {
'dashboard' : {
templateUrl: './templates/dashboard.html'
}
}
}).state('dashboard.personalDetails', {
url : '/personalDetails',
views : {
'main' : {
templateUrl: "./templates/personalDetails.html"
}
}
}).state('shipment',{
url : '/personalDetails',
views : {
localView1 : {}
localView2 : //reuse the personal details view
}
})
This is just one example, I basically want to reuse my views as widgets across many pages. Is it possible with ui-router? If yes, then how?
PS: Don't go on this specific example. The actual implementation is for a trading detail page in banking application. And due to policies I cannot share that code structure.
Not really sure what you are asking however you can always create objects to represent similar views and pass those objects to a state definition
var subviews_1={
'widget_1' {templateUrl: '..'},
'widget_2' {templateUrl: '..'},
}
$stateProvider.state('shipment',{
url : '/personalDetails',
views : subviews_1
})
I'm using ionic v1.0.0 and do not understand the way parallel history managed by $ionicHistory works.
Especially on Android devices, when using the (formerly hardware-)back button, my Angular app sometimes behaves strange and I'd like to understand why. (example: navigating back opens a view closed by $ionicGoBack() long time ago)
For me it seems like some of the ui-router navigations create new history stacks and others put the history items in the root history, even when going from state to sub-state should append to the history where state is recorded IMO.
Questions
Can anybody explain in which cases ui-sref or $state.go(...) append history items to a newly created stack?
When are they appended to root?
Are modals treated in a special way?
Sorry for not being more specific, but the app is rather complicated and I don't know how to isolate the problems in a single plunkr. Maybe I missed a piece of good documentation...
I'll try to answer this question even if you might not find all the info you're asking.
Lot of people - included myself - seem to struggle to understand how the navigation system and the history works.
I've answered a question earlier trying to explain why things don't work as expected.
It seems the navigation keeps track of each view the user has visited using a collection.
Actually there are 2 collections in the $ionicHistory object.
The first one $ionicHistory.viewHistory().views seems to keep track of each visited view in the current stack while the other $ionicHistory.viewHistory().histories keeps track of all the histories for the whole app.
You might have different types of history for tabs, sidemenus or regular views.
You can see how parallel independent histories work in this codepen.
There are 2 different histories there. One is for the home tab and the second is for the about tab.
Navigating through the sub-items in each tab and going back to the previous tab you'll notice that the navigation system has remember the previous state.
I've prepared another plunker here where you can see how the navigation works with some details displayed in the page.
The collection of views $ionicHistory.viewHistory().views is updated each time the user visits a new page (the current view in the collection is wrapped in square brackets).
If the view has been added to the collection it won't (it shouldn't) be added again.
You can change the behaviour clearing the history ($ionicHistory.clearHistory()) or setting the root for the current history:
$ionicHistory.nextViewOptions({
historyRoot: true
});
In the Invoice page of my plunker there's a green button (Other Root View). When pressed I set the new history root and change state:
$ionicHistory.nextViewOptions({
historyRoot: true
});
$state.go('otherviewroot');
Things work as expected and in fact now I don't have a back view and my stack contains only the current view.
Things get messed up when you try the sequence:
Home - Contacts - Invoices - Home (button in the header).
Now it's seems Ionic has lost control of the sequence and keeps on adding views to the collection.
Pressing the home button should clear the back button, since we are on the root for the current history, but it doesn't happen.
Using the same pattern over and over increases the size of the collection indefinitely.
I guess this is not the right behaviour and a fix is needed.
Going back to your question.
The back button in Android works ... meaning the it follows the same pattern.
Modals luckily are not treated as regular views and don't affect the collection.
As additional information to the previous post.
A history stack is always linked to an "ion-nav-view" template. So the root history stack is created for the first "ion-nav-view" item.
If you plan on using different history stacks I would advise to always link the ion-nav-view to a view name:
<ion-nav-view name="default"></ion-nav-view> instead of <ion-nav-view></ion-nav-view>
In this CodePen you have a RootStack for the tab pages and the other pages (named "tabs", "other", "other1") and then inside the tab pages you have one substack for each tab.
The tab pages and other pages run inside the Root ion-nav-view. During my tests I needed to assign a name to the root ion-nav-view and then reference this view name in controller. Otherwise the stack was always recreated when switching between the main pages (Other => Tabs => Other => ..)
Html for Root Stack:
<ion-nav-view name="default"></ion-nav-view>
Controller:
.state('other', {
url: "/other",
views: {
'default': {
templateUrl: "templates/other.html",
}
},
.state('tabs', {
url: "/tab",
abstract: true,
views: {
'default': {
templateUrl: "templates/tabs.html",
}
}
})
Inside the tabs template I assigned a new ion-nav-view for the different tab stacks (as it is done in the default ionic tabs template)
For example for the home-tab (and home stack) the Html looks like this (pages home, facts):
<ion-nav-view name="home-tab"></ion-nav-view>
Controller:
.state('tabs.home', {
url: "/home",
views: {
'home-tab': {
templateUrl: "templates/home.html",
},
}
})
.state('tabs.facts', {
url: "/facts",
views: {
'home-tab': {
templateUrl: "templates/facts.html",
},
}
})
I also submitted a bug report for the codepen above. It is wrongly switching back across histories.
https://github.com/driftyco/ionic/issues/4086
I'm not sure of my fix though.
Maybe the history feature needs to be redesigned more globally to address issues ..
I have a state named 'index' that, depending on the ui-view on the page, display different information and have different templateUrl and share the same URL:
$stateProvider.state('index', {
url: '/',
views: {
'all#': {
templateUrl: '/templates/partials/all/index',
controller: function(){
console.log('main');
}
},
'#': {
templateUrl: '/templates/partials/home/index',
controller: Controllers.Index,
controllerAs: 'academia',
resolve: {
templates: Preload.go(),
academia: ['Info', (Academia: Academia.Services.Info) => {
return Academia.get();
}],
plans: ['Planner', (Planner: ngPlanner.Planner) => {
return Planner.init();
}]
}
}
}
});
when the page is loaded with a <div ui-view></div>, it works as intended (matching the '#' rule). but when the page is loaded with <div ui-view="all"></div> it doesn't.
A plunkr showing the problem http://plnkr.co/edit/a2SWbB?p=preview
The resolve from the unnamed # shouldn't interfere with the all# view. The current resolve is being rejected because it doesn't belong on the current view (since there's no unnamed view when the page is loaded). Is it a bug, or am I doing something wrong?
At these days, the UI-Router implementation is driven by state management. And despite of the fact, that there are some voices/requirements to split the resolve of the views (well, not the parent view vs child view - but that is discussed below) ... I would say, current behavior in the Q&A described above is simply correct.
Firstly, what is the problem in above question? In general there are two or more sibling views, with their own resolve definitions:
views: {
'view1': {
....
resolve : { // could fail
}
},
'view2': {
...
resolve: { // could fail as well
...
What is state?
A state is a cluster of settings, representing the state. (sounds like self proving statement, but I am serious). All the settings are about state definition, about one, specific state.
We can use more views to represent it (e.g. title and content).
We can use some resolve statements (for all sibling views, for some of them) to support these views.
We have still only ONE state.
And that's it. It is all or nothing. The state is one, one cluster of settings, which all of them must be workign/resolved - or the whole state is failing.
Another story: parent - child
There are also some requirements to let the parent view to render once all its stuff is resolved. Why? Currently, if we navigate directly to child state, all/both resolves must be resolved before the state is initiated, the views are rendered.
That could be really improved. Make sense. And as far as I remember it is planned to happen in later releases (I'would swear I've seen that in one of the comments by UI-Router team member - but cannot find it).
It is different scenario. Because 1) we can go to parent (list view) first. And then 2) select child (detail). In this scenario it could really happen that list is working, detail fails.
So going directly to child should render parent, next fail on a child... But this is different story then the sibling described above.
Suggestions:
My suggestion would be, keep in resolve only stuff which is essential, absolute MUST for a state. Move the rest into Controllers injected services. This is my suggestion and my way...
I was thinking last night about how to port our crud application to a tab based app, like gmail. First, reading a lot about ui-router, i thought in create a tabService that will create a new tab for each state change (listen $rootScope.stateChangeSuccess), the new tab would include the corresponding view (ui-view="bancos") that will display the state template.
My first test and my first problem, when shown the list of items, a click over one of the item (itemId=4 for example) should open a new tab and display the item with id=4 in this tab, inside the corresponding (ui-view="bancos/4"). Note how i'm trying to map named ui-view with states to display the state defined templates in the corresponding ui-view.
I know that ui-router sample seems to do what i'm trying, but they are using nested states with unamed ui-view inside parent state template. In my case, the parent state ui-view element and his child state ui-view element should be sibling.
Considering the nature of angular, tree structured, and the nature of ui-router states, tree structured too, can i use ui-router to implement my requirements (crud application with tab based design).
Regards
Danny
Here is me example, if you have questions i can answer
http://plnkr.co/edit/tqfPVFaesSe9Q6ZkPMzE?p=preview
Short explain: don't use tabs instead of that create a fake, works well and you save a lot of work.
Best
I was looking for the same info, does the following example help you?http://plnkr.co/edit/gMsKK9l7J0B8vZIMIVfD?p=preview (Not my coding.)
Edit:
I've found more useful data.
https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
In my code, I have set it up like this:
.state('surveys.list', {
url: '/list',
templateUrl: "survey/surveys_overview.tpl.html",
controller: 'SurveyOverview'
})
.state('surveys.create', {
abstract: true,
templateUrl: "survey/survey.create.tpl.html",
controller: 'SurveyManageController'
})
.state('surveys.create.views', {
url: '/create',
views: {
'details': {
templateUrl: "survey/survey.create.details.tpl.html"
},
'steps': {
templateUrl: "survey/survey.create.steps.tpl.html"
}
}
});
The surveys.create.tpl.html just has a and two tabs with each one div:
<div ui-view="details"> & <div ui-view="steps">