Feature: Inject class without intantiating
I'd like to be able to inject a un-instantiated class, so I can more easily mock classes for tests. Right now, here's what I'd have to do
function GetPromise () {
return Promise;
}
@Inject(GetPromise)
class Deferred {
constructor(Promise) {
this.promise = new Promise();
}
}
Yep, just do:
@Provide(Promise)
function getPromise () {
return Promise;
}
@Inject(Promise)
class Deferred {
constructor(Promise) {
this.promise = new Promise();
}
}
It is verbose, but this is not a common pattern. Do you have any other use cases?
I'm thinking, we might provide some helper for defining "values":
// when somebody asks for "Promise", give him the value of Promise (do not instantiate it)
var m1 = bind(Promise).toValue(Promise);
var c = bind('config').toValue({crashOnStart: true, ignoreAllErrors: false});
var injector = new Injector([m1, c]);
Since the third parameter to an injector is a map of providers, where at the moment each "Provider" must be an object of the form,
{ provider: ..., params: ..., create: ... }
I was playing with the idea of a helper along the lines of:
class ValueProvider {
constructor(value, isPromise = false) {
this.provider = value;
this.params = [];
this.isPromise = isPromise;
}
create(args) {
return this.provider;
}
}
and then you could do...
var providers = new Map;
providers.set(Promise, new ValueProvider(Promise));
var injector = new Injector([modules], null, providers);
Or this could be turned into some kind of annotation...
@ProvideValue
export class Promise { ... }
@ProvideValue('config')
export var config = { ... };
@petebacondarwin @ProvideValue annotation would only work with functions (annotation is just setting a .annotations property and setting this property on an object is dangerous IMHO as your object would suddenly get the annotations property overriden).
The third argument to the constructor is kind of internal thing, I would rather not have people using that.
... some stream of consciousness... probably do not read this...
I need to think about this some more... maybe @ProvideValue is a good idea and it will be fine with objects... but I'm still thinking that the whole @Provide thing is wrong and we should rather go bind(x).to(y) way as that creates a new "binding" object rather than adding a property on the provider. The problem is that annotations are attached to the provider... let's say you have a single class (a provider) LocalStorageCache that should be bound to multiple tokens (say TemplateCache, and UsersCache)... with annotations there is no way to do that (except creating another LocalStorageForUsers that inherits from LocalStorageCache)... basically it is all right to append information about what the provider needs (@Inject, @InjectLazy, @InjectPromise) as that will never change for given provider.... but the information of what token it provides... is something that should probably not be tight to the provider... the same probably applies to scope annotations...
So right now, I'm thinking bind(x).to(y) would be a syntactic sugar to create a Provider (something that injector creates internally - for each provider, it parses the annotations and create either ClassProvider or FactoryProvider). and thus it would override the annotations on the provider.
Regarding this value thing: In @jeffbcross's case I think that what he really needs is for the requester of the service to be able to decide whether or not to get the class or the instance. In most cases you might like to get the instance but there are a smaller number of cases where, when testing for instance, you might like the actual class.
Is this not what the @InjectLazy annotation is supposed to support? Or if not, perhaps something similar could be implemented?
@Provide(Promise)
class Promise { ... }
@InjectLazy(Promise)
class Deferred {
constructor(createPromise) {
this.promise = createPromise();
}
}
You could use @LazyInject for this, but it's not as pretty either, you would have to annotate the Promise, so that you can pass it the "resolver" argument. Something like this:
annotate(Promise, new Inject('resolver'));
@InjectLazy(Promise)
class Deferred {
constructor(createPromise) {
this.promise = createPromise('resolver', function(resolve, reject) {});
}
}
So I think this use case is really, you are asking for the class, in which case I don't think having a simple wrapper factory is that big of deal. You do this once per project.
I was also thinking, maybe we could support "default providers" for non-annotated stuff. Default provider is basically taking the token and instantiating. I'm suggesting, if the token (default provider) is not annotated with @Inject, we take it as a direct value. Then, you could do just:
@Inject(Promise)
class Deferred {
constructor(Promise) {
this.p = new Promise(function(resolve, reject) {});
}
}
This would be also very convenient in Node, where you could use it to inject all the stuff that does not expect to be instantiated (such as most of the core modules). For instance:
var fs = require('fs');
@Inject(fs.writeFile)
class DocWriter {
constructor(writeFile) {
// ...
}
And mock it out in the test:
use(function fakeWrite() {}).as(fs.writeFile);
The only thing I'm worrying is that if you forget to put @Inject it will suddenly behave differently - injecting the class/function directly, rather then calling it... What do you think?
@vojtajina I have been thinking of the same solution.
+1 for injecting just the value for tokens that are not annotated.
For some of the modules I have been writing I have had to wrap other non di annotated modules just to get them into the di paradigm. I have only wrapped a few and can see this becoming quite an overhead as I use more external libraries. This would make what I am working on much quicker, easier and cleaner.
@RGBboy you can give it a try, it's implemented in https://github.com/angular/di.js/tree/non-annotated-tokens-inject-as-values
The only problem with this approach is that now you have to put @Inject on every provider, even if it does not accept any argument.
I just got round to trying this out. I like the change. I don't mind having to put the Inject annotation on a provider. I think it makes code more clear as to what it is trying to achieve. Are there any other downsides to this approach?
This would be also very convenient in Node, where you could use it to inject all the stuff that does not expect to be instantiated (such as most of the core modules).
Yes, it would. I'm using di.js in production on a large app and I'm having to create a wrapper factory for every Node module I need to be able to mock in my tests.
My app is currently a cocktail of simple Node requires and di.js injections. It would be nice if everything was injected using di.js.
@OliverJAsh Did you have any workaround on the above?
:+1: