Skip to content

Commit

Permalink
feat(di/decorators): create all Di param and class decorators
Browse files Browse the repository at this point in the history
closes #34
  • Loading branch information
Hotell committed Dec 26, 2015
1 parent 946d6e8 commit 010785e
Show file tree
Hide file tree
Showing 6 changed files with 527 additions and 0 deletions.
3 changes: 3 additions & 0 deletions ng-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import './test/pipe.spec';
import './test/life_cycle.spec';
import './test/providers.spec';

import './test/di/decorators.spec';
import './test/util/decorators.spec';

describe( 'ng-metadata', ()=> {

} );
87 changes: 87 additions & 0 deletions src/di/decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
InjectMetadata,
OptionalMetadata,
InjectableMetadata,
SelfMetadata,
HostMetadata,
SkipSelfMetadata
} from './metadata';
import {makeDecorator, makeParamDecorator} from '../util/decorators';

/**
* Factory for creating {@link InjectMetadata}.
*/
export interface InjectFactory {
(token: any): any;
new (token: any): InjectMetadata;
}

/**
* Factory for creating {@link OptionalMetadata}.
*/
export interface OptionalFactory {
(): any;
new (): OptionalMetadata;
}

/**
* Factory for creating {@link InjectableMetadata}.
*/
export interface InjectableFactory {
(): any;
new (): InjectableMetadata;
}

/**
* Factory for creating {@link SelfMetadata}.
*/
export interface SelfFactory {
(): any;
new (): SelfMetadata;
}

/**
* Factory for creating {@link HostMetadata}.
*/
export interface HostFactory {
(): any;
new (): HostMetadata;
}

/**
* Factory for creating {@link SkipSelfMetadata}.
*/
export interface SkipSelfFactory {
(): any;
new (): SkipSelfMetadata;
}

/**
* Factory for creating {@link InjectMetadata}.
*/
export const Inject: InjectFactory = makeParamDecorator(InjectMetadata,InjectMetadata.paramDecoratorForNonConstructor);

/**
* Factory for creating {@link OptionalMetadata}.
*/
export const Optional: OptionalFactory = makeParamDecorator(OptionalMetadata);

/**
* Factory for creating {@link InjectableMetadata}.
*/
export const Injectable: InjectableFactory = <InjectableFactory>makeDecorator(InjectableMetadata);

/**
* Factory for creating {@link SelfMetadata}.
*/
export const Self: SelfFactory = makeParamDecorator(SelfMetadata);

/**
* Factory for creating {@link HostMetadata}.
*/
export const Host: HostFactory = makeParamDecorator(HostMetadata);

/**
* Factory for creating {@link SkipSelfMetadata}.
*/
export const SkipSelf: SkipSelfFactory = makeParamDecorator(SkipSelfMetadata);
246 changes: 246 additions & 0 deletions src/di/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import {CONST, stringify, isBlank, isPresent, isString} from "../facade/lang";
import {InjectFactory} from "./decorators";

/**
* A parameter metadata that specifies a dependency.
*
* ### Example ([live demo](http://plnkr.co/edit/6uHYJK?p=preview))
*
* ```typescript
* class Engine {}
*
* @Injectable()
* class Car {
* engine;
* constructor(@Inject("MyEngine") engine:Engine) {
* this.engine = engine;
* }
* }
*
* var injector = Injector.resolveAndCreate([
* provide("MyEngine", {useClass: Engine}),
* Car
* ]);
*
* expect(injector.get(Car).engine instanceof Engine).toBe(true);
* ```
*
* When `@Inject()` is not present, {@link Injector} will use the type annotation of the parameter.
*
* ### Example
*
* ```typescript
* class Engine {}
*
* @Injectable()
* class Car {
* constructor(public engine: Engine) {} //same as constructor(@Inject(Engine) engine:Engine)
* }
*
* var injector = Injector.resolveAndCreate([Engine, Car]);
* expect(injector.get(Car).engine instanceof Engine).toBe(true);
* ```
*/
@CONST()
export class InjectMetadata {

static paramDecoratorForNonConstructor(
annotationInstance: InjectMetadata,
target: any,
propertyKey: string,
paramIndex: number
) {

const annotateMethod = target[ propertyKey ];

annotateMethod.$inject = annotateMethod.$inject || [];
annotateMethod.$inject[ paramIndex ] = annotationInstance.token;

}

constructor( public token: any ) {}

toString(): string { return `@Inject(${stringify( this.token )})`; }
}

/**
* A parameter metadata that marks a dependency as optional. {@link Injector} provides `null` if
* the dependency is not found.
*
* ### Example ([live demo](http://plnkr.co/edit/AsryOm?p=preview))
*
* ```typescript
* class Engine {}
*
* @Injectable()
* class Car {
* engine;
* constructor(@Optional() engine:Engine) {
* this.engine = engine;
* }
* }
*
* var injector = Injector.resolveAndCreate([Car]);
* expect(injector.get(Car).engine).toBeNull();
* ```
*/
@CONST()
export class OptionalMetadata {
toString(): string { return `@Optional()`; }
}

/**
* A marker metadata that marks a class as available to {@link Injector} for creation.
*
* ### Example ([live demo](http://plnkr.co/edit/Wk4DMQ?p=preview))
*
* ```typescript
* @Injectable()
* class UsefulService {}
*
* @Injectable()
* class NeedsService {
* constructor(public service:UsefulService) {}
* }
*
* var injector = Injector.resolveAndCreate([NeedsService, UsefulService]);
* expect(injector.get(NeedsService).service instanceof UsefulService).toBe(true);
* ```
* {@link Injector} will throw {@link NoAnnotationError} when trying to instantiate a class that
* does not have `@Injectable` marker, as shown in the example below.
*
* ```typescript
* class UsefulService {}
*
* class NeedsService {
* constructor(public service:UsefulService) {}
* }
*
* var injector = Injector.resolveAndCreate([NeedsService, UsefulService]);
* expect(() => injector.get(NeedsService)).toThrowError();
* ```
*/
@CONST()
export class InjectableMetadata {
}

/**
* Specifies that an {@link Injector} should retrieve a dependency only from itself.
*
* ### Example ([live demo](http://plnkr.co/edit/NeagAg?p=preview))
*
* ```typescript
* class Dependency {
* }
*
* @Injectable()
* class NeedsDependency {
* dependency;
* constructor(@Self() dependency:Dependency) {
* this.dependency = dependency;
* }
* }
*
* var inj = Injector.resolveAndCreate([Dependency, NeedsDependency]);
* var nd = inj.get(NeedsDependency);
*
* expect(nd.dependency instanceof Dependency).toBe(true);
*
* var inj = Injector.resolveAndCreate([Dependency]);
* var child = inj.resolveAndCreateChild([NeedsDependency]);
* expect(() => child.get(NeedsDependency)).toThrowError();
* ```
*/
@CONST()
export class SelfMetadata {
toString(): string { return `@Self()`; }
}

/**
* Specifies that the dependency resolution should start from the parent injector.
*
* ### Example ([live demo](http://plnkr.co/edit/Wchdzb?p=preview))
*
* ```typescript
* class Dependency {
* }
*
* @Injectable()
* class NeedsDependency {
* dependency;
* constructor(@SkipSelf() dependency:Dependency) {
* this.dependency = dependency;
* }
* }
*
* var parent = Injector.resolveAndCreate([Dependency]);
* var child = parent.resolveAndCreateChild([NeedsDependency]);
* expect(child.get(NeedsDependency).dependency instanceof Depedency).toBe(true);
*
* var inj = Injector.resolveAndCreate([Dependency, NeedsDependency]);
* expect(() => inj.get(NeedsDependency)).toThrowError();
* ```
*/
@CONST()
export class SkipSelfMetadata {
toString(): string { return `@SkipSelf()`; }
}

/**
* Specifies that an injector should retrieve a dependency from any injector until reaching the
* closest host.
*
* In Angular, a component element is automatically declared as a host for all the injectors in
* its view.
*
* ### Example ([live demo](http://plnkr.co/edit/GX79pV?p=preview))
*
* In the following example `App` contains `ParentCmp`, which contains `ChildDirective`.
* So `ParentCmp` is the host of `ChildDirective`.
*
* `ChildDirective` depends on two services: `HostService` and `OtherService`.
* `HostService` is defined at `ParentCmp`, and `OtherService` is defined at `App`.
*
*```typescript
* class OtherService {}
* class HostService {}
*
* @Directive({
* selector: 'child-directive'
* })
* class ChildDirective {
* constructor(@Optional() @Host() os:OtherService, @Optional() @Host() hs:HostService){
* console.log("os is null", os);
* console.log("hs is NOT null", hs);
* }
* }
*
* @Component({
* selector: 'parent-cmp',
* providers: [HostService],
* template: `
* Dir: <child-directive></child-directive>
* `,
* directives: [ChildDirective]
* })
* class ParentCmp {
* }
*
* @Component({
* selector: 'app',
* providers: [OtherService],
* template: `
* Parent: <parent-cmp></parent-cmp>
* `,
* directives: [ParentCmp]
* })
* class App {
* }
*
* bootstrap(App);
*```
*/
@CONST()
export class HostMetadata {
toString(): string { return `@Host()`; }
}
29 changes: 29 additions & 0 deletions src/di/opaque_token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {CONST} from '../facade/lang';

/**
* Creates a token that can be used in a DI Provider.
*
* ### Example ([live demo](http://plnkr.co/edit/Ys9ezXpj2Mnoy3Uc8KBp?p=preview))
*
* ```typescript
* var t = new OpaqueToken("value");
*
* var injector = Injector.resolveAndCreate([
* provide(t, {useValue: "providedValue"})
* ]);
*
* expect(injector.get(t)).toEqual("providedValue");
* ```
*
* Using an `OpaqueToken` is preferable to using strings as tokens because of possible collisions
* caused by multiple providers using the same string as two different tokens.
*
* Using an `OpaqueToken` is preferable to using an `Object` as tokens because it provides better
* error messages.
*/
@CONST()
export class OpaqueToken {
constructor(private _desc: string) {}

toString(): string { return `Token ${this._desc}`; }
}
Empty file added src/di/provider.ts
Empty file.
Loading

0 comments on commit 010785e

Please sign in to comment.