I'm trying to implement lazy loading in an Ember.js application. Ideally, I'd prefer to have all the relevant code for each module, including any controller and route definitions, in a separate .js file that gets lazy loaded.
Right now, the js file gets loaded correctly when I transition to the route, but because Ember implicitly generates a route definition, the implicitly-generated route object is used instead of the route in my lazy-loaded js file.
In my lazy-loaded js file, I've got a route App.UsersManagerRoute that should be linked to the users.manager route. In the Ember Inspector, I can see that an implicitly generated route is being used instead, even after I've loaded the js file.
To try to fix this, I've tried to manually register the route after loading the js file, but it doesn't seem to be working. This is my code that does the lazy loading:
Ember.Router.reopen({
_doTransition: function (_targetRouteName, models, _queryParams) {
var resourceName = _targetRouteName.split('.')[0];
var self = this;
var transition = self._super(_targetRouteName, models, _queryParams);
transition.abort();
var fullRouteName = 'route:' + camelizeRouteName(_targetRouteName);
self.container.unregister(fullRouteName);
App.lazyLoad(resourceName, function() {
self.container.register(fullRouteName, App[sentenceCasedRouteName(_targetRouteName) + 'Route']);
transition.retry();
});
return transition;
}
});
After I unregister the implicitly generated route and register my lazy-loaded route, the route definition seems to be used correctly, but for some reason, I see a blank page instead of the right template. I'm not too sure what I'm missing here, or if what I'm trying to do is the recommended approach.
All the examples of lazy loading in Ember I've seen so far place the Route outside the lazy-loaded file. Is that the only option that would work?
Auto generation of ember routes is caused by link-to component through href computed property. Never fight against it. Ember will not work properly and you will loose. But you should know it deeply in order to understand the mechanism.
href LinkToComponent method ask for a URL. Before answer, Ember looks for the route. If it doesn't exist, Ember creates one from route:basic.
Container and Registry have some useful method: reset and lookup the former, register and unregister the latter.
register and unregister modify the registry.
lookup creates instances if they don't exist, looking for the factory in factoryCache, and storing them in cache. If the factory doesn't exist there, it asks the Registry.
reset clears cache and factoryCache of the specified member.
That said, the right sequence in order to achieve lazy loading should be:
unregister(fullName);
reset(fullName);
register(fullName, factory);
lookup(fullName);
For an initial solution, have a look at https://github.com/ricottatosta/ember-wiz
Auto generation of ember object is caused by link-to component through href computed property. In order to avoid this behavior (it could be responsible of blank pages) I advice to change href to avoid calling function that calculate so called 'intention' that autogenerate missing objects.
Related
I am new to ember, so please treat me like a fool. What I'm trying to do first is to understand the concept.
In my application I heavily rely on few jQuery plugins they fetch new portion of data in their callbacks, that's how these plugins are designed, but I am not sure how can I trigger them to fetch a new portion of data from API passing to API updated query parameters after plugin has been rendered.
I have wrapped the plugin in a component, in component's template I send data to it as (I use emblem.js syntax here)
= plotly-chart chartData=model
In model I have
//app/models/data-points.js
import DS from 'ember-data';
export default DS.Model.extend({
// time: DS.attr(),
ch1: DS.attr(),
ch2: DS.attr(),
ch3: DS.attr(),
temperature: DS.attr(),
});
And then in component itself I fetch data
//app/components/plotly-chart.js
dataPoints: Ember.computed.map('chartData', function(item){
return item.getProperties('ch1', 'ch2', 'ch3', 'temperature');
}),
and make some manipulations with data, which isn't so important for the question itself.
Ah, and I have a route graph/ which later calls that component
//app/routes/graph.js
import Ember from 'ember';
export default Ember.Route.extend({
queryParams: {
start_timestamp: {
refreshModel: true
},
end_timestamp: {
refreshModel: true
}
},
model(params) {
return this.get('store').query('data-point', params);
}
});
So as you see I have tried to fetch new properties via query params, finally it works great if I just update the url in browser, but now can I trigger new call to API and fetch new data and get this new data in a component itself?
Also I'm struggling to understand what role controllers play in all of these. It is mentioned that controllers will be deprecated soon, but still used here https://guides.emberjs.com/v2.10.0/routing/query-params/
My code seems to work without controllers, so this is really confusing.
Also I suspect maybe I should use services for what I'm trying to achieve, but not sure how.
Ember experts, could you please point me into a right direction? The most important thing is how to fetch new portion of data from API with updated query parameters (query parameters to API itself, not nessesarely the ember application, but I suspect in ember-data it is the same thing? or not %) %) %)).
UPDATE 1
Really I should use services for that, shouldn't I? Pass arguments into a service and query a store there. Would that be a correct pattern?
Then query parameters in url are not the same as querying the store and is an independent thing. Am I right?
but how can I trigger new call to API and fetch new data and get this new data in a component itself?
If you change your queryParam values in a controller using an action (combined with your current route setup) it will adjust your route and re-call your API, as the values are bound together to make this particular use case simple :-) You're about 98% of the way there ... :-)
Re controllers going away, they won't for a long time as the replacement hasn't been worked out yet. You could do some of this in a service if you want to, but there is no need as you are almost done.
Thanks, that make sense though. I just worried I'm doing it wrong.
By they way finally I found a way to access store from the controller Access store from component but:
1. I was unable to take out the data from that variable, probably it's me being stupid.
2. I double it's the right way to access store directly in a component and better to use services for that or rely on “Data Down Actions Up” (DDAU) paradigm?
Finally I was able to fetch new portion of a data calling a controller's action from within the controller, but then the next problem raised - the data was updated, but the JS code did not know about that because I feed the jQuery plugin with this data and it did not pick up changes automatically. I think I might be doing it a wrong way there %)
But finally I get it working by adding an Ember's observer to that variable and in observer calling a redraw function (for chart in this particular place).
#acorncom Thanks!
I am typically searching for answers here but I finlly gotten to the point where I can't find a good answer.
I am looking to build an ember app which only initially loads in the things that it needs just to start and open the main route. All other controllers, views, templates, etc. Would be loaded lazily when a specific route gets triggered.
I have found a good example of how to accomplIsh this here:
http://madhatted.com/2013/6/29/lazy-loading-with-ember
My main question is to determine what build tools out there support this theory of lazy loading application code? So far, I've seen that Brunch, Yeoman, and Ember App Kit seemed to minify and concatenate all the scripts and templates. I am very happy with minification but need those files separate. I have thought about just putting this code into the app/assets location so that it gets copied over without concat but it does not get minified.
Does anyone have a solution? Thanks!
You can do this with brunch by adding the following to your brunch config
files: {
javascripts: {
joinTo: {
'javascripts/app.js': /^app(\/|\\)(?!admin)/, // concat everything in app, except /app/admin
'javascripts/vendor.js': /^vendor/,
'javascripts/admin.js': /^app(\/|\\)admin/ // concat only /app/admin
}
}
}
Grunt (used in yeoman and ember app kit) is ridiculously flexible, so I'm sure you can set up the same thing there by diving into Gruntfile.js
The question was: "I am looking to build an ember app which only initially loads in the things that it needs just to start and open the main route. All other controllers, views, templates, etc. Would be loaded lazily when a specific route gets triggered.".
Ember expects to have anything it needs right there when the page gets loaded. I wouldn't be wrong, but lazy loading of routes doesn't seem to be a feature of Ember. Ember CLI is the same. It uses bundling and minification to reduce the overload. But everything should be there to make it work.
Instead, people like me would like to load things only when they are required.
When you try to implement lazy loading in Ember, everything should be represented by a module (file.js): a route, a module; a controller, a module; and so on.
You should follow a schema (like POD), to which apply a mechanism to find things where they are supposed to be.
Every module should know its dependencies. But some of them are very frequent (route, controller, template).
You should use a module loader for the browser. It can be requirejs or whatever you like. But ES6 is at the door. Let's think about that.
Many people use beforeModel hook to achieve a result. I did it, and it works, if you don't use link-to component. Otherwise everything crashes. Why? Because of href computed property. When a link-to has been inserted, an href is calculated for it. Because of that, Ember looks for the route where the link points to. If the route doesn't exist, one is created from route:basic.
A solution could be the preloading of all the routes represented by all link-tos inserted in the page. Too much expensive!
An integration to this answer can be found at Lazy loading route definitions in Ember.js
For an initial solution to lazy loading of routes organized in POD, have a look at https://github.com/ricottatosta/ember-wiz. It is an ES6 based approach, which relay on SystemJS as module loader.
I have a route that has a dynamic segment:
this.resource('dog', {path: '/dog/:pet_id'});
For debugging purposes, I would like to linkTo dog with the specific dynamic segment of '666'. But
{{#linkTo 'dog' '666'}}Click to go to dog{{/linkTo}}
is giving me "undefined" instead of "666". Do you know why?
See it running on jsbin.
See the code on jsbin.
Your working jsbin: http://jsbin.com/iwiruw/346/edit
The linkTo helper does not accept strings as a parameter, but instead model from which to pick up the dynamic segments defined in your router map. If you don't have a model at hand leave the parameter out, and all you need to do is to hook into the serialize function of your DogRoute (if you don't have one defined just define it to instruct ember to use yours instead of the automatically defined) and return an object/hash containing the dynamic segments your route expects, this could be anything you want:
App.DogRoute = Ember.Route.extend({
serialize: function(model) {
return {pet_id: 666};
}
});
Hope it helps.
I cleaned up the code a little bit by removing unused bits and switching to the fixture adapter. Here's a working version without the need for a serialize method: http://jsbin.com/iwiruw/347
Ultimately, nothing needed to be changed in the base code beyond using a newer version of Ember and properly setting up the actual model classes and data.
I'm trying to use the new Router API (at commit 6a165ad), and I have some problems.
Given this router:
Router.map(function(match) {
match("/posts").to("posts", function(match) {
match("/new").to('new', function(match) {
match("/author").to('author');
});
});
});
I'm trying to transition to the new route.
Using new.index: this.transitionTo('new.index')
It works, but as you can see the route name is not really explicit (we don't even know that it's for a new post). It's consequently not a viable solution.
Using posts.new: this.transitionTo('posts.new')
I hoped it works, but an error is thrown:
The route posts.new was not found.
I believed the transition to the index was made automatically, but it seems not.
Using customized route name:
Since the commit specified above, Ember allows custom route naming. As my previous attempt does not work, I tried to force the new route to be posts.new, but it still does not work (idem if it was foo.new).
It looks like its not possible to go to a customized route that has nested routes.
TL;DR
I'd like to transition to the new route (and specifying posts). How should it be done ?
Before the router v2.1, I had routes that has child without to (i.e. match("/posts", function(match) { ... })), is it still working ? If so, what's the name of its children ?
This was actually a bug in Ember. Because index is implicit, you should not need to explicitly provide it.
The bug was fixed on master.
If you want to go to a route that has child routes, you should transitionTo the specified name of the route, and Ember will automatically add the index for you.
i'm trying to migrate to the new Router in Ember. the use case is this: user is not logged in but requests a URL that requires login. he is redirected to a login route, after successful login he is redirected to his original destination.
i achieved this with the prior Router by overriding Router.route(path) and intercepting path requests when the app was in unauthorized state.
the new Router doesn't have a route() function, also, i don't know how to override it now that the Router instance is created automatically by Ember. i probably shouldn't do that anyway.
there is a Route.redirect() hook that looks useful. however, Route no longer extends Path in the v2 Router, so there is no Root.path, and no path information is passed into Route.redirect(), so i don't know how to save the path info for calling transitionTo() later.
i've supplied my general approach below. how can i accomplish this? it seems like a very common use case for many application.
// i imagine something like this should happen
App.AuthRequiredRoute = Ember.Route.extend({
redirect: function() {
if(!App.controllerFor('login').get('isLoggedIn')) {
var pathToSave = ????
App.controllerFor('login').set('pathAfterLogin',pathToSave);
this.transitionTo('login');
}
}
}
// and then after login, the LoginController would call App.router.transitionTo(this.pathAfterLogin)
I have done a lot of research into this myself in the last day or two. I can share with you what I have discovered, and a couple of thoughts.
First of all, you can some information regarding the current path and contexts like so:
this.router.router.currentHandlerInfos
This returns an array. Each object has both a name and a context property. The names correspond to the name of the router you would call in transitionTo.
In my opinion, although you could work with something like this, it would be messy. The API docs don't refer to this and it may not be the intention to use this as a public API.
I see issues with the above solution for deeper nested dynamic segments too. Given how new the router v2 is, I think it will continue to evolve and a better solution is likely to present itself. It's a fairly common thing to want to save the current location and return to it at a later date.
In the meantime, rather than a redirect, perhaps use a conditional block in your template that presents a login rather than an outlet if the authenticated flag is not set on the ApplicationController? I know it's not as "right" but it is "right now".