fix(core): export inject() from @angular/core (#22389)

inject() supports the ngInjectableDef-based configuration of the injector
(otherwise known as tree-shakeable services). It was missing from the
exported API of @angular/core, this PR adds it.

The test added here is correct in theory, but may pass accidentally due
to the decorator side-effect replacing the inject() call at runtime. An
upcoming compiler PR will strip reified decorators from the output
entirely.

Fixes #22388

PR Close #22389
This commit is contained in:
Alex Rickabaugh 2018-02-23 09:04:55 -08:00 committed by Alex Eagle
parent 7d65356ae3
commit f8749bfb70
8 changed files with 123 additions and 7 deletions

View File

@ -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) {}
}

View File

@ -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'; }
@Injectable()
export class Dep {
readonly data = 'fromToken';
}
export class Service {
constructor(readonly dep: Dep) {}
}

View File

@ -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: '<dep-app></dep-app>',
url: '/',
}).then(html => {
expect(html).toMatch(/>true<\//);
done();
});
});
});

View File

@ -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';

View File

@ -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';

View File

@ -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<T>(
token: Type<T>| InjectionToken<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
export function inject<T>(

View File

@ -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<T>(
token: Type<T>|InjectionToken<T>, 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<MyService>('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
});
});
}

View File

@ -447,6 +447,9 @@ export interface HostDecorator {
/** @stable */
export declare const HostListener: HostListenerDecorator;
/** @experimental */
export declare function inject<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
/** @stable */
export declare const Inject: InjectDecorator;