Catch app 'ready' event outside the app - ember.js

I need to run some code after ember application got initialized. I don't want to invoke this code from App.ready to avoid tight coupling. It would be nice to have something like this:
App.on 'ready, -> console.log('do stuff')
But it won't work since Em.Application object is not subscribable and ready isn't really an event despite that docs said so

A simple way you can achieve this would be to extend your Application class with the Ember.Evented mixin:
App = Ember.Application.createWithMixins(Ember.Evented, {
ready: function() {
console.log('App ready');
this.trigger('appReady');
}
});
And you hook inside the ready event inside your app and trigger your custom event using this.trigger(...)
At this point you can use .on(...) to be notified when the event is triggered.
App.on('appReady', function() {
console.log('App already ready');
});
Example demo.
Hope it helps.

An other possibility may be to invoke your code from the resolve callback of the application.
App.then(function(app) {
console.log("App is resolved, so it's ready");
});
example stolen from #intuitivepixel ;) http://jsbin.com/AdOVala/66/edit
Edit/Note:
App.then() has been deprecated, see http://emberjs.com/deprecations/v1.x/#toc_code-then-code-on-ember-application:
As part of the Ember.DeferredMixin deprecation, using .then on an
Ember.Application instance itself has been deprecated.
You can use the ready hook or initializers to defer/advance readiness instead.

Related

Some questions about async in Ember

I'm working with emberjs during some time and now I want to refine my understanding of ember's nature.
Question 1.
Am I right that the route's model() hook is the best place to write asynchronous code (to get all needed data)? Should I try to place all my network requests (or at least most of them) inside routes' models?
Question 2.
The component hooks have synchronous nature. Does that mean it's a bad decision to write async code inside hooks?
Say, I have async init() hook where some data is calculated and didRender() hook where I expect to use that data. When ember calls init() it returns a Promise, so it's moved from a stack to a special queue and ember doesn't wait until event loop returns that code back to a stack. So ember runs next hooks, and when didRender() is being executed the init() hook may not be fulfilled and the expected data may not exist. Is that right?
Question 3.
Services hooks should also be synchronous. Because when a service is injected inside a component and is used ember also doesn't wait until the async hook is fulfilled.
Say, I have a shopping cart service with products property. The products ids are stored in localstorage and I want to get those products from a server to set them into products property.
import Service from '#ember/service';
import { A } from '#ember/array';
import { inject as service } from '#ember/service';
export default Service.extend({
store: service(),
init(...args) {
this._super(args);
this.set('products', A());
},
async loadProducts() {
const cartProducts = A();
const savedCartProducts = localStorage.getItem('cartProducts');
if (savedCartProducts) {
const parsedSavedCartProducts = JSON.parse(savedCartProducts);
const ids = Object.keys(parsedSavedCartProducts);
if (ids.length > 0) {
const products = await this.store.query('product', {id: Object.keys(parsedSavedCartProducts)});
products.forEach(p => {
p.set('cartQuantity', Number(parsedSavedCartProducts[p.id].qty));
cartProducts.pushObject(p);
});
}
}
this.products.pushObjects(cartProducts);
},
...
});
If I call loadProducts() from service's init() I can't use this.cart.products in controllers/application.js, for example. Because service is ready, but async init() is still executed. So, should I call it in routes/application.js model() hook?
Question 4.
If there is some general component that doesn't refer to any route, but this component needs some data from a server where should I make async requests? Or are computed properties and observers are the only solutions?
Thanks a lot.
Good questions here, you’re on the right track!
Question 1: yes, the general rule of thumb is that until you are familiar with things, doing async work in the route hooks (beforeModel, model and afterModel) make it easier to think about what is going on. Eventually you may want to start bending the defaults to suit your custom UI needs, but it’s simplest to start with this rule
Questions 2-4: you’re asking several questions here about async code, so will answer here more broadly.
First off, services can do async or not but unless you call those methods from a hook (like a route hook) you are responsible for handling the asynchronous results. For instance, the Ember Data store is a service that does return data asynchronously
But if you hit cases where you do need to do async in a component, the recommended addon to help with that is Ember Concurrency: http://ember-concurrency.com/docs/introduction/
Ember Concurrency helps solve many of the async bugs and edge cases that you haven’t yet hit, but will, if you start doing async code in components. So I’d highly recommend learning more about it.
Good luck!

Ember.js route hook to be called on every transition

Is there a route hook in Ember.js that is called on every transition, even if the new route is the same as the old route (for example, clicking a top-level navigation link to the same route).
I tried activate, but it's only being called once, and is not being called again when I use the top-level navigation to go to the same route I'm already in.
Example jsFiddle: When I click "Test" the first time, the activate hook is called, but when I click it a second time, it does not.
You can setup a didTransition in the router, exactly how Ember does it for Google Analytics.
App.Router.reopen({
doSomething: function() {
// do something here
return;
}.on('didTransition')
});
See example here: http://emberjs.com/guides/cookbook/helpers_and_components/adding_google_analytics_tracking/
UPDATE: (new link since old is broken)
https://guides.emberjs.com/v1.10.0/cookbook/helpers_and_components/adding_google_analytics_tracking/
Activate is not being called a second time because This hook is executed when the router enters the route... And when you click on that link-to a second time, the router isn't doing anything... As in, no transition is being made (although it is "attempted").
http://emberjs.com/api/classes/Ember.Route.html#method_activate
The method that I have found to work best is observing the currentPath from within a controller. I use this for animations between routes.
In your application controller you can do something like the following:
currentPathChange: function () {
switch(this.get('currentPath')){
case 'test.index':
this.doSomething();
break;
case 'test.new':
this.doSomethingElse();
break;
}
}.observes('currentPath')
You should be able to access almost any part of your app from the application controller, so it's a nice "root hook," I suppose.
Example: http://jsfiddle.net/mattblancarte/jxWjh/2/
Did you already consider the hook willTransition?
http://emberjs.com/guides/routing/preventing-and-retrying-transitions/
App.SomeRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
// do your stuff
}
}
});
Alter/Hack EmberJS code and add a jQuery event trigger inside the doTransition() Function. This is Best but kind of defeating the point.
As of today, 1 year later and Ember 2.0 kind of out, there is NO OTHER WAY :(
Ember does not provide a way to track route-change attempts! This includes URLattemts(history), link-to attempts, hash change attempts etc..

Catch All Action Handler

Now, this is really a question that's evolved from this. There is a whole lot more info there, but I guess, it's better to ask a direct specific question. So here it goes:
We typically define actions in a controller like so:
var FooController = Ember.Controller.extend({
actions: {
login: function() {
}
}
});
Is there a way to define a catch all action handler, like so (hypothetically):
actions: {
login: function() {
},
*: function(actionName, paramArray) {
}
}
This would be analogous to embers catch all route which I believe has been implemented, though I haven't tried it.
I need this because my Ember.Component renders a user supplied partial template using the {{partial}} helper. This partial might have {{action}}'s specified in them. These actions don't bubble up to the calling controller or route and are lost inside the component. This fact is mentioned in the docs in para 4.
If a catch all action was possible, my component could implement it and send the action back to the caller using something like this:
actions: {
*: function(actionName, paramArray) {
this.sendAction(actionName, paramArray)
}
}
The functionality around the actions hash on controllers, views and routes is provided by the action handler mixin [1]. As you can see, it injects the send(actionName) method and makes enables an object to receive actions. You can now easily override this function and catch all actions instead of looking for it in the actions hash -- which is what the implementation does.
[1] https://github.com/emberjs/ember.js/blob/v1.3.0/packages/ember-runtime/lib/mixins/action_handler.js#L8

How can I directly invoke an event handler on a route in Emberjs?

I'm hoping to stub/spy on a route's event handler in ember.js using my testing framework of choice, jasmine. Usually this involves overwriting the function of interest with a spy, which requires access to the object on which the method is defined:
spy = spyOn(someObject, "methodOnThatObject")
But in Ember, my event handlers for my routes are defined as follows:
App.ActivityRoute = Ember.Route.extend({
events: {
show: function(context) {
}
}
});
I would like to stub the function show, but I don't know how to get the object on which it is eventually defined? Or is it ever defined on an object? Perhaps it's invoked with #call or #apply? If so, how does one stub this?
I've tried digging around the source, but didn't manage to figure out how this is handled. Any pointers to where I should look in the source would also be helpful.
Cheers,
Kevin
You can use send('eventName', [optional record]):
If you're calling from a controller under the same route do:
this.get('target').send('show', this.get('content'))
Silly me. I can just do the following:
route = App.__container__.lookup('route:myRoute')
spy = spyOn(route.get('events'), 'show')
controller.send('show')
expect(spy).toHaveBeenCalled()
And that works.

Is there a way to get a callback when Ember.js has finished loading everything?

I am building an Ember.js app and I need to do some additional setup after everything is rendered/loaded.
Is there a way to get such a callback? Thanks!
There are also several functions defined on Views that can be overloaded and which will be called automatically. These include willInsertElement(), didInsertElement(), afterRender(), etc.
In particular I find didInsertElement() a useful time to run code that in a regular object-oriented system would be run in the constructor.
You can use the ready property of Ember.Application.
example from http://awardwinningfjords.com/2011/12/27/emberjs-collections.html:
// Setup a global namespace for our code.
Twitter = Em.Application.create({
// When everything is loaded.
ready: function() {
// Start polling Twitter
setInterval(function() {
Twitter.searchResults.refresh();
}, 2000);
// The default search is empty, let's find some cats.
Twitter.searchResults.set("query", "cats");
// Call the superclass's `ready` method.
this._super();
}
});
LazyBoy's answer is what you want to do, but it will work differently than you think. The phrasing of your question highlights an interesting point about Ember.
In your question you specified that you wanted a callback after the views were rendered. However, for good 'Ember' style, you should use the 'ready' callback which fires after the application is initialized, but before the views are rendered.
The important conceptual point is that after the callback updates the data-model you should then let Ember update the views.
Letting ember update the view is mostly straightforward. There are some edge cases where it's necessary to use 'didFoo' callbacks to avoid state-transition flickers in the view. (E.g., avoid showing "no items found" for 0.2 seconds.)
If that doesn't work for you, you might also investigate the 'onLoad' callback.
You can use jQuery ajax callbacks for this:
$(document).ajaxStart(function(){ console.log("ajax started")})
$(document).ajaxStop(function(){ console.log("ajax stopped")})
This will work for all ajax requests.
I simply put this into the Application Route
actions: {
loading: function(transition, route) {
this.controllerFor('application').set('isLoading', true);
this.router.on('didTransition', this, function(){
this.controllerFor('application').set('isLoading', false);
});
}
}
And then anywhere in my template I can enable and disable loading stuff using {{#if isLoading}} or I can add special jQuery events inside the actual loading action.
Very simple but effective.