I'd like to "free" one nested route, so that also users who are not even logged in can access this route.
For example:
posts
- /create
- /edit
- /show
On the posts route I used the AuthenticatedRouteMixin.
With this, all sub routes are automatically protected. Now I only want to make /show accessable. I know that I could use a mixin on /create and /edit and remove it from posts route, but if you have 10+ nested routes and only 1 of them should be available also for not logged in users, it's kind of inconvenient.
Do you know any other solution to that challenge?
If not, I think I have to write an additional mixin for that...
Thanks!
ember-simple-auth's AuthenticatedRouteMixin uses beforeModel hook to check whether session.isAuthenticated or not. You need to override the beforeModel in 'show' route to either skip the Auth check by bypassing AuthenticatedRouteMixin's super() implementation all together.
beforeModel (transition, skipAuthCheck) {
if (!skipAuthCheck) {
return this._super(...arguments, true);
}
}
Check if the 'show' beforeModel has dependency with parent route .i.e 'posts', implement this check at the parent route.
You could fake a nested route, by using the path parameter:
this.route('posts', function() {
this.route('create');
this.route('edit');
});
this.route('posts-show', { path: '/posts/show' });
Related
Working for a few years with ember.js now, it's still not quite clear to me, what should be considered as best practice for structuring list, view, create and update routes.
The projects I've worked with so far mostly used to routing trees per entity. The pluralized entity name for listing with a subroute for create and the singular entity name for detail view with a subroute for editing. As an example a post model would have these for routes: /posts for listing posts, /posts/new for the create functionality, /post/:post_id for showing a single post and /post/:post_id/edit for editing that one. The corresponding router would look like this one:
Router.map(function() {
this.route('post', { path: '/post/:post_id' }, function() {
this.route('edit');
});
this.route('posts', function() {
this.route('new');
});
});
This approach is working quite nicely for detail and edit view cause they are sharing the same model. So the model hook of the edit route could just reuse the model of the detail view route. In ember code this looks like the following:
// app/routes/post.js
import Route from '#ember/routing/route';
export default Route.extend({
model({ post_id }) {
return this.get('store').findRecord('post', post_id);
}
});
// app/routes/post/edit.js
import Route from '#ember/routing/route';
export default Route.extend({
model() {
return this.modelFor('post');
}
});
Normally we would return a collection of posts from posts route model hook and not implementing the model hook of posts.new route (or returning a POJO / Changeset there depending on architecture but that's not the question here). Assuming we are not implementing the model hook of posts.new the routes would look like:
// app/routes/posts.js
import Route from '#ember/routing/route';
export default Route.extend({
model({ post_id }) {
return this.get('store').findAll('post');
}
});
// app/routes/posts/new.js
import Route from '#ember/routing/route';
export default Route.extend({
});
But now this approach is not working well anymore cause a transition to posts.new route is blocked until the collection of posts are loaded. Since we don't need this collection to create a list of posts (at least if we only show them in posts.index route and not on all subroutes) this doesn't feel right.
Side note for those ones not that familiar with ember: Nested routes model hooks are executed in order. So in our case first the model hook of application route, afterwards posts route and then posts.new route waiting for any promise executed by one of them.
So what should then be considered as best practice?
Should the fetching of posts live in posts.index route if we are not showing them on nested routes?
Shouldn't the create route be a nested under the list route? So should we have posts, post-new, post and post.edit routes? Feels confusing since the post related code is splited over three route trees. Also it would go against the goal of the improved file layout being developed currently since the code would be splitted over three directories.
Should we just take the tradeoff of unnecessarily fetching the collection of posts since mostly the user flow comes from this route before the creation route and therefore the model hook is in most cases already loaded anyway?
Would appreciate any thoughts on that one. Decided to not ask that question in the community slack to better document the answer.
The main point of having a nested route in ember is to nest the output of your child route within the parent route. While your current structure works, it doesn't really match up with how ember has structured route functionality.
You should use a singular nested route with an explicitly defined index route.
At every level of nesting (including the top level), Ember
automatically provides a route for the / path named index. To see when
a new level of nesting occurs, check the router, whenever you see a
function, that's a new level.
Router.map(function() {
this.route('posts', function() {
this.route('favorites');
});
});
is equivalent to
Router.map(function() {
this.route('index', { path: '/' });
this.route('posts', function() {
this.route('index', { path: '/' });
this.route('favorites');
});
});
If you create an explicit posts/index.js file, this can be used as your list route. Doing this will help your avoid the issue where all posts are fetched before transitioning into the create route.
While different from the structure you currently have, I'd suggest the following.
Router.map(function() {
this.route('posts', function() {
this.route('index'); // /posts - posts/index.js
this.route('new'); // /posts/new - posts/new.js
this.route('view', { path: '/:post_id' } // /posts/1234 - posts/view.js
this.route('edit', { path: '/:post_id/edit' } // /posts/1234/edit - posts/edit.js
});
});
Depending on the complexity of logic in the new and edit, you can consider combining the two routes into one, or simply transitioning the new to edit after generating the empty model.
The benefits of this include:
Simplicity
You don't have to re-define your paths for all of the routes. Everything falls under posts/ and the route specifies the next piece.
Consistency
the JSONapi schema uses plural routes for both fetching a collection as well as a singular object.
Logic wrapping
If, you use and explicit index.js file, you can use the old posts.js file as a common wrapper for all items within the post namespace. Posts.js will have an outlet that the index, new, edit, and view routes are placed into
If you need your view and edit route to share the same model generation, you can nest your view/edit into a common group so they share a parent model.
this.route('posts', function() {
this.route('post', { path: '/:post_id' }, function() {
this.route('view', { path: '/' }) ;
this.route('edit', { path: '/edit' });
})
})
I have a "copy link" input in my template that should display the currently loaded URL (with query params) to the user for them to copy & paste.
The input with the URL is part of the template belonging to a parent route that has a number of children:
- Parent route <-- Copy URL link in this template
- Child route
- Child route
- Child route
This means that as you navigate the child routes, the URL should update in the parent route. This is turning out to be very difficult to implement.
AFAIK there is no native event for URL change so I can't create a component and simply track any browser event. I tried hashchange on the window but this obviously only tracks the #and not URL or query params.
I can't use window.location.href when the page loads as any subsequent transitions to the child routes will not be reflected in the input.
I can't get the URL with window.location.href in the didTransition action (or any other route hooks) on the parent route because at that point the URL hasn't updated yet (i.e. I get the previous URL)
EDIT:
At the moment, this is the only approach that seems to work:
// In the parent route:
actions: {
didTransition() {
this._super(...arguments);
Ember.run.schedule('afterRender', () => {
this.set('controller.currentURL', window.location.href);
});
}
}
but seems pretty hacky
Since Ember 2.15 you can use the route service for this.
Example:
import { inject as service } from '#ember/service';
import { alias } from '#ember/object/computed';
export default Component.extend({
router: service(),
currentRoute: alias('router.currentURL')
});
I think you can benefit from router's location's path property. What I mean is, you can inject router to the controller you like with an instance-initializer and define a computed property to watch the current path. Please check out the following twiddle. I wrote an instance initializer named instance-initializers\routerInjector to inject application's router to every controller. I defined a computed property named location within application.js controller as follows:
location: Ember.computed.oneWay('router.location.path')
I added this to application.hbs. If I got what you want correctly; this is what you want.
I'm working out an idea in Ember that has some pretty deeply nested routes.
Lets say my route is simulator.scenario.alien-invasion.situation.in-the-woods.something-else.etc
Is the only way to get to that route, to:
{{$link-to 'simulator.scenario.alien-invasion.situation.in-the-woods.something-else.etc'}}
<span>Next</span>
{{/link-to}}
Or is there some way to know that I'm already "in the woods?" and then have the route relative in some way?
(I'm not really going to have a route this deep, but it brought up the question. : )
You could use resetNamespace.
Quoting the documentation: http://emberjs.com/api/classes/Ember.Router.html#method_map
resetNamespace: false by default; when nesting routes, ember will
combine the route names to form the fully-qualified route name, which
is used with {{link-to}} or manually transitioning to routes. Setting
resetNamespace: true will cause the route not to inherit from its
parent route's names. This is handy for resources which can be
accessed in multiple places as well as preventing extremely long route
names. Keep in mind that the actual URL path behavior is still
retained.
App.Router.map(function(){
this.route('simulator', function() {
this.route('scenario', function() {
this.route('alien-invasion', function() {
this.route('situation', function() {
this.route('in-the-woods', { resetNamespace: true } function() {
this.route('something-else');
});
});
});
});
});
});
You should then be able to use relative links like:
{{#link-to "in-the-woods.something-else"}}Link Name{{/link-to}}
Regardless of where you are.
This is a very helpful talk about route nesting:
http://alexspeller.com/embercamp-london-talk-routing/
I don't think there is. The documentation is comprehensive for this, and it makes no mention. Glancing through the link-to component code and following it back to the router doesn't indicate it either.
I needs to apply an "active" class to a bootstrap tab depending on the current route name. The route object contains "routeName" but how to I access this from a controller or component?
Use this this.controllerFor('application').get('currentRouteName');
In fact, you don't need to apply active class by yourself. A link-to helper will do it for you.
See here:
{{link-to}} will apply a CSS class name of 'active' when the application's current route matches the supplied routeName. For example, if the application's current route is 'photoGallery.recent' the following use of {{link-to}}:
{{#link-to 'photoGallery.recent'}}
Great Hamster Photos
{{/link-to}}
will result in
<a href="/hamster-photos/this-week" class="active">
Great Hamster Photos
</a>
In the absolutely desperate case, you can look up the router, or the application controller (which exposes a 'currentRouteName' property) via this.container.lookup("router:main") or this.container.lookup("controller:application") from within the component.
If it was a common trend for me, I would make a CurrentRouteService and inject it into my component(s) so that I can mock things more easily in my tests.
There may also be a better answer to come along - but the container.lookup() should knock down your current blocker.
Since Ember 2.15 you can do this through the public Router service.
router: service(),
myRouteName: computed('router.currentRouteName', function () {
return this.get('router.currentRouteName') + 'some modification';
}
https://www.emberjs.com/api/ember/release/classes/RouterService
Which worked really well for me since I wanted something computed off of the current route. The service exposes currentRouteName, currentURL, location, and rootURL.
currentURL has the query params, but you would need to parse them from the URL.
For Ember 2, from a controller you can try :
appController: Ember.inject.controller('application'),
currentRouteName: Ember.computed.reads('appController.currentRouteName')
Then you can pass it to component.
Try this.
export default Ember.Route.extend({
routeName: null,
beforeModel(transition){
//alert(JSON.stringify(transition.targetName) + 'typeof' + typeof transition.targetName);
this.set('routeName', transition.targetName);
},
model(){
// write your logic here to determine which one to set 'active' or pass the routeName to controller or component
}
`
Using insights from #maharaja-santhir's answer, one can think of setting the routeName property on the target controller to use, e.g., in the target's template. This way there's no need for defining the logic in multiple locations and hence code-reusability. Here's an example of how to accomplish that:
// app/routes/application.js
export default Ember.Route.extend({
...
actions: {
willTransition(transition) {
let targetController = this.controllerFor(transition.targetName);
set(targetController, 'currentRouteName', transition.targetName);
return true;
}
}
});
Defining this willTransition action in the application route allows for propagating the current route name to anywhere in the application. Note that the target controller will retain the currentRouteName property setting even after navigating away to another route. This requires manual cleanup, if needed, but it might be acceptable depending on your implementation and use case.
I am trying to use the resolver system to resolve a model for /posts/create.
My router mapping looks like:
this.resource('posts', function () {
this.route('create', {
path: '/create'
});
this.route('index', {
path: '/:post_id'
});
});
When I go to the /posts/1234 route, my resolveModel method on the resolver is called, but when I go to /posts/create, it is not. I'm assuming that I'm missing a naming convention here, but I want to get /posts/create to use the resolver rather than creating a PostsCreateRoute just to have a one liner in the model hook.
Any help would be appreciated. I'd love to know if I'm approaching this incorrectly as well. Thanks!
resolveModel is being called in the first route because ember has a special convention for routes that include path params that use the convention :model_id. When ember sees this it will try to find an instance of the model with the id of the path param. You can see this behavior here https://github.com/emberjs/ember.js/blob/master/packages/ember-routing/lib/system/route.js#L871-L888.
The second route has no path params so ember's default behavior is to do nothing. If you want to create a new instance of the post model when a user enters that url you will need to declare your own model function to perform this action. For example:
App.PostCreateRoute = Ember.Route.extend({
model: function() {
return this.store.createRecord('post', {});
}
});