I have a state in my Angular 1.3 app:
.state('department.filetransfer', {
url: '/filetransfer/:deptID',
templateUrl: function($stateParams, $log) {
return 'modules/department/templates/department.filetransfer.'+$stateParams.deptID+'.html';
},
controller: 'DeptFiletransferCtrl as ftc',
data: {
pageTitle: 'filetransfer',
access: 'public'
}
});
And then in my application I have a link such as
ui-sref="department.filetransfer.accounting"
But I get a console error
Error: Could not resolve 'department.filetransfer.accounting' from state 'department'
What did I miss?
There is a working example
The way how to pass param is via an object {paramName1:value1, paramName2: value2}:
ui-sref="department.filetransfer({deptID:accounting})"
The above example is suitable when accounting as a variable in $scope.
In case, that we need to pass string value, we should do it like this:
ui-sref="department.filetransfer({deptID:'accounting'})"
As documented here
ui-sref
A directive that binds a link (<a> tag) to a state. If the state has an associated URL, the directive will automatically generate & update the href attribute via the $state.href() method. Clicking the link will trigger a state transition with optional parameters.
Also middle-clicking, right-clicking, and ctrl-clicking on the link will be handled natively by the browser.
You can also use relative state paths within ui-sref, just like the relative paths passed to $state.go(). You just need to be aware that the path is relative to the state that the link lives in, in other words the state that loaded the template containing the link.
You can specify options to pass to $state.go() using the ui-sref-opts attribute. Options are restricted to location, inherit, and reload.
Related
I'm using AngularJs V1.6. Ui- Router V 1.0.3
I've been trying to learn how to work with ui-router for the past few eeks and I came upon this code today which has me totally confused :-
Html side -
<ul ng-if="!isAuthenticated()" class="nav navbar-nav pull-right">
<li>Login</li>
<li>Sign up</li>
</ul>
JS -
$stateProvider
.state('home', {
url: '/',
controller: 'HomeCtrl',
templateUrl: 'partials/home.html'
})
.state('login', {
url: '/login',
templateUrl: 'partials/login.html',
controller: 'LoginCtrl',
resolve: {
skipIfLoggedIn: skipIfLoggedIn
}
})
This is code from Satellizer.
I tried replicating it but all it did was end up showing me the folder structure of my working directory in the browser.
However, I was using ui-router visualizer and upon clicking the route in that, it worked properly. I can't find any samples where ui-router is used this way, how exactly is this above snippet working?
I also read here that typing
<a ui-sref="party">Go To Party</a>
will turn it into
Go To Party
in our browser. However, in the example I posted intially, there is no ui-sref with the href. Once again, how exactly or what exactly is happening? Is it only working because it's retrieving a separate html file?
I tried replicating it but all it did was end up showing me the folder
structure of my working directory in the browser.
This happened because the first example in your question uses hash-prefix for generated links and the second example uses html5Mode for links.
When you click on a link with hash prefix mode eg. #/home, the request of that resource is handled at client-side by ui-router and is not sent to the server. But when you click on a link that is without prefix eg. /home, the request goes to the server. Your server needs to understand about this type of request (look at the referenced link below).
The default mode generates # as prefix. If you don't want to have hash prefix # in generated links then you need to enable HTML5Mode like this:
app.config(["$locationProvider", function($locationProvider) {
$locationProvider.html5Mode(true);
}]);
If you enable this mode, then don't forget to inform Angular about the root URL of your app by adding following to the head section of your HTML file:
<base href="/">
Reference:
Read more about how to configure your server to work with html5Mode here.
About ui-sref directive:
The preferred way of route/state resolution using ui-router in HTML templates is to use ui-sref which is a directive that binds a link i.e. anchor tag ( tag) to a state. If a state has an associated URL, the directive will automatically generate, update the href attribute for you.
This way your HTML template just needs to refer a state name and the link resolution will be done for you which is good as if in future you change the underlying link for states, your templates will still work. This directive uses $state.href() method for link value.
You can directly use the associated link in anchor tag without using this directive. But doing this, you will always need to revise your link if you change url in route config. Let ui-router module help you to manage this without writing any extra line of code and to ease state management.
The usage of this directive is:
ui-sref='stateName' - Navigate to state, no params. 'stateName' can be any valid absolute or relative state, following the same syntax rules as $state.go()
ui-sref='stateName({param: value, param: value})' - Navigate to state, with params.
Example: If you html has following link:
<a ui-sref="home">Home</a>
The generated HTML (Html5Mode Off results in prepended '#') will be following provided your route config has a valid state named as home:
Home
Reference:
- UI Router ui-sref directive
When use you are write this routing sample
Home
You must be write this code in the routing
$locationProvider.html5Mode(true)
This link can help you
Suppose I have a function in my controller and a variable. And there's another controller with another function that takes a variable as parameter.
I need to redirect to that controller from my controller, call that function, and pass variable value from my controller.
How can I do this in a single page application?
Its a lot like when we redirect from one page to another in MVC and pass a value through URL. I'm trying to pass a variable to another controller that has its own page.
You can do it two ways.
One is through angular services and other is via ui-routing resolve functionality.
Services are more preferable according to best practices standards.
You should use $routeParams to get the behavior similar to MVC. Just define a route like this:
{
url: '/page/:pageNo',
config: {
templateUrl: "page.html",
controller: "pageController"
}
}
From the controller you want to redirect from, use $location.path
$location.path('/page/' + $scope.pageNo);
Finally, in the controller where you redirect to, you will access this param using $routeParams
$routeParams.pageNo
Here is my route config
$stateProvider.state('layout', {
abstract: true,
controller: "MenuCtrl",
templateUrl: "views/layout/MainLayout.html"
}).
state('layout.home', {
url: '/',
templateUrl: 'views/Main.html',
controller: 'MainCtrl'
}).state('layout.tag.add', {
url: '/addTag',
templateUrl: 'views/AddTag.html',
controller: 'AddTagCtrl'
})
Later I have in my code function:
var goToAddTagPage = function(){
$state.go('layout.tag.add');
};
When I call this function I get Could not resolve 'layout.tag.add' from state 'layout'. If I rename this to layout.addTag it works correctly. Nested tag causes issue. How I can correctly nest states like that?
EDIT: I have added empty state
state('layout.tag',{
}).
Now exception is gone. However the view is now not rendered. I get empty screen. I try to add abstract : true for state but it didn't helped.This state need some configuration?
In your state hierarchy, there really must be all three states.
state('layout', {...}
state('layout.tag', {...}
state('layout.tag.add', {...}
Because a '.' (dot) in the state name simply represents hierarchy (parents, grand parents)
But once we add new parent between grand-parent and child, we need to be sure, that
parent contains a view "target" for a child.
child explicitly uses absolute view naming to target grand-parent
So, this would work (and I would prefer that, because we gain inheritance parent-child)
state('layout.tag', {
template: '<div ui-view ></div>'
...
}
So now, there is the element <div ui-view ></div> injected into grand-parent, and also serves as an anchor/target for child.
Check the doc:
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.
The second approach is to use absolute view naming and skip grand parent
.state('layout.tag.add', {
url: '/addTag',
views: {
'#layout': { // target unnamed view anchor in grand-parent
templateUrl: 'views/AddTag.html',
controller: 'AddTagCtrl'
}
}
})
View Names - Relative vs. Absolute Names
Behind the scenes, every view gets assigned an absolute name that follows a scheme of viewname#statename, where viewname is the name used in the view directive and state name is the state's absolute name, e.g. contact.item. You can also choose to write your view names in the absolute syntax.
You may(dont quote me on this, but try) need to introduce an intermediate layout.tag state if you want to use this hierarchy.
UI router could be failing on 'dot-notation' based nesting because youre skipping a state essentially.
Update: Based on the regex in this snippet from the source
var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
It does look for a parent state named 'layout.tag'.
So you will either need to accept the 'layout.addTag' hierarchy or introduce an intermediate 'layout.tag' state
As you can notice in the following code, I would like to have a view for the parent "colors" state (which will show a table with list of colors), and then each color should have it's own view, not inherited from "colors".
So the hierarchy should only apply to URL's, not to views.
Any idea ?
.state('colors', {
url: "/colors",
templateUrl: 'views/colors/colors.html'
})
.state('colors.yellow', {
url: "/yellow",
templateUrl: 'views/colors/yellow.html'
})
I understand that you've found your answer. But let me append other approach and extend your solution with some more dynamic stuff. It'll a bit overcome your question, but could help you to see the magic around UI-Router
I created an example here
Firstly, we can have this kind of parent template (colors.html)
<div ui-view="">
// the content of the parent template, e.g. list of colors
</div>
So, because the ui-view="" is defined on the parent root element, child will in fact replace it. And what's more, we would gain $scope inheritance:
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.
And now even more. Let's imagine that we would have more colors, not only yellow. That could lead to change in the approach, and color could become parameter:
.state('colors', {
url: "/colors",
...
})
.state('colors.color', {
url: "/:color",
...
That's a big change, because now we can have url like /colors/yellow or /colors/red and all will be managed by stat colors.color
Let's continue, using the solution from here: Trying to Dynamically set a templateUrl in controller based on constant - and we can even have many templates, different by each color name.
We can then define them as constants inside of the angular module:
.value('myTemplates', {
"yellow" : "views/colors/yellow.html",
"red" : "views/colors/red.html",
"default" : "views/colors/default.html",
}
)
And our child state could use them in the run-time, based on the parameter. This would be the call:
<a ui-sref="colors.color({color: 'yellow'})">
<a ui-sref="colors.color({color: 'red'})">
<a ui-sref="colors.color({color: 'white'})">
And this will be the adjusted child state:
.state('colors.color', {
url: "/:color",
templateProvider: ['$templateRequest', '$stateParams', 'myTemplates',
function($templateRequest, $stateParams, myTemplates) {
var templateName = myTemplates[$stateParams.color]
|| myTemplates["default"];
return $templateRequest(templateName);
}],
})
Check that all in action here
I have two parallel views in my AngularJS template. One is the main page view and the second is the navigation bar view. If user is logged in, I display private data in the main view as well as a link to user account in the navigation view. If user is not logged in, I display a welcome screen as well as a login link. So, that's why I'm using two parallel views.
When using ui-router with one ui-view directive in the template, things work as expected. When using two named ui-view directives in my template, $state.go('nameOfState') doesn't work anymore.
Here's a Plunk that's failing in triggering state with $state.go() because it has two views. Here's a Plunk that shows how the same code works when there's only one view.
Why is $state.go() not working?
The problem is the controller for your home state is not being instantiated, meaning the $state.go call is never happening. The controllers are instantiated only on demand. Specifically, the documentation states:
Warning: The controller will not be instantiated if template is not defined.
In order to get mainCtrl to be instantiated, you can add a template to the home state and add an unnamed ui-view to index.html, or you can add a template for one or more of the existing named views (e.g. "main") for the home state and move the mainCtrl to be the controller for those views. E.g. if you replace your existing home state with the following, it should work as expected:
.state('home', {
url: '/',
views: {
'main': {
template: 'main template',
controller: mainCtrl
}
}
})