ralphinator80
ralphinator80

Reputation: 641

RequireJS dependency override for configurable dependency injection

I'm working with something that seems a perfect fit for DI but it's being added to an existing framework that did not have that in mind when it was written. The config that defines dependencies is coming from a back-end model. Really it's not a full config at this point it basically contains a key that can be used to determine a particular view should be available or not.

I'm using require so the dependency looks something like this

// Dependency
define(['./otherdependencies'], function(Others) {
    return {
        dep: "I'm a dependency"
    };
});

And right now the injector looks something like this

// view/injector
define([
    './abackendmodel',
    './dependency'
], function(Model, Dependency) {
    return {
        show: function() {
            if (model.showDepency) {
                var dep = new Dependency();
                this.$el.append(dep);
            }
        }
    };
});

This is a far stretch from the actual code but the important part is how require works. Notice in the injector code the dependency is required and used in the show method but only if the model says it should be shown. The problem is that there maybe additional things required by the dependency that aren't available when it shouldn't be shown. So what I'd really like to do is not have to specify that dependency unless the model.showDependency is true. I've come up with a couple of ideas but nothing that I like.

Idea one Have another async require call based on that model attribute. So the injector would look like this.

// Idea 2 view/injector
define([
    './abackendmodel'
], function(Model) {
    var Dep1 = null;
    if (model.showDepedency) {
        require([
            './dependency'
        ], function(Dependency) {
            Dep1 = Dependency;
        });
    }
    return {
        show: function() {
            if (Dep1) {
                var dep = new Dep1();
                this.$el.append(dep);
            }
        }
    };
});

Obviously this has issues. If show is called before the async require call is finished then Dep1 will still be null. So we don't really show the dependency which is a goal and obviously there's JS errors that will be thrown in this case. Also we're still using an if check on show which I don't like but the use case is that a dependency may or may not be present and we just don't want to require it if it's not needed so I might not be able to get around that. Also keep in mind that the model.showDependency is not actually a boolean value. It can be have multiple values which would call for different dependencies to be required. I'm just stripping it down here for simplicity of understanding the basic issue.

Idea two This is less solidified i.e. I don't think this will even work but I've considered playing with the require config.path stuff. My idea was basically having two configs so that './dependency' pointed to different places. Problem with that is despite what the model.showDependency value is the config is the same require config so can't change that at run-time. Maybe there's some magic that could be done here like having separate view directory path defined and using a factory type object to return the one that we care about but since that would ultimately result in the same async behaviour in Idea one I don't think that buys me anything (it's basically the same).

Idea three Have the dependency return null base on the model.showDependency attribute. This might be the best solution right now. I'm still stuck with some ifs but I don't think that's going away. Also this prevent in initialization code from being called.

Any better ideas?

Upvotes: 2

Views: 1160

Answers (1)

David Driscoll
David Driscoll

Reputation: 1419

Why not try using a promise, for loading the dependency?

You have two choices, depending on how your code needs to work.

Option 1) Return a promise for the result of the module 'view/injector', the result of this promise would be the current object result show above.

Option 2) Use a promise to load the dependency, and then execute the logic once the promise has been resolved.

Below is an example of Option 2, using the jQuery style deferred. I typically prefer when.js or Q. This example might fall apart if order of appending is important.

// Option 2
define([
    './abackendmodel'
], function(Model) {
    var dep1Promise = null;
    if (model.showDepedency) {
        var dep1Deferred = $.Deferred();
        dep1Promise = dep1Deferred.promise();

        require([
            './dependency'
        ], function(Dependency) {
            dep1Deferred.resolve(Dependency);
        }, dep1Deferred.reject); // Optionally reject the promise if there is an error finding the dependency.

    }
    return {
        show: function() {
            if (dep1Promise) {
                dep1Promise.then(function(Dep1) { 
                    var dep = new Dep1();
                    this.$el.append(dep);
                });
            }
        }
    };
});

Upvotes: 1

Related Questions