diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/BUILD.bazel b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/BUILD.bazel
index daf7755a35..ec996eb8c1 100644
--- a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/BUILD.bazel
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/BUILD.bazel
@@ -16,6 +16,7 @@ ng_module(
"//packages/core",
"//packages/platform-browser",
"//packages/platform-server",
+ "//packages/router",
"@rxjs",
],
)
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root.ts
new file mode 100644
index 0000000000..d04e603618
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root.ts
@@ -0,0 +1,43 @@
+/**
+ * @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 {APP_ROOT_SCOPE, 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';
+
+import {LazyModuleNgFactory} from './root_lazy.ngfactory';
+
+@Component({
+ selector: 'root-app',
+ template: '',
+})
+export class AppComponent {
+}
+
+export function children(): any {
+ console.error('children', LazyModuleNgFactory);
+ return LazyModuleNgFactory;
+}
+
+
+@NgModule({
+ imports: [
+ BrowserModule.withServerTransition({appId: 'id-app'}),
+ ServerModule,
+ RouterModule.forRoot(
+ [
+ {path: '', pathMatch: 'prefix', loadChildren: children},
+ ],
+ {initialNavigation: 'enabled'}),
+ ],
+ declarations: [AppComponent],
+ bootstrap: [AppComponent],
+})
+export class RootAppModule {
+}
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root_lazy.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root_lazy.ts
new file mode 100644
index 0000000000..1a56081266
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root_lazy.ts
@@ -0,0 +1,35 @@
+/**
+ * @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, NgModule, Optional, Self} from '@angular/core';
+import {RouterModule} from '@angular/router';
+import {Service} from './root_service';
+
+@Component({
+ selector: 'lazy-route',
+ template: '{{service}}:{{serviceInLazyInjector}}',
+})
+export class RouteComponent {
+ service: boolean;
+ serviceInLazyInjector: boolean;
+ constructor(@Optional() service: Service, @Optional() @Self() lazyService: Service) {
+ this.service = !!service;
+ this.serviceInLazyInjector = !!lazyService;
+ }
+}
+
+@NgModule({
+ declarations: [RouteComponent],
+ imports: [
+ RouterModule.forChild([
+ {path: '', pathMatch: 'prefix', component: RouteComponent},
+ ]),
+ ],
+})
+export class LazyModule {
+}
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root_service.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root_service.ts
new file mode 100644
index 0000000000..77a749b42f
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/root_service.ts
@@ -0,0 +1,15 @@
+/**
+ * @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 {APP_ROOT_SCOPE, Injectable} from '@angular/core';
+
+@Injectable({
+ scope: APP_ROOT_SCOPE,
+})
+export class Service {
+}
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 3cf74df8f9..907941877c 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
@@ -10,6 +10,7 @@ import {enableProdMode} from '@angular/core';
import {renderModuleFactory} from '@angular/platform-server';
import {BasicAppModuleNgFactory} from 'app_built/src/basic.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';
import {TokenAppModuleNgFactory} from 'app_built/src/token.ngfactory';
@@ -55,4 +56,14 @@ describe('ngInjectableDef Bazel Integration', () => {
done();
});
});
+
+ it('APP_ROOT_SCOPE works', done => {
+ renderModuleFactory(RootAppModuleNgFactory, {
+ document: '',
+ url: '/',
+ }).then(html => {
+ expect(html).toMatch(/>true:false<\//);
+ done();
+ });
+ });
});
diff --git a/packages/core/src/di.ts b/packages/core/src/di.ts
index bb49c55376..79e4993603 100644
--- a/packages/core/src/di.ts
+++ b/packages/core/src/di.ts
@@ -23,3 +23,4 @@ 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';
diff --git a/packages/core/src/di/scope.ts b/packages/core/src/di/scope.ts
new file mode 100644
index 0000000000..b9f546ea50
--- /dev/null
+++ b/packages/core/src/di/scope.ts
@@ -0,0 +1,27 @@
+/**
+ * @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 {Type} from '../type';
+import {InjectionToken} from './injection_token';
+
+
+// APP_ROOT_SCOPE is cast as a Type to allow for its usage as the scope parameter of @Injectable().
+
+/**
+ * A scope which targets the root injector.
+ *
+ * When specified as the `scope` parameter to `@Injectable` or `InjectionToken`, this special
+ * scope indicates the provider for the service or token being configured belongs in the root
+ * injector. This is loosely equivalent to the convention of having a `forRoot()` static
+ * function within a module that configures the provider, and expecting users to only import that
+ * module via its `forRoot()` function in the root injector.
+ *
+ * @experimental
+ */
+export const APP_ROOT_SCOPE: Type = new InjectionToken(
+ 'The presence of this token marks an injector as being the root injector.') as any;
diff --git a/packages/core/src/view/ng_module.ts b/packages/core/src/view/ng_module.ts
index 252edef56b..f145e6c4c4 100644
--- a/packages/core/src/view/ng_module.ts
+++ b/packages/core/src/view/ng_module.ts
@@ -8,6 +8,7 @@
import {resolveForwardRef} from '../di/forward_ref';
import {InjectFlags, Injector, setCurrentInjector} from '../di/injector';
+import {APP_ROOT_SCOPE} from '../di/scope';
import {NgModuleRef} from '../linker/ng_module_factory';
import {stringify} from '../util';
@@ -43,8 +44,12 @@ export function moduleProvideDef(
export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition {
const providersByKey: {[key: string]: NgModuleProviderDef} = {};
const modules = [];
+ let isRoot: boolean = false;
for (let i = 0; i < providers.length; i++) {
const provider = providers[i];
+ if (provider.token === APP_ROOT_SCOPE) {
+ isRoot = true;
+ }
if (provider.flags & NodeFlags.TypeNgModule) {
modules.push(provider.token);
}
@@ -57,6 +62,7 @@ export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition
providersByKey,
providers,
modules,
+ isRoot,
};
}
@@ -119,8 +125,13 @@ export function resolveNgModuleDep(
return data._parent.get(depDef.token, notFoundValue);
}
+function moduleTransitivelyPresent(ngModule: NgModuleData, scope: any): boolean {
+ return ngModule._def.modules.indexOf(scope) > -1;
+}
+
function targetsModule(ngModule: NgModuleData, def: InjectableDef): boolean {
- return def.scope != null && ngModule._def.modules.indexOf(def.scope) > -1;
+ return def.scope != null && (moduleTransitivelyPresent(ngModule, def.scope) ||
+ def.scope === APP_ROOT_SCOPE && ngModule._def.isRoot);
}
function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModuleProviderDef): any {
diff --git a/packages/core/src/view/types.ts b/packages/core/src/view/types.ts
index 44c190eb10..8fbbe55078 100644
--- a/packages/core/src/view/types.ts
+++ b/packages/core/src/view/types.ts
@@ -43,6 +43,7 @@ export interface NgModuleDefinition extends Definition {}
diff --git a/packages/core/test/view/ng_module_spec.ts b/packages/core/test/view/ng_module_spec.ts
index 5592356384..8e5c8c2ea2 100644
--- a/packages/core/test/view/ng_module_spec.ts
+++ b/packages/core/test/view/ng_module_spec.ts
@@ -97,7 +97,7 @@ function makeProviders(classes: any[], modules: any[]): NgModuleDefinition {
}));
const providersByKey: {[key: string]: NgModuleProviderDef} = {};
providers.forEach(provider => providersByKey[tokenKey(provider.token)] = provider);
- return {factory: null, providers, providersByKey, modules};
+ return {factory: null, providers, providersByKey, modules, isRoot: true};
}
describe('NgModuleRef_ injector', () => {
diff --git a/packages/platform-browser/src/browser.ts b/packages/platform-browser/src/browser.ts
index 7043cdb8b5..7aaefaf46e 100644
--- a/packages/platform-browser/src/browser.ts
+++ b/packages/platform-browser/src/browser.ts
@@ -7,7 +7,7 @@
*/
import {CommonModule, PlatformLocation, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
-import {APP_ID, ApplicationModule, ErrorHandler, ModuleWithProviders, NgModule, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, RootRenderer, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore} from '@angular/core';
+import {APP_ID, APP_ROOT_SCOPE, ApplicationModule, ErrorHandler, ModuleWithProviders, NgModule, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, RootRenderer, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore} from '@angular/core';
import {BrowserDomAdapter} from './browser/browser_adapter';
import {BrowserPlatformLocation} from './browser/location/browser_platform_location';
@@ -71,6 +71,7 @@ export function _document(): any {
@NgModule({
providers: [
BROWSER_SANITIZATION_PROVIDERS,
+ {provide: APP_ROOT_SCOPE, useValue: true},
{provide: ErrorHandler, useFactory: errorHandler, deps: []},
{provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts
index 3774caf383..e5bc87993e 100644
--- a/tools/public_api_guard/core/core.d.ts
+++ b/tools/public_api_guard/core/core.d.ts
@@ -112,6 +112,9 @@ export declare const APP_ID: InjectionToken;
/** @experimental */
export declare const APP_INITIALIZER: InjectionToken<(() => void)[]>;
+/** @experimental */
+export declare const APP_ROOT_SCOPE: Type;
+
/** @experimental */
export declare class ApplicationInitStatus {
readonly done: boolean;