Use query params of parent route to refresh the model in subroute - ember.js

I have a route main which has a query param search.
I have subroutes main.sub1, main.sub2
Going to /main/sub1?search=hello or /main/sub2?search=hello sets the query param of the main route to hello. This part works fine.
Now I would like to be able to refresh the model of main.sub1 when the query param search in route main changes, so I here is the code of the route main.sub1:
export default Ember.Route.extend({
queryParams: {
search: {
refreshModel: true
}
},
model(params) {
// here I can use params.search to fetch the model for this route
}
}
I thought that since there is no search query param in the controller of main.sub1 Ember would be smart enough to guess it has to use the one from main. But now, when I go to /main/sub1 I have this error message:
Assertion Failed: You're not allowed to have more than one controller property map to the same query param key, but both main:search and main.sub1:search map to search. You can fix this by mapping one of the controller properties to a different query param key via the as config option, e.g. search: { as: 'other-search' }
My guess is that Ember automatically creates a query param search in main.sub1 which conflicts with the one from main, and does not even try to use the one from main.
How could I overcome this problem?
Simply put: how can I use an attribute of a parent route as a query param of a subroute?
Thanks!

I had the same issue.
It is completely enough to use the queryParams of the "main" route.
You don't need to add any queryParams to the sub-routes.
The refreshModel setting belongs to the main-route and all child routes will refresh as well.
Also you can use for the sub-routes model:
this.paramsFor(‘main’)
See: https://discuss.emberjs.com/t/refresh-model-when-the-queryparam-of-a-parent-route-changes/14903/2

Related

Tracking the current URL in a template

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.

Making a child route's model query dependent on parent route - promises?

I'm new to Ember and getting very stuck! I have a child route that needs to wait for the parent route to return it's record set. The child route then needs to do a findByIds for all the keys returned in the parent route (parent returns many keys). My child route looks like this:
export default Ember.Route.extend({
model: function(params, transition) {
var allPermissions = Ember.ArrayProxy.create({ content: this.modelFor('permissions') });
return this.store.findByIds('outlet', allPermissions.map(function(item, index, enumerable){
console.log('in map');
return item.get('howya_system_key');
}));
}
});
What I'm trying to do here is get all the parent route records, then return the result of of findByIds on each of the parent id's returned.
When I transition into the route it does not error and there is no output from the console.log. I think my problem is that the new ArrayProxy has not been created by the point that findByIds is executed so allPermissions.map has nothing to iterate over.
I'm guessing that I need to implement this via a Promise though I can't see how I'd do it. Can anybody point me in the right direction?
Much appreciated.
UPDATE:
I think I've worked out how to do this but I'm not sure I'm doing it the 'Ember' way. I've added a promise and my route now looks like this:
export default Ember.Route.extend({
model: function(params, transition) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve) {
resolve( _this.store.findByIds('outlet', _this.store.peekAll('permission').map(function(item, index, enumerable){
console.log(item.get('howya_system_key'));
return item.get('howya_system_key');
})))
});
}
});
Question is, is there a better way and is 'peekAll' the best thing to be using or am I going to end up looking at all store records for a model, rather than just store records for a model related to a parent route query?
UPDATE
OK, solved this. Can't use relationships as suggested below as the parent keys can point at different tables depending on type.
The solution was to use the promise as defined above, but use:
_this.modelFor('ROUTE_NAME').map
Instead of:
_this.store.peekAll('MODEL_NAME').map
I'll also need to filter the ID's to search for depending on Type and present different types in separate routes - but that's for another day.
For the final outcome of this where I was shown how to use the promise correctly, please see my other question

Access current route name from a controller or component

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.

Custom resolver to resolve model for /posts/create route

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', {});
}
});

Emberjs: transitionToRoute in the same route with different model value

I have an Ember application composed from 3 routes:
router.route('territory', { path: 'localhost/app/territory/:tid' });
router.route('aggregator', { path: localhost/app/territory/:tid:/aggregator/:aid' });
router.route(territory, { path: 'localhost/app/territory/:tid/aggregator/:aid/item/:iid' });
the possibles transition are from territory to aggregator, from aggregator to item, and from item to a sub item.
The sub item use the same route (the 3rd), just changing the iID value in the model of route.
I had created an action that allows the user to move into a particular route with some logic and at the end run the command:
model={
tid: "ttt"
aid: "aaa"
iid: "iii"
}
destination = 'item'; //the name of item route
controller.transitionToRoute(destination, model);
If I'm in the item route and I want to move to an other item, the URL will update, but not the content of the page. Obviously if I refresh the page with the generate URL the content will update.
Where is the problem? in the transition method that is Deprecated, or i have to use something different?
IMPORTANT: I'm using EmberJS - V1.0.0-RC.1
Is not a bug is just a normal situation in emberjs because every route have model and setupController.
The model function is used to retrive asynchronously from WS or Data module the necessary information (is a RSVP.Promise). When is complete the information will be passed to setupController function, where will be possible set properties of the controller connected with the view of current route.
Every time that i change the value of path but not the route, only setupController will be called.
To conclude, in my case, the problem was just an organisation problem of code.