refactor(core): simplify decorators

Every decorator now is made of the following:
- a function that can be used
as a decorator or as a constructor. This function
also can be used for `instanceof` checks.
- a type for this function (callable and newable)
- a type that describes the shape of the data
  that the user needs to pass to the decorator
  as well as the instance of the metadata

The docs for decorators live at the followig places
so that IDEs can discover them correctly:
- General description of the decorator is placed on the
  `...Decorator` interface on the callable function
  definition
- Property descriptions are placed on the interface
  that describes the metadata produces by the decorator
This commit is contained in:
Tobias Bosch
2016-09-12 09:44:20 -07:00
committed by Igor Minar
parent 26d1423ae9
commit 1b15170c89
25 changed files with 1573 additions and 2466 deletions

View File

@ -1,109 +0,0 @@
/**
* @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 {makeDecorator, makeParamDecorator} from '../util/decorators';
import {HostMetadata, InjectMetadata, InjectableMetadata, OptionalMetadata, SelfMetadata, SkipSelfMetadata} from './metadata';
/**
* Factory for creating {@link InjectMetadata}.
* @stable
*/
export interface InjectMetadataFactory {
(token: any): any;
new (token: any): InjectMetadata;
}
/**
* Factory for creating {@link OptionalMetadata}.
* @stable
*/
export interface OptionalMetadataFactory {
(): any;
new (): OptionalMetadata;
}
/**
* Factory for creating {@link InjectableMetadata}.
* @stable
*/
export interface InjectableMetadataFactory {
(): any;
new (): InjectableMetadata;
}
/**
* Factory for creating {@link SelfMetadata}.
* @stable
*/
export interface SelfMetadataFactory {
(): any;
new (): SelfMetadata;
}
/**
* Factory for creating {@link HostMetadata}.
* @stable
*/
export interface HostMetadataFactory {
(): any;
new (): HostMetadata;
}
/**
* Factory for creating {@link SkipSelfMetadata}.
* @stable
*/
export interface SkipSelfMetadataFactory {
(): any;
new (): SkipSelfMetadata;
}
/**
* Factory for creating {@link InjectMetadata}.
* @stable
* @Annotation
*/
export var Inject: InjectMetadataFactory = makeParamDecorator(InjectMetadata);
/**
* Factory for creating {@link OptionalMetadata}.
* @stable
* @Annotation
*/
export var Optional: OptionalMetadataFactory = makeParamDecorator(OptionalMetadata);
/**
* Factory for creating {@link InjectableMetadata}.
* @stable
* @Annotation
*/
export var Injectable: InjectableMetadataFactory =
<InjectableMetadataFactory>makeDecorator(InjectableMetadata);
/**
* Factory for creating {@link SelfMetadata}.
* @stable
* @Annotation
*/
export var Self: SelfMetadataFactory = makeParamDecorator(SelfMetadata);
/**
* Factory for creating {@link HostMetadata}.
* @stable
* @Annotation
*/
export var Host: HostMetadataFactory = makeParamDecorator(HostMetadata);
/**
* Factory for creating {@link SkipSelfMetadata}.
* @stable
* @Annotation
*/
export var SkipSelf: SkipSelfMetadataFactory = makeParamDecorator(SkipSelfMetadata);

View File

@ -7,239 +7,369 @@
*/
import {stringify} from '../facade/lang';
import {makeParamDecorator} from '../util/decorators';
/**
* A parameter metadata that specifies a dependency.
* Type of the Inject decorator / constructor function.
*
* ### 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);
* ```
* @stable
*/
export class InjectMetadata {
constructor(public token: any) {}
toString(): string { return `@Inject(${stringify(this.token)})`; }
export interface InjectMetadataFactory {
/**
* 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);
* ```
* @stable
*/
(token: any): any;
new (token: any): Inject;
}
/**
* A parameter metadata that marks a dependency as optional. {@link Injector} provides `null` if
* the dependency is not found.
* Type of the Inject metadata.
*
* ### 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();
* ```
* @stable
*/
export class OptionalMetadata {
toString(): string { return `@Optional()`; }
export interface Inject { token: any; }
/**
* Inject decorator and metadata.
*
* @stable
* @Annotation
*/
export const Inject: InjectMetadataFactory = makeParamDecorator([['token', undefined]]);
/**
* Type of the Optional decorator / constructor function.
*
* @stable
*/
export interface OptionalMetadataFactory {
/**
* 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();
* ```
* @stable
*/
(): any;
new (): Optional;
}
/**
* `DependencyMetadata` is used by the framework to extend DI.
* This is internal to Angular and should not be used directly.
* Type of the Optional metadata.
*
* @stable
*/
export class DependencyMetadata {
get token(): any { return null; }
export interface Optional {}
/**
* Optional decorator and metadata.
*
* @stable
* @Annotation
*/
export const Optional: OptionalMetadataFactory = makeParamDecorator([]);
/**
* Type of the Injectable decorator / constructor function.
*
* @stable
*/
export interface InjectableMetadataFactory {
/**
* 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();
* ```
* @stable
*/
(): any;
new (): Injectable;
}
/**
* A marker metadata that marks a class as available to {@link Injector} for creation.
* Type of the Injectable metadata.
*
* ### 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();
* ```
* @stable
*/
export class InjectableMetadata {
constructor() {}
export interface Injectable {}
/**
* Injectable decorator and metadata.
*
* @stable
* @Annotation
*/
export const Injectable: InjectableMetadataFactory = makeParamDecorator([]);
/**
* Type of the Self decorator / constructor function.
*
* @stable
*/
export interface SelfMetadataFactory {
/**
* 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();
* ```
* @stable
*/
(): any;
new (): Self;
}
/**
* Specifies that an {@link Injector} should retrieve a dependency only from itself.
* Type of the Self metadata.
*
* ### 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();
* ```
* @stable
*/
export class SelfMetadata {
toString(): string { return `@Self()`; }
export interface Self {}
/**
* Self decorator and metadata.
*
* @stable
* @Annotation
*/
export const Self: SelfMetadataFactory = makeParamDecorator([]);
/**
* Type of the SkipSelf decorator / constructor function.
*
* @stable
*/
export interface SkipSelfMetadataFactory {
/**
* 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();
* ```
* @stable
*/
(): any;
new (): SkipSelf;
}
/**
* Specifies that the dependency resolution should start from the parent injector.
* Type of the SkipSelf metadata.
*
* ### 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();
* ```
* @stable
*/
export class SkipSelfMetadata {
toString(): string { return `@SkipSelf()`; }
export interface SkipSelf {}
/**
* SkipSelf decorator and metadata.
*
* @stable
* @Annotation
*/
export const SkipSelf: SkipSelfMetadataFactory = makeParamDecorator([]);
/**
* Type of the Host decorator / constructor function.
*
* @stable
*/
export interface HostMetadataFactory {
/**
* 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 {
* }
*```
* @stable
*/
(): any;
new (): Host;
}
/**
* Specifies that an injector should retrieve a dependency from any injector until reaching the
* closest host.
* Type of the Host metadata.
*
* 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 {
* }
*```
* @stable
*/
export class HostMetadata {
toString(): string { return `@Host()`; }
}
export interface Host {}
/**
* Host decorator and metadata.
*
* @stable
* @Annotation
*/
export const Host: HostMetadataFactory = makeParamDecorator([]);
// TODO(tbosch): remove this
export {
Host as HostMetadata,
Inject as InjectMetadata,
Injectable as InjectableMetadata,
Optional as OptionalMetadata,
Self as SelfMetadata,
SkipSelf as SkipSelfMetadata
};

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from './decorators';
import {Injectable} from './metadata';
/**
* Creates a token that can be used in a DI Provider.

View File

@ -12,7 +12,7 @@ import {reflector} from '../reflection/reflection';
import {Type} from '../type';
import {resolveForwardRef} from './forward_ref';
import {DependencyMetadata, HostMetadata, InjectMetadata, OptionalMetadata, SelfMetadata, SkipSelfMetadata} from './metadata';
import {HostMetadata, InjectMetadata, OptionalMetadata, SelfMetadata, SkipSelfMetadata} from './metadata';
import {ClassProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ValueProvider} from './provider';
import {InvalidProviderError, MixingMultiProvidersWithRegularProvidersError, NoAnnotationError} from './reflective_errors';
import {ReflectiveKey} from './reflective_key';
@ -256,12 +256,6 @@ function _extractToken(
} else if (paramMetadata instanceof SkipSelfMetadata) {
lowerBoundVisibility = paramMetadata;
} else if (paramMetadata instanceof DependencyMetadata) {
if (isPresent(paramMetadata.token)) {
token = paramMetadata.token;
}
depProps.push(paramMetadata);
}
}