feat(core): Adds DI support for providedIn: 'platform'|'any' (#32154)

Extend the vocabulary of the `providedIn` to also include  `'platform'` and `'any'`` scope.
```
@Injectable({
  providedId: 'platform', // tree shakable injector for platform injector
})
class MyService {...}
```

PR Close #32154
This commit is contained in:
Misko Hevery
2019-08-22 19:19:41 -07:00
parent 8a47b48912
commit 77c382ccba
16 changed files with 138 additions and 64 deletions

View File

@ -8,6 +8,7 @@
import {CommonModule} from '@angular/common';
import {Attribute, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, Host, HostBinding, INJECTOR, Inject, Injectable, InjectionToken, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, forwardRef, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core';
import {ɵINJECTOR_SCOPE} from '@angular/core/src/core';
import {ViewRef} from '@angular/core/src/render3/view_ref';
import {TestBed} from '@angular/core/testing';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
@ -866,6 +867,37 @@ describe('di', () => {
});
});
describe('Tree shakable injectors', () => {
it('should support tree shakable injectors scopes', () => {
@Injectable({providedIn: 'any'})
class AnyService {
constructor(public injector: Injector) {}
}
@Injectable({providedIn: 'root'})
class RootService {
constructor(public injector: Injector) {}
}
@Injectable({providedIn: 'platform'})
class PlatformService {
constructor(public injector: Injector) {}
}
const testBedInjector: Injector = TestBed.get(Injector);
const childInjector = Injector.create([], testBedInjector);
const anyService = childInjector.get(AnyService);
expect(anyService.injector).toBe(childInjector);
const rootService = childInjector.get(RootService);
expect(rootService.injector.get(ɵINJECTOR_SCOPE)).toBe('root');
const platformService = childInjector.get(PlatformService);
expect(platformService.injector.get(ɵINJECTOR_SCOPE)).toBe('platform');
});
});
describe('service injection', () => {
it('should create instance even when no injector present', () => {

View File

@ -1,6 +1,6 @@
[
{
"name": "APP_ROOT"
"name": "INJECTOR_SCOPE"
},
{
"name": "CIRCULAR"

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {NgModuleRef} from '@angular/core';
import {NgModuleRef, ɵINJECTOR_SCOPE as INJECTOR_SCOPE} from '@angular/core';
import {InjectFlags, inject} from '@angular/core/src/di';
import {Injector} from '@angular/core/src/di/injector';
import {INJECTOR} from '@angular/core/src/di/injector_compatibility';
@ -16,8 +16,6 @@ import {moduleDef} from '@angular/core/src/view/ng_module';
import {createNgModuleRef} from '@angular/core/src/view/refs';
import {tokenKey} from '@angular/core/src/view/util';
import {APP_ROOT} from '../../src/di/scope';
class Foo {}
class MyModule {}
@ -133,7 +131,7 @@ function makeFactoryProviders(
function makeModule(modules: any[], providers: NgModuleProviderDef[]): NgModuleDefinition {
const providersByKey: {[key: string]: NgModuleProviderDef} = {};
providers.forEach(provider => providersByKey[tokenKey(provider.token)] = provider);
return {factory: null, providers, providersByKey, modules, isRoot: true};
return {factory: null, providers, providersByKey, modules, scope: 'root'};
}
describe('NgModuleRef_ injector', () => {
@ -273,19 +271,24 @@ describe('NgModuleRef_ injector', () => {
};
}
it('sets isRoot to `true` when APP_ROOT is `true`', () => {
const def = moduleDef([createProvider(APP_ROOT, true)]);
expect(def.isRoot).toBe(true);
it('sets scope to `root` when INJECTOR_SCOPE is `root`', () => {
const def = moduleDef([createProvider(INJECTOR_SCOPE, 'root')]);
expect(def.scope).toBe('root');
});
it('sets isRoot to `false` when APP_ROOT is absent', () => {
it('sets scope to `platform` when INJECTOR_SCOPE is `platform`', () => {
const def = moduleDef([createProvider(INJECTOR_SCOPE, 'platform')]);
expect(def.scope).toBe('platform');
});
it('sets scope to `null` when INJECTOR_SCOPE is absent', () => {
const def = moduleDef([]);
expect(def.isRoot).toBe(false);
expect(def.scope).toBe(null);
});
it('sets isRoot to `false` when APP_ROOT is `false`', () => {
const def = moduleDef([createProvider(APP_ROOT, false)]);
expect(def.isRoot).toBe(false);
it('sets isRoot to `null` when INJECTOR_SCOPE is `null`', () => {
const def = moduleDef([createProvider(INJECTOR_SCOPE, null)]);
expect(def.scope).toBe(null);
});
});
});