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/
Related
I´m currently developing an application based on user authentication where each user can register a student-campus as a teacher and currently, I'm on a feature where I have two routes:
Route 1: It has a Datagrid where I'm listing all of the student campuses that I've already created and each row has an edit button that navigates to "Route 2" and the purpose of that is to edit the already created student campus.
Route 2: It has a form with all the necessary fields to create a student-campus.
As you can see I need to pass the student-campus ID to fetch data in the ngOnInit to fill the fields and be able to edit the above-mentioned, so I have several options in consideration:
Option 1: Pass ID in the URL.
this.router.navigate(['planteles/registrar', idPlantel]);
https://myapplication/planteles/registrar/1
Option 2: Pass ID in the URL with queryParams.
this.router.navigate(['planteles/registrar'], { queryParams: { ID: idPlantel } });
https://myapplication/planteles/registrar?ID=1
Option 3: Pass ID in the state object of navigation extras.
this.router.navigate(['planteles/registrar'], { state: { id: idPlantel } });
Option 4: Shared service and BehaviorSubject to subscribe to data.
I owe you the code
I'm able to use any of these but I have a problem with each one of them.
I can't use Option 1 and Option 2 because the ID cannot be changed by the teacher because that gives him the possibility to fetch the student-campus data of another teacher and edit it, so it isn't safe.
The problem with option 3 and option 4 is when I refresh the page the state is lost.
Currently, I have a workaround with option 3 which is to redirect the user to the previous page if the state is undefined but I don't like that solution. I'd like to persist data if the user reloads the page without using LocalStorage.
Thanks in advance, all help or contribution is well appreciated.
Option 1 is the correct option here (and the way you will find most sites in the real world are implemented... including this one we're on now). The problem is your approach to web security, and what you need to fix is your backend. You're approaching web security as though front end security is real, it's not. Web security exists on your backend. Users should not be able to fetch or view or manipulate data that does not belong to them, and this must be enforced by your backend.
A high level example of how this might work: some secure authentication token should be granted when the user logs in, then this authentication token should be attached to each request. The API then uses this token to check which user is making the request and ensures they have the proper permissions. If they do not (as in the case of the user editing their URL param to some ID they do not have permissions for) or if there is no token, the API should return a 401 or 403 response and the front end should handle it appropriately (ie sending them back to list, or showing an error page, whatever you decide)... how to issue this token, make it secure, and make use of it is an entirely separate topic beyond the scope of this answer.
In any of the options, I could open my dev tools, and view any API requests being made, and change the ID's and use that to view or manipulate other people's data without any effort at all. So options 3 / 4 are barely more "safe" than 1 or 2. As none of these are safe without properly implemented backend security.
Front end "security" exists only as user experience. Me and you are both using the same URL to view this page, but we see different options and buttons, like you can edit or delete your post and accept answers, while I can't, but I can edit or delete my answer etc. This isn't for true security purposes, SO's servers enforce who can and can't take what actions. It's just showing me and you the UI that reflects our different permissions, ie, its all just UX.
There's another way too, which is defined in Angular docs itself.
NavigationExtras
Example:
let navigationExtras: NavigationExtras = {
queryParams: {
"firstname": "Nic",
"lastname": "Raboy"
}
};
this.router.navigate(["page2"], navigationExtras);
I have a task of redirecting an user to the last visited page after s/he logs in. Now I clearly have to store the router Url of the last visited page in my backend. My question is how can I do that so that I do not have to make a lot of backend calls? I am using Angular 4 + Spring boot. Please help me with suggestions and ideas.
Save the last navigation status in local storage, which you can refer to later. For an example, you add something like following to your app component.
export class AppComponent {
constructor(private router: Router) {
const lastVisitedUrl: string = localStorage.getItem('last_visited_url');
localStorage.removeItem('last_visited_url');
if (lastVisitedUrl) {
this.router.navigateByUrl(lastVisitedUrl);
}
router.events.subscribe((event: RouterEvent) => {
if (event instanceof NavigationEnd) {
localStorage.setItem('last_visited_url', event.url);
}
});
}
}
storing the last route in local storage seems like a good fit here, since this shouldn't be sensitive data (unless your routes contain sensitive data). this avoids having to make network requests on each route change, or on initial load.
There are two downsides to this approach
local storage doesn't persist across multiple devices.
a user can always manually clear out what's in their local storage. but these would be for users that want to spend the time doing this.
For the sake of completeness, I'll also mention that you could use a cookie to store this data. Though, I would recommend local storage over cookie for this type data. keeping in mind that cookies suffer from the same downside as listed in point 2 above.
if the requirements give you leeway where the above aren't concerns, I suggest local storage. However, if the requirement for maintaining the last visited page is critical to the application, there is no avoiding the extra HTTP requests as you are persisting state at this point. In this scenario, I would suggest explaining the trade off to the stakeholders to see if these use cases are acceptable.
Good luck
To be honest, I think that storing the URL each time it changes is the most effective solution because identifying when the user stops using the application is more complicated and has a lot of use cases to consider.
We have a site with most of the content managed by Wordpress, however when the user navigates to search pages (user searches for a product), it's handled by React JS.
It's all on the same domain, so the user never knows that they are interfacing with two different applications.
Google Analytics on the site, however, doesn't seem to perceive sessions correctly. It's logging entrances (landing pages) to the site as search pages with rather long URLs:
There are thousands of landing pages like this, and the site is new, so there's no way this is all traffic is coming in from external links
Referrer path for all of these sessions is "(not set)"
Internal IP addresses are filtered
The traffic is coming from various sources/mediums, suggesting that sessions are somehow breaking (screenshot below)
Currently, GA is set up with GTM. I tried using this to fire the GTM tag in React.
Also attempted making the GA tag within GTM fire on browser history changes rather than page views (history changes fire when in React, normal page views in Wordpress). But the issue still persists with these modifications.
Note that these sessions are not specific to any one browser:
The issue you're experiencing comes from the fact upon search, you are switching your entry point and doing a hard refresh of your page to the React app. Even though the domain doesn't seem to change, it's still considered by the browser as a fresh page load and thus showing like so in your analytics, as shown by this request:
You haven't really told if you were using react-router in your app (I'm assuming you are given the different paths), a way to get around the problem would be to use react-ga instead of the default GA script and leverage the onUpdate callback of react-router.
First import and initialize the module with your GA id:
import ReactGA from 'react-ga'
ReactGA.initialize('UA-000000-01')
Then in your routes configuration, add the onUpdate property on the <Router> and create a method that will call the google analytics with only the pathname so you won't end up with all the query parameters that are quite obnoxious in the dashboard.
const onUpdate = () => {
ReactGA.set({ page: window.location.pathname })
ReactGA.pageview(window.location.pathname)
}
<Router onUpdate={onUpdate}>
...
</Router>
Still, if you want to keep track of the user search, I would recommend using events instead, and doing something like the following upon search:
ReactGA.event({
category: 'User',
action: 'Search',
value: 'your formatted search value'
})
It will also give you the ability to format the value of the search any way you want, which would be more readable for you than query parameters.
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.
I am working on a very basic SPA using Backbone.js. My app has few routes. Among them there are 2 that give me issues: the index route ("/#index") and menu route ("/#mainmenu").
A simple workflow in my app is as follows: the user fills a form -> clicks to login -> trigger ajax request -> if login successful go to "/#mainmenu" route. if login failed, remain on "/#index" route.
On "/#mainmenu" if the user clicks on logout -> ajax request -> if logout success go to "/#index". if logout failed remain on "/#mainmenu".
The issues that I am struggling with are:
A clean way to trigger transition to "/#mainmenu" after successful login (I currently use router.navigate("mainmenu", {trigger: true}); but read that should avoid using this approach, in derrick bailey's article https://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ )
A clean way to prevent the user to go back to the "/#index" when pressing Back button in the browser from "/#mainmenu" route. I will also would like to preserve the url hash to reflect the current view.
Prevent the user to go forward to "/#mainmenu" after successful logout.
Is that even possible to prevent url hash change when clicking browsers back/forward buttons?
When I say "clean" I refer to "what are the best practices?". I partially solved some issues by saving url hashes and restore the appropriate hash (by router.navigate(currentRoute, {replace: true}); ) but I feel that it's a hacky approach.
Any feedback is welcome and much appreciated.
One way to solve this problem is by applying an async before filter on the routes that require an auth status check before the actual callback route is executed.
For example:
https://github.com/fantactuka/backbone-route-filter
The philosophy of avoiding {trigger: true} is based on the fact that when the router gets triggered with this flag, the entire initialization procedure for that route gets triggered. You will lose the benefit of having previously defined appstates because the app will have to re-initialize all content while this work had alrady been done before.
In practice, I think that it is useful to assess what your web app actually does. If losing appstate isn't an issue because the views you want to render are entirely new, then I don't see a problem with creating a client side redirect that re-inintializes your app.
If, on the other hand, your app has many views already rendered for which you want to maintain the same state as before, you can listen for an auth state event on each component that requires it, and make only those views re-render accordingly if they need to.
I don't think there's anything wrong with triggering routes, have been doing this without any issue for 2+ years. It all boils down to your requirements, read the article looks like a lot of work to me.
There are multiple ways to do this. First, you can disable back/forward buttons using window.history.forward(). Second, my favourite, is to do the processing in Router#execute. A sample might look like :
execute: function(callback, args, name) {
if (!loggedIn) {
goToLogin();
return false; //the privileged route won't trigger
}
if (callback) callback.apply(this, args);
}