I am trying to see if I can use ui-router to delegate setting of state to my app's sub-components by implementing lazy loading of the states. While I managed to get the lazy loading part to work using $state.go or equivalent, I can't get it to work using the URL.
For example, on launch my app will only setup the following 2 states: view1 and view2. When view1 state is loaded, it then setup it's own children states of: view1.profile and view1.interest. Take a look at this sample site from Gist:
http://bl.ocks.org/marcoslin/raw/b59cfa9a9a44bde04f9f/
As you will see from the example above, View1Profile is not a valid link on launch, but if you click on it, it will load view1 and then load view1profile with resulting url:
http://bl.ocks.org/marcoslin/raw/b59cfa9a9a44bde04f9f/#/view1/profile
However, if you click on the generated url above, the app reloads and no longer knows about view1profile and redirect you back to home. Any recommendation on how I can address this? More specifically, is there anyway I can get the url to trigger $stateNotFound event?
Perhaps the answer is in part of their cryptic documentation on How to: Lazy load states. I wasn't able to figure out what they mean by:
how to set the retry promise on the event
how to define the unfoundState using stored provider and resolve the promise
Marcos,
See http://christopherthielen.github.io/ui-router-extras/example/future/index.html for integration of FutureState with AngularAMD.
See http://christopherthielen.github.io/ui-router-extras/#/future/ for API and overview of the $futureStateProvider.
Related
I'm using $transitions to detect state changes and take some action based on the difference between some data defined in the 'from' and 'to' states. This is working great for me, but this code is not hit when the user navigates directly to a state via url. For example, browsing to mysite/search does not trigger this, but navigating within the site from the home page to the search page triggers this.
Is there any way to detect that the state was directly navigated to? That would allow me to execute the logic that I need to.
$transitions.onSuccess({}, trans => {
myFunc(trans.$from().data, trans.$to().data);
return true;
});
My problem was that I didn't register the transaction early enough; it was in a service that wasn't loaded until after transitions were handled for the initial launch of the app.
Like this question, I want to dynamically add ui-router states, but I'm not sure how to do it given the following:
We start with a small set of routes (max 5) that allow the user to navigate the login process: The user logs in (multi step login process), then selects the product type and role they want to use (assuming user has more than one product type/role, else they will bypass this view). At that point, I want to go out to my service and get the list of routes the user has access to, given the userid, product type, & role - I plan to send down an array of data structures that very closely match what is provided to $stateProvider.state(...).
In my app.run.js, I am checking $rootScope.$on("$stateChangeStart" and moving the user along from view to view in the multi-step login process and therefore know when I need to go out to my service to load up the available routes.
When the routes are loaded, I'll probably put a flag in a cookie to indicate that it was properly loaded for this user/product/role.
I don't think a provider really makes sense here since I don't want the routes loaded at config. It feels wrong (not sure why) to call the service to load the routes in the stateChangeStart.
Is this approach a reasonable one?
Note: I also see that there is an outstanding request to be able to remove states from $stateProvider. In the meantime until this request is implemented, how do we clear the $stateProvider of routes (apart from a browser refresh)?
For adding states after the config phase, you should use the Future State functionality in the ui-router-extra package.
Since there's no official support for deleting routes, you could probably merge in this PR to get something going, but you'd have to add functionality to remove the condition from urlRouterProvider as well since that's a bug with that PR.
We are using the latest version of Angular and AngularUI Router
A user is on some page /foo. They click a link to navigate to /bar. The bar state defines a resolve function that fetches some data from the server. To handle failure cases we have a $stateChangeError handler that examines the error and takes you to the correct error page (eg 500, 403, 401 etc). When the user does encounter an error going to /bar they wind up on something like /errors/500. But, this isn't right. When you navigate to urls on the web and encounter an error the url you wind up on should be the url you intended to go to. ie if you encounter a 500 error going to /bar your location should be /bar.
Has anyone figured this out in ui-router? Things attempted:
Building the target url using toState and toParams and then calling $location.url/path in the error handler after going to the error page. This re-runs the route and attempts to fetch the data again.
Calling history.replaceState after going to the error page. This really seems to mess with ui-router and seems to trigger unexpected state changes.
Just biting the bullet and using the error states, eg /errors/500. This breaks the back button. If you are landing on a page and the server is returning an error when trying to load it the error handler will change state to the error page, so when you hit the back button you're back to /foo and it tries to resolve again and hits an error again and takes you back to /errors/500.
What I did was create a few error states, such as:
.state('notFound', {
url: '/errors/404',
template: '<h1>Not Found</h1>'
})
.state('accessDenied', {
url: '/errors/403',
template: '<h1>Access Denied</h1>'
})
Then, when you are handling the error, add in the option to not update the location:
$state.go('notFound', {}, {location: false});
This results in changing to the notFound state, but it doesn't update the browser address bar, so the user is still seeing the previous url as you desire.
This is a very interesting question, and I had my share of wondering how to do that correctly !
My take on this is to handle navigation statuses with a service attached to a main controller.
I do the following:
Use named views, and define a root abstract state (to hold common views, such as error pages).
Define a NavigationStatus service, with a status attribute. status can be an object formatted as you desire (Example: {code:403, reason:'Nope ! No way !'})
Have the NavigationStatus service attached to the Main controller, wrapping my entire page.
Wrap my variable content (ui-view) in a conditional block (ng-if) bound to the navigation state registered, so that it can remove the content when an error occurs.
Add other conditional blocks in a switch/case fashion, to display error statuses. These can be templatized, expecting your navigation status object to have a defined set of attributes, and use views from your root abstract state.
On content retrieval, systematically push a success or error state to the NavigationStatus service. I typically do it in Controller, but I guess you could do it an a ui-router resolve promise as well. On success, my main ui-view gets displayed. On error, it's replaced by a friendly message, without changing the URL.
Hope that helps ! :)
PS : (I can come with a working example if you're interested, but right now I'm a bit too tired)
I have a confusing situation somebody probably already had.
I am implementing login / authentication with angular.js.
I've done everything like described in this link
https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec
Now I separated www.url.com/login.html and www.url.com/index.html#dashboard.
Dashboard is the logged in area.
If I am on the login page (www.url.com/login.html) and in url i enter www.url.com/index.html#dashboard the dashboard starts loading and then redirects me back to login.html.
The problem is that I see the dashboard page for a few seconds and then I get redirected after seeing the dashboard loading.
Using window.location in $rootScope.$on('$stateChangeStart', function (event, next) ..
Is there any way to bypass / override this behaviour?
That article is not the show of brightests techniques. There are lots of $rootScope.$broadcast which are considered a code smell. Anyway, as I can see, he is using AnguarUI router. So, that component (same as generic router) has one feature you are looking for - resolves.
Idea is simple - that definition describe a variable that can be injected into Controller. But, if that variable is a promise, Controller will delay rendering of the view until that promise is resolved. So there will be no glitch.
Egghead made a screencast from which you can get the idea how this feature works. But - integrating this feature into login process can be hard if you are not very talented and creative programmer. I didn't find any tutorial about that.
On the documentation for routing at emberjs.com, it states
Is the user currently logged in? Are they an admin user? What post are they looking at? Is the settings screen open? Are they editing the current post?
In Ember.js, each of the possible states in your application is represented by a URL.
I can't seem to understand: How is it supposed to happen to have every possible of the above states is represented by a URL?
Explanation:
As far as i can see, the Ember Router is a strictly hierarchical tree structure. That makes perfect sense to me for straightforward URLs, e.g.
user/:user_id/posts/:post_id/comments
which is the locator for the comments of a single post.
Now, where would the login state of the user go? Wouldn't that create multiple URLS for the same resource, or does that not matter?
If your settings screen is a modal that can be accessed from anywhere on the site, how would that be reflected in the url?
I'm not asking for a workaround solution, but just wanted to get some opinions of how this is actually meant in the guides or what the best practises are.
Great question Conrad.
I'll offer some experience from the multiple projects I've done in Ember.
Rarely do I add the user to the route, there's a good chance you aren't going to be sending down anything but the current logged in user (unless of course you're building an admin resource). So defining the user id in the url is probably incorrect, since it doesn't define the current page, it defines the user who was viewing that current page.
The login state would be unrelated to the other resource routes. It would live at the root, and after login you would redirect to an authorized route. In the event that someone navigates directly to an authorized route, there are some good patterns for pausing that transition, navigating to the login route, then upon a valid login redirecting to the authorized route.
Generally you have multiple routes for a single resource.
App.Router.map(function() {
this.resource('login');
this.resource('post', { path: '/post/:post_id' }, function() {
this.route('edit');
this.route('someViewWithTheSameResource');
this.route('someViewWithTheSameResource2');
this.resource('comments', function() {
this.route('new');
this.route('update');
});
});
});
The grand-daddy issue is modals. Originally that was just a don't handle it in the url, there is no nice way to handle this without having to add a million routes under different settings. With the upcoming change of query params you can modify settings at a root level without having to muck up the entire resource tree. This is still in beta, so it may not quite work as expected, but the goal is to be able to handle such a use case.
http://emberjs.com/guides/routing/query-params/