Skip to content
Permalink
Browse files

feat: tree-shakeable providers API updates (#22655)

Rename @Injectable({scope -> providedIn}).

Instead of {providedIn: APP_ROOT_SCOPE}, accept {providedIn: 'root'}.
Also, {providedIn: null} implies the injectable should not be added
to any scope.

PR Close #22655
  • Loading branch information...
alxhub authored and kara committed Mar 7, 2018
1 parent 21e44c6 commit db56836425fe200f42e299bce3e76bca0a6021e9
Showing with 219 additions and 114 deletions.
  1. +1 −1 integration/injectable-def/src/lib1.ts
  2. +1 −1 packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/dep.ts
  3. +1 −1 packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root.ts
  4. +2 −2 packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root_service.ts
  5. +1 −1 packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/self.ts
  6. +1 −1 packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/string.ts
  7. +1 −1 packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts
  8. +1 −0 packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/BUILD.bazel
  9. +39 −3 packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts
  10. +1 −1 packages/compiler-cli/integrationtest/bazel/injectable_def/lib1/module.ts
  11. +13 −13 packages/compiler-cli/test/ngc_spec.ts
  12. +1 −1 packages/compiler/src/compile_metadata.ts
  13. +1 −1 packages/compiler/src/core.ts
  14. +10 −2 packages/compiler/src/injectable_compiler.ts
  15. +1 −1 packages/compiler/src/metadata_resolver.ts
  16. +1 −0 packages/core/src/core_private_export.ts
  17. +0 −1 packages/core/src/di.ts
  18. +22 −10 packages/core/src/di/injectable.ts
  19. +7 −4 packages/core/src/di/injection_token.ts
  20. +5 −13 packages/core/src/di/scope.ts
  21. +8 −13 packages/core/src/view/ng_module.ts
  22. +33 −1 packages/core/src/view/services.ts
  23. +0 −5 packages/core/src/view/types.ts
  24. +2 −2 packages/core/test/render3/compiler_canonical/ng_module_spec.ts
  25. +18 −17 packages/core/test/view/ng_module_spec.ts
  26. +33 −3 packages/core/testing/src/test_bed.ts
  27. +4 −4 packages/examples/core/di/ts/injector_spec.ts
  28. +2 −2 packages/platform-browser/src/browser.ts
  29. +9 −9 tools/public_api_guard/core/core.d.ts
@@ -3,7 +3,7 @@ import {Injectable, NgModule} from '@angular/core';
@NgModule({})
export class Lib1Module {}

@Injectable({scope: Lib1Module})
@Injectable({providedIn: Lib1Module})
export class Service {
static instance = 0;
readonly instance = Service.instance++;
@@ -35,7 +35,7 @@ export class AppComponent {
export class DepAppModule {
}

@Injectable({scope: DepAppModule})
@Injectable({providedIn: DepAppModule})
export class ShakeableService {
constructor(readonly normal: NormalService) {}
}
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {APP_ROOT_SCOPE, Component, Injectable, NgModule, Optional, Self} from '@angular/core';
import {Component, Injectable, NgModule, Optional, Self} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {ServerModule} from '@angular/platform-server';
import {RouterModule} from '@angular/router';
@@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/

import {APP_ROOT_SCOPE, Injectable} from '@angular/core';
import {Injectable} from '@angular/core';

@Injectable({
scope: APP_ROOT_SCOPE,
providedIn: 'root',
})
export class Service {
}
@@ -36,6 +36,6 @@ export class AppComponent {
export class SelfAppModule {
}

@Injectable({scope: SelfAppModule})
@Injectable({providedIn: SelfAppModule})
export class ShakeableService {
}
@@ -32,7 +32,7 @@ export class AppComponent {
export class StringAppModule {
}

@Injectable({scope: StringAppModule})
@Injectable({providedIn: StringAppModule})
export class Service {
constructor(@Inject('someStringToken') readonly data: string) {}
}
@@ -17,7 +17,7 @@ export class TokenModule {
}

export const TOKEN = new InjectionToken('test', {
scope: TokenModule,
providedIn: TokenModule,
factory: () => new Service(inject(Dep)),
});

@@ -14,6 +14,7 @@ ts_library(
deps = [
"//packages/compiler-cli/integrationtest/bazel/injectable_def/app",
"//packages/core",
"//packages/core/testing",
"//packages/platform-server",
],
)
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/

import {enableProdMode} from '@angular/core';
import {Component, Injectable, NgModule} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {renderModuleFactory} from '@angular/platform-server';
import {BasicAppModuleNgFactory} from 'app_built/src/basic.ngfactory';
import {DepAppModuleNgFactory} from 'app_built/src/dep.ngfactory';
@@ -16,8 +17,6 @@ import {SelfAppModuleNgFactory} from 'app_built/src/self.ngfactory';
import {StringAppModuleNgFactory} from 'app_built/src/string.ngfactory';
import {TokenAppModuleNgFactory} from 'app_built/src/token.ngfactory';

enableProdMode();

describe('ngInjectableDef Bazel Integration', () => {
it('works in AOT', done => {
renderModuleFactory(BasicAppModuleNgFactory, {
@@ -88,4 +87,41 @@ describe('ngInjectableDef Bazel Integration', () => {
done();
});
});

it('allows provider override in JIT for root-scoped @Injectables', () => {
@Injectable({
providedIn: 'root',
useValue: new Service('default'),
})
class Service {
constructor(readonly value: string) {}
}

TestBed.configureTestingModule({});
TestBed.overrideProvider(Service, {useValue: new Service('overridden')});

expect(TestBed.get(Service).value).toEqual('overridden');
});

it('allows provider override in JIT for module-scoped @Injectables', () => {

@NgModule()
class Module {
}

@Injectable({
providedIn: Module,
useValue: new Service('default'),
})
class Service {
constructor(readonly value: string) {}
}

TestBed.configureTestingModule({
imports: [Module],
});
TestBed.overrideProvider(Service, {useValue: new Service('overridden')});

expect(TestBed.get(Service).value).toEqual('overridden');
});
});
@@ -13,7 +13,7 @@ export class Lib1Module {
}

@Injectable({
scope: Lib1Module,
providedIn: Lib1Module,
})
export class Service {
static instanceCount = 0;
@@ -2058,13 +2058,13 @@ describe('ngc transformer command-line', () => {
import {Module} from './module';
@Injectable({
scope: Module,
providedIn: Module,
})
export class Service {}
`);
expect(source).toMatch(/ngInjectableDef = .+\.defineInjectable\(/);
expect(source).toMatch(/ngInjectableDef.*token: Service/);
expect(source).toMatch(/ngInjectableDef.*scope: .+\.Module/);
expect(source).toMatch(/ngInjectableDef.*providedIn: .+\.Module/);
});

it('ngInjectableDef in es5 mode is annotated @nocollapse when closure options are enabled',
@@ -2081,7 +2081,7 @@ describe('ngc transformer command-line', () => {
import {Module} from './module';
@Injectable({
scope: Module,
providedIn: Module,
})
export class Service {}
`);
@@ -2096,7 +2096,7 @@ describe('ngc transformer command-line', () => {
export const CONST_SERVICE: Service = null;
@Injectable({
scope: Module,
providedIn: Module,
useValue: CONST_SERVICE
})
export class Service {}
@@ -2113,7 +2113,7 @@ describe('ngc transformer command-line', () => {
export class Existing {}
@Injectable({
scope: Module,
providedIn: Module,
useExisting: Existing,
})
export class Service {}
@@ -2130,7 +2130,7 @@ describe('ngc transformer command-line', () => {
export class Existing {}
@Injectable({
scope: Module,
providedIn: Module,
useFactory: (existing: Existing|null) => new Service(existing),
deps: [[new Optional(), Existing]],
})
@@ -2150,7 +2150,7 @@ describe('ngc transformer command-line', () => {
export class Existing {}
@Injectable({
scope: Module,
providedIn: Module,
useFactory: (existing: Existing) => new Service(existing),
deps: [[new SkipSelf(), Existing]],
})
@@ -2166,18 +2166,18 @@ describe('ngc transformer command-line', () => {
import {Inject, Injectable, InjectionToken} from '@angular/core';
import {Module} from './module';
export const TOKEN = new InjectionToken('desc', {scope: Module, factory: () => true});
export const TOKEN = new InjectionToken('desc', {providedIn: Module, factory: () => true});
@Injectable({
scope: Module,
providedIn: Module,
})
export class Service {
constructor(@Inject(TOKEN) value: boolean) {}
}
`);
expect(source).toMatch(/ngInjectableDef = .+\.defineInjectable\(/);
expect(source).toMatch(/ngInjectableDef.*token: Service/);
expect(source).toMatch(/ngInjectableDef.*scope: .+\.Module/);
expect(source).toMatch(/ngInjectableDef.*providedIn: .+\.Module/);
});

it('generates exports.* references when outputting commonjs', () => {
@@ -2189,15 +2189,15 @@ describe('ngc transformer command-line', () => {
"files": ["service.ts"]
}`);
const source = compileService(`
import {Inject, Injectable, InjectionToken, APP_ROOT_SCOPE} from '@angular/core';
import {Inject, Injectable, InjectionToken} from '@angular/core';
import {Module} from './module';
export const TOKEN = new InjectionToken<string>('test token', {
scope: APP_ROOT_SCOPE,
providedIn: 'root',
factory: () => 'this is a test',
});
@Injectable({scope: APP_ROOT_SCOPE})
@Injectable({providedIn: 'root'})
export class Service {
constructor(@Inject(TOKEN) token: any) {}
}
@@ -140,7 +140,7 @@ export interface CompileInjectableMetadata {
symbol: StaticSymbol;
type: CompileTypeMetadata;

module?: StaticSymbol;
providedIn?: StaticSymbol;

useValue?: any;
useClass?: StaticSymbol;
@@ -127,7 +127,7 @@ export interface ModuleWithProviders {
providers?: Provider[];
}
export interface Injectable {
scope?: Type|any;
providedIn?: Type|'root'|any;
useClass?: Type|any;
useExisting?: Type|any;
useValue?: any;
@@ -89,16 +89,24 @@ export class InjectableCompiler {
}

injectableDef(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
let providedIn: o.Expression = o.NULL_EXPR;
if (injectable.providedIn) {
if (typeof injectable.providedIn === 'string') {
providedIn = o.literal(injectable.providedIn);
} else {
providedIn = ctx.importExpr(injectable.providedIn);
}
}
const def: MapLiteral = [
mapEntry('factory', this.factoryFor(injectable, ctx)),
mapEntry('token', ctx.importExpr(injectable.type.reference)),
mapEntry('scope', ctx.importExpr(injectable.module !)),
mapEntry('providedIn', providedIn),
];
return o.importExpr(Identifiers.defineInjectable).callFn([o.literalMap(def)]);
}

compile(injectable: CompileInjectableMetadata, ctx: OutputContext): void {
if (injectable.module) {
if (injectable.providedIn) {
const className = identifierName(injectable.type) !;
const clazz = new o.ClassStmt(
className, null,
@@ -801,7 +801,7 @@ export class CompileMetadataResolver {
return {
symbol: type,
type: typeMetadata,
module: meta.scope || undefined,
providedIn: meta.providedIn,
useValue: meta.useValue,
useClass: meta.useClass,
useExisting: meta.useExisting,
@@ -13,6 +13,7 @@ export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/cha
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 {APP_ROOT as ɵAPP_ROOT} from './di/scope';
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';
@@ -23,4 +23,3 @@ export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provid
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
export {ReflectiveKey} from './di/reflective_key';
export {InjectionToken} from './di/injection_token';
export {APP_ROOT_SCOPE} from './di/scope';
@@ -55,9 +55,9 @@ export interface InjectableDecorator {
* @stable
*/
(): any;
(options?: {scope: Type<any>}&InjectableProvider): any;
(options?: {providedIn: Type<any>| 'root' | null}&InjectableProvider): any;
new (): Injectable;
new (options?: {scope: Type<any>}&InjectableProvider): Injectable;
new (options?: {providedIn: Type<any>| 'root' | null}&InjectableProvider): Injectable;
}

/**
@@ -66,7 +66,7 @@ export interface InjectableDecorator {
* @experimental
*/
export interface Injectable {
scope?: Type<any>;
providedIn?: Type<any>|'root'|null;
factory: () => any;
}

@@ -109,12 +109,19 @@ export function convertInjectableProviderToFactory(
}

/**
* Define injectable
* Construct an `InjectableDef` which defines how a token will be constructed by the DI system, and
* in which injectors (if any) it will be available.
*
* @experimental
*/
export function defineInjectable(opts: Injectable): Injectable {
return opts;
export function defineInjectable<T>(opts: {
providedIn?: Type<any>| 'root' | null,
factory: () => T,
}): InjectableDef<T> {
return {
providedIn: opts.providedIn || null,
factory: opts.factory,
};
}

/**
@@ -125,19 +132,24 @@ export function defineInjectable(opts: Injectable): Injectable {
*/
export const Injectable: InjectableDecorator = makeDecorator(
'Injectable', undefined, undefined, undefined,
(injectableType: Type<any>, options: {scope: Type<any>} & InjectableProvider) => {
if (options && options.scope) {
(injectableType: Type<any>,
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider) => {
if (options && options.providedIn) {
(injectableType as InjectableType<any>).ngInjectableDef = defineInjectable({
scope: options.scope,
providedIn: options.providedIn,
factory: convertInjectableProviderToFactory(injectableType, options)
});
}
});

export interface InjectableDef<T> {
providedIn: Type<any>|'root'|null;
factory: () => T;
}

/**
* Type representing injectable service.
*
* @experimental
*/
export interface InjectableType<T> extends Type<T> { ngInjectableDef?: Injectable; }
export interface InjectableType<T> extends Type<T> { ngInjectableDef: InjectableDef<T>; }

0 comments on commit db56836

Please sign in to comment.
You can’t perform that action at this time.