I have a UI-Router site with the following states:
$stateProvider
.state('order', {
url: '/order/:serviceId',
abstract:true,
controller: 'OrderController'
})
.state('order.index', {
url:'',
controller: 'order-IndexController'
})
.state('order.settings', {
url:'',
controller: 'order-SettingsController'
})
Where my two states do NOT have a url set, meaning they should only be reachable through interaction with the application. However the order.index state is automatically loaded by default because of the order in which I have defined the states.
I am noticing that trying to do a ui-sref="^.order.settings" or $state.go("^.order.settings") then ui-router first navigates to the order.settings state and then it immediately navigates to the order.index state (default state). I think this is happening because the url changing is causing a second navigation to occur and since the state.url == '' for both, it automatically defaults to the order.index state...
I tested this out by setting the {location: false} object in the $state.go('^order.settings', null, {location:false}). This caused the correct state to load (order.settings), but the url did not change. Therefore I think the url changing is triggering a second navigation.
I'd understand, can imagine, that you do not like my answer, but:
DO NOT use two non-abstract states with same url defintion
This is not expected, therefore hardly supported:
.state('order.index', {
url:'', // the same url ...
...
})
.state('order.settings', {
url:'', // .. used twice
...
})
And while the UI-Router is really not requiring url definition at all (see How not to change url when show 404 error page with ui-router), that does not imply, that 2 url could be defined same. In such case, the first will always be evaluated, unless some special actions are used...
I would strongly sugest, provide one of (non default) state with some special url
.state('order.settings', {
url:'/settings', // ALWAYS clearly defined
...
})
Related
$stateProvider.state('state1', {
url:'/state1/:param1/and/{param2:.+}',
templateUrl: 'state1.html',
controller: 'State1Controller',
});
I'm trying to make param2 required by using regex as seen above. If it's empty, then the default state should load:
$urlRouterProvider.otherwise("/");
$stateProvider.state('otherwise',{
url: '/',
templateUrl:'default.html'
});
Now the results:
state1/1/and/1 goes to state1. Good.
state1/1/and goes to otherwise. Good.
But,
state1/1/and/ goes to no state! Neither states are loaded. It's not redirecting back to /. What?!
How do I properly make a parameter required?
Angular js ui-router url parameters are optional by default. For your case above we could make use of $stateParams to check if the required parameter is defined or not. Please check the code below.
if ($stateParams.param2=== undefined) {
// Navigate to home.
$location.path('/');
}
Hope this would solve your issue. Thanks.
I have a simple form that can be used to initiate a forecast request. I created this as a parent state requests (Initiate Forecast).
Desired behavior
When a request is submitted, that immediate request is shown in a child state (View Status) as most recent request. This View Status state will also hold a grid of all past requests, meaning I will be refreshing the grid with data every time this state is invoked.
Both parent and child states are navigable from a sidebar menu.
So, if a user clicks on parent (Initiate Forecast), he should be able to see only the form to submit a request. If a user directly clicks on the 'View Status'(child), then he should be able to see both the form and the grid of requests.
app.js
function statesCallback($stateProvider) {
$stateProvider
.state('requests', {
url: '',
views: {
'header': {
templateUrl: 'initiateforecasting.html',
controller: 'requestsInitiateController'
},
'content': {
template: '<div ui-view></div>'
}
},
params: {
fcId: null,
fcIndex: null
}
})
.state('requests.viewStatus', {
url: '/ViewStatus',
templateUrl: 'viewstatus.html',
controller: 'requestsStatusController'
});
}
var requestsApp = angular.module('requestsApp', ['ui.router']);
requestsApp.config(['$stateProvider', statesCallback]);
requestsApp.run(['$state', function($state) {
$state.go('requests');
}]);
Plunker of my attempts so far.
This is all working for me as shown in the plunker, but I am doing it by not setting a URL to the parent. Having no URL for the parent state is allowing me to see both states. If I add a URL to parent state, then clicking on View Status link is not going anywhere (it stays on Initiate).
How do I change this so that I can have a URL for parent state and still retain the behaviour I need from the parent and child states as described above?
Note: I am fine without the URL for parent state in standalone sample code, but when I integrate this piece with backend code, having no URL fragment on the parent state is making an unnecessary request to the server. This is visible when I navigate to the child state and then go to the parent state. It effectively gives the impression of reloading the page which I think is unnecessary and can be avoided if a URL can be set to the parent state.
You shall not directly write url when using ui.router, try like this:
<a ui-sref="requests.viewStatus">View Status</a>
You are writing state name in ui-sref directive and it automatically resolves url. It's very comfortable because you can change urls any time and it will not break navigation.
tl:dr;
I'm trying to iterate through each state in $state.get() to see if the current URL matches the pattern of one of the states.
preface
I have all of my app's states inheriting from a base abstract state that utilizes resolved dependencies to ensure that the user has logged in before loading the state.
abstract state
$stateProvider.state 'nx', state =
abstract: true
url: ''
template: '<ui-view></ui-view>'
resolve: { session: [
'$q'
'coreService'
($q, sessionService)->
sessionService.withSession()
.then (rsp)-> rsp
.catch (err)-> $q.reject err
]}
example child state
This state inherits all the resolved dependencies.
$stateProvider.state 'test', state =
parent: 'nx'
url: '^/2112'
controller: class TestController
constructor: (#session)-> # the resolved session data, assuming it's valid
controllerAs: 'vc'
templateUrl: './views/view.sandbox.html'
problem
In some cases the user hasn't logged in so the resolved session data errors out. After the user logs in, I trigger a reload via this code:
reload after login
$state.go($state.current, {}, {reload:true})
When the app first loads, if the user hasn't logged in, then the intended state doesn't load leaving the app in it initial abstract state. Since you can't navigate directly to an abstract state it throws the error:
Error: Cannot transition to abstract state '[object Object]'
attempted solution
I was hoping to loop through all the registered states in $state.get() and checking the current URL against each state to see if there is a match, then using that state for the reload logic. Something like this:
url = $location.path()
for state in $state.get()
if url matches state.url # pseudo-code here
$state.go(state, {}, {reload:true})
The problem is that I don't have a good way to match the current URL against the state's URL pattern. It's not a standard regular expression and when attempting to define a UrlMatcher I can't seem to load any states.
I have an Angular application using ui-router and I am having issues whenever I refresh the page. I am using nested views, named views to build the application. Whenever I refresh the page, ui-router doesn't reload the current state and just leaves the page blank.
On page load $state.current is equal to
Object {name: "", url: "^", views: null, abstract: true}
I am reading my navigation from a .json file via $http and looping through the states. So this is what I can show:
stateProvider.state(navElement.stateName, {
url: navElement.regexUrl ? navElement.regexUrl : url,
searchPage: navElement.searchPage, //something custom i added
parent: navElement.parent ? navElement.parent : "",
redirectTo: navElement.redirectTo,
views: {
'subNav#index': {
templateUrl: defaults.secondaryNavigation,
controller: 'secondaryNavigationController as ctrl' //static
},
'pageContent#index': {
template: navElement.templateUrl == null
? '<div class="emptyContent"></div>'
: undefined,
templateUrl: navElement.templateUrl == null
? undefined
: navElement.templateUrl,
controller: navElement.controller == null
? undefined
: navElement.controller + ' as ctrl'
}
}
});
This code gets executed for each item in the nested json object. If there is anything else that would be helpful, let me know.
There is a question: AngularJS - UI-router - How to configure dynamic views with one answer, which shows how to do that.
What is happening? On refresh, the url is evaluated sooner, then states are registered. We have to postpone that. And solution is driven by UI-Router native feature deferIntercept(defer)
$urlRouterProvider.deferIntercept(defer)
As stated in the doc:
Disables (or enables) deferring location change interception.
If you wish to customize the behavior of syncing the URL (for example, if you wish to defer a transition but maintain the current URL), call this method at configuration time. Then, at run time, call $urlRouter.listen() after you have configured your own $locationChangeSuccess event handler.
In a nutshell, we will stop URL handling in config phase:
app.config(function ($urlRouterProvider) {
// Prevent $urlRouter from automatically intercepting URL changes;
// this allows you to configure custom behavior in between
// location changes and route synchronization:
$urlRouterProvider.deferIntercept();
})
And we will re-enable that in .run() phase, once we configured all dynamic states from JSON:
.run(function ($rootScope, $urlRouter, UserService) {
...
// Once the user has logged in, sync the current URL
// to the router:
$urlRouter.sync();
// Configures $urlRouter's listener *after* your custom listener
$urlRouter.listen();
});
There is a plunker from the linked Q & A
I don't know how are all your routes.. but if you refresh a page of a child state, you need to pass all parameters of the parents states to be resolved correctly.
I have an angular app with a homepage that shows a list of things. Each thing has a type. In the nav, there are selectors corresponding to each thing type. Clicking one of these selectors causes the home controller to filter the things shown to those of the selected type. As such, I see the selectors as corresponding to states of the home page.
Now, I'd like to map each of these states to a url route: myapp.com/home loads the home page in default (unfilitered) state, myapp.com/home/foo opens the home page with the foo-type selector activated, and switching from there to myapp.com/home/bar switches to the bar-filtered state without reloading the page.
It's that last bit - triggering "state" changes without reloading the page, that's been particularly tricky to figure out. There are numerous SO/forum questions on this topic but none have quite hit the spot, so I'm wondering if I'm thinking about this in the wrong way: Should I be thinking of these "states" as states at all? Is there a simpler approach?
Also, I'm open to using either ngRoute or ui.router - is there anything about one or the other that might make it simpler to implement this?
Using ui-router, you can approach it like this:
$stateProvider
.state('home', {
url: "/home",
controller: "HomeController",
templateUrl: "home.html"
// .. other options if required
})
.state('home.filtered', {
url: "/{filter}",
controller: "HomeController",
templateUrl: "home.html"
// .. other options if required
})
This creates a filtered state as a child of the home state and means that you can think of the URL to the filtered state as /home/{filter}. Where filter is a state parameter that can then be accessed using $stateParams.
Since you don't want to switch views, you inject $stateParams into your controller, watch $stateParams.filter, and react to it how you wish.
$scope.$watch(function () { return $stateParams.filter }, function (newVal, oldVal) {
// handle it
});