From 0a2132ef1043e2fd84476e4dcd48d098ea935d7e Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Wed, 14 Sep 2016 09:43:01 -0700 Subject: [PATCH] docs(di): update docs on di --- modules/@angular/core/src/di/injector.ts | 33 +-- modules/@angular/core/src/di/metadata.ts | 244 ++++++---------- modules/@angular/core/src/di/provider.ts | 268 +++++------------- modules/@angular/core/src/metadata/di.ts | 2 - .../examples/core/di/ts/injector_spec.ts | 31 ++ .../examples/core/di/ts/metadata_spec.ts | 176 ++++++++++++ .../examples/core/di/ts/provider_spec.ts | 149 ++++++++++ 7 files changed, 526 insertions(+), 377 deletions(-) create mode 100644 modules/@angular/examples/core/di/ts/injector_spec.ts create mode 100644 modules/@angular/examples/core/di/ts/metadata_spec.ts create mode 100644 modules/@angular/examples/core/di/ts/provider_spec.ts diff --git a/modules/@angular/core/src/di/injector.ts b/modules/@angular/core/src/di/injector.ts index 6e86524f15..3c9a750092 100644 --- a/modules/@angular/core/src/di/injector.ts +++ b/modules/@angular/core/src/di/injector.ts @@ -22,6 +22,23 @@ class _NullInjector implements Injector { } /** + * @whatItDoes Injector interface + * @howToUse + * ``` + * const injector: Injector = ...; + * injector.get(...); + * ``` + * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. + * + * ### Example + * + * {@example core/di/ts/injector_spec.ts region='Injector'} + * + * `Injector` returns itself when given `Injector` as a token: + * {@example core/di/ts/injector_spec.ts region='injectInjector'} + * * @stable */ export abstract class Injector { @@ -34,22 +51,6 @@ export abstract class Injector { * - Throws {@link NoProviderError} if no `notFoundValue` that is not equal to * Injector.THROW_IF_NOT_FOUND is given * - Returns the `notFoundValue` otherwise - * - * ### Example ([live demo](http://plnkr.co/edit/HeXSHg?p=preview)) - * - * ```typescript - * var injector = ReflectiveInjector.resolveAndCreate([ - * {provide: "validToken", useValue: "Value"} - * ]); - * expect(injector.get("validToken")).toEqual("Value"); - * expect(() => injector.get("invalidToken")).toThrowError(); - * ``` - * - * `Injector` returns itself when given `Injector` as a token. - * - * ```typescript - * var injector = ReflectiveInjector.resolveAndCreate([]); - * expect(injector.get(Injector)).toBe(injector); * ``` */ get(token: any, notFoundValue?: any): any { return unimplemented(); } diff --git a/modules/@angular/core/src/di/metadata.ts b/modules/@angular/core/src/di/metadata.ts index f5a759fd7c..8dff4459e2 100644 --- a/modules/@angular/core/src/di/metadata.ts +++ b/modules/@angular/core/src/di/metadata.ts @@ -16,45 +16,29 @@ import {makeParamDecorator} from '../util/decorators'; */ export interface InjectDecorator { /** - * A parameter metadata that specifies a dependency. - * - * ### Example ([live demo](http://plnkr.co/edit/6uHYJK?p=preview)) - * - * ```typescript - * class Engine {} - * + * @whatItDoes A parameter decorator that specifies a dependency. + * @howToUse + * ``` * @Injectable() * class Car { - * engine; - * constructor(@Inject("MyEngine") engine:Engine) { - * this.engine = engine; - * } + * constructor(@Inject("MyEngine") public engine:Engine) {} * } - * - * var injector = Injector.resolveAndCreate([ - * {provide: "MyEngine", useClass: Engine}, - * Car - * ]); - * - * expect(injector.get(Car).engine instanceof Engine).toBe(true); * ``` * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. + * + * ### Example + * + * {@example core/di/ts/metadata_spec.ts region='Inject'} + * * When `@Inject()` is not present, {@link Injector} will use the type annotation of the * parameter. * * ### Example * - * ```typescript - * class Engine {} + * {@example core/di/ts/metadata_spec.ts region='InjectWithoutDecorator'} * - * @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; @@ -84,25 +68,23 @@ export const Inject: InjectDecorator = makeParamDecorator('Inject', [['token', u */ export interface OptionalDecorator { /** - * 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 {} - * + * @whatItDoes A parameter metadata that marks a dependency as optional. + * {@link Injector} provides `null` if the dependency is not found. + * @howToUse + * ``` * @Injectable() * class Car { - * engine; - * constructor(@Optional() engine:Engine) { - * this.engine = engine; - * } + * constructor(@Optional() public engine:Engine) {} * } - * - * var injector = Injector.resolveAndCreate([Car]); - * expect(injector.get(Car).engine).toBeNull(); * ``` + * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. + * + * ### Example + * + * {@example core/di/ts/metadata_spec.ts region='Optional'} + * * @stable */ (): any; @@ -131,35 +113,25 @@ export const Optional: OptionalDecorator = makeParamDecorator('Optional', []); */ export interface InjectableDecorator { /** - * 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); + * @whatItDoes A marker metadata that marks a class as available to {@link Injector} for creation. + * @howToUse * ``` + * @Injectable() + * class Car {} + * ``` + * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. + * + * ### Example + * + * {@example core/di/ts/metadata_spec.ts region='Injectable'} + * * {@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 {} + * {@example core/di/ts/metadata_spec.ts region='InjectableThrows'} * - * class NeedsService { - * constructor(public service:UsefulService) {} - * } - * - * var injector = Injector.resolveAndCreate([NeedsService, UsefulService]); - * expect(() => injector.get(NeedsService)).toThrowError(); - * ``` * @stable */ (): any; @@ -188,31 +160,22 @@ export const Injectable: InjectableDecorator = makeParamDecorator('Injectable', */ export interface SelfDecorator { /** - * 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(); + * @whatItDoes Specifies that an {@link Injector} should retrieve a dependency only from itself. + * @howToUse * ``` + * @Injectable() + * class Car { + * constructor(@Self() public engine:Engine) {} + * } + * ``` + * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. + * + * ### Example + * + * {@example core/di/ts/metadata_spec.ts region='Self'} + * * @stable */ (): any; @@ -242,29 +205,22 @@ export const Self: SelfDecorator = makeParamDecorator('Self', []); */ export interface SkipSelfDecorator { /** - * 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(); + * @whatItDoes Specifies that the dependency resolution should start from the parent injector. + * @howToUse * ``` + * @Injectable() + * class Car { + * constructor(@SkipSelf() public engine:Engine) {} + * } + * ``` + * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. + * + * ### Example + * + * {@example core/di/ts/metadata_spec.ts region='SkipSelf'} + * * @stable */ (): any; @@ -293,56 +249,24 @@ export const SkipSelf: SkipSelfDecorator = makeParamDecorator('SkipSelf', []); */ export interface HostDecorator { /** - * 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); - * } + * @whatItDoes Specifies that an injector should retrieve a dependency from any injector until + * reaching the + * host element of the current component. + * @howToUse + * ``` + * @Injectable() + * class Car { + * constructor(@Host() public engine:Engine) {} * } + * ``` * - * @Component({ - * selector: 'parent-cmp', - * providers: [HostService], - * template: ` - * Dir: - * `, - * directives: [ChildDirective] - * }) - * class ParentCmp { - * } + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. + * + * ### Example + * + * {@example core/di/ts/metadata_spec.ts region='Host'} * - * @Component({ - * selector: 'app', - * providers: [OtherService], - * template: ` - * Parent: - * `, - * directives: [ParentCmp] - * }) - * class App { - * } - *``` * @stable */ (): any; diff --git a/modules/@angular/core/src/di/provider.ts b/modules/@angular/core/src/di/provider.ts index 6c266028ae..d22c829f4f 100644 --- a/modules/@angular/core/src/di/provider.ts +++ b/modules/@angular/core/src/di/provider.ts @@ -9,50 +9,45 @@ import {Type} from '../type'; /** - * Configures the {@link Injector} to return an instance of `Type` when `Type' is used as token. + * @whatItDoes Configures the {@link Injector} to return an instance of `Type` when `Type' is used + * as token. + * @howToUse + * ``` + * @Injectable() + * class MyService {} + * + * const provider: TypeProvider = MyService; + * ``` + * + * @description * * Create an instance by invoking the `new` operator and supplying additional arguments. * This form is a short form of `TypeProvider`; * + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. + * * ### Example - * ```javascript - * @Injectable() - * class Greeting { - * text: 'Hello'; - * } * - * @Injectable() - * class MyClass { - * greeting:string; - * constructor(greeting: Greeting) { - * this.greeting = greeting.text; - * } - * } - * - * const injector = Injector.resolveAndCreate([ - * Greeting, // Shorthand for { provide: Greeting, useClass: Greeting } - * MyClass // Shorthand for { provide: MyClass, useClass: MyClass } - * ]); - * - * const myClass: MyClass = injector.get(MyClass); - * expect(myClass.greeting).toEqual('Hello'); - * ``` + * {@example core/di/ts/provider_spec.ts region='TypeProvider'} * * @stable */ export interface TypeProvider extends Type {} /** - * Configures the {@link Injector} to return a value for a token. + * @whatItDoes Configures the {@link Injector} to return a value for a token. + * @howToUse + * ``` + * const provider: ValueProvider = {provide: 'someToken', useValue: 'someValue'}; + * ``` + * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. * * ### Example - * ```javascript - * const injector = Injector.resolveAndCreate([ - * {provide: String, useValue: 'Hello'} - * ]); * - * expect(injector.get(String)).toEqual('Hello'); - * ``` + * {@example core/di/ts/provider_spec.ts region='ValueProvider'} + * * @stable */ export interface ValueProvider { @@ -71,62 +66,31 @@ export interface ValueProvider { * providers spread across many files to provide configuration information to a common token. * * ### Example - * ```javascript - * var locale = new OpaqueToken('local'); * - * const injector = Injector.resolveAndCreate([ - * { provide: locale, multi: true, useValue: 'en' }, - * { provide: locale, multi: true, useValue: 'sk' }, - * ]); - * - * const locales: string[] = injector.get(locale); - * expect(locales).toEqual(['en', 'sk']); - * ``` + * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'} */ multi?: boolean; } /** - * Configures the {@link Injector} to return an instance of `useClass` for a token. + * @whatItDoes Configures the {@link Injector} to return an instance of `useClass` for a token. + * @howToUse + * ``` + * @Injectable() + * class MyService {} + * + * const provider: ClassProvider = {provide: 'someToken', useClass: MyService}; + * ``` + * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. * * ### Example - * ```javascript - * abstract class Shape { - * name: string; - * } * - * class Square extends Shape { - * name = 'square'; - * } + * {@example core/di/ts/provider_spec.ts region='ClassProvider'} * - * const injector = Injector.resolveAndCreate([ - * {provide: Shape, useClass: Square} - * ]); - * - * const shape: Shape = injector.get(Shape); - * expect(shape.name).toEqual('square'); - * expect(shape instanceof Square).toBe(true); - * ``` - * - * Note that following is not equal: - * ```javascript - * class Greeting { - * salutation = 'Hello'; - * } - * - * class FormalGreeting extends Greeting { - * salutation = 'Greetings'; - * } - * - * const injector = Injector.resolveAndCreate([ - * FormalGreeting, - * {provide: Greeting, useClass: FormalGreeting} - * ]); - * - * // The injector returns different instances. - * // See: {provide: ?, useExisting: ?} if you want the same instance. - * expect(injector.get(FormalGreeting)).not.toBe(injector.get(Greeting)); - * ``` + * Note that following two providers are not equal: + * {@example core/di/ts/provider_spec.ts region='ClassProviderDifference'} * * @stable */ @@ -146,56 +110,26 @@ export interface ClassProvider { * providers spread across many files to provide configuration information to a common token. * * ### Example - * ```javascript - * abstract class Locale { - * name: string; - * }; * - * @Injectable() - * class EnLocale extends Locale { - * name: 'en'; - * }; - * - * @Injectable() - * class SkLocale extends Locale { - * name: 'sk'; - * }; - * - * const injector = Injector.resolveAndCreate([ - * { provide: Locale, useValue: EnLocale, multi: true }, - * { provide: Locale, useValue: SkLocale, multi: true }, - * ]); - * - * const locales: Locale[] = injector.get(Locale); - * const localeNames: string[] = locals.map((l) => l.name); - * expect(localeNames).toEqual(['en', 'sk']); - * ``` + * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'} */ multi?: boolean; } /** - * Configures the {@link Injector} to return a value of another `useExisting` token. + * @whatItDoes Configures the {@link Injector} to return a value of another `useExisting` token. + * @howToUse + * ``` + * const provider: ExistingProvider = {provide: 'someToken', useExisting: 'someOtherToken'}; + * ``` + * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. * * ### Example - * ```javascript - * class Greeting { - * salutation = 'Hello'; - * } * - * class FormalGreeting extends Greeting { - * salutation = 'Greetings'; - * } + * {@example core/di/ts/provider_spec.ts region='ExistingProvider'} * - * const injector = Injector.resolveAndCreate([ - * FormalGreeting, - * {provide: Greeting, useExisting: FormalGreeting} - * ]); - * - * expect(injector.get(Greeting).name).toEqual('Hello'); - * expect(injector.get(FormalGreeting).name).toEqual('Hello'); - * expect(injector.get(Salutation).name).toBe(injector.get(Greeting)); - * ``` * @stable */ export interface ExistingProvider { @@ -214,52 +148,32 @@ export interface ExistingProvider { * providers spread across many files to provide configuration information to a common token. * * ### Example - * ```javascript - * abstract class Locale { - * name: string; - * }; * - * @Injectable() - * class EnLocale extends Locale { - * name: 'en'; - * }; - * - * @Injectable() - * class SkLocale extends Locale { - * name: 'sk'; - * }; - * - * const injector = Injector.resolveAndCreate([ - * EnLocale, - * SkLocale - * { provide: Locale, useExisting: EnLocale, multi: true }, - * { provide: Locale, useExisting: SkLocale, multi: true }, - * ]); - * - * const locales: Locale[] = injector.get(Locale); - * const localeNames: string[] = locals.map((l) => l.name); - * expect(localeNames).toEqual(['en', 'sk']); - * ``` + * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'} */ multi?: boolean; } /** - * Configures the {@link Injector} to return a value by invoking a `useFactory` function. + * @whatItDoes Configures the {@link Injector} to return a value by invoking a `useFactory` + * function. + * @howToUse + * ``` + * function serviceFactory() { ... } + * + * const provider: FactoryProvider = {provide: 'someToken', useFactory: serviceFactory, deps: []}; + * ``` + * + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. * * ### Example - * ```javascript - * const HASH = new OpaqueToken('hash'); * - * const injector = Injector.resolveAndCreate([ - * {provide: Location, useValue: window.location}, - * {provide: HASH, useFactory: (location: Location) => location.hash, deps: [Location]} - * ]); + * {@example core/di/ts/provider_spec.ts region='FactoryProvider'} * + * Dependencies can also be marked as optional: + * {@example core/di/ts/provider_spec.ts region='FactoryProviderOptionalDeps'} * - * // Assume location is: http://angular.io/#someLocation - * expect(injector.get(HASH)).toEqual('someLocation'); - * ``` * @stable */ export interface FactoryProvider { @@ -285,65 +199,21 @@ export interface FactoryProvider { * providers spread across many files to provide configuration information to a common token. * * ### Example - * ```javascript - * class Locale { - * constructor(public name: string) {} - * }; - * const PRIMARY = new OpequeToken('primary'); - * const SECONDARY = new OpequeToken('secondary'); * - * const injector = Injector.resolveAndCreate([ - * { provide: PRIMARY: useValue: 'en'}, - * { provide: SECONDARY: useValue: 'sk'}, - * { provide: Locale, useFactory: (n) => new Locale(n), deps: [PRIMARY], multi: true}, - * { provide: Locale, useFactory: (n) => new Locale(n), deps: [SECONDARY], multi: true}, - * ]); - * - * const locales: Locale[] = injector.get(Locale); - * const localeNames: string[] = locals.map((l) => l.name); - * expect(localeNames).toEqual(['en', 'sk']); - * ``` + * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'} */ multi?: boolean; } /** - * Describes how the {@link Injector} should be configured. - * + * @whatItDoes Describes how the {@link Injector} should be configured. + * @howToUse * See {@link TypeProvider}, {@link ValueProvider}, {@link ClassProvider}, {@link ExistingProvider}, * {@link FactoryProvider}. * - * ```javascript - * class Greeting { - * salutation = 'Hello'; - * } + * @description + * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}. * - * class FormalGreeting extends Greeting { - * salutation = 'Greetings'; - * } - * - * abstract class Operation { - * apply(a,b): any; - * } - * - * class AddOperation extends Operation { - * apply(a,b) { return a+b; } - * } - * - * - * const injector = Injector.resolveAndCreate([ - * FormalGreeting, - * {provide: String, useValue: 'Hello World!'}, - * {provide: Greeting, useExisting: FormalGreeting}, - * {provide: Operation, useClass: AddOperation}, - * {provide: Number, useFactory: (op) =>op.apply(1,2), deps: [Operation] } - * ]); - * - * expect(injector.get(FormalGreeting).name).toEqual('Greetings'); - * expect(injector.get(String).name).toEqual('Hello World!'); - * expect(injector.get(Greeting).name).toBe(injector.get(FormalGreeting)); - * expect(injector.get(Number).toEqual(3); - * ``` * @stable */ export type Provider = diff --git a/modules/@angular/core/src/metadata/di.ts b/modules/@angular/core/src/metadata/di.ts index b6dee1ab50..63c2ff6eb1 100644 --- a/modules/@angular/core/src/metadata/di.ts +++ b/modules/@angular/core/src/metadata/di.ts @@ -111,8 +111,6 @@ export interface AttributeDecorator { /** * Type of the Attribute metadata. - * - * @stable */ export interface Attribute { attributeName?: string; } diff --git a/modules/@angular/examples/core/di/ts/injector_spec.ts b/modules/@angular/examples/core/di/ts/injector_spec.ts new file mode 100644 index 0000000000..9b0a327705 --- /dev/null +++ b/modules/@angular/examples/core/di/ts/injector_spec.ts @@ -0,0 +1,31 @@ +/** + * @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, ReflectiveInjector} from '@angular/core'; + +export function main() { + describe('injector metadata examples', () => { + it('works', () => { + // #docregion Injector + const injector: Injector = + ReflectiveInjector.resolveAndCreate([{provide: 'validToken', useValue: 'Value'}]); + expect(injector.get('validToken')).toEqual('Value'); + expect(() => injector.get('invalidToken')).toThrowError(); + expect(injector.get('invalidToken', 'notFound')).toEqual('notFound'); + // #enddocregion + }); + + it('injects injector', () => { + // #docregion injectInjector + const injector = ReflectiveInjector.resolveAndCreate([]); + expect(injector.get(Injector)).toBe(injector); + // #enddocregion + + }); + }); +} diff --git a/modules/@angular/examples/core/di/ts/metadata_spec.ts b/modules/@angular/examples/core/di/ts/metadata_spec.ts new file mode 100644 index 0000000000..1182676a4e --- /dev/null +++ b/modules/@angular/examples/core/di/ts/metadata_spec.ts @@ -0,0 +1,176 @@ +/** + * @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 {Component, Directive, Host, Inject, Injectable, Optional, ReflectiveInjector, Self, SkipSelf} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; + +export function main() { + describe('di metadata examples', () => { + describe('Inject', () => { + it('works', () => { + // #docregion Inject + class Engine {} + + @Injectable() + class Car { + constructor(@Inject('MyEngine') public engine: Engine) {} + } + + const injector = + ReflectiveInjector.resolveAndCreate([{provide: 'MyEngine', useClass: Engine}, Car]); + + expect(injector.get(Car).engine instanceof Engine).toBe(true); + // #enddocregion + }); + + it('works without decorator', () => { + // #docregion InjectWithoutDecorator + class Engine {} + + @Injectable() + class Car { + constructor(public engine: Engine) { + } // same as constructor(@Inject(Engine) engine:Engine) + } + + const injector = ReflectiveInjector.resolveAndCreate([Engine, Car]); + expect(injector.get(Car).engine instanceof Engine).toBe(true); + // #enddocregion + }); + }); + + describe('Optional', () => { + it('works', () => { + // #docregion Optional + class Engine {} + + @Injectable() + class Car { + constructor(@Optional() public engine: Engine) {} + } + + const injector = ReflectiveInjector.resolveAndCreate([Car]); + expect(injector.get(Car).engine).toBeNull(); + // #enddocregion + }); + }); + + describe('Injectable', () => { + it('works', () => { + // #docregion Injectable + @Injectable() + class UsefulService { + } + + @Injectable() + class NeedsService { + constructor(public service: UsefulService) {} + } + + const injector = ReflectiveInjector.resolveAndCreate([NeedsService, UsefulService]); + expect(injector.get(NeedsService).service instanceof UsefulService).toBe(true); + // #enddocregion + }); + + it('throws without Injectable', () => { + // #docregion InjectableThrows + class UsefulService {} + + class NeedsService { + constructor(public service: UsefulService) {} + } + + expect(() => ReflectiveInjector.resolveAndCreate([NeedsService, UsefulService])).toThrow(); + // #enddocregion + }); + }); + + describe('Self', () => { + it('works', () => { + // #docregion Self + class Dependency {} + + @Injectable() + class NeedsDependency { + constructor(@Self() public dependency: Dependency) {} + } + + let inj = ReflectiveInjector.resolveAndCreate([Dependency, NeedsDependency]); + const nd = inj.get(NeedsDependency); + + expect(nd.dependency instanceof Dependency).toBe(true); + + inj = ReflectiveInjector.resolveAndCreate([Dependency]); + const child = inj.resolveAndCreateChild([NeedsDependency]); + expect(() => child.get(NeedsDependency)).toThrowError(); + // #enddocregion + }); + }); + + describe('SkipSelf', () => { + it('works', () => { + // #docregion SkipSelf + class Dependency {} + + @Injectable() + class NeedsDependency { + constructor(@SkipSelf() public dependency: Dependency) { this.dependency = dependency; } + } + + const parent = ReflectiveInjector.resolveAndCreate([Dependency]); + const child = parent.resolveAndCreateChild([NeedsDependency]); + expect(child.get(NeedsDependency).dependency instanceof Dependency).toBe(true); + + const inj = ReflectiveInjector.resolveAndCreate([Dependency, NeedsDependency]); + expect(() => inj.get(NeedsDependency)).toThrowError(); + // #enddocregion + }); + }); + + describe('Host', () => { + it('works', () => { + // #docregion Host + 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: + ` + }) + class ParentCmp { + } + + @Component({ + selector: 'app', + providers: [OtherService], + template: `Parent: ` + }) + class App { + } + // #enddocregion + + TestBed.configureTestingModule({ + declarations: [App, ParentCmp, ChildDirective], + }); + expect(() => TestBed.createComponent(App)).not.toThrow(); + }); + }); + + }); +} diff --git a/modules/@angular/examples/core/di/ts/provider_spec.ts b/modules/@angular/examples/core/di/ts/provider_spec.ts new file mode 100644 index 0000000000..31d2c4c739 --- /dev/null +++ b/modules/@angular/examples/core/di/ts/provider_spec.ts @@ -0,0 +1,149 @@ +/** + * @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 {Inject, Injectable, OpaqueToken, Optional, ReflectiveInjector, ValueProvider} from '@angular/core'; + +export function main() { + describe('Provider examples', () => { + describe('TypeProvider', () => { + it('works', () => { + // #docregion TypeProvider + @Injectable() + class Greeting { + salutation = 'Hello'; + } + + const injector = ReflectiveInjector.resolveAndCreate([ + Greeting, // Shorthand for { provide: Greeting, useClass: Greeting } + ]); + + expect(injector.get(Greeting).salutation).toBe('Hello'); + // #enddocregion + }); + }); + + describe('ValueProvider', () => { + it('works', () => { + // #docregion ValueProvider + const injector = + ReflectiveInjector.resolveAndCreate([{provide: String, useValue: 'Hello'}]); + + expect(injector.get(String)).toEqual('Hello'); + // #enddocregion + }); + }); + + describe('MultiProviderAspect', () => { + it('works', () => { + // #docregion MultiProviderAspect + const injector = ReflectiveInjector.resolveAndCreate([ + {provide: 'local', multi: true, useValue: 'en'}, + {provide: 'local', multi: true, useValue: 'sk'}, + ]); + + const locales: string[] = injector.get('local'); + expect(locales).toEqual(['en', 'sk']); + // #enddocregion + }); + }); + + describe('ClassProvider', () => { + it('works', () => { + // #docregion ClassProvider + abstract class Shape { name: string; } + + class Square extends Shape { + name = 'square'; + } + + const injector = ReflectiveInjector.resolveAndCreate([{provide: Shape, useClass: Square}]); + + const shape: Shape = injector.get(Shape); + expect(shape.name).toEqual('square'); + expect(shape instanceof Square).toBe(true); + // #enddocregion + }); + + it('is different then useExisting', () => { + // #docregion ClassProviderDifference + class Greeting { + salutation = 'Hello'; + } + + class FormalGreeting extends Greeting { + salutation = 'Greetings'; + } + + const injector = ReflectiveInjector.resolveAndCreate( + [FormalGreeting, {provide: Greeting, useClass: FormalGreeting}]); + + // The injector returns different instances. + // See: {provide: ?, useExisting: ?} if you want the same instance. + expect(injector.get(FormalGreeting)).not.toBe(injector.get(Greeting)); + // #enddocregion + }); + }); + + describe('ExistingProvider', () => { + it('works', () => { + // #docregion ExistingProvider + class Greeting { + salutation = 'Hello'; + } + + class FormalGreeting extends Greeting { + salutation = 'Greetings'; + } + + const injector = ReflectiveInjector.resolveAndCreate( + [FormalGreeting, {provide: Greeting, useExisting: FormalGreeting}]); + + expect(injector.get(Greeting).salutation).toEqual('Greetings'); + expect(injector.get(FormalGreeting).salutation).toEqual('Greetings'); + expect(injector.get(FormalGreeting)).toBe(injector.get(Greeting)); + // #enddocregion + }); + }); + + describe('FactoryProvider', () => { + it('works', () => { + // #docregion FactoryProvider + const Location = new OpaqueToken('location'); + const Hash = new OpaqueToken('hash'); + + const injector = ReflectiveInjector.resolveAndCreate([ + {provide: Location, useValue: 'http://angular.io/#someLocation'}, { + provide: Hash, + useFactory: (location: string) => location.split('#')[1], + deps: [Location] + } + ]); + + expect(injector.get(Hash)).toEqual('someLocation'); + // #enddocregion + }); + + it('supports optional dependencies', () => { + // #docregion FactoryProviderOptionalDeps + const Location = new OpaqueToken('location'); + const Hash = new OpaqueToken('hash'); + + const injector = ReflectiveInjector.resolveAndCreate([{ + provide: Hash, + useFactory: (location: string) => `Hash for: ${location}`, + // use a nested array to define metadata for dependencies. + deps: [[new Optional(), new Inject(Location)]] + }]); + + expect(injector.get(Hash)).toEqual('Hash for: null'); + // #enddocregion + }); + }); + + }); +}