I'm building an angular application using ui-router, and there's one thing I can't figure out.
There's a sidebar on the left, and a content area. Both are controlled by own controllers. When I choose an item in the sidebar, the content area shall be updated, but the sidebar must remain its state. What it does instead is that the sidebar reloads as well when selected an item.
app.coffee:
$urlRouterProvider.otherwise "/items/near/map"
$stateProvider
.state('items',
url: '/items'
abstract: true
templateUrl: "items.html"
)
.state('items.near',
url: '/near'
abstract: true
views:
'sidebar#items':
templateUrl: 'items-near-list.html'
controller: 'ItemsNearListCtrl'
)
.state('items.near.map',
url: '/map'
views:
'content#items':
templateUrl: 'items-near-map.html'
controller: 'ItemsNearMapCtrl'
)
.state('items.near.detail',
url: '/detail/:id'
views:
'content#items':
templateUrl: 'item-detail.html'
controller: 'ItemsNearDetailCtrl'
)
items.html:
<div>
<div ui-view="sidebar"></div>
<div ui-view="content"></div>
</div>
A probably related problem it that if I enter a detail view state directly (/items/near/detail/x/), the sidebar does not even load anything. Any idea what I'm doing wrong?
Hmm I never used the absolute # syntax for the view names but after looking into the docs it seems this may be the problem.
Please try without the #items or use the full state/view-combination like sidebar#items.near, content#items.near.map and content#items.near.detail.
Related
Is there any problem putting default code inside of a <ui-view> element. It appears to work, but I can't find anything saying one way or another if it's okay to use or not.
My current usage is I want the "default" view to be a list of items. Upon clicking one of those items, it switches to an "editor" child state, which replaces the <ui-view> content with the editor child.
Are there any gotchas I should be aware of before continuing with this approach?
Here is an example of what I'm looking at:
routes.js:
.config(($stateProvider) => {
$stateProvider
.state('admin', {
url: '/admin',
templateUrl: 'admin.html'
})
.state('admin.items', {
url: '/admin/items',
templateUrl: 'admin.items.html'
})
});
admin.html:
<ui-view>Default Stuff Here</ui-view>
items.html:
<p ng-repeat="item in items">{{item}}</p>
Now, I know I can do:
.state('admin.default', {
url: '',
templateUrl: 'admin.default.html'
});
And then put that would show in ui-view. However, that needlessly adds a new state and template file, when it seems to work just fine putting the would-be contents of admin.default.html directly into the ui-view of admin.html.
In my case, I'm not talking about a completely stateless option using otherwise(), I'm talking about a defined parent state with a default child state.
We generally do not put anything inside <ui-view></ui-view>, instead we create a default state and use that.
routerApp.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home.html'
})
.state('about', {
// we'll get to this in a bit
});
});
Here the default one is /home, like that you can create a default one.
First, As said in the other answer, we never give any data within the ui-view.. But while dealing with the ui-router, you should give all the specific states in .config()
app.config(function($stateProvider,$urlRouterProvider){
$stateProvider
.state('home',{
url : '/home',
controller: 'homeCtrl',
templateUrl : 'home.html'
}).state('login',{
url : '/login',
controller: 'loginCtrl',
templateUrl : 'login.html'
});
//AND HERE YOU PROVIDE THE DEFAULT PLACE WHERE THE USER IS TO BE REDIRECTED
//IN CASE OF IMPROPER URL
$urlRouterProvider.otherwise('/login');
});
Secondly, If you want to use the <ui-view></ui-view> to hold some data, you certainly can put some data within them.. But make sure that from the state you define, It should not supply any template or templateUrl. So the data within the <ui-view></ui-view> stays as it is.
And third.. Why to keep a default state..
in case you are defining a state admin in your config(), and then in your admin.routes.js file you can define a state as follows..
.state('admin.login',{
url:'',
controller:'loginCtrl',
templateUrl:'admin.login.html'
});
So in case the url is YOURURL/admin It will directly open the login page by default.. so there is no chance of redundancy...
I've been working with Angular for a year or 2 now, but this is my first project using ui-router. I'm running into a few issues with views and sub-views. The app is a standard left-side menu bar, with the views on the right changing depending on what's clicked in the menu bar.
On index.html
<body>
<div ui-view></div>
</body>
In the config.js file, which defines the routes
.state("dashboard", {
url: "/dashboard",
templateUrl: "components/dashboard/dashboard.html",
data: {
pageTitle: "Dashboard",
requiresLogin: false
}
})
.state("dashboard.welcome", {
url: "/welcome",
templateUrl: "components/welcome/welcome.html",
data: {
pageTitle: "Welcome",
requiresLogin: false
}
})
In the dashboard.html file
<div class="dashboard">
<div class="container-fluid">
<div class="row">
<div class="col-xs-12 col-md-8">
<div ui-view>
The /dashboard path loads correctly, and will load the left-side navigation bar with a blank right side. But changing the state to dashboard.welcome (/welcome) will not load the welcome.html template.
Whenever working with ui-router you need to understand that the concept of states is different from routes. When you define a sub-state, its defined relative to its parent state. In your scenario dashboard.welcome is defined as a child state of dashboard. The routes to substate is relative to the parent and is {parent url}/{child url}. Hence you should use either of the below 2 to route to that state:
Using $state.go change the state by specifying state name
$state.go('dashboard.welcome');
Using $location.path change the route by specifying url
$location.path('/dashboard/welcome');
It sounds like you want links to /welcome to be for state dashboard.welcome. Here is a plunker showing how this can be done. I show two sets of dashboard and welcome states. The first set of states (dashboard & welcome) shows that /dashboard/welcome will bring you to the dashboard.welcome state.
The second set (dashboard2 & welcome2) shows that /welcome will go to state dashboard2.welcome2. I believe this is what you were looking for.
If you hover over the links you can see where they will take you.
https://plnkr.co/edit/AVKPFa?p=info
Nested routes in ui-router get nested urls. I would however recommend using named-views for this kind of structure. You can find more info about it here:
https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
The gist of it is: you can specify a named component (ui-view) for your left menu navigation and another one for content, which gives you much more control down the line, because named components can be overwritten in child states or they can keep the default template, depending on your needs.
Example:
.state('root', {
url: '',
abstract: true,
views: {
'header': {
templateUrl: 'templates/partials/header.html',
controller: 'headerCtrl'
},
'logo': {
templateUrl: 'templates/partials/logoView.html'
},
'footer':{
templateUrl: 'templates/partials/footer.html',
controller: 'footerCtrl'
}
}
})
.state('root.login', {
url: '/login',
views: {
'header#': {
template: ''
},
'container#': {
templateUrl: 'templates/login.html',
controller: 'loginController'
}
}
})
.state('root.report', {
url: '/report',
views: {
'container#': {
templateUrl: 'templates/eu_dashboard.html',
controller: 'reportController'
}
}
})
And in your index.html:
<div ui-view="logo"></div>
<div ui-view="header"></div>
<div id="mainView" ui-view="container"></div>
<div ui-view="footer"></div>
I followed this example to set up a page with 2 child views.
I have everything in place now, and if I click on the specific link, the subviews are displayed as expected.
Here's my app.js file, in which you can see all of my states:
$stateProvider.state('app',
name: 'app'
url: '/app'
abstract: true
templateUrl: './sections/menu/menu.tpl.html'
).state('app.home',
name: 'home'
url: '/home'
templateUrl: './sections/Home/Home.tpl.html'
).state('app.details',
name: 'appDetails'
url: '/details/:zoneID'
templateUrl: './sections/zoneDetails/zoneDetails.tpl.html'
controller: 'currentZoneFilter'
).state('app.details.overview',
name: 'appDetailsOverview'
url: '/details/:zoneID'
templateUrl: './sections/zoneDetails/zoneDetailsOverview.tpl.html'
).state('app.details.edit',
name: 'appDetailsEdit'
url: '/details/edit/:zoneID'
templateUrl: './sections/zoneDetails/zoneDetailsEdit.tpl.html'
).state('app.setup',
name: 'setup'
url: '/setup'
templateUrl: './sections/setup/setup.tpl.html'
).state 'app.about',
name: 'about'
url: '/about'
templateUrl: './sections/about/about.tpl.html'
controller: 'info'
$urlRouterProvider.otherwise 'app/home'
as you can see, I have app.details, app.details.overview, app.details.edit.
app.details is the parent, and here's the page code:
This it the parent page
<a ui-sref="app.details.edit">Show edit</a>
<a ui-sref="app.details.overview">Show overview</a>
<div ui-view></div>
If I click on the link, the right template and page section is displayed. My question here is: How can I have Overview loaded by default when I reach this page?
I had a look at $urlRouterProvider.when, that I think that .when is good if you have a different URL.
My URL should be details/:zoneID for the page with overview template loaded and details/edit/:zoneID when the edit template is loaded, so I think that .when is not a good approach. Any help? thanks
You have to use the rule function of $urlRouterProvider.
$urlRouterProvider.rule(function ($injector, $location) {
// check if the location is the desired location then move to the //new location.
});
At the end, I finally solved this.
I changed state URLs for app.details / app.details.overview / app.details.edit like this:
.state('app.details',
name: 'appDetails'
url: '/:zoneID'
templateUrl: './sections/zoneDetails/zoneDetails.tpl.html'
controller: 'currentZoneFilter'
.state('app.details.overview',
name: 'appDetailsOverview'
url: '/details'
templateUrl: './sections/zoneDetails/zoneDetailsOverview.tpl.html'
.state('app.details.edit',
name: 'appDetailsEdit'
url: '/edit/day:day'
templateUrl: './sections/zoneDetails/zoneDetailsEdit.tpl.html'
so that now the URL starts with the zoneID and then it appends the details or edit to display the right template.
Here are a couple way. Check https://github.com/angular-ui/ui-router/wiki for implementation details.
Listen for the $StateChangeStart event. If this matches your state, perform a redirect. Be sure to execute event.preventDefault() to cancel current route change.
Add a controller to your parent state, it will check the $state.current value and if the value match his state will redirect to the child (this is what i have in my application).
EDIT : as requested in comment :
if($state.current.name != 'app.details'){
$state.go('app.details.overview');
}
I have a project for which I need 2 layouts. One which is basically 1 column (kind of as a landing page) and one which has a menu at the top, and is basically the same 1 column layout.
I've set up 2 layout HTMLs, which I'm using in my states, but the problem is that every time I switch from one state to the other, the whole HTML in the top-most ui-view (the one in the body) is changed.
Here's my setup:
Routes
.state('root', {
url: '',
abstract: true,
templateUrl: 'app/layouts/logged-in.html'
})
.state('root.homepage', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainController',
controllerAs: 'main'
})
.state('candidate', {
url: '/candidate',
abstract: true,
templateUrl: 'app/layouts/logged-in.html'
})
.state('candidate.profile', {
url: '/profile',
templateUrl: 'app/candidate/profile.html',
controller: 'CandidateProfileController',
controllerAs: 'profile'
})
index.html
<body>
<div ui-view></div>
</body>
logged-in.html
<div>
<my-navbar></my-navbar>
</div>
<div ui-view></div>
logged-out.html
<div ui-view></div>
The problem is that when switching between root.homepage and candidate.profile, my-navbar gets removed and then re-appended, which the user can briefly see (until the new HTML loads). I'd understand if one of the states would have the logged-out.html layout and the other the logged-in.html layout, but we're talking about the same file, and I'm looking to update only the ui-view from inside logged-in.html file.
Might be better to create an abstract state for the logged-in (including the navbar) and then inside introduce div for placing named view mainContent. And configure all your states to extend logged-in state, with defining views:{'mainContent': {controller:... , templateUrl: ... } }
I'm having trouble with a simple ui-router sample I have set up. I have a company page, whose default sub-state should show CompanyProfile, but it defaults to nothing until I click profile. Once I clicked employees, I have to click profile twice to get it to show again. Ideally I want ui-sref="company()" and ui-sref="company.profile()" to display the same screens. It seems like I'm missing something small..
Here's the plnkr:
http://plnkr.co/edit/A3LHGqQIuRlK1QdjuzrP?p=preview
HTML:
<a ui-sref="company()">company</a>
| <a ui-sref="company.profile()">profile</a>
| <a ui-sref="company.employees()">employees</a>
JS:
$stateProvider
.state('company', {
url: '/',
templateUrl: 'company.html',
controller: 'CompanyCtrl as CompanyCtrl'
})
.state('company.profile', {
url: '',
templateUrl: 'profile.html',
controller: 'CompanyProfileCtrl as CompanyProfileCtrl'
})
.state('company.employees', {
url: '/employees',
templateUrl: 'employees.html',
controller: 'CompanyEmployeesCtrl as CompanyEmployeesCtrl'
});
btw, I'm writing everything as components and decided to define the routes in each component, so you'll find the 3 state definitions in the 3 controllers. I'm not entirely sure this is the best approach or not yet.
The default state is entirely dependent on how you call $urlRouterProvider.otherwise(), passing it a url transitions the application to the particular url, wherein ui-router detects and looks for the very first state it sees.
In your main.js configuration, defines the / url as the default url for the application, which is technically the company state's url and is the very first state in the chain of parent states and children states, making it the default state. This in fact, is also the resulting url for the company.profile state that you wanted your application to default to.
To solve this problem, depends on the use cases for your application.
Use case: If your application defines the company state as a non-navigational state, then setting it to an abstract state solves the problem.
DEMO
CompanyCtrl.js
$stateProvider
.state('company', {
abstract: true,
url: '/',
templateUrl: 'company.html',
controller: 'CompanyCtrl as CompanyCtrl'
});
Use case: If the company state is nagivational, then simply remove the url definition in the company state and change the url defintion for the company.profile state to '/'. The only caveat for this solution would be the loss of the href attribute to be applied for for any anchor tags defined with the ui-sref="company" state which also implies the application of the text cursor. To mitigate this problem you might as well define all anchor tags with ui-sref attribute with a pointer cursor.
DEMO
CompanyCtrl.js
$stateProvider
.state('company', {
templateUrl: 'company.html',
controller: 'CompanyCtrl as CompanyCtrl'
});
CompanyProfileCtrl.js
$stateProvider
.state('company.profile', {
url: '/',
templateUrl: 'profile.html',
controller: 'CompanyProfileCtrl as CompanyProfileCtrl'
})
style.css
a[ui-sref] {
cursor: pointer;
}
UPDATE:
Use Case: The same with use case #2 but making the company state an abstract state.
DEMO
CompanyCtrl.js
$stateProvider
.state('company', {
abstract: true,
templateUrl: 'company.html',
controller: 'CompanyCtrl as CompanyCtrl'
});