One of the more interesting features of Angular 2 is its improved dependency injection mechanism
Unlike Angular 1.X you don't need to specify the dependencies manually in order to preserve minification capabilities
Angular 2 is smart enough to use the type information of the dependency and inject the correct implementation at run time
Can we do the same with Angular 1.X ? Sure, keep reading …
First lets describe the way we define a controller
import {MyApp} from '../App'; import {Controller} from '../Common/Decorators'; import {Contact, ContactService} from '../Services/ContactService'; @Controller(MyApp, "HomeCtrl") class HomeCtrl { contacts: Contact[]; constructor(contactService: ContactService) { contactService.getAll().then(contacts => { this.contacts = contacts; }); } }
And the service dependency
import {MyApp} from '../App'; import {Service, Inject} from '../Common/Decorators'; @Service(MyApp, "contactService") export class ContactService { constructor(@Inject("$http") private $http: ng.IHttpService) { } getAll(): ng.IPromise<Contact[]> { return this.$http.get("/contacts.json").then(response => response.data); } } export interface Contact { id: number; name: string; }
Second we need to define the Controller and Service decorators (A.K.A attribute/annotation)
export function Controller(module: ng.IModule, name: string) { return function (target: Function) { Reflect.defineMetadata("controller", target, { controllerName: name }); module.controller(name, target); set$inject(target); } } export function Service(module: ng.IModule, name: string) { return function (target: Function) { Reflect.defineMetadata("service", { serviceName: name }, target); module.service(name, target); set$inject(target); } }
For more information on ES7 decorators read here. Reflect.defineMetadata is part of the ES7 reflection API. I am using this polyfill
The interesting implementation reside inside the set$inject function which fills Angular's $inject metadata with all correct dependencies
function set$inject(target) { var paramtypes: any[] = Reflect.getMetadata("design:paramtypes", target); var $inject = [ ]; paramtypes.forEach((type, index) => { var parameterName = null; var metadata; if (metadata = Reflect.getMetadata("service", type)) { parameterName = metadata.serviceName; } else { throw new Error("Failed to resolve dependency: " + type); } $inject.push(parameterName); }); target.$inject = $inject; }
We use Reflect.getMetadata API and extract all parameter types from the constructor. For each parameter type we check if it is a registered service and extract its name
We can extend that solution and support Angular 1.X components and other injectables. A nice component declaration may look like below
import {MyApp} from '../App'; import {Component, Inject} from '../Common/Decorators'; @Component(MyApp, "clock", { templateUrl: "/Scripts/Components/Clock.html", }) class ClockComponent { time: Date; constructor( @Inject("$interval") $interval) { this.time = new Date(); $interval(() => { this.time = new Date(); }, 1000); } }
If you are interested with the full solution then contact me through my e-mail ori.calvo@gmail.com