diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/dep.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/dep.ts new file mode 100644 index 0000000000..a02cbe6e2e --- /dev/null +++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/dep.ts @@ -0,0 +1,41 @@ +/** + * @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, Injectable, NgModule} from '@angular/core'; +import {BrowserModule} from '@angular/platform-browser'; +import {ServerModule} from '@angular/platform-server'; + +@Injectable() +export class NormalService { +} + +@Component({ + selector: 'dep-app', + template: '{{found}}', +}) +export class AppComponent { + found: boolean; + constructor(service: ShakeableService) { this.found = !!service.normal; } +} + +@NgModule({ + imports: [ + BrowserModule.withServerTransition({appId: 'id-app'}), + ServerModule, + ], + declarations: [AppComponent], + bootstrap: [AppComponent], + providers: [NormalService], +}) +export class DepAppModule { +} + +@Injectable({scope: DepAppModule}) +export class ShakeableService { + constructor(readonly normal: NormalService) {} +} \ No newline at end of file diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts index e557195ded..b458c405c6 100644 --- a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts +++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Inject, InjectionToken, NgModule, forwardRef} from '@angular/core'; +import {Component, Inject, Injectable, InjectionToken, NgModule, forwardRef, inject} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {ServerModule} from '@angular/platform-server'; -export interface IService { readonly data: string; } +export interface IService { readonly dep: {readonly data: string;}; } @NgModule({}) export class TokenModule { @@ -18,7 +18,7 @@ export class TokenModule { export const TOKEN = new InjectionToken('test', { scope: TokenModule, - factory: () => new Service(), + factory: () => new Service(inject(Dep)), }); @@ -28,7 +28,7 @@ export const TOKEN = new InjectionToken('test', { }) export class AppComponent { data: string; - constructor(@Inject(TOKEN) service: IService) { this.data = service.data; } + constructor(@Inject(TOKEN) service: IService) { this.data = service.dep.data; } } @NgModule({ @@ -37,10 +37,18 @@ export class AppComponent { ServerModule, TokenModule, ], + providers: [forwardRef(() => Dep)], declarations: [AppComponent], bootstrap: [AppComponent], }) export class TokenAppModule { } -export class Service { readonly data = 'fromToken'; } \ No newline at end of file +@Injectable() +export class Dep { + readonly data = 'fromToken'; +} + +export class Service { + constructor(readonly dep: Dep) {} +} diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts index 907941877c..74da00c714 100644 --- a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts +++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts @@ -9,6 +9,7 @@ import {enableProdMode} from '@angular/core'; import {renderModuleFactory} from '@angular/platform-server'; import {BasicAppModuleNgFactory} from 'app_built/src/basic.ngfactory'; +import {DepAppModuleNgFactory} from 'app_built/src/dep.ngfactory'; import {HierarchyAppModuleNgFactory} from 'app_built/src/hierarchy.ngfactory'; import {RootAppModuleNgFactory} from 'app_built/src/root.ngfactory'; import {SelfAppModuleNgFactory} from 'app_built/src/self.ngfactory'; @@ -66,4 +67,14 @@ describe('ngInjectableDef Bazel Integration', () => { done(); }); }); + + it('can inject dependencies', done => { + renderModuleFactory(DepAppModuleNgFactory, { + document: '', + url: '/', + }).then(html => { + expect(html).toMatch(/>true<\//); + done(); + }); + }); }); diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index bf0a7dbe54..26e714858f 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -12,6 +12,7 @@ export {devModeEqual as ɵdevModeEqual} from './change_detection/change_detectio export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/change_detection_util'; export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants'; export {Console as ɵConsole} from './console'; +export {setCurrentInjector as ɵsetCurrentInjector} from './di/injector'; export {ComponentFactory as ɵComponentFactory} from './linker/component_factory'; export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver'; export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities'; diff --git a/packages/core/src/di.ts b/packages/core/src/di.ts index 79e4993603..756904237a 100644 --- a/packages/core/src/di.ts +++ b/packages/core/src/di.ts @@ -17,7 +17,7 @@ export {defineInjectable, Injectable, InjectableDecorator, InjectableProvider, I export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref'; -export {InjectFlags, Injector} from './di/injector'; +export {inject, InjectFlags, Injector} from './di/injector'; export {ReflectiveInjector} from './di/reflective_injector'; export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider'; export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider'; diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts index 0758464957..e428aa9a96 100644 --- a/packages/core/src/di/injector.ts +++ b/packages/core/src/di/injector.ts @@ -410,6 +410,20 @@ export function setCurrentInjector(injector: Injector | null): Injector|null { return former; } +/** + * Injects a token from the currently active injector. + * + * This function must be used in the context of a factory function such as one defined for an + * `InjectionToken`, and will throw an error if not called from such a context. For example: + * + * {@example core/di/ts/injector_spec.ts region='ShakeableInjectionToken'} + * + * Within such a factory function `inject` is utilized to request injection of a dependency, instead + * of providing an additional array of dependencies as was common to do with `useFactory` providers. + * `inject` is faster and more type-safe. + * + * @experimental + */ export function inject( token: Type| InjectionToken, notFoundValue?: undefined, flags?: InjectFlags): T; export function inject( diff --git a/packages/examples/core/di/ts/injector_spec.ts b/packages/examples/core/di/ts/injector_spec.ts index ab57f44d0e..729f5123e0 100644 --- a/packages/examples/core/di/ts/injector_spec.ts +++ b/packages/examples/core/di/ts/injector_spec.ts @@ -6,7 +6,25 @@ * found in the LICENSE file at https://angular.io/license */ -import {InjectionToken, Injector, ReflectiveInjector} from '@angular/core'; +import {APP_ROOT_SCOPE, InjectFlags, InjectionToken, Injector, ReflectiveInjector, Type, inject, ɵsetCurrentInjector as setCurrentInjector} from '@angular/core'; + +class MockRootScopeInjector implements Injector { + constructor(readonly parent: Injector) {} + + get( + token: Type|InjectionToken, defaultValue?: any, + flags: InjectFlags = InjectFlags.Default): T { + if ((token as any).ngInjectableDef && (token as any).ngInjectableDef.scope === APP_ROOT_SCOPE) { + const old = setCurrentInjector(this); + try { + return (token as any).ngInjectableDef.factory(); + } finally { + setCurrentInjector(old); + } + } + return this.parent.get(token, defaultValue, flags); + } +} { describe('injector metadata examples', () => { @@ -37,5 +55,25 @@ import {InjectionToken, Injector, ReflectiveInjector} from '@angular/core'; expect(url).toBe('http://localhost'); // #enddocregion }); + + it('injects a tree-shaekable InjectionToken', () => { + class MyDep {} + const injector = new MockRootScopeInjector(ReflectiveInjector.resolveAndCreate([MyDep])); + + // #docregion ShakeableInjectionToken + class MyService { + constructor(readonly myDep: MyDep) {} + } + + const MY_SERVICE_TOKEN = new InjectionToken('Manually constructed MyService', { + scope: APP_ROOT_SCOPE, + factory: () => new MyService(inject(MyDep)), + }); + + const instance = injector.get(MY_SERVICE_TOKEN); + expect(instance instanceof MyService).toBeTruthy(); + expect(instance.myDep instanceof MyDep).toBeTruthy(); + // #enddocregion + }); }); } diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 8f82abbeea..835512649c 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -447,6 +447,9 @@ export interface HostDecorator { /** @stable */ export declare const HostListener: HostListenerDecorator; +/** @experimental */ +export declare function inject(token: Type | InjectionToken, notFoundValue?: undefined, flags?: InjectFlags): T; + /** @stable */ export declare const Inject: InjectDecorator;