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?
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);
});
}
}
};
});
Related
I have a strange issue with Dust.js contexts. Previously, I would pass the view model along as a plain old JavaScript object, e.g. res.render('page', { something: [1,2,3] } etc. When doing that, I could access something on the local context with {#something}...{/something} etc.
However, I changed the way I manage the context to use dust.makeBase, so that I can have globals and some sort of stack, rather than just an object. I'm using consolidate with express FWIW.
Now, I create the baseViewModel.
//
// at application init
//
app.baseViewModel = dust.makeBase({
someGlobal: 'example'
})
Later on, when rendering, I may extend it like so:
//
// in route handler
//
const viewModel = app.baseViewModel.push({
collection: someCollection
})
res.render('index', viewModel)
But then, the context stack looks like this:
{
"settings": {
// snip
},
"stack": {
"isObject": true,
"head": {
"collection": [
// snip
]
}
},
"global": {
"someGlobal": "example"
}
}
The problem is, now, to access collection, I must prefix the variables with stack.head.:
{#stack.head.collection}
<!-- etc -->
{/stack.head.collection}
Does anyone know why this is, and how I can get back to the simple way of just referring to {#collection} etc?
Thank you.
I managed to get this working, but it's a hack and should have to be done this way. There is a GitHub ticket now for this issue:
https://github.com/linkedin/dustjs/issues/743
Anyway, this is what I did:
dust.helpers.collection = (chunk, context, bodies, params) => {
return context.current().get('collection')
}
And then modified my markup to:
{#collection}
{.Name} etc
{/collection}
Strange, but I guess that's the way it works!
As per our comment chain, here's what I suspect to be the issue.
When you create a new Context object in Dust, it checks to see if you're passing in an existing Context to hydrate from. This is done using an instanceof check.
If the object does not seem to be an instance of a Context, it gets wrapped in a new Context, which would account for the behavior you're seeing here. This isn't great, so I'll go work on a PR to use a flag instead of an instanceof check.
You're using consolidate, which includes a dependency on dust, and I suspect that the version it includes is different than whatever you depend on in your package.json. I would check to see how many copies of dustjs-linkedin are in your tree (one in your root and one in consolidate's node_modules, perhaps). If you require dustjs-helpers it simply requires dustjs-linkedin itself, so the latter is what you really care about.
Let's say that I have the following:
lib/modules/module1.js
var m2 = require('module2');
module.exports = function(){
return {
// ...
get: function(cb){
m2.someMethod(params, function(error, data){
if(error){
cb(error)
}
cb(null, data)
})
},
// ...
}
}
Now let's say that I have a set of tests in another dir, e.g. tests/testModule1.js. From this file, I create an instance of module1 to peform some tests.
I would like to mock the objects passed by the m2.someMethod to it's callback function (not the cb function), from the file testModule1.js.
I've looked into Sinon.js, but I couldn't figure a way to do this. Actually, I that even possible?
Thanks.
You could use something like proxyquire, but I'm not a fan of modifying the built in require.
Personally, I would suggest rewriting your code to use dependency injection:
module.exports = function(m2){
return {
// ...
get: function(cb){
m2.someMethod(params, function(error, data){
if(error){
cb(error)
}
cb(null, data)
})
},
// ...
}
}
Note that I moved m2 to be a parameter in your exported function. Then somewhere else (app, or main, or whatever), you could do this:
app.js
var module1Creator = require('module1');
var module2 = require('module2');
var module1 = module1Creator(module2);
Then when you need to test it...
testModule1.js
var module1Creator = require('module1');
// inject the "fake" version containing test data, spies, etc
var module2Mocked = require('module2mock');
var module1 = module1Creator(module2mocked);
I would normaly agree that about changing the design and, as suggested by #dvlsg, DI would be my choice as well.
However, the project I'm working on is already on the move, and has a considerable size. By doing this change would imply in a huge manpower cost that, in this case specifically, might not be worth doing it.
As a solution, I've realized that once you do a require('someModule'), the someModule is loaded and it's stored as a singleton, in some sort of global cache (I don't fully understand this mechanism, but I'll look into it), and doesn't matter if you require('someModule') from another file, you will receive the cached version.
So, if in lib/modules/module1.js I do require('module2'), module2 is loaded and stored in this cache and, I can require('module2') and mock it in tests/testModule1.js. This will reflect when the get() from lib/modules/module1.js is called.
For that, I used Sinon.js to create the mocks in the test files.
The procedure above actually solved my problem, in a way that I didn't have to change the whole design, and I was able to do the tests. Thats why I'm posting this as an answer. However, I'll not set this as the correct answer here because, as I said before, I don't fully understand this mechanism and change required modules is not a good practice.
I would like to see what other devs have to say about this and, if the discussion leads to acceptance, I'll ultimately set this as correct.
I've got a Backbone/AMD app and what I would like to do is have a model/object be fetched in the main module when the app loads, then be either accessible to or be able to be loaded into subsequent modules without the overhead of re-fetching it (it's a permissions model and thus is required pretty much everywhere).
Is there a clean way of doing this?
Simply make it its own dependency:
define(["backbone", "text!api/v1/user/permissions"],
function(Backbone, permissionJSON) {
return new Backbone.Model(JSON.parse(permissionJSON));
});
If you require a module for which you want to keep state you can do as follows
stateMod.js
require(['jquery', 'lib1','lib2'], function($, l1,l2) {
var thisVariableHoldsStateBetweenModules = "initValue"
var thisIsTheAPIOfYourModule = function(newValue) {
thisVariableHoldsStateBetweenModules = newValue
}
var getInternalState = function () {
return thisVariableHoldsStateBetweenModules
}
return {
set: thisIsTheAPIOfYourModule,
get: getInternalState
}
}
})
What you get back when you require this module is thisIsAPIOfYourModule, so you can just use it to change thisVariableHoldsStateBetweenModules:
otherMod.js
require(['stateMod'], function(stateMod) {
stateMod.set("Hello world")
})
and later on, in module nextMod.js:
require(['stateMod'], function(stateMod) {
stateMod.get() // outputs Hello World
})
requirejs saves the object returned from the first time it is required, and this why you don't re-load (network wiise) d3js and jQuery or other common libs every time you need them in a module, otherwise it would be pretty crap.
Give this a go and let me know how it worked for you.
I'm trying to use a library -- Google's libphonenumber -- in my require application that is not AMD. What is the best way to consume this? I know I can create a module like this:
define(['module'], function (module) {
// insert and return library code here.
});
But that doesn't seem great. It seems like I would have to refactor some of their code to get that working (e.g., turn it all into an object and return that object). I see a lot of libraries using a different pattern where they use an immediately invoked function that defines the module on the window object and returns it.
(function() {
var phoneformat = {};
window.phoneformat = phoneformat;
if (typeof window.define === "function" && window.define.amd) {
window.define("phoneformat", [], function() {
return window.phoneformat;
});
}
})();
** UPDATE **
Is there any reason not to just do this?
define(['lib/phoneformatter'], function(phoneformatter) {
});
I get access to all of my methods but now it seems they are global because I did not wrap the library in a define...
Use RequireJS's shim. It'll look something like this
requirejs.config({
shim: {
'libphonenumber': {
exports: 'libphonenumber' // Might not apply for this library
}
}
});
This will load libphonenumber and put its variables in the global scope
This ended up working for me:
define(['module'], function (module) {
// insert and return library code here.
});
I am not entirely sure why 'module' was necessary. But it doesn't work without it. Also, I just returned an object and attached functions to it like so:
return {
countryForE164Number: countryForE164Number,
nextFunction: nextFunction,
// more functions as needed.
}
There is not much in the way of documentation for using 'module' but from what I can ascertain: Module is a special dependency that is processed by requireJS core. It gives you information about the module ID and location of the current module. So it is entirely possible that I messed up the paths in config.
I'm a strange behavior with RequireJS using the CommonJS syntax. I'll try to explain as better as possible the context I'm working on.
I have a JS file, called Controller.js, that registers for input events (a click) and uses a series of if statement to perform the correct action. A typical if statement block can be the following.
if(something) {
// RequireJS syntax here
} else if(other) { // ...
To implement the RequireJS syntax I tried two different patterns. The first one is the following. This is the standard way to load modules.
if(something) {
require(['CompositeView'], function(CompositeView) {
// using CompositeView here...
});
} else if(other) { // ...
The second, instead, uses the CommonJS syntax like
if(something) {
var CompositeView = require('CompositeView');
// using CompositeView here...
} else if(other) { // ...
Both pattern works as expected but I've noticed a strange behavior through Firebug (the same happens with Chrome tool). In particular, using the second one, the CompositeView file is already downloaded even if I haven't follow the branch that manages the specific action in response to something condition. On the contrary, with the first solution the file is downloaded when requested.
Am I missing something? Is it due to variable hoisting?
This is a limitation of the support for CommonJS-style require. The documentation explains that something like this:
define(function (require) {
var dependency1 = require('dependency1'),
dependency2 = require('dependency2');
return function () {};
});
is translated by RequireJS to:
define(['require', 'dependency1', 'dependency2'], function (require) {
var dependency1 = require('dependency1'),
dependency2 = require('dependency2');
return function () {};
});
Note how the arguments to the 2 require calls become part of the array passed to define.
What you say you observed is consistent with RequireJS reaching inside the if and pulling the required module up to the define so that it is always loaded even if the branch is not taken. The only way to prevents RequireJS from always loading your module is what you've already discovered: you have to use require with a callback.