4
\$\begingroup\$

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:

  1. 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.

  2. Wrap the initialize method

    var 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 to initialize.

  3. Define a "convention-based" property injectArgs, which is used, if present

    var 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.

  4. 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 the Backbone objects must be creatable by extend, so no factory tricks.

\$\endgroup\$
1

1 Answer 1

2
\$\begingroup\$

If I had to pick, I would pick the second option. It's the clearest to me and aligns well with how the plugin injects properties. I don't think it's "lying" to the user because it's obvious that the needs.args function is doing something with the arguments before passing back control to the actual initialize function. But like you said, long arg lists will be annoying.

My only other note would be that I'm unsure how useful injection is in a dynamic language like javascript. I've used in statically typed languages like Java where it's definitely useful, but never entertained the idea in js. I'd definitely be curious to see the final result.

Edit: After some research there looks like another alternative - reflection: http://merrickchristensen.com/articles/javascript-dependency-injection.html

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your input, and sorry for the delay in reply. Argument name reflection would be neat, but unfortunately it breaks down when code is minified and argument names get mangled. I think DI in JS can be very helpful (especially for testability), since it gives structure to dependency resolution as compared to just using some global object and attaching properties to it. Work in progress is here: github.com/fencliff/needly - haven't had a chance to work on it for a while, but hope to make the push to get it good and going soon. \$\endgroup\$
    – fencliff
    Mar 1, 2013 at 13:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.