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:
parent
7d65356ae3
commit
f8749bfb70
|
@ -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) {}
|
||||||
|
}
|
|
@ -6,11 +6,11 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {BrowserModule} from '@angular/platform-browser';
|
||||||
import {ServerModule} from '@angular/platform-server';
|
import {ServerModule} from '@angular/platform-server';
|
||||||
|
|
||||||
export interface IService { readonly data: string; }
|
export interface IService { readonly dep: {readonly data: string;}; }
|
||||||
|
|
||||||
@NgModule({})
|
@NgModule({})
|
||||||
export class TokenModule {
|
export class TokenModule {
|
||||||
|
@ -18,7 +18,7 @@ export class TokenModule {
|
||||||
|
|
||||||
export const TOKEN = new InjectionToken('test', {
|
export const TOKEN = new InjectionToken('test', {
|
||||||
scope: TokenModule,
|
scope: TokenModule,
|
||||||
factory: () => new Service(),
|
factory: () => new Service(inject(Dep)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export const TOKEN = new InjectionToken('test', {
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
data: string;
|
data: string;
|
||||||
constructor(@Inject(TOKEN) service: IService) { this.data = service.data; }
|
constructor(@Inject(TOKEN) service: IService) { this.data = service.dep.data; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -37,10 +37,18 @@ export class AppComponent {
|
||||||
ServerModule,
|
ServerModule,
|
||||||
TokenModule,
|
TokenModule,
|
||||||
],
|
],
|
||||||
|
providers: [forwardRef(() => Dep)],
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
export class TokenAppModule {
|
export class TokenAppModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Service { readonly data = 'fromToken'; }
|
@Injectable()
|
||||||
|
export class Dep {
|
||||||
|
readonly data = 'fromToken';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Service {
|
||||||
|
constructor(readonly dep: Dep) {}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {enableProdMode} from '@angular/core';
|
import {enableProdMode} from '@angular/core';
|
||||||
import {renderModuleFactory} from '@angular/platform-server';
|
import {renderModuleFactory} from '@angular/platform-server';
|
||||||
import {BasicAppModuleNgFactory} from 'app_built/src/basic.ngfactory';
|
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 {HierarchyAppModuleNgFactory} from 'app_built/src/hierarchy.ngfactory';
|
||||||
import {RootAppModuleNgFactory} from 'app_built/src/root.ngfactory';
|
import {RootAppModuleNgFactory} from 'app_built/src/root.ngfactory';
|
||||||
import {SelfAppModuleNgFactory} from 'app_built/src/self.ngfactory';
|
import {SelfAppModuleNgFactory} from 'app_built/src/self.ngfactory';
|
||||||
|
@ -66,4 +67,14 @@ describe('ngInjectableDef Bazel Integration', () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can inject dependencies', done => {
|
||||||
|
renderModuleFactory(DepAppModuleNgFactory, {
|
||||||
|
document: '<dep-app></dep-app>',
|
||||||
|
url: '/',
|
||||||
|
}).then(html => {
|
||||||
|
expect(html).toMatch(/>true<\//);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,7 @@ export {devModeEqual as ɵdevModeEqual} from './change_detection/change_detectio
|
||||||
export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/change_detection_util';
|
export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/change_detection_util';
|
||||||
export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants';
|
export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants';
|
||||||
export {Console as ɵConsole} from './console';
|
export {Console as ɵConsole} from './console';
|
||||||
|
export {setCurrentInjector as ɵsetCurrentInjector} from './di/injector';
|
||||||
export {ComponentFactory as ɵComponentFactory} from './linker/component_factory';
|
export {ComponentFactory as ɵComponentFactory} from './linker/component_factory';
|
||||||
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';
|
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';
|
||||||
export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities';
|
export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities';
|
||||||
|
|
|
@ -17,7 +17,7 @@ export {defineInjectable, Injectable, InjectableDecorator, InjectableProvider, I
|
||||||
|
|
||||||
export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
|
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 {ReflectiveInjector} from './di/reflective_injector';
|
||||||
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
|
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
|
||||||
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
|
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
|
||||||
|
|
|
@ -410,6 +410,20 @@ export function setCurrentInjector(injector: Injector | null): Injector|null {
|
||||||
return former;
|
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>(
|
export function inject<T>(
|
||||||
token: Type<T>| InjectionToken<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
|
token: Type<T>| InjectionToken<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
|
||||||
export function inject<T>(
|
export function inject<T>(
|
||||||
|
|
|
@ -6,7 +6,25 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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', () => {
|
describe('injector metadata examples', () => {
|
||||||
|
@ -37,5 +55,25 @@ import {InjectionToken, Injector, ReflectiveInjector} from '@angular/core';
|
||||||
expect(url).toBe('http://localhost');
|
expect(url).toBe('http://localhost');
|
||||||
// #enddocregion
|
// #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
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -447,6 +447,9 @@ export interface HostDecorator {
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare const HostListener: HostListenerDecorator;
|
export declare const HostListener: HostListenerDecorator;
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare function inject<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare const Inject: InjectDecorator;
|
export declare const Inject: InjectDecorator;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue