I have an angular application using Angular Router.
My main view is a tabbed view, which has 10 tabs defined in it. For each tab there is a named view for the angular router to inject templates into. Each tab knows nothing about its content, other than a header which is configured through state definition.
state('personView', {
route: "people/:personId/",
views: {
"tab1": {
tabConfig: {
name: 'tab1',
icon: 'info_outline'
}
template: "person-details.tpl",
controller: 'personDetailsController as vm'
},
"tab2": {
tabConfig: {
name: 'tab2',
icon: 'info_outline'
}
template: "children-details.tpl",
controller: 'childrenDetailsController as vm'
}
}})
What I would like to do is include a counter in the tab header which displays how many items are inside each tab. using above example, if there were 4 children, I would like the tab header to display the number 4 as a badge. But the knowledge of the children is that of the childrenDetailsController and that alone.
So how best to get the count up into the tabbed header without giving my childrenDetailsController the knowledge that it is being used in a tabbed template? Ideally, I would configure the tabbed view to 'look into' the childDetailsController and pull out a certain property, which could be used for the counter. But binding into child scopes is not something I am familiar with.
$state.get()
get(stateName) - A method for retrieving the configuration object for any state, by passing the name as a string.
get() - Returns an array of all state config objects.
or
$state.current
Related
Currently there is an existing website already that uses the main index as the parent index. All the other view and controller uses the parent's side navigation and header. Basically it uses the ui-view main for each of these controllers to display their content.
However, I now need to create a new page within the same project that needs to not use the parent's header and side navigation. Rewriting the whole children will take too much time because there are literally hundreds of views.
Any recommendation for the new route to break out of the iframe or parent?
EDIT: Or perhaps is it possible to hide the parent data while in my new page?
I have done something like this in my project :
$stateProvider.state('landing', {
url: "/landing",
templateUrl: "/appViews/landing.html",
resolve: loadSequence('landingCtrl'),
title: 'Landing'
//************ Set up b2b states*****************************************************************
}).state('b2b', {
url: "/b2b",
template: '<div ui-view class="fade-in-right-big smooth"></div>',
abstract: true
}).state('b2b.login', {
url: '/login',
template: '<div ui-view class="fade-in-right-big smooth"></div>',
abstract: true
});
as you see landing and b2b use completely two different templates here.
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.
My UI includes nested tabs (using Bootstrap). The tab in question is a template with its own controller, currently specified on a view with UI-Router:
.state('editBase.edit', {
url: '/:id',
views: {
'fooTab#editBase.edit': {
templateUrl: 'foo.html',
controller: 'FooController'
},
// more views...
The template uses ng-repeat to create a list of links. Each link goes to an editor child state:
<a ui-sref="editBase.edit.subEdit({itemId: item.id})">{{item.name}}</a>
What I want to happen when the link is clicked is for the contents of the tab (the list) to be replaced with the editor. On clicking a save button, the list would reappear refreshed.
I'm not looking for an in-place editor for a list; I want to hide the entire list, but leave the surrounding templates/tabs intact. In other words, I don't want to replace the root view. How can I do this?
Note: I found this post that explains multiple nested views and view names; this helped me a lot.
In the edit state, create a view that targets the list's <ui-view>
A parent state, just for demonstration. The parent state has a ui-view that the list state plugs into
$stateProvider.state({
name: 'app',
template: '<div ui-view="list"></div>'
});
The list state targets the <ui-view="list">, which was created by the parent state
$stateProvider.state({
name: 'app.list',
url: '/objects',
views: {
list: {
controller: ...
template: `<ul><li ng-repeat="..."></li></ul>
}
}
});
The edit state targets the <ui-view="list"> that was created by the app state. The ui-view was previously filled in by the app.list state. However, the child state's view targeting that ui-view takes precedence over the parent state view.
$stateProvider.state({
name: 'app.list.edit',
url: '/edit/:id',
views: {
"list#app": {
controller: ...
template: "<a ui-sref="^">Go back...</a><form>...</form>"
}
}
}) ;
Here's a working plunker that demonstrates it: http://plnkr.co/edit/Uc39R2ZHUg2Ru3xyEkfe?p=preview
I believe I'm on the right track with these terms, "nested parameterized routes", but haven't found what I'm looking for yet.
My objective is to create intuitive routes for my API, something like the following example:
/api/v1/project/list
/api/v1/project/1/item/list
/api/v1/project/1/item/1/edit
/api/v1/project/2/item/3/delete
It's relatively easy and clear how to setup project states, but not the item states within each project.
{
state: 'project'
config: {
url:'/project'
}
},
{
state: 'project.list'
config: {
url: '/list'
}
},
{
state: 'project.detail'
config: {
url: '/:project_id'
}
}
It's not clear to me where to go from there so that items are relative or nested within projects.
I'll assume you have a REST api (based on your example containing /api/v1) which you want to expose/parallel as a UI. I'll assume you want to allow the user to drill down some hierarchical data model.
Choices!
There are many ways you could organize your states, for this drill-down list/details pattern. None is the "correct" way, but some are probably better than others. I will highlight two approaches that I've used:
Sibling states for list and details
One approach is to keep the "item list" states and "item details" states as siblings. This is what you did with project.list and project.details. This approach can be seen in the UI-Router Extras Demos source code.
When taking this approach
you must take care to move the user from the list state to the detail state when drilling down.
This approach has the benefit of easy-to-understand nesting of UI-Views. The ui-view for the detail view replaces the ui-view for the list view, when drilling down, because you are navigating to a sibling state.
Your choice whether or not the detail for an entity also retrieves the list of sub-entities (does the detail for a project also show the items list for that product?)
States:
projectlist // template plugs into parent ui-view
projectdetail // template plugs into parent ui-view, replacing projectlist
projectdetail.itemslist // template plugs into parent ui-view (#projectdetail)
projectdetail.itemdetail // template plugs into parent ui-view (#projectdetail), replacing itemslist
Details state as a substate of List state
Another approach is to make the detail state a child of the list state. This is organized similar to your REST routes.
When taking this approach
States hierarchy closely resembles the REST routes being exposed
Drilling down is simple and intuitive
You must manage the visual display of list/detail.
When drilling down from list state to the details substate, you probably want to hide the list.
We use named views, and absolute naming in order to replace the parent list state's template with the template for the the detail state. This is called "view targetting".
States:
top // theoretical parent state
top.projects // lists projects. Plugs into parent ui-view (#top)
top.projects.project // details for project. Its named view targets the grandparent ui-view (#top), replacing the template from top.projects list state.
top.projects.project.items // lists items. Plugs into parent ui-view (#top.projects.project)
top.projects.project.items.item // details for item. Its named view targets the grandparent ui-view (#top.projects.project), replacing the template from top.projects.project.items list state.
Here's an example of using named view targeting to accomplish the second approach:
$stateProvider.state('top', {
url: '/',
template: '<ui-view/>',
});
$stateProvider.state('top.projects', {
url: '/projects',
resolve: {
projects: function(ProjectsRoute) {
return ProjectsRoute.getProjects();
}
},
controller: function($scope, projects) { $scope.projects = projects; },
template: '<li ng-repeat="project in projects"> <ui-view/>'
});
$stateProvider.state('top.projects.project', {
url: '/:projectid',
resolve: {
project: function(ProjectsRoute, $stateParams) {
return ProjectsRoute.getProject($stateParams.projectid);
}
}
views: {
'#top': {
controller: function($scope, project) { $scope.project = project; },
template: 'Project details: {{ project.name }} <a ui-sref=".items">view items</a> <ui-view/>'
}
});
$stateProvider.state('top.projects.project.items', {
url: '/projects',
resolve: {
items: function(ItemsRoute, project) {
return ItemsRoute.getItemsForProject(project.id);
}
},
controller: function($scope, items) { $scope.items = items; },
template: '<li ng-repeat="item in items"> <ui-view/>'
});
$stateProvider.state('top.projects.project.items.item', {
url: '/:itemid',
resolve: {
item: (ItemsRoute, $stateParams) {
return ItemsRoute.getItem($stateParams.itemid);
}
},
views: {
'#top.projects.project': {
controller: function($scope, item) { $scope.item = item; },
template: 'Item details: {{ item.name }}'
}
});
I checked the Github Wiki, the Abstract States is enough.
I'm using Angular's ui-router on my application to try and route to child views of a main view. Both the main and the child have their own associated IDs. Currently I can navigate to the parent, but my link to the child is not working.
In my Application.js
$stateProvider
//Working Route
.state('Project', {
url: '/Project/{projectId}',
views: {
"ContentMain" : {
templateUrl: "/Scripts/Dashboard/templates/MainContent/ProjectMainContent.html",
controller: function ($stateParams) {
console.log("Project state hit!");
}
},
...
}
})
//Non-Working Route
.state('Project.ViewResource', {
url: '/Resource/:resourceId',
parent: 'Project',
views: {
"ContentMain" : {
templateUrl: "/Scripts/Dashboard/templates/MainContent/ProjectResourceViewContent.html"
controller: function ($stateParams) {
console.log("Project.ViewResource state hit!");
}
},
...
}
});
In my HTML:
<!-- Working Link-->
<a ui-sref="Project({ projectId: 5 })"><h3> My Projects </h3></a>
<!-- Non-working Links -->
<a ui-sref="Project.ViewResource({ projectId: 5, resourceId: 3 })">View Project Resource. </a>
<a ui-sref="Project.ViewResource({ resourceId: 3})">I'm a Resource Image. </a>
The first link works, however when I click either of the "non-working" child links my browser updates to: "Home/Index/#/Project/5/Resource/3" which is the desired route, however the page content
Any idea where I'm going wrong?
Edit1: To add the lines of code in the 'views' object which should be swapping out.
Edit2: To further demonstrate the issue, I've added the controller code blocks. When I hit the first html link, my console outputs "Project state hit!" When I click either of the non-working links, there is no new output to the console. Ie, the route is likely not being hit.
Figured out what was happening. After taking a closer look at the document on multiple named views here, I realized that my child view was searching for ui-view tags within the parent template, rather than the root template. Essentially, my child was trying to nest within my parent, while my desired behavior was to replace the parent views.
So, to target ui-views within the root, my solution looked like:
.state('Project.Resource', {
url: '/Resource/{resourceId}',
parent: 'Project',
views: {
"MainControls#": { templateUrl: "/Scripts/Dashboard/templates/MainControls/MainControls.html" },
"ContentMain#": {
...
},
...
}
})