feat(core): introduce ModuleWithProviders.

Modules can now provider helper functions that allow
to import a module together with an array of providers.

Part of #10043
This commit is contained in:
Tobias Bosch
2016-07-25 01:39:50 -07:00
parent d6b65db9a7
commit f02da4e91a
6 changed files with 90 additions and 21 deletions

View File

@ -14,7 +14,7 @@ import {AnimateCmp} from './animate';
import {BasicComp} from './basic'; import {BasicComp} from './basic';
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components'; import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
import {CompWithProviders, CompWithReferences} from './features'; import {CompWithProviders, CompWithReferences} from './features';
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomeLibModule, SomePipeInRootModule, SomeService} from './module_fixtures'; import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, someLibModuleWithProviders, SomePipeInRootModule, SomeService} from './module_fixtures';
import {ProjectingComp} from './projection'; import {ProjectingComp} from './projection';
import {CompWithChildQuery, CompWithDirectiveChild} from './queries'; import {CompWithChildQuery, CompWithDirectiveChild} from './queries';
@ -25,7 +25,7 @@ import {CompWithChildQuery, CompWithDirectiveChild} from './queries';
CompWithDirectiveChild, CompUsingRootModuleDirectiveAndPipe, CompWithProviders, CompWithDirectiveChild, CompUsingRootModuleDirectiveAndPipe, CompWithProviders,
CompWithReferences CompWithReferences
], ],
imports: [BrowserModule, FormsModule, SomeLibModule], imports: [BrowserModule, FormsModule, someLibModuleWithProviders()],
providers: [SomeService], providers: [SomeService],
entryComponents: [ entryComponents: [
AnimateCmp, BasicComp, CompWithEntryComponents, CompWithAnalyzeEntryComponentsProvider, AnimateCmp, BasicComp, CompWithEntryComponents, CompWithAnalyzeEntryComponentsProvider,

View File

@ -7,7 +7,7 @@
*/ */
import {LowerCasePipe, NgIf} from '@angular/common'; import {LowerCasePipe, NgIf} from '@angular/common';
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Directive, Inject, Injectable, Input, NgModule, OpaqueToken, Pipe} from '@angular/core'; import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Directive, Inject, Injectable, Input, ModuleWithProviders, NgModule, OpaqueToken, Pipe} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
@Injectable() @Injectable()
@ -60,11 +60,21 @@ export function provideValueWithEntryComponents(value: any) {
@NgModule({ @NgModule({
declarations: [SomeDirectiveInLibModule, SomePipeInLibModule, CompUsingLibModuleDirectiveAndPipe], declarations: [SomeDirectiveInLibModule, SomePipeInLibModule, CompUsingLibModuleDirectiveAndPipe],
exports: [CompUsingLibModuleDirectiveAndPipe],
entryComponents: [CompUsingLibModuleDirectiveAndPipe], entryComponents: [CompUsingLibModuleDirectiveAndPipe],
providers: [
ServiceUsingLibModule,
provideValueWithEntryComponents([{a: 'b', component: CompUsingLibModuleDirectiveAndPipe}])
],
}) })
export class SomeLibModule { export class SomeLibModule {
} }
// TODO(tbosch): Make this a static method in `SomeLibModule` once
// our static reflector supports it.
// See https://github.com/angular/angular/issues/10266.
export function someLibModuleWithProviders(): ModuleWithProviders {
return {
ngModule: SomeLibModule,
providers: [
ServiceUsingLibModule,
provideValueWithEntryComponents([{a: 'b', component: CompUsingLibModuleDirectiveAndPipe}])
]
};
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, AttributeMetadata, ChangeDetectionStrategy, ComponentMetadata, HostMetadata, Inject, InjectMetadata, Injectable, NgModule, NgModuleMetadata, Optional, OptionalMetadata, Provider, QueryMetadata, SelfMetadata, SkipSelfMetadata, ViewMetadata, ViewQueryMetadata, resolveForwardRef} from '@angular/core'; import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, AttributeMetadata, ChangeDetectionStrategy, ComponentMetadata, HostMetadata, Inject, InjectMetadata, Injectable, ModuleWithProviders, NgModule, NgModuleMetadata, Optional, OptionalMetadata, Provider, QueryMetadata, SelfMetadata, SkipSelfMetadata, ViewMetadata, ViewQueryMetadata, resolveForwardRef} from '@angular/core';
import {Console, LIFECYCLE_HOOKS_VALUES, ReflectorReader, createProvider, isProviderLiteral, reflector} from '../core_private'; import {Console, LIFECYCLE_HOOKS_VALUES, ReflectorReader, createProvider, isProviderLiteral, reflector} from '../core_private';
import {MapWrapper, StringMapWrapper} from '../src/facade/collection'; import {MapWrapper, StringMapWrapper} from '../src/facade/collection';
@ -206,16 +206,24 @@ export class CompileMetadataResolver {
const exportedPipes: cpl.CompilePipeMetadata[] = []; const exportedPipes: cpl.CompilePipeMetadata[] = [];
const importedModules: cpl.CompileNgModuleMetadata[] = []; const importedModules: cpl.CompileNgModuleMetadata[] = [];
const exportedModules: cpl.CompileNgModuleMetadata[] = []; const exportedModules: cpl.CompileNgModuleMetadata[] = [];
const providers: any[] = [];
const entryComponents: cpl.CompileTypeMetadata[] = [];
if (meta.imports) { if (meta.imports) {
flattenArray(meta.imports).forEach((importedType) => { flattenArray(meta.imports).forEach((importedType) => {
if (!isValidType(importedType)) { let importedModuleType: Type;
throw new BaseException( if (isValidType(importedType)) {
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`); importedModuleType = importedType;
} else if (importedType && importedType.ngModule) {
const moduleWithProviders: ModuleWithProviders = importedType;
importedModuleType = moduleWithProviders.ngModule;
if (moduleWithProviders.providers) {
providers.push(
...this.getProvidersMetadata(moduleWithProviders.providers, entryComponents));
} }
let importedModuleMeta: cpl.CompileNgModuleMetadata; }
if (importedModuleMeta = this.getNgModuleMetadata(importedType, false)) { if (importedModuleType) {
importedModules.push(importedModuleMeta); importedModules.push(this.getNgModuleMetadata(importedModuleType, false));
} else { } else {
throw new BaseException( throw new BaseException(
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`); `Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
@ -274,8 +282,6 @@ export class CompileMetadataResolver {
}); });
} }
const providers: any[] = [];
const entryComponents: cpl.CompileTypeMetadata[] = [];
if (meta.providers) { if (meta.providers) {
providers.push(...this.getProvidersMetadata(meta.providers, entryComponents)); providers.push(...this.getProvidersMetadata(meta.providers, entryComponents));
} }

View File

@ -16,13 +16,13 @@ import {ChangeDetectionStrategy} from '../src/change_detection/change_detection'
import {AnimationEntryMetadata} from './animation/metadata'; import {AnimationEntryMetadata} from './animation/metadata';
import {AttributeMetadata, ContentChildMetadata, ContentChildrenMetadata, QueryMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata} from './metadata/di'; import {AttributeMetadata, ContentChildMetadata, ContentChildrenMetadata, QueryMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata} from './metadata/di';
import {ComponentMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, InputMetadata, OutputMetadata, PipeMetadata} from './metadata/directives'; import {ComponentMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, InputMetadata, OutputMetadata, PipeMetadata} from './metadata/directives';
import {NgModuleMetadata} from './metadata/ng_module'; import {ModuleWithProviders, NgModuleMetadata} from './metadata/ng_module';
import {ViewEncapsulation, ViewMetadata} from './metadata/view'; import {ViewEncapsulation, ViewMetadata} from './metadata/view';
export {ANALYZE_FOR_ENTRY_COMPONENTS, AttributeMetadata, ContentChildMetadata, ContentChildrenMetadata, QueryMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata} from './metadata/di'; export {ANALYZE_FOR_ENTRY_COMPONENTS, AttributeMetadata, ContentChildMetadata, ContentChildrenMetadata, QueryMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata} from './metadata/di';
export {ComponentMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, InputMetadata, OutputMetadata, PipeMetadata} from './metadata/directives'; export {ComponentMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, InputMetadata, OutputMetadata, PipeMetadata} from './metadata/directives';
export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './metadata/lifecycle_hooks'; export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './metadata/lifecycle_hooks';
export {NgModuleMetadata} from './metadata/ng_module'; export {ModuleWithProviders, NgModuleMetadata} from './metadata/ng_module';
export {ViewEncapsulation, ViewMetadata} from './metadata/view'; export {ViewEncapsulation, ViewMetadata} from './metadata/view';
import {makeDecorator, makeParamDecorator, makePropDecorator, TypeDecorator,} from './util/decorators'; import {makeDecorator, makeParamDecorator, makePropDecorator, TypeDecorator,} from './util/decorators';
@ -498,7 +498,7 @@ export interface NgModuleMetadataFactory {
(obj?: { (obj?: {
providers?: any[], providers?: any[],
declarations?: Array<Type|any[]>, declarations?: Array<Type|any[]>,
imports?: Array<Type|any[]>, imports?: Array<Type|ModuleWithProviders|any[]>,
exports?: Array<Type|any[]>, exports?: Array<Type|any[]>,
entryComponents?: Array<Type|any[]> entryComponents?: Array<Type|any[]>
}): NgModuleDecorator; }): NgModuleDecorator;

View File

@ -9,6 +9,16 @@
import {InjectableMetadata} from '../di/metadata'; import {InjectableMetadata} from '../di/metadata';
import {Type} from '../facade/lang'; import {Type} from '../facade/lang';
/**
* A wrapper around a module that also includes the providers.
*
* @experimental
*/
export interface ModuleWithProviders {
ngModule: Type;
providers?: any[];
}
/** /**
* Declares an Angular Module. * Declares an Angular Module.
* @experimental * @experimental
@ -65,6 +75,7 @@ export class NgModuleMetadata extends InjectableMetadata {
/** /**
* Specifies a list of modules whose exported directives/pipes * Specifies a list of modules whose exported directives/pipes
* should be available to templates in this module. * should be available to templates in this module.
* This can also contain {@link ModuleWithProviders}.
* *
* ### Example * ### Example
* *
@ -76,7 +87,7 @@ export class NgModuleMetadata extends InjectableMetadata {
* } * }
* ``` * ```
*/ */
imports: Array<Type|any[]>; imports: Array<Type|ModuleWithProviders|any[]>;
/** /**
* Specifies a list of directives/pipes/module that can be used within the template * Specifies a list of directives/pipes/module that can be used within the template

View File

@ -9,7 +9,7 @@
import {LowerCasePipe, NgIf} from '@angular/common'; import {LowerCasePipe, NgIf} from '@angular/common';
import {CompilerConfig, NgModuleResolver, ViewResolver} from '@angular/compiler'; import {CompilerConfig, NgModuleResolver, ViewResolver} from '@angular/compiler';
import {MockNgModuleResolver, MockViewResolver} from '@angular/compiler/testing'; import {MockNgModuleResolver, MockViewResolver} from '@angular/compiler/testing';
import {ANALYZE_FOR_ENTRY_COMPONENTS, Compiler, Component, ComponentFactoryResolver, ComponentRef, ComponentResolver, DebugElement, Directive, Host, HostBinding, Inject, Injectable, Injector, Input, NgModule, NgModuleMetadata, NgModuleRef, OpaqueToken, Optional, Pipe, Provider, ReflectiveInjector, SelfMetadata, SkipSelf, SkipSelfMetadata, ViewMetadata, forwardRef, getDebugNode, provide} from '@angular/core'; import {ANALYZE_FOR_ENTRY_COMPONENTS, Compiler, Component, ComponentFactoryResolver, ComponentRef, ComponentResolver, DebugElement, Directive, Host, HostBinding, Inject, Injectable, Injector, Input, ModuleWithProviders, NgModule, NgModuleMetadata, NgModuleRef, OpaqueToken, Optional, Pipe, Provider, ReflectiveInjector, SelfMetadata, SkipSelf, SkipSelfMetadata, ViewMetadata, forwardRef, getDebugNode, provide} from '@angular/core';
import {Console} from '@angular/core/src/console'; import {Console} from '@angular/core/src/console';
import {ComponentFixture, configureCompiler} from '@angular/core/testing'; import {ComponentFixture, configureCompiler} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
@ -443,6 +443,28 @@ function declareTests({useJit}: {useJit: boolean}) {
} }
const compFixture = createComp(CompUsingModuleDirectiveAndPipe, SomeModule);
compFixture.detectChanges();
expect(compFixture.debugElement.children[0].properties['title'])
.toBe('transformed someValue');
});
it('should support exported directives and pipes if the module is wrapped into an `ModuleWithProviders`',
() => {
@NgModule(
{declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]})
class SomeImportedModule {
}
@NgModule({
declarations: [CompUsingModuleDirectiveAndPipe],
imports: [{ngModule: SomeImportedModule}],
entryComponents: [CompUsingModuleDirectiveAndPipe]
})
class SomeModule {
}
const compFixture = createComp(CompUsingModuleDirectiveAndPipe, SomeModule); const compFixture = createComp(CompUsingModuleDirectiveAndPipe, SomeModule);
compFixture.detectChanges(); compFixture.detectChanges();
expect(compFixture.debugElement.children[0].properties['title']) expect(compFixture.debugElement.children[0].properties['title'])
@ -876,6 +898,26 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(injector.get('token1')).toBe('imported'); expect(injector.get('token1')).toBe('imported');
}); });
it('should add the providers of imported ModuleWithProviders', () => {
@NgModule()
class ImportedModule {
}
@NgModule({
imports: [
{ngModule: ImportedModule, providers: [{provide: 'token1', useValue: 'imported'}]}
]
})
class SomeModule {
}
const injector = createModule(SomeModule).injector;
expect(injector.get(SomeModule)).toBeAnInstanceOf(SomeModule);
expect(injector.get(ImportedModule)).toBeAnInstanceOf(ImportedModule);
expect(injector.get('token1')).toBe('imported');
});
it('should overwrite the providers of imported modules', () => { it('should overwrite the providers of imported modules', () => {
@NgModule({providers: [{provide: 'token1', useValue: 'imported'}]}) @NgModule({providers: [{provide: 'token1', useValue: 'imported'}]})
class ImportedModule { class ImportedModule {