I understand that ember-data is still in a fairly unstable state. I just would like to confirm that what I'm experiencing is expected behavior or a bug. And hopefully find some manner of work around.
I have an application that functions correctly in every expected way except one. Best I can tell I've traced it back to way the application routes on initial load. If I start the application from a route #/posts or #/post/1 where the id is valid it works fine. Application starts, routes, and loads the model. Any valid route works fine. If I were to use a route with a bad id #/post/1a534b where ember-data will not be able to find an underlying model with that id, the application never routes.
I've enabled LOG_TRANSITIONS on my application and confirmed it never transitions to a route, doesn't error on routing, never even injects my application template into the DOM. This problem is unique to initial load as it seems to wait for the model to load before injecting. This never happens because the promised model doesn't exist.
So is this expected behavior or is something else at play here?
I will say that my application isn't loaded til after dom ready and is pulled down async on dom ready. This shouldn't make a difference as the application runs fine when loaded with a proper route.
In the mean time I'll see if I can get a jsfiddle as an example since I can't use my code directly.
Unfortunately I believe this is expected behavior at the moment:
https://github.com/emberjs/ember.js/issues/1454
I think there is work being done to address errors and the router in general here:
https://github.com/emberjs/ember.js/pull/2740
In the route, transition to another route if the model fails to load.
model(params) {
return this.store.findRecord('account', params.account_id)
.catch(()=>{
this.transitionTo('admin.accounts');
});
},
I'm currently building an app with Ember 2.10.0
Related
I am having a problem with route-level actions that occur asynchronously occasionally not getting handled.
In my Ember app I sometimes receive the error:
Nothing handled the action 'X'. If you did handle the action, this error
can be caused by returning true from an action handler in a controller,
causing the action to bubble.
Where X is the name of some action. The problem is not that the action is bubbling or that the handler is not defined, because it works almost all the time.
The problem, rather, relates to to fact the this.send('X') is called in a promise callback, e.g., after saving a model:
model.save().then(() => {
this.send('X');
});
If the save encounters some network congestion or a delay on my server then the user might have navigated to another route by the time the promise is resolved. And that route might not have an action X defined (or worse perhaps it's the wrong action of the same name).
Is using actions here a fundamental design flaw?
If so, this would seem to severely limit the utility of actions, because they couldn't be used in any asynchronous context.
Or is there some way that transitions out of a route can delayed until any pending async stuff is resolved? (though this might create it's own undesirable symptoms)
IMHO there is not a design flaw in the case you have described. This is the nature of the promises. The same thing can happen within components and there might occur some errors related with trying to set properties on destroyed Ember components. There is a great addon I use to handle such cases: Ember Concurrency
If you take a look at this addon; you will see that there are similar cases explained, which are very similar to the one you have described. I would suggest benefiting from a popular, already heavily used library like this one instead of trying to tackle it by your own. My best regards.
For the simplicity of coding, I would suggest to move such actions to Application Controller and inject it wherever needed.
Application Controller can be injected as
import Ember from 'ember';
export default Ember.Controller.extend({
appController: Ember.inject.controller('application')
});
To use it in Template:
<button {{action 'X' target=appController}}> Login </button>
To invoke it from your code
this.get('appController').send('X');
You can also use Service to achieve this.
You can use isDestroyed guard check before calling send. But I would encourage you to use ember-concurrency addon for this use case. If you can go through How to do you check for isDestroyed? discussion forum, you will get the idea.
When using the Emberfire (Firebase) adapter, I'm getting various errors that indicate the tests are not waiting for data operations to complete.
eg: Error: Assertion Failed: You can only unload a record which is not inFlight. when I try to create, check, and then delete a record
Also:
FIREBASE WARNING: Exception was thrown by user callback. Error: Called stop() outside of a test context at Object.extend.stop (http://localhost:4200/assets/test-support.js:3000:10) at exports.default._emberTestingAdaptersAdapter.default.extend.asyncStart
These errors do not occur while manually navigating through my app, nor when using the standard ember data adapter.
What is causing these errors and how do I avoid them?
Edit: although the symptoms are a bit different (no error thrown), it sounds like this problem may have the same root cause as the errors I've been seeing.
tl;dr
To work around the issue, I've been using a custom test waiter. You can install it with ember install ember-cli-test-model-waiter (works with Ember v2.0+)
Longer answer:
The root cause of these problems is that the ember testing system doesn't know how to handle Firebase's asynchronicity. With most adapters, this isn't a problem, because the testing system instruments AJAX calls and ensures they have completed before proceeding, but this doesn't work with Firebase's websockets communication.
So although these errors don't occur when interacting manually, I believe they technically could if it were possible to click fast enough.
These problems are known to occur with ember-pouch and will likely also occur with other non-AJAX adapters (eg. localstorage adapters (1, 2), or any other websockets-based adapter. It may occur with the fixture adapter, but that may return results immediately and so not trigger this problem). It also happens with other asynchronous processes, like liquid-fire animations (fixed in a similar way)
The custom test waiter I mentioned in the tl;dr works by waiting for all models to resolve before proceeding with the test, and so should work with all of these non-AJAX adapters.
For more background on how ember's testing deals with asynchronicity under the hood, Cory Forsyth has a helpful blog post, and this gist gives another more flexible solution approach but that requires more manual book-keeping.
In ember's blog post about 1.12 release, mentions an example using application.deferReadiness inside an instance initializer. However, it doesn't seem to be working. The first parameter to the initialize function doesn't have a deferReadiness method. Any ideas?
I think it's reasonable to post #tomdale explanation here as an answer.
#tomdale: "It's not possible to defer app readiness in an instance initializer, since by definition instance initializers are only run after the app has finished booting.
Sidebar on the semantics of application booting: "App readiness" (as in, deferReadiness() and advanceReadiness()) refers to whether all of the code for the application has loaded. Once all of the code has loaded, a new instance is created, which is your application.
To restate, the lifecycle of an Ember application running in the browser is:
Ember loads.
You create an Ember.Application instance global (e.g.
App).
At this point, none of your classes have been loaded yet.
As your JavaScript file is evaluated, you register classes on the
application (e.g. App.MyController = Ember.Controller.extend(…);)
Ember waits for DOM ready to ensure that all of your JavaScript
included via <script> tags has loaded.
Initializers are run.
If you need to lazily load code or wait for additional setup, you can call deferReadiness().
Once everything is loaded, you can call advanceReadiness().
At this point, we say that the Application is
ready; in other words, we have told Ember that all of the classes
(components, routes, controllers, etc.) that make up the app are
loaded.
A new instance of the application is created, and instance
initializers are run.
Routing starts and the UI is rendered to the
screen.
If you want to delay showing the UI because there is some runtime setup you need to do (for example, you want to open a WebSocket before the app starts running), the correct solution is to use the beforeModel/model/afterModel hooks in the ApplicationRoute. All of these hooks allow you to return a promise that will prevent child routes from being evaluated until they resolve.
Using deferReadiness() in an initializer is an unfortunate hack that many people have come to rely on. I call it a hack because, unlike the model promise chain in the router, it breaks things like error and loading substates. By blocking rendering in initializers, IMO you are creating a worse experience for users because they will not see a loading or error substate if the promise is slow or rejects, and most of the code I've seen doesn't have any error handling code at all. This leads to apps that can break with just a blank white screen and no indication to the user that something bad has happened."
See also discussion: https://github.com/emberjs/ember.js/issues/11247
Problem
After upgrading to the newest ember.js I get this error:
Uncaught Error: assertion failed: Could not find state for path: "loading"
Looking at the framework code, it seems that ember wants to route to a 'loading' route until the ember data promises return. I didn't see any documentation about a 'loading' route, so just tried adding one like this:
loading: Ember.Route.extend({
}),
which results in the error:
Uncaught Error: assertion failed: Loading state should not be a route
Clues
Running build from master (commit 693c8185fbda70df523bb60d21f3dc6d9d94b018)
Using the old router build (ember-old-router.js) so I can upgrade without rewriting my router
Error only happens when I navigate directly to the URL. It does not happen when I route from a sibling route.
Question
Should I be defining a loading route somewhere? If so, what is the syntax? If not, how can I prevent this error?
That's a curious issue, because I thought Ember.StateManager/Ember.State were no longer used, but it seems like you're using them somewhere.
Why is this?
Nevertheless, by declaring loading as an Ember.State, it should at least fix your problem, but unless I'm horribly mistaken, you should be moving well away from Ember.State in favour of the new router.
loading: Ember.State.extend();
I'm used to thinking about a single-page application startup happening like this: 1. Bootstrap some data into critical models, 2. Instantiate a master controller, and 3. Call it's render() method to kick things off.
How is this accomplished with Ember? Following the (meager, sigh) examples in the documentation, it seems like things sort of kick off on their own when the page loads -- templates are compiled, views render like magic when the page loads. I feel like I am missing something fundamental. It there an example online of a more complex app, say something with tabbed or dynamically loaded views?
Lightbulb, going off it is not.
I've started a blog series about getting up and running with Ember on Rails. Here's Part 1:
http://www.cerebris.com/blog/2012/01/24/beginning-ember-js-on-rails-part-1/
I hope you'll find it useful, even if you're not planning to use Ember with Rails. Most of the interesting details are client-side and thus server-independent. The posts so far cover creating an Ember.Application object, loading data dynamically through a REST interface, and then rendering an Ember view on a page in handlebars. I hope it's enough to get you started.
When you extend an ember Application object you can provide a ready function which will be called when the application starts. You have to make sure to call this._super() or else it will break your application. Check out my sample sproucore 2.0 application (ember is the new name of sproutcore 2.0).
The way that ember works is that it sets up a run loop which responds to events. Whenever an event fires, the run loop basically calls the necessary handlers and runs any bindings that need to be updated. Since everything typically happens in the run loop you often don't really write any code to update things. Instead you write bindings which are fired when needed.
Another thing I've done is use an Em.StateManager to bootstrap.
App.Loader = Em.StateManager.create({
start: Em.State.create({
enter: function(mgmt, ctx) {
// this code will execute right away, automatically
}
})
});
Since you use create instead of extend, the object will be instantiated immediately. If you define a state called start, it will be recognized as the default initial state (or you can specify another one by name). So the new StateManager object will immediately enter the initial state, and when the StateManager enters a new state, it will always look for a method of that state called enter and fire it if present.
A state manager is the natural place to initialize your app because the object provides ways for you to micromanage execution order during an async loading process without entangling yourself in too many callbacks.