I'm writing a dependency injection plugin for the Backbone javascript framework, and I'm not sure what the API for constructor argument injection should look like.
Just for reference, Backbone objects are defined as follows:
var LoginView = Backbone.View.extend({
//constructor
initialize: function(options) {
//define an instance property
this.loginProvider = options.loginProvider;
},
//method
authenticate: function() { },
//prototypal property (available via instance, shared between all instances)
navigation: App.navigationService
});
My plugin supports instance property injection as follows:
var LoginView = Backbone.View.extend({
//...
loginProvider: needs.property('LoginProvider')
});
var loginView = new LoginView();
loginView.loginProvider.doLogin(); //loginProvider is auto-injected
I'm quite happy with this, but I also want to support constructor argument injection. Here I have run into doubt regarding best way forward. Here are the options I've considered:
Add class method
inject
var LoginView = Backbone.View.extend({ initialize: function(options, bar) { //property "foo" will be injected to options //second argument "bar" will be injected } }).inject({foo:'Foo'}, 'Bar');
This is clean, but it "hides" the injection to the bottom of the class definition. I feel the constructor injection configuration should be somewhere near the constructor, at the very least.
Wrap the
initialize
methodvar LoginView = Backbone.View.extend({ initialize: needs.args({foo:'Foo'}, 'Bar', function(options, bar) { //property "foo" will be injected to options //second argument "bar" will be injected } });
Now the injected arguments are right next to the constructor definition, but this gets a bit messy when you have more than one or two dependencies:
var LoginView = Backbone.View.extend({ initialize: needs.args( { foo:'Foo', bar:'Bar' }, 'BazService', 'QuxFactory', function(options, baz, qux) { //... }) });
This method also lies to the user, because the arguments are not actually injected to the
initialize
method directly. Instead they are injected into the class constructor function, which in turn passes them toinitialize
.Define a "convention-based" property
injectArgs
, which is used, if presentvar LoginView = Backbone.View.extend({ injectArgs: [{foo:'Foo'}, 'Bar'], initialize: function(options, bar) { //property "foo" will be injected to options //second argument "bar" will be injected } });
This solves problems posed by options 1 and 2, but it feels... icky. Because the property is defined on the class prototype, it will continue to hang around on every instance of the object even after initialization and I don't think it has any business being there.
None of the above
Got a better idea? I want to keep the plugin unobtrusive, so there are two maxima I want to keep to: No modifying
Object
prototype, and theBackbone
objects must be creatable byextend
, so no factory tricks.