diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 6d9719e916b..946053b5af7 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -517,21 +517,34 @@ export type PipeTypeList = export const unusedValueExportToPlacateAjd = 1; /** - * NgModule scope info as provided by NgModule decorator. + * NgModule scope info as provided by AoT compiler + * + * In full compilation Ivy resolved all the "module with providers" and forward refs the whole array + * if at least one element is forward refed. So we end up with type `Type[]|(() => + * Type[])`. + * + * In local mode the compiler passes the raw info as they are to the runtime functions as it is not + * possible to resolve them any further due to limited info at compile time. So we end up with type + * `RawScopeInfoFromDecorator[]`. */ export interface NgModuleScopeInfoFromDecorator { /** List of components, directives, and pipes declared by this module. */ - declarations?: Type[]|(() => Type[]); + declarations?: Type[]|(() => Type[])|RawScopeInfoFromDecorator[]; - /** List of modules or `ModuleWithProviders` imported by this module. */ - imports?: Type[]|(() => Type[]); + /** List of modules or `ModuleWithProviders` or standalone components imported by this module. */ + imports?: Type[]|(() => Type[])|RawScopeInfoFromDecorator[]; /** * List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this * module. */ - exports?: Type[]|(() => Type[]); + exports?: Type[]|(() => Type[])|RawScopeInfoFromDecorator[]; } +/** + * The array element type passed to: + * - NgModule's annotation imports/exports/declarations fields + * - standalone component annotation imports field + */ export type RawScopeInfoFromDecorator = Type|ModuleWithProviders|(() => Type)|(() => ModuleWithProviders); diff --git a/packages/core/src/render3/local_compilation.ts b/packages/core/src/render3/local_compilation.ts index 331bd5cd8e8..a6350f4d564 100644 --- a/packages/core/src/render3/local_compilation.ts +++ b/packages/core/src/render3/local_compilation.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {Type} from '../interface/type'; - -import {DependencyTypeList, RawScopeInfoFromDecorator} from './interfaces/definition'; +import {depsTracker} from './deps_tracker/deps_tracker'; +import {ComponentType, DependencyTypeList, RawScopeInfoFromDecorator} from './interfaces/definition'; export function ɵɵgetComponentDepsFactory( - type: Type, rawImports?: RawScopeInfoFromDecorator): () => DependencyTypeList { - // TODO(pmvald): Implement this runtime using deps tracker. - return () => []; + type: ComponentType, rawImports?: RawScopeInfoFromDecorator[]): () => DependencyTypeList { + return () => { + return depsTracker.getComponentDependencies(type, rawImports).dependencies; + }; } diff --git a/packages/core/src/render3/scope.ts b/packages/core/src/render3/scope.ts index 3e38b73dd87..4ed7e299002 100644 --- a/packages/core/src/render3/scope.ts +++ b/packages/core/src/render3/scope.ts @@ -6,12 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ +import {isForwardRef, resolveForwardRef} from '../di/forward_ref'; import {Type} from '../interface/type'; import {noSideEffects} from '../util/closure'; import {EMPTY_ARRAY} from '../util/empty'; import {extractDefListOrFactory, getNgModuleDef} from './definition'; -import {ComponentDef, ComponentType, NgModuleScopeInfoFromDecorator} from './interfaces/definition'; +import {depsTracker} from './deps_tracker/deps_tracker'; +import {ComponentDef, ComponentType, NgModuleScopeInfoFromDecorator, RawScopeInfoFromDecorator} from './interfaces/definition'; +import {isModuleWithProviders} from './jit/util'; /** * Generated next to NgModules to monkey-patch directive and pipe references onto a component's @@ -43,8 +46,27 @@ export function ɵɵsetComponentScope( export function ɵɵsetNgModuleScope(type: any, scope: NgModuleScopeInfoFromDecorator): unknown { return noSideEffects(() => { const ngModuleDef = getNgModuleDef(type, true); - ngModuleDef.declarations = scope.declarations || EMPTY_ARRAY; - ngModuleDef.imports = scope.imports || EMPTY_ARRAY; - ngModuleDef.exports = scope.exports || EMPTY_ARRAY; + ngModuleDef.declarations = convertToTypeArray(scope.declarations || EMPTY_ARRAY); + ngModuleDef.imports = convertToTypeArray(scope.imports || EMPTY_ARRAY); + ngModuleDef.exports = convertToTypeArray(scope.exports || EMPTY_ARRAY); + + depsTracker.registerNgModule(type, scope); }); } + +function convertToTypeArray(values: Type[]|(() => Type[])| + RawScopeInfoFromDecorator[]): Type[]|(() => Type[]) { + if (typeof values === 'function') { + return values; + } + + if (values.some(isForwardRef)) { + return () => values.map(resolveForwardRef).map(maybeUnwrapModuleWithProviders); + } else { + return values.map(maybeUnwrapModuleWithProviders); + } +} + +function maybeUnwrapModuleWithProviders(value: any): Type { + return isModuleWithProviders(value) ? value.ngModule : value as Type; +} diff --git a/packages/core/test/acceptance/local_compilation_spec.ts b/packages/core/test/acceptance/local_compilation_spec.ts new file mode 100644 index 00000000000..537773a7ae3 --- /dev/null +++ b/packages/core/test/acceptance/local_compilation_spec.ts @@ -0,0 +1,118 @@ +/** + * @license + * Copyright Google LLC 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, forwardRef, ɵɵdefineNgModule, ɵɵgetComponentDepsFactory, ɵɵsetNgModuleScope} from '@angular/core'; +import {ComponentType} from '@angular/core/src/render3'; + +describe('component dependencies in local compilation', () => { + it('should compute correct set of dependencies when importing ng-modules directly', () => { + @Component({selector: 'sub'}) + class SubComponent { + } + + class SubModule { + static ɵmod = ɵɵdefineNgModule({type: SubModule}); + } + ɵɵsetNgModuleScope(SubModule, {exports: [SubComponent]}); + + @Component({}) + class MainComponent { + } + + class MainModule { + static ɵmod = ɵɵdefineNgModule({type: MainModule}); + } + ɵɵsetNgModuleScope(MainModule, {imports: [SubModule], declarations: [MainComponent]}); + + const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType)(); + + expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent])); + }); + + it('should compute correct set of dependencies when importing ng-modules with providers', () => { + @Component({selector: 'sub'}) + class SubComponent { + } + + class SubModule { + static ɵmod = ɵɵdefineNgModule({type: SubModule}); + } + ɵɵsetNgModuleScope(SubModule, {exports: [SubComponent]}); + + @Component({}) + class MainComponent { + } + + class MainModule { + static ɵmod = ɵɵdefineNgModule({type: MainModule}); + } + ɵɵsetNgModuleScope( + MainModule, + {imports: [{ngModule: SubModule, providers: []}], declarations: [MainComponent]}); + + const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType)(); + + expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent])); + }); + + it('should compute correct set of dependencies when importing ng-modules using forward ref', + () => { + @Component({selector: 'sub'}) + class SubComponent { + } + + class SubModule { + static ɵmod = ɵɵdefineNgModule({type: SubModule}); + } + ɵɵsetNgModuleScope(SubModule, {exports: [forwardRef(() => SubComponent)]}); + + @Component({}) + class MainComponent { + } + + class MainModule { + static ɵmod = ɵɵdefineNgModule({type: MainModule}); + } + ɵɵsetNgModuleScope(MainModule, { + imports: [forwardRef(() => SubModule)], + declarations: [forwardRef(() => MainComponent)] + }); + + const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType)(); + + expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent])); + }); + + it('should compute correct set of dependencies when importing ng-modules with providers using forward ref', + () => { + @Component({selector: 'sub'}) + class SubComponent { + } + + class SubModule { + static ɵmod = ɵɵdefineNgModule({type: SubModule}); + } + ɵɵsetNgModuleScope(SubModule, {exports: [SubComponent]}); + + @Component({}) + class MainComponent { + } + + class MainModule { + static ɵmod = ɵɵdefineNgModule({type: MainModule}); + } + ɵɵsetNgModuleScope(MainModule, { + imports: [forwardRef(() => ({ngModule: SubModule, providers: []}))], + declarations: [MainComponent] + }); + + const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType)(); + + expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent])); + }); +});