diff --git a/packages/core/src/di/r3_injector.ts b/packages/core/src/di/r3_injector.ts index 5d7f151393..f3ed6489d5 100644 --- a/packages/core/src/di/r3_injector.ts +++ b/packages/core/src/di/r3_injector.ts @@ -11,6 +11,7 @@ import '../util/ng_dev_mode'; import {OnDestroy} from '../interface/lifecycle_hooks'; import {Type} from '../interface/type'; import {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors'; +import {deepForEach} from '../util/array_utils'; import {stringify} from '../util/stringify'; import {resolveForwardRef} from './forward_ref'; @@ -497,10 +498,6 @@ function makeRecord( }; } -function deepForEach(input: (T | any[])[], fn: (value: T) => void): void { - input.forEach(value => Array.isArray(value) ? deepForEach(value, fn) : fn(value)); -} - function isValueProvider(value: SingleProvider): value is ValueProvider { return value !== null && typeof value == 'object' && USE_VALUE in value; } diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index b6f2e47588..4c7aa17e60 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -13,7 +13,7 @@ import {reflectDependencies} from '../../di/jit/util'; import {Type} from '../../interface/type'; import {Component} from '../../metadata'; import {ModuleWithProviders, NgModule, NgModuleDef, NgModuleTransitiveScopes} from '../../metadata/ng_module'; -import {flatten} from '../../util/array_utils'; +import {deepForEach, flatten} from '../../util/array_utils'; import {assertDefined} from '../../util/assert'; import {getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef} from '../definition'; import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from '../fields'; @@ -200,9 +200,10 @@ function verifySemanticsOfNgModuleDef( verifySemanticsOfNgModuleImport(mod, moduleType); verifySemanticsOfNgModuleDef(mod, false, moduleType); }); - ngModule.bootstrap && ngModule.bootstrap.forEach(verifyCorrectBootstrapType); - ngModule.bootstrap && ngModule.bootstrap.forEach(verifyComponentIsPartOfNgModule); - ngModule.entryComponents && ngModule.entryComponents.forEach(verifyComponentIsPartOfNgModule); + ngModule.bootstrap && deepForEach(ngModule.bootstrap, verifyCorrectBootstrapType); + ngModule.bootstrap && deepForEach(ngModule.bootstrap, verifyComponentIsPartOfNgModule); + ngModule.entryComponents && + deepForEach(ngModule.entryComponents, verifyComponentIsPartOfNgModule); } // Throw Error if any errors were detected. @@ -273,7 +274,7 @@ function verifySemanticsOfNgModuleDef( // We know we are component const component = getAnnotation(type, 'Component'); if (component && component.entryComponents) { - component.entryComponents.forEach(verifyComponentIsPartOfNgModule); + deepForEach(component.entryComponents, verifyComponentIsPartOfNgModule); } } } diff --git a/packages/core/src/util/array_utils.ts b/packages/core/src/util/array_utils.ts index ab846bbf7b..1aa1436a8c 100644 --- a/packages/core/src/util/array_utils.ts +++ b/packages/core/src/util/array_utils.ts @@ -38,4 +38,8 @@ export function flatten(list: any[], dst?: any[]): any[] { } } return dst; -} \ No newline at end of file +} + +export function deepForEach(input: (T | any[])[], fn: (value: T) => void): void { + input.forEach(value => Array.isArray(value) ? deepForEach(value, fn) : fn(value)); +} diff --git a/packages/core/test/acceptance/ng_module_spec.ts b/packages/core/test/acceptance/ng_module_spec.ts new file mode 100644 index 0000000000..8691e9ecab --- /dev/null +++ b/packages/core/test/acceptance/ng_module_spec.ts @@ -0,0 +1,77 @@ +/** + * @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} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; + +describe('NgModule', () => { + @Component({template: 'hello'}) + class TestCmp { + } + + @Component({template: 'hello'}) + class TestCmp2 { + } + + describe('entryComponents', () => { + it('should throw when specified entry component is not added to a module', () => { + @NgModule({entryComponents: [TestCmp, [TestCmp2]]}) + class MyModule { + } + + TestBed.configureTestingModule({imports: [MyModule]}); + + expect(() => { + TestBed.createComponent(TestCmp); + TestBed.createComponent(TestCmp2); + }).toThrowError(/not part of any NgModule/); + }); + + it('should not throw when specified entry component is added to a module', () => { + @NgModule({declarations: [TestCmp, TestCmp2], entryComponents: [TestCmp, [TestCmp2]]}) + class MyModule { + } + + TestBed.configureTestingModule({imports: [MyModule]}); + + expect(() => { + TestBed.createComponent(TestCmp); + TestBed.createComponent(TestCmp2); + }).not.toThrow(); + }); + }); + + describe('bootstrap', () => { + it('should throw when specified bootstrap component is not added to a module', () => { + @NgModule({bootstrap: [TestCmp, [TestCmp2]]}) + class MyModule { + } + + TestBed.configureTestingModule({imports: [MyModule]}); + + expect(() => { + TestBed.createComponent(TestCmp); + TestBed.createComponent(TestCmp2); + }).toThrowError(/not part of any NgModule/); + }); + + it('should not throw when specified bootstrap component is added to a module', () => { + @NgModule({declarations: [TestCmp, TestCmp2], bootstrap: [TestCmp, [TestCmp2]]}) + class MyModule { + } + + TestBed.configureTestingModule({imports: [MyModule]}); + + expect(() => { + TestBed.createComponent(TestCmp); + TestBed.createComponent(TestCmp2); + }).not.toThrow(); + }); + }); + +});