I'm using ui-router 0.2.10. My application has two different templates, index.html & index2.html. I've created an abstract state, and i'm referencing both indexs' in their respective states, The problem is I can access the first route fine, but when I try to access the next route it just keeps defaulting to the first route. The abstract templates live in the same folder.
What am I missing here?
.config(["$stateProvider", "$urlRouterProvider", function(sp, urp) {
urp.otherwise("/index1");
sp.state("index1", {
abstract:true,
url: "/index1",
templateUrl: "index.html"
});
sp.state("index1.id", {
url: "/id",
template: "views/partials/index.partial.html",
controller: function($scope,$state){
$state.go('index1.id');
}
});
sp.state("index2", {
abstract:true,
url: "/index2",
templateUrl: "index2.html"
});
sp.state("index2.id", {
url: "/id",
template: "views/partials/index2.partial.html",
controller: function($scope,$state){
$state.go('index2.id');
}
});
}])
I'm assuming that you're trying to go to an abstract state and that won't work as abstract states will only be activated when you go to a child of that abstract state, i.e. you can't direct go to index1 or index2, so this is not legal:
urp.otherwise("/index1"); it should be: urp.otherwise("/index1/id"); instead.
also why are you going to the states again within their own controller? $state.go('index2.id'); is pointless since by the time that line of code is reached you are already in the state index2.id. I think you meant to put those lines of code inside the controllers of the abstract states! Last bit not least don't forget that your abstract states MUST have a ui-view themselves as they are hosting child states.
See plunk.
Related
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 an Angular app which has several dynamic fields, each of these fields are changed updated based on config which comes from a backend database.
In order to control what config is used I need to dynamically switch a single variable - I've decided that the URL is the best way to set/switch the variable as there need to be multiple permutations of the site based on the URL so:-
/:dynamicVariable/
I'm looking for some guidance as to whether this is the best way to do it and what the best way to do it would be? I'm struggling as I don't want to have to set each route for each section like this /:dynamicVariable/homepage /:dynamicVariable/about-us etc etc. Ideally the core module checks it and sets it but the routing ignores it so /:dynamicVariable/ becomes the root.
Hope that makes sense, thanks in advance for your help.
I ended up doing this by using ui.router and nesting states within an abstract parent state which held the client, like so :-
.state('rootClient', {
abstract: true,
url: '/:client',
templateUrl: 'app/layout/layout.html',
controller: 'Layout',
controllerAs: 'layout',
noClient: false,
resolve: {
client: function ($stateParams, ClientService) {
return ClientService.getClient($stateParams.client);
}
}
})
.state('rootClient.home', {
url: '/homepage',
views: {
'content': {
templateUrl: 'app/homepage/homepage.html',
controller: 'Homepage',
controllerAs: 'home'
}
}
});
This way all the routes are under the parent route, I also added a resolve to make sure the client exists before moving to the route. Hopefully this will help someone else down the line.
Cheers
I debated a while on this but I got a Plunk that reproduce it.
I have a state "Contact" that get loaded by default. with $state.transitionTo
Inside that state I have some views, they all get loaded and everything work.
If I click to change the state to "Home" by default or by "ui-sref" and in the "Home" state/template I have ui-sref="contacts". When we click back to set the state to contacts it should work, but all the sub views are now not being called properly.
It seems that when ui-sref call the state this one behave differently that when it is loaded by default.
Why $state.transitionTo(''); seems to work differently than ui-sref.
<script>
var myapp = angular.module('myapp', ["ui.router"])
myapp.config(function($stateProvider, $urlRouterProvider){
// For any unmatched url, send to /
$urlRouterProvider.otherwise("/")
$stateProvider
.state('home', {
templateUrl: 'home.html',
controller: function($scope){
}
})
.state('contacts', {
templateUrl: 'contacts.html',
controller: function($scope){
}
})
.state('contacts.list', {
views:{
"":{
template: '<h1>Contact.List Working wi no Data defined.</h1>'
},
"stateSubView":{
template: '<h2>StateSubView Working</h2>'
},
"absolute#":{
template: '<h2>Absolute item</h2>'
}
}
});
});
myapp.controller('MainCtrl', function ($state) {
$state.transitionTo('contacts.list');
})
Q2:
Why is the Absolute tag that is under contact work when I add the view in the Index, but is not working when it is inside the contact.html file. Absolute reference work only with the Index and not if called everywhere?
"absolute#":{
template: '<h2>Absolute item</h2>'
}
I saw that in index.html you have an empty ui-view tag. What do you expect to go there? I think you can not do this. The router just doesn't know with which state (home or contacts) it should replace. Apparently it picks the second one (contacts). I'd suggest to put url: '/' in the home state and you'll see the difference.
This is for sure one issue.
Other than that:
You can't simply access views from contacts.list in contacts afaik.
The empty ui-view work as a wild card and can be use to switch across multiple route even if we have nested element. But if we have a nested view contact.list it can only be access if we put the whole path in ui-sref="contacts.list" because the list child of contact cannot be access only by using ui-sref="contacts"
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'
});
<html ng-app="app">
<head>
...
</head>
<body>
<div id="header"></div>
<div id="notification"></div>
<div id="container"></div>
<div id="footer"></div>
</body>
</html>
With the given structure of the app (derived from angular-app):
header: Here the site navigation, login/out toolbar etc comes in. This is dynamic and has it's own Controller
notification: Global notification container.
container: This used to be my <ng-view>. So this is where all other modules loads in.
footer: Global footer.
How do the state hierarchy looks like? I've gone through the example which shows a single module (contacts) but typically an app would have a global (root) state and inside the root state other module states are rendered.
What I'm thinking is my app module probably have the root state and then each module should have it's own state and I have to inherit from root state. Am I right?
Also from ui-state example, they have used both $routeProvider and $urlRouterProvider as well as $stateProvider has url defined. My understand was $stateProvide also handles routing. If I'm wrong, which provider should I use for routing?
EDIT: http://plnkr.co/edit/wqKsKwFq1nxRQ3H667LU?p=preview
Thanks!
The approach you took in your plunker is close. #ben-schwartz's solution demonstrates how you'd set defaults in your root state for the three essentially-static views. The thing missing in your plunker is that your child states still need to reference the top container view.
.state('root',{
url: '',
views: {
'header': {
templateUrl: 'header.html',
controller: 'HeaderCtrl'
},
'footer':{
templateUrl: 'footer.html'
}
}
})
.state('root.about', {
url: '/about',
views: {
'container#': {
templateUrl: 'about.html'
}
}
});
Note views: { 'container#': ...} instead of just templateUrl: ... in 'root.about'
What you may also be asking about is whether you can have modules define their own state-sets, which you then attach to your app's state hierarchy. A sort of plug-and-play for the routes/states each module provides.
To achieve this you'll have tightly couple your modules to your main app.
In the module:
angular.module('contact', ['ui.router'])
.constant('statesContact', [
{ name: 'root.contact',
options: {
url: 'contact',
views: {
'container#': {
templateUrl: 'contacts.html'
}
},
controller:'ContactController'
}}
])
.config(['$stateProvider', function($stateProvider){
}])
Then, in the app:
var app = angular.module('plunker', ['ui.router', 'contact']);
app.config( ['$stateProvider', 'statesContact',
function($stateProvider, statesContact){
$stateProvider
.state('root',{ ... })
.state('root.home', { ... })
.state('root.about', { ... })
angular.forEach(statesContact, function(state) {
$stateProvider.state(state.name, state.options);
})
}]);
This means all your modules will need to be compatible with this pattern set out in your app. But if you accept this constraint you can then choose include any combination of your modules, and their states magically get added to your app. If you want to get even fancier, you can modify state.options.url in your statesModuleName loop to, for example, prefix your module's url structure.
Also note that the module ui.compat is only necessary when you are transitioning from $routeProvider to $stateProvider. You should normally use ui.state instead.
Also don't forget to adjust in header.html $state.includes('root.contact'))
updated plunker
Although confusing, the FAQ in the ui-router wiki seems to say that this isn't possible: ui-router/wiki/faq
One approach is to allow each feature to define it's own root state (as in this example: AngularJS State Management with ui-router)
Another would be to define the entire state hierarchy in your "myApp" module and just leverage controllers etc. from the dependent modules. This works especially well when you are maintaining a mobile and desktop site; define each site's state hierarchy in a mobileApp and desktopApp module, and have the functionality (e.g. controllers, services, etc.) shared in separate modules.
It depends how you prefer to approach it.
All scopes inherit from rootScope so you may place global state there. The NG literature specifically mentions that you should only put global state there, and not functionality. Functionality required across the system belongs in services, and specifically you should not implement services whose sole purpose is to retain state. All the advice seems to be implicitly shared in the context of how it either facilitates or hampers your ability to easily do end to end testing.