injection-js
Version:
Dependency Injection library for JavaScript and TypeScript
322 lines • 10.6 kB
JavaScript
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { Injector, THROW_IF_NOT_FOUND } from './injector';
import { setCurrentInjector } from './injector_compatibility';
import { Self, SkipSelf } from './metadata';
import { cyclicDependencyError, instantiationError, noProviderError, outOfBoundsError } from './reflective_errors';
import { ReflectiveKey } from './reflective_key';
import { resolveReflectiveProviders, } from './reflective_provider';
// Threshold for the dynamic version
const UNDEFINED = new Object();
/**
* A ReflectiveDependency injection container used for instantiating objects and resolving
* dependencies.
*
* An `Injector` is a replacement for a `new` operator, which can automatically resolve the
* constructor dependencies.
*
* In typical use, application code asks for the dependencies in the constructor and they are
* resolved by the `Injector`.
*
* ### Example ([live demo](http://plnkr.co/edit/jzjec0?p=preview))
*
* The following example creates an `Injector` configured to create `Engine` and `Car`.
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var injector = ReflectiveInjector.resolveAndCreate([Car, Engine]);
* var car = injector.get(Car);
* expect(car instanceof Car).toBe(true);
* expect(car.engine instanceof Engine).toBe(true);
* ```
*
* Notice, we don't use the `new` operator because we explicitly want to have the `Injector`
* resolve all of the object's dependencies automatically.
*
* @stable
*/
export class ReflectiveInjector {
/**
* Turns an array of provider definitions into an array of resolved providers.
*
* A resolution is a process of flattening multiple nested arrays and converting individual
* providers into an array of {@link ResolvedReflectiveProvider}s.
*
* ### Example ([live demo](http://plnkr.co/edit/AiXTHi?p=preview))
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var providers = ReflectiveInjector.resolve([Car, [[Engine]]]);
*
* expect(providers.length).toEqual(2);
*
* expect(providers[0] instanceof ResolvedReflectiveProvider).toBe(true);
* expect(providers[0].key.displayName).toBe("Car");
* expect(providers[0].dependencies.length).toEqual(1);
* expect(providers[0].factory).toBeDefined();
*
* expect(providers[1].key.displayName).toBe("Engine");
* });
* ```
*
* See {@link ReflectiveInjector#fromResolvedProviders} for more info.
*/
static resolve(providers) {
return resolveReflectiveProviders(providers);
}
/**
* Resolves an array of providers and creates an injector from those providers.
*
* The passed-in providers can be an array of `Type`, {@link Provider},
* or a recursive array of more providers.
*
* ### Example ([live demo](http://plnkr.co/edit/ePOccA?p=preview))
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var injector = ReflectiveInjector.resolveAndCreate([Car, Engine]);
* expect(injector.get(Car) instanceof Car).toBe(true);
* ```
*
* This function is slower than the corresponding `fromResolvedProviders`
* because it needs to resolve the passed-in providers first.
* See {@link Injector#resolve} and {@link Injector#fromResolvedProviders}.
*/
static resolveAndCreate(providers, parent) {
const ResolvedReflectiveProviders = ReflectiveInjector.resolve(providers);
return ReflectiveInjector.fromResolvedProviders(ResolvedReflectiveProviders, parent);
}
/**
* Creates an injector from previously resolved providers.
*
* This API is the recommended way to construct injectors in performance-sensitive parts.
*
* ### Example ([live demo](http://plnkr.co/edit/KrSMci?p=preview))
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var providers = ReflectiveInjector.resolve([Car, Engine]);
* var injector = ReflectiveInjector.fromResolvedProviders(providers);
* expect(injector.get(Car) instanceof Car).toBe(true);
* ```
* @experimental
*/
static fromResolvedProviders(providers, parent) {
// tslint:disable-next-line:no-use-before-declare
return new ReflectiveInjector_(providers, parent);
}
}
// tslint:disable-next-line:class-name
export class ReflectiveInjector_ {
/**
* Private
*/
constructor(_providers, _parent) {
/** @internal */
this._constructionCounter = 0;
this._providers = _providers;
this._parent = _parent || null;
const len = _providers.length;
this.keyIds = new Array(len);
this.objs = new Array(len);
for (let i = 0; i < len; i++) {
this.keyIds[i] = _providers[i].key.id;
this.objs[i] = UNDEFINED;
}
}
get(token, notFoundValue = THROW_IF_NOT_FOUND) {
return this._getByKey(ReflectiveKey.get(token), null, notFoundValue);
}
get parent() {
return this._parent;
}
resolveAndCreateChild(providers) {
const ResolvedReflectiveProviders = ReflectiveInjector.resolve(providers);
return this.createChildFromResolved(ResolvedReflectiveProviders);
}
createChildFromResolved(providers) {
const inj = new ReflectiveInjector_(providers);
inj._parent = this;
return inj;
}
resolveAndInstantiate(provider) {
return this.instantiateResolved(ReflectiveInjector.resolve([provider])[0]);
}
instantiateResolved(provider) {
return this._instantiateProviderContext(provider);
}
getProviderAtIndex(index) {
if (index < 0 || index >= this._providers.length) {
throw outOfBoundsError(index);
}
return this._providers[index];
}
/** @internal */
_new(provider) {
if (this._constructionCounter++ > this._getMaxNumberOfObjects()) {
throw cyclicDependencyError(this, provider.key);
}
return this._instantiateProviderContext(provider);
}
_getMaxNumberOfObjects() {
return this.objs.length;
}
_instantiateProviderContext(provider) {
const previousInjector = setCurrentInjector(this);
try {
return this._instantiateProvider(provider);
}
finally {
setCurrentInjector(previousInjector);
}
}
_instantiateProvider(provider) {
if (provider.multiProvider) {
const res = new Array(provider.resolvedFactories.length);
for (let i = 0; i < provider.resolvedFactories.length; ++i) {
res[i] = this._instantiate(provider, provider.resolvedFactories[i]);
}
return res;
}
else {
return this._instantiate(provider, provider.resolvedFactories[0]);
}
}
_instantiate(provider, ResolvedReflectiveFactory) {
const factory = ResolvedReflectiveFactory.factory;
let deps;
try {
deps = ResolvedReflectiveFactory.dependencies.map((dep) => this._getByReflectiveDependency(dep));
}
catch (e) {
if (e.addKey) {
e.addKey(this, provider.key);
}
throw e;
}
let obj;
try {
obj = factory(...deps);
}
catch (e) {
throw instantiationError(this, e, e.stack, provider.key);
}
return obj;
}
_getByReflectiveDependency(dep) {
return this._getByKey(dep.key, dep.visibility, dep.optional ? null : THROW_IF_NOT_FOUND);
}
_getByKey(key, visibility, notFoundValue) {
// tslint:disable-next-line:no-use-before-declare
if (key === INJECTOR_KEY) {
return this;
}
if (visibility instanceof Self) {
return this._getByKeySelf(key, notFoundValue);
}
else {
return this._getByKeyDefault(key, notFoundValue, visibility);
}
}
_getObjByKeyId(keyId) {
for (let i = 0; i < this.keyIds.length; i++) {
if (this.keyIds[i] === keyId) {
if (this.objs[i] === UNDEFINED) {
this.objs[i] = this._new(this._providers[i]);
}
return this.objs[i];
}
}
return UNDEFINED;
}
/** @internal */
_throwOrNull(key, notFoundValue) {
if (notFoundValue !== THROW_IF_NOT_FOUND) {
return notFoundValue;
}
else {
throw noProviderError(this, key);
}
}
/** @internal */
_getByKeySelf(key, notFoundValue) {
const obj = this._getObjByKeyId(key.id);
return obj !== UNDEFINED ? obj : this._throwOrNull(key, notFoundValue);
}
/** @internal */
_getByKeyDefault(key, notFoundValue, visibility) {
let inj;
if (visibility instanceof SkipSelf) {
inj = this._parent;
}
else {
inj = this;
}
while (inj instanceof ReflectiveInjector_) {
const inj_ = inj;
const obj = inj_._getObjByKeyId(key.id);
if (obj !== UNDEFINED)
return obj;
inj = inj_._parent;
}
if (inj !== null) {
return inj.get(key.token, notFoundValue);
}
else {
return this._throwOrNull(key, notFoundValue);
}
}
get displayName() {
const providers = _mapProviders(this, (b) => ' "' + b.key.displayName + '" ').join(', ');
return `ReflectiveInjector(providers: [${providers}])`;
}
toString() {
return this.displayName;
}
}
const INJECTOR_KEY = ReflectiveKey.get(Injector);
function _mapProviders(injector, fn) {
const res = new Array(injector._providers.length);
for (let i = 0; i < injector._providers.length; ++i) {
res[i] = fn(injector.getProviderAtIndex(i));
}
return res;
}
//# sourceMappingURL=reflective_injector.js.map