How to make route parameters optional in Ember.js? - ember.js

In Ember.js, given these routes:
this.route('chefs', { path: ":country/chefs"});
this.route('recipes', { path: ":country/recipes"});
I am required to put the positional params in the link-to component, like:
{{#link-to 'chefs' 'mx'}}Mexico Chefs{{/link-to}}
How can I avoid specifying some params in all my link-to components?
I'm thinking the 'chefs' route could use a default parameter coming from a service. But how? Any other ideas?
Here is a Twiddle:
https://ember-twiddle.com/fe34f195723fffa88869558e94f3fabc
Edit
Another example for this need is when using nested routes. Imagine a menu component with links to "chefs" and "recipes". When placed inside a parent route named 'country', say "/ca", the links don't need params.
{{#link-to 'chefs'}}Chefs{{/link-to}}
{{#link-to 'recipes'}}Recipes{{/link-to}}
These will navigate between '/ca/chefs' and '/ca/recipes'. But when this hypothetical menu is put on a root-level route, say '/login', the menu will error out "Assertion Failed: You attempted to define a {{link-to "chefs"}} but did not pass the parameters required for generating its dynamic segments..."
I would like to give the route a default "country" parameter when one is not passed by the link.
One alternative I see, which doesn't seem too elegant, is to create a service and inject the "country" param in every link.

Related

How do you create nested dynamic routes with ember.js where child route replaces parent's template?

On Ember 3.15 (Octane)
I'm trying to create the following routes in my application
/cars
/cars/:car_id
/cars/:car_id/:model_id
The first two are fine and I've been able to create them using ember-cli
ember g route cars
ember g route cars/car
The problem with the third one is that I need the model template to be shown under the cars template, ie. replace the car template. So if I put an {{outlet}} on the car template, the model template is rendered in it fine but obviously with that {{outlet}} nothing happens when I navigate to /cars/5/2
My router looks like this
Router.map(function() {
this.route('cars', function(){
this.route('car', { path: ':car_id'}, function() {
this.route('model', { path: ':model_id' });
});
});
});
I've tried using an index route ember g route cars/car/index but the problem with this that the params are not available in the index route and can only be accessed on the car route.
As plan b I've created the car and model routes as top level routes (based on this answer), but I'd like to achieve the above because of a few reasons
it seems more logical ie, structuring the app based on the nesting of the routes
I have many nested routes and creating all of them as first level routes will become hard to maintain
ember doesn't apply the active class correctly with this configuration. For example if I have a navbar with Cars as an link, I'd want it to have the active styling on all three pages. But this doesn't work anymore because the second route will be called something like car and the third one something like model.
there are some issues with the urls that are created by default using <LinkTo />.
So if I have something like this in my car template
<ul>
{{#each #model.models as |model|}}
<li><LinkTo #route="model" #model={{model}}> {{model.title}} </LinkTo></li>
{{/each}}
</ul>
The actual link works properly in that it takes me to the correct model page, however, the url is shown as /cars/undefined/undefined. (Although this is fixable by passing #models={{array #model.id model.id}}, it seems like a workaround at this point)
The solution is indeed to use the index route.
So have cars.car.index and cars.car.model. However you should use the cars.car route to load the common car model. Then you can access it in the cars.car.index route with this.modelFor('cars.car').
Then you can access the params.car_id in the cars.car route, use it to load the car, and then access this car from the cars.car.index and cars.car.model routes with modelFor.

rendering templates with #link-to and dynamic segments

I have a simple app to show a list of clients, and a detail page for each specific client.
So basically, two URLs:
/clients/
/clients/:slug/
I've been having some trouble rendering the results from my Client model into a template, specifically when using the #link-to helper with dynamic segments.
I've found two methods for doing so, and while I can get them both to work, I don't know which one is the best and why.
Here is some (simplified) code from my app to explain.
// app/models/client.js
export default DS.Model.extend({
name: DS.attr('string'),
slug: DS.attr('string')
});
(my actual model is larger, but these are the only relevant fields)
and here are my routes:
// app/router.js
Router.map(function() {
this.route('clients');
this.route('client', { path: 'clients/:slug' });
});
NOTE: I didn't nest the routes, because I didn't want to use the {{outlet}} nested template feature.
Here is the route for Clients, where I retrieve my list of clients
// app/routes/clients.js
export default Route.extend({
model: function() {
return this.get('store').findAll('client'); // fetch all Clients
}
});
Finally, here is the route to fetch info for a single Client:
// app/routes/client.js
export default Route.extend({
model: function(params) {
return this.store.query('client', {
filter: { slug: params.slug } // fetch one specific client, by slug
});
}
});
Everything works fine up to here, but my issue starts when displaying the model data in the templates.
There are two "ways", which one is correct??
OPTION A:
//app/templates/clients.hbs
// list all clients using #each
{{#each model as |client|}}
{{#link-to "client" client}} // pass the "client" object to generate my dynamic routes
{{client.name}}
{{/link-to}}
{{/each}}
Clicking on any of the generated links will render the client detail template, client.hbs
//app/templates/client.hbs
<h1>Client - {{model.name}}</h1>
In this example, I can use model.name to render my model object. That's fine, until I refresh the page! Then the info returned by model.name is obliterated. Same thing if I try to visit the URL directly. I have to go back to /clients and click on the link again to see my client's name.
I then looked for another way to display my data, where the model information would survive a page reload.
OPTION B:
After much reading I found a suggestion to use the specific client slug/id as param for #link-to
// app/templates/clients.hbs
// list all clients using #each
{{#each model as |client|}}
{{#link-to "client" client.slug}} // specifically use the actual slug/id, and not the object
{{client.name}}
{{/link-to}}
{{/each}}
But... by passing client.slug instead of client as parameter to #link-to, I can no longer use model.name to display my data. It simply returns nothing!
//app/templates/client.hbs
<h1>Client - {{model.name}}</h1> <-- model.name now returns nothing D:
However, using a loop DOES work for some reason:
//app/templates/client.hbs
{{#each model as |client|}}
<h1>Client - {{client.name}}</h1> <-- using loop even though I only have one record :(
{{/each}}
So option B works, and the information is displayed correctly after a page reload, or a direct visit from the URL.
But I'm now using a loop to display a single record! And I can't find an alternative solution that actually works.
To summarize:
option A feels like the correct way, but reloading the page obliterates the data.
option B actually works and returns the data, but I have to use a loop to iterate through a single record, which doesn't feel idiomatic / efficient.
I'm extremely confused, any clarification would be greatly appreciated.
Thanks in advance!
This is one of those fun 'magic' things that is supposed to simplify onboarding and make Ember seem easy - but really just caused the most confusion for what should be the most common use-case for the router.
At least it's in the guides now.
https://guides.emberjs.com/v2.12.0/routing/specifying-a-routes-model/#toc_dynamic-models
"Note: A route with a dynamic segment will always have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), and a model context is provided (second argument to link-to), then the hook is not executed. If an identifier (such as an id or slug) is provided instead then the model hook will be executed."
So, that's one thing...
But the other thing - is that you are getting back an array - because you are using query + filter - to create an array of records.
So, if you use queryRecord() - which is meant to get 1 record - then you'll get what you want. : )
I'd just add that {{outlets}} are cool. Here's how I normally do it - but t I can see that my route always loads all of the data this way... / which I normally want - but could see how in many cases - it wouldn't be desired.
And also - if you run into any more whacky params issues... investigate the underscore in the dynamic segment - because it's probably trolling you.

How show component only in a specific route?

I have a componente called hero (in application.hbs) and I wish display this componente only in home page.
I researched about how do this but without any success. Thanks!
After a few minutes and some searches on GitHub...
Just install ember install ember-truth-helpers and check the route name:
{{#if (eq currentRouteName 'index')}}
{{hero}}
{{/if}}
Glad to help!
I need more specifics, however, I am going to make the assumption that your home route is the '/' route.
The '/' route is actually your index route, so if you create an index.hbs file it will act as the template for your index route. And then your should just move the hero component to your index.hbs file.
I can't be sure your reasons, but I suspect that this could be a solution.
There is an invisible 'application' route... there is also an implicit 'index' route, but you can skip the confusion of that and just create a 'home' route and give it a path to the root. The application template will house the outlet - and then you can place your component just in the 'home' template;
(don't write an application route like this, but just for visualization)
Router.map(function() {
// overarching 'application' route
this.route('application', function() {
this.route('home', { path: '/' });
this.route('other');
});
});
Here is a twiddle with the full example in place. If this doesn't do what you want, then refer to the conditional suggestions. : )
Router.map(function() {
// here's an example of skipping of skipping the mysterious 'index' in another situation
this.route('books', function() {
this.route('books-list', { path: '/' });
this.route('book');
});
});
You can also render a component dynamically using component helper which save you a conditional statement inside your template.
The first parameter of the helper is the name of a component to render, as a string. So {{component 'blog-post'}} is just the same as using {{blog-post}}.
When the parameter passed to {{component}} evaluates to null or undefined, the helper renders nothing. When the parameter changes, the currently rendered component is destroyed and the new component is created and brought in.
So you can safely pass in anything to the component helper, in your case you can make the component name dynamically without worry an error will raised.
https://guides.emberjs.com/v2.1.0/components/defining-a-component/#toc_dynamically-rendering-a-component

How to define two ember routes with same name with different number of query params?

I have defined two ember routes as follows.
export default Router.map(function () {
this.route('stock-overview', {path: '/:lan/stock-overview/:companyId'});
this.route('stock-overview', {path: '/:lan/stock-overview/:exchange/:symbol'});
same name but different number of params. but when i add link-to helper as follows it gives an error saying.
<li>{{#link-to 'stock-overview' 'en' '70'}}Stock Overview{{/link-to}}</li>
<li>{{#link-to 'stock-overview' 'en' 'exchange' 'someSymbol'}}Stock Overview{{/link-to}}</li>
Console Error:
Uncaught Error: More context objects were passed than there are dynamic segments for the route: stock-overview
How can I handle this situation.? My ember version is:1.11.1
Appreciate any help.
I'm not %100 sure, but if i'm not wrong, you can only have one model for a route.
IMO, it would be better for you to do this with nested route.
Update:
I found some Q-A's from SO. Maybe those can be helpful for you:
Is resource nesting the only way to enable multiple dynamic segments?
Multiple Dynamic Segments in a Single Resource in Ember.js (see also Kingpin2k's answer)

Passing parameter for link-to in Emberjs

for some reason i need to pass my route one extra parameter with linkTo and use this parameter in my Route i try to find a way and find some source but its not enough my suggestion is like this :
{{#link-to 'someRoute' parameter1}}
{{#link-to 'someRoute' parameter2}}
App.SomeRoute = Ember.Route.extend({
//using parameter
});
is it possible to use transitionTo and pass parameter when changing?
You can use multiple parameters with the link to helper.
{{#link-to 'someNestedRoute' model1 model2}}Deep Route{{/link-to}}
You also can use transitionTo from a route (or transitionToRoute from controller) and send in multiple models as well.
transitionTo('someNestedRoute', model1, model2);