diff --git a/src/injection.js b/src/injection.js index 6e82f2a..6a06f2d 100644 --- a/src/injection.js +++ b/src/injection.js @@ -6,24 +6,16 @@ import {_emptyParameters} from './container'; */ export function autoinject(potentialTarget?: any): any { let deco = function(target) { - let previousInject = target.inject ? target.inject.slice() : null; //make a copy of target.inject to avoid changing parent inject + let previousInject = target.hasOwnProperty('inject') ? target.inject : null; let autoInject: any = metadata.getOwn(metadata.paramTypes, target) || _emptyParameters; if (!previousInject) { target.inject = autoInject; } else { - for (let i = 0; i < autoInject.length; i++) { - //check if previously injected. - if (previousInject[i] && previousInject[i] !== autoInject[i]) { - const prevIndex = previousInject.indexOf(autoInject[i]); - if (prevIndex > -1) { - previousInject.splice(prevIndex, 1); - } - previousInject.splice((prevIndex > -1 && prevIndex < i) ? i - 1 : i, 0, autoInject[i]); - } else if (!previousInject[i]) {//else add + for (let i = 0; i++; i < autoInject.length) { + if (!previousInject[i]) { previousInject[i] = autoInject[i]; } } - target.inject = previousInject; } }; diff --git a/src/resolvers.js b/src/resolvers.js index 644d06e..9e970b3 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -1,4 +1,4 @@ -import {protocol} from 'aurelia-metadata'; +import {protocol, metadata} from 'aurelia-metadata'; import {Container} from './container'; /** @@ -315,6 +315,9 @@ export class NewInstance { } export function getDecoratorDependencies(target, name) { + if (!target.hasOwnProperty('inject')) { + Object.defineProperty(target, 'inject', {value: undefined, writable: true}); + } let dependencies = target.inject; if (typeof dependencies === 'function') { throw new Error('Decorator ' + name + ' cannot be used with "inject()". Please use an array instead.'); diff --git a/test/container.spec.js b/test/container.spec.js index b6bbdab..d16b35b 100644 --- a/test/container.spec.js +++ b/test/container.spec.js @@ -587,20 +587,19 @@ describe('container', () => { it('provides a function which, when called, will return the instance using decorator', () => { class Logger {} - class App1 { - static inject = [Logger]; - constructor(getLogger) { + class App { + constructor(getLogger: () => Logger) { this.getLogger = getLogger; } } - - lazy(Logger)(App1, null, 0); + decorators( Reflect.metadata('design:paramtypes', [()=>Logger]) ).on(App); + lazy(Logger)(App, null, 0); + decorators( autoinject() ).on(App); let container = new Container(); - let app1 = container.get(App1); - - let logger = app1.getLogger; + let app = container.get(App); + let logger = app.getLogger; expect(logger()).toEqual(jasmine.any(Logger)); }); }); @@ -639,13 +638,13 @@ describe('container', () => { class Logger extends LoggerBase {} class App { - static inject = [LoggerBase]; constructor(loggers) { this.loggers = loggers; } } - + decorators( Reflect.metadata('design:paramtypes', [LoggerBase]) ).on(App); all(LoggerBase)(App, null, 0); + decorators( autoinject() ).on(App); let container = new Container(); container.registerSingleton(LoggerBase, VerboseLogger); @@ -703,13 +702,13 @@ describe('container', () => { class Logger {} class App { - static inject = [Logger]; constructor(logger) { this.logger = logger; } } - - optional(Logger)(App, null, 0); + decorators( Reflect.metadata('design:paramtypes', [Logger]) ).on(App); + optional()(App, null, 0); + decorators( autoinject() ).on(App); let container = new Container(); container.registerSingleton(Logger, Logger); @@ -741,13 +740,13 @@ describe('container', () => { class Logger {} class App { - static inject = [Logger]; constructor(logger) { this.logger = logger; } } - - optional(Logger)(App, null, 0); + decorators( Reflect.metadata('design:paramtypes', [Logger]) ).on(App); + optional()(App, null, 0); + decorators( autoinject() ).on(App); let container = new Container(); container.registerSingleton(VerboseLogger, Logger); @@ -794,6 +793,29 @@ describe('container', () => { expect(app.logger).toBe(null); }); + it('doesn\'t check the parent container hierarchy when checkParent is false using decorator', () => { + class Logger {} + + class App { + constructor(logger) { + this.logger = logger; + } + } + decorators( Reflect.metadata('design:paramtypes', [Logger]) ).on(App); + optional(false)(App, null, 0); + decorators( autoinject() ).on(App); + + let parentContainer = new Container(); + parentContainer.registerSingleton(Logger, Logger); + + let childContainer = parentContainer.createChild(); + childContainer.registerSingleton(App, App); + + let app = childContainer.get(App); + + expect(app.logger).toBe(null); + }); + it('checks the parent container hierarchy when checkParent is true or default', () => { class Logger {} @@ -814,6 +836,29 @@ describe('container', () => { expect(app.logger).toEqual(jasmine.any(Logger)); }); + + it('checks the parent container hierarchy when checkParent is true or default using decorator', () => { + class Logger {} + + class App { + constructor(logger) { + this.logger = logger; + } + } + decorators( Reflect.metadata('design:paramtypes', [Logger]) ).on(App); + optional()(App, null, 0); + decorators( autoinject() ).on(App); + + let parentContainer = new Container(); + parentContainer.registerSingleton(Logger, Logger); + + let childContainer = parentContainer.createChild(); + childContainer.registerSingleton(App, App); + + let app = childContainer.get(App); + + expect(app.logger).toEqual(jasmine.any(Logger)); + }); }); describe('Parent', ()=> { @@ -845,13 +890,13 @@ describe('container', () => { class Logger {} class App { - static inject = [Logger]; constructor(logger) { this.logger = logger; } } - + decorators( Reflect.metadata('design:paramtypes', [Logger]) ).on(App); parent(App, null, 0); + decorators( autoinject() ).on(App); let parentContainer = new Container(); let parentInstance = new Logger(); @@ -890,13 +935,13 @@ describe('container', () => { class Logger {} class App { - static inject = [Logger]; constructor(logger) { this.logger = logger; } } - + decorators( Reflect.metadata('design:paramtypes', [Logger]) ).on(App); parent(App, null, 0); + decorators( autoinject() ).on(App); let container = new Container(); let instance = new Logger(); @@ -959,24 +1004,24 @@ describe('container', () => { class Logger {} class Service { - static inject= [Logger]; constructor(getLogger, data) { this.getLogger = getLogger; this.data = data; } } - + decorators( Reflect.metadata('design:paramtypes', [() => Logger]) ).on(Service); factory(Logger)(Service, null, 0); + decorators( autoinject() ).on(Service); class App { - static inject = [Service]; constructor(GetService) { this.GetService = GetService; this.service = new GetService(data); } } - + decorators( Reflect.metadata('design:paramtypes', [() => Service]) ).on(App); factory(Service)(App, null, 0); + decorators( autoinject() ).on(App); beforeEach(() => { container = new Container(); @@ -1021,13 +1066,13 @@ describe('container', () => { it('decorate to inject a new instance of a dependency', () => { class App1 { - static inject = [Logger]; constructor(logger) { this.logger = logger; } } - + decorators( Reflect.metadata('design:paramtypes', [Logger]) ).on(App1); newInstance(Logger)(App1, null, 0); + decorators( autoinject() ).on(App1); let container = new Container(); let logger = container.get(Logger); @@ -1056,13 +1101,14 @@ describe('container', () => { it('decorate to inject a new instance of a dependency, with instance dynamic dependency', () => { class App1 { - static inject = [Logger]; constructor(logger) { this.logger = logger; } } + decorators( Reflect.metadata('design:paramtypes', [Logger]) ).on(App1); newInstance(Logger, Dependency)(App1, null, 0); + decorators( autoinject() ).on(App1); let container = new Container(); let logger = container.get(Logger); @@ -1092,13 +1138,14 @@ describe('container', () => { it('decorate to inject a new instance of a dependency, with resolver dynamic dependency', () => { class App1 { - static inject = [Logger]; constructor(logger) { this.logger = logger; } } + decorators( Reflect.metadata('design:paramtypes', [Logger]) ).on(App1); newInstance(Logger, Lazy.of(Dependency))(App1, null, 0); + decorators( autoinject() ).on(App1); let container = new Container(); let logger = container.get(Logger); @@ -1111,4 +1158,173 @@ describe('container', () => { }); }); }); + + describe('autoinject with custom resolvers', () => { + class Dependency {} + class LoggerBase { + constructor(dep?) { + this.dep = dep; + } + } + class VerboseLogger extends LoggerBase {} + class Logger extends LoggerBase {} + class Service {} + class SubService1 {} + class SubService2 {} + + it('loads dependencies in tree classes', function() { + class ParentApp { + constructor(logger) { + this.logger = logger; + } + } + decorators(Reflect.metadata('design:paramtypes', [()=>Logger])).on(ParentApp); + lazy(Logger)(ParentApp, null, 0); + decorators( autoinject() ).on(ParentApp); + + class ChildApp extends ParentApp { + constructor(service, ...rest) { + super(...rest); + this.service = service; + } + } + decorators(Reflect.metadata('design:paramtypes', [Service, ()=>Logger])).on(ChildApp); + lazy(Logger)(ChildApp, null, 1); + decorators( autoinject() ).on(ChildApp); + + class SubChildApp1 extends ChildApp { + constructor(subService1, ...rest) { + super(...rest); + this.subService1 = subService1; + } + } + decorators(Reflect.metadata('design:paramtypes', [()=>SubService1, Service, ()=>Logger])).on(SubChildApp1); + lazy(SubService1)(SubChildApp1, null, 0); + lazy(Logger)(SubChildApp1, null, 2); + decorators( autoinject() ).on(SubChildApp1); + + class SubChildApp2 extends ChildApp { + constructor(subService2, ...rest) { + super(...rest); + this.subService2 = subService2; + } + } + decorators(Reflect.metadata('design:paramtypes', [()=>SubService2, Service, ()=>Logger])).on(SubChildApp2); + lazy(SubService2)(SubChildApp2, null, 0); + newInstance(Service)(SubChildApp2, null, 2); + lazy(Logger)(SubChildApp2, null, 2); + decorators( autoinject() ).on(SubChildApp2); + + class SubChildApp3 extends ChildApp { + } + + class SubChildApp4 extends ChildApp { + constructor(logger, subService1, service) { + super(service, logger); + this.subService1 = subService1; + } + } + decorators(Reflect.metadata('design:paramtypes', [()=>Logger, ()=>SubService1, Service])).on(SubChildApp4); + lazy(SubService1)(SubChildApp4, null, 1); + lazy(Logger)(SubChildApp4, null, 0); + decorators( autoinject() ).on(SubChildApp4); + + let container = new Container(); + + let p_app = container.get(ParentApp); + expect(p_app.logger()).toEqual(jasmine.any(Logger)); + + let c_app = container.get(ChildApp); + expect(c_app.service).toEqual(jasmine.any(Service)); + expect(c_app.logger()).toEqual(jasmine.any(Logger)); + + let app1 = container.get(SubChildApp1); + expect(app1.subService1()).toEqual(jasmine.any(SubService1)); + expect(app1.service).toEqual(jasmine.any(Service)); + expect(app1.logger()).toEqual(jasmine.any(Logger)); + + let app2 = container.get(SubChildApp2); + expect(app2.subService2()).toEqual(jasmine.any(SubService2)); + expect(app2.service).toEqual(jasmine.any(Service)); + expect(app2.logger()).toEqual(jasmine.any(Logger)); + + let app3 = container.get(SubChildApp3); + expect(app3.service).toEqual(jasmine.any(Service)); + expect(app3.logger()).toEqual(jasmine.any(Logger)); + + let app4 = container.get(SubChildApp4); + expect(app4.subService1()).toEqual(jasmine.any(SubService1)); + expect(app4.service).toEqual(jasmine.any(Service)); + expect(app4.logger()).toEqual(jasmine.any(Logger)); + }); + + it('allows multiple parameter decorators', () => { + let data = 'test'; + + class MyService { + constructor(getLogger, data) { + this.getLogger = getLogger; + this.data = data; + } + } + decorators( Reflect.metadata('design:paramtypes', [() => Logger]) ).on(MyService); + factory(Logger)(MyService, null, 0); + decorators( autoinject() ).on(MyService); + + class App { + constructor(getLogger: () => Logger, loggers, optionalLogger, parentLogger, newLogger, GetService) { + this.getLogger = getLogger; + this.loggers = loggers; + this.optionalLogger = optionalLogger; + this.parentLogger = parentLogger; + this.newLogger = newLogger; + this.GetService = GetService; + this.service = new GetService(data); + } + } + + decorators( Reflect.metadata('design:paramtypes', [()=>Logger, LoggerBase, Logger, Logger, Logger, MyService]) ).on(App); + lazy(Logger)(App, null, 0); + all(LoggerBase)(App, null, 1); + optional()(App, null, 2); + parent(App, null, 3); + newInstance(Logger, Dependency)(App, null, 4); + factory(MyService)(App, null, 5); + decorators( autoinject() ).on(App); + + let parentContainer = new Container(); + let parentInstance = new Logger(); + parentContainer.registerInstance(Logger, parentInstance); + + let container = parentContainer.createChild(); + let childInstance = new Logger(); + container.registerSingleton(LoggerBase, VerboseLogger); + container.registerTransient(LoggerBase, Logger); + container.registerSingleton(Logger, Logger); + container.registerInstance(Logger, childInstance); + container.registerSingleton(App, App); + + let app = container.get(App); + + let logger = app.getLogger; + expect(logger()).toEqual(jasmine.any(Logger)); + + expect(app.loggers).toEqual(jasmine.any(Array)); + expect(app.loggers.length).toBe(2); + expect(app.loggers[0]).toEqual(jasmine.any(VerboseLogger)); + expect(app.loggers[1]).toEqual(jasmine.any(Logger)); + + expect(app.optionalLogger).toEqual(jasmine.any(Logger)); + + expect(app.parentLogger).toBe(parentInstance); + + expect(app.newLogger).toEqual(jasmine.any(Logger)); + expect(app.newLogger).not.toBe(logger); + expect(app.newLogger.dep).toEqual(jasmine.any(Dependency)); + + let service = app.GetService; + expect(service()).toEqual(jasmine.any(MyService)); + expect(app.service.data).toEqual(data); + }); + }); });