Apps have global (to the page) data. Some apps have very little – maybe just a currentUser
. Others, like ours, have a lot – currentUser
, currentAccount
, userPermissions
, locales
, timeZones
, voiceSettings
, …
Where do you put this data?
We have come up with a few different options. Each has pros and cons.
Directly on the App
Assigning the current user object to App.currentUser
makes it very easy to get to. Any controller can have something like
FooController = Em.Object.extend({
isEnabledBinding: Em.Binding.oneWay('App.currentUser.canDoFoo')
});
We’ve found three major problems with this. First, it makes that controller difficult to test. We need to change the global in our setup and replace it with the original in our teardown (so we don’t affect other tests). Second, bindings to globals like this leak memory, though that problem may be specific to Ember 0.9. Last – and this is related to the testing difficulties – it’s generally fragile; if we want to delay fetching the currentUser
, many other things may have to change.
Directly on the App, Local Aliases
We can solve the testability problem by making a local alias to globals:
FooController = Em.Object.extend({
currentUser: Em.Binding.oneWay('App.currentUser'),
isEnabledBinding: Em.Binding.oneWay('currentUser.canDoFoo')
});
This is better, but (a) we still have the binding to the global, which (at least on 0.9) leaks memory; and (b) we’re still reaching out for our dependencies instead of having them injected.
“instance” Properties
We can avoid binding to the global by moving the singleton to a class property:
User = Em.Object.extend({
...
});
User.current = User.create({...})
FooController = Em.Object.extend({
currentUser: User.current,
isEnabledBinding: Em.Binding.oneWay('currentUser.canDoFoo')
});
We’re still reaching out to dependencies, though.
Injecting through Routes
We could move all of these globals to the app’s IndexRoute
or a more specific route if the globals aren’t needed in all sections of the app:
App.IndexRoute = Ember.Route.extend({
currentUser: App.User.create(),
setupController: function(controller) {
controller.set('currentUser', this.get('currentUser');
}
});
Now we’re getting somewhere (in theory)! The definition of the globals is centralized. Better yet, the globals are defined on an instance level, which means they won’t get automatically instantiated when loading the app for unit tests.
Unfortunately, I can’t get this to work very automatically in nested routes. I end up calling into IndexRoute#setupController
manually from my nested routes.