diff --git a/modules/@angular/common/src/directives/ng_component_outlet.ts b/modules/@angular/common/src/directives/ng_component_outlet.ts
index 9ad6c3988f..d20dacbc8b 100644
--- a/modules/@angular/common/src/directives/ng_component_outlet.ts
+++ b/modules/@angular/common/src/directives/ng_component_outlet.ts
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, OnChanges, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
+import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgModuleFactory, NgModuleRef, OnChanges, OnDestroy, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
+
/**
@@ -20,16 +21,17 @@ import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, OnCh
*
* You can control the component creation process by using the following optional attributes:
*
- * * `ngOutletInjector`: Optional custom {@link Injector} that will be used as parent for the
- * Component.
- * Defaults to the injector of the current view container.
+ * * `ngComponentOutletInjector`: Optional custom {@link Injector} that will be used as parent for
+ * the Component. Defaults to the injector of the current view container.
*
- * * `ngOutletProviders`: Optional injectable objects ({@link Provider}) that are visible to the
- * component.
+ * * `ngComponentOutletProviders`: Optional injectable objects ({@link Provider}) that are visible
+ * to the component.
*
- * * `ngOutletContent`: Optional list of projectable nodes to insert into the content
- * section of the component, if exists. ({@link NgContent}).
+ * * `ngComponentOutletContent`: Optional list of projectable nodes to insert into the content
+ * section of the component, if exists.
*
+ * * `ngComponentOutletNgModuleFactory`: Optional module factory to allow dynamically loading other
+ * module, then load a component from that module.
*
* ### Syntax
*
@@ -38,14 +40,20 @@ import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, OnCh
*
* ```
*
- * Customized
+ * Customized injector/content
* ```
*
+ * content: contentNodesExpression;">
*
* ```
*
+ * Customized ngModuleFactory
+ * ```
+ *
+ *
+ * ```
* # Example
*
* {@example common/ngComponentOutlet/ts/module.ts region='SimpleExample'}
@@ -53,34 +61,55 @@ import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, OnCh
* A more complete example with additional options:
*
* {@example common/ngComponentOutlet/ts/module.ts region='CompleteExample'}
+
+ * A more complete example with ngModuleFactory:
+ *
+ * {@example common/ngComponentOutlet/ts/module.ts region='NgModuleFactoryExample'}
*
* @experimental
*/
@Directive({selector: '[ngComponentOutlet]'})
-export class NgComponentOutlet implements OnChanges {
+export class NgComponentOutlet implements OnChanges, OnDestroy {
@Input() ngComponentOutlet: Type;
@Input() ngComponentOutletInjector: Injector;
@Input() ngComponentOutletContent: any[][];
+ @Input() ngComponentOutletNgModuleFactory: NgModuleFactory;
- componentRef: ComponentRef;
+ private _componentRef: ComponentRef = null;
+ private _moduleRef: NgModuleRef = null;
- constructor(
- private _cmpFactoryResolver: ComponentFactoryResolver,
- private _viewContainerRef: ViewContainerRef) {}
+ constructor(private _viewContainerRef: ViewContainerRef) {}
ngOnChanges(changes: SimpleChanges) {
- if (this.componentRef) {
- this._viewContainerRef.remove(this._viewContainerRef.indexOf(this.componentRef.hostView));
+ if (this._componentRef) {
+ this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._componentRef.hostView));
}
this._viewContainerRef.clear();
- this.componentRef = null;
+ this._componentRef = null;
if (this.ngComponentOutlet) {
let injector = this.ngComponentOutletInjector || this._viewContainerRef.parentInjector;
- this.componentRef = this._viewContainerRef.createComponent(
- this._cmpFactoryResolver.resolveComponentFactory(this.ngComponentOutlet),
- this._viewContainerRef.length, injector, this.ngComponentOutletContent);
+ if ((changes as any).ngComponentOutletNgModuleFactory) {
+ if (this._moduleRef) this._moduleRef.destroy();
+ if (this.ngComponentOutletNgModuleFactory) {
+ this._moduleRef = this.ngComponentOutletNgModuleFactory.create(injector);
+ } else {
+ this._moduleRef = null;
+ }
+ }
+ if (this._moduleRef) {
+ injector = this._moduleRef.injector;
+ }
+
+ let componentFactory =
+ injector.get(ComponentFactoryResolver).resolveComponentFactory(this.ngComponentOutlet);
+
+ this._componentRef = this._viewContainerRef.createComponent(
+ componentFactory, this._viewContainerRef.length, injector, this.ngComponentOutletContent);
}
}
+ ngOnDestroy() {
+ if (this._moduleRef) this._moduleRef.destroy();
+ }
}
diff --git a/modules/@angular/common/test/directives/ng_component_outlet_spec.ts b/modules/@angular/common/test/directives/ng_component_outlet_spec.ts
index ff78514614..24c01fe006 100644
--- a/modules/@angular/common/test/directives/ng_component_outlet_spec.ts
+++ b/modules/@angular/common/test/directives/ng_component_outlet_spec.ts
@@ -8,8 +8,8 @@
import {CommonModule} from '@angular/common';
import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
-import {Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, Optional, Provider, QueryList, ReflectiveInjector, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
-import {TestBed, async} from '@angular/core/testing';
+import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, Optional, Provider, QueryList, ReflectiveInjector, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
+import {TestBed, async, fakeAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
@@ -143,6 +143,69 @@ export function main() {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('projected foo');
}));
+
+ it('should resolve components from other modules, if supplied', async(() => {
+ const compiler = TestBed.get(Compiler) as Compiler;
+ let fixture = TestBed.createComponent(TestComponent);
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('');
+
+ fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
+ fixture.componentInstance.currentComponent = Module2InjectedComponent;
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('baz');
+ }));
+
+ it('should clean up moduleRef, if supplied', async(() => {
+ let destroyed = false;
+ const compiler = TestBed.get(Compiler) as Compiler;
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
+ fixture.componentInstance.currentComponent = Module2InjectedComponent;
+ fixture.detectChanges();
+
+ const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'];
+ spyOn(moduleRef, 'destroy').and.callThrough();
+
+ expect(moduleRef.destroy).not.toHaveBeenCalled();
+ fixture.destroy();
+ expect(moduleRef.destroy).toHaveBeenCalled();
+ }));
+
+ it('should not re-create moduleRef when it didn\'t actually change', async(() => {
+ const compiler = TestBed.get(Compiler) as Compiler;
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
+ fixture.componentInstance.currentComponent = Module2InjectedComponent;
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement).toHaveText('baz');
+
+ const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'];
+ fixture.componentInstance.currentComponent = Module2InjectedComponent2;
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement).toHaveText('baz2');
+ expect(moduleRef).toBe(fixture.componentInstance.ngComponentOutlet['_moduleRef']);
+ }));
+
+ it('should re-create moduleRef when changed', async(() => {
+ const compiler = TestBed.get(Compiler) as Compiler;
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
+ fixture.componentInstance.currentComponent = Module2InjectedComponent;
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement).toHaveText('baz');
+
+ fixture.componentInstance.module = compiler.compileModuleSync(TestModule3);
+ fixture.componentInstance.currentComponent = Module3InjectedComponent;
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement).toHaveText('bat');
+ }));
});
}
@@ -158,15 +221,16 @@ class InjectedComponentAgain {
}
const TEST_CMP_TEMPLATE =
- ``;
+ ``;
@Component({selector: 'test-cmp', template: TEST_CMP_TEMPLATE})
class TestComponent {
currentComponent: Type;
injector: Injector;
projectables: any[][];
+ module: NgModuleFactory;
- get cmpRef(): ComponentRef { return this.ngComponentOutlet.componentRef; }
- set cmpRef(value: ComponentRef) { this.ngComponentOutlet.componentRef = value; }
+ get cmpRef(): ComponentRef { return this.ngComponentOutlet['_componentRef']; }
+ set cmpRef(value: ComponentRef) { this.ngComponentOutlet['_componentRef'] = value; }
@ViewChildren(TemplateRef) tplRefs: QueryList>;
@ViewChild(NgComponentOutlet) ngComponentOutlet: NgComponentOutlet;
@@ -182,3 +246,33 @@ class TestComponent {
})
export class TestModule {
}
+
+@Component({selector: 'mdoule-2-injected-component', template: 'baz'})
+class Module2InjectedComponent {
+}
+
+@Component({selector: 'mdoule-2-injected-component-2', template: 'baz2'})
+class Module2InjectedComponent2 {
+}
+
+@NgModule({
+ imports: [CommonModule],
+ declarations: [Module2InjectedComponent, Module2InjectedComponent2],
+ exports: [Module2InjectedComponent, Module2InjectedComponent2],
+ entryComponents: [Module2InjectedComponent, Module2InjectedComponent2]
+})
+export class TestModule2 {
+}
+
+@Component({selector: 'mdoule-3-injected-component', template: 'bat'})
+class Module3InjectedComponent {
+}
+
+@NgModule({
+ imports: [CommonModule],
+ declarations: [Module3InjectedComponent],
+ exports: [Module3InjectedComponent],
+ entryComponents: [Module3InjectedComponent]
+})
+export class TestModule3 {
+}
\ No newline at end of file
diff --git a/modules/@angular/examples/common/ngComponentOutlet/ts/e2e_test/ngComponentOutlet_spec.ts b/modules/@angular/examples/common/ngComponentOutlet/ts/e2e_test/ngComponentOutlet_spec.ts
index 57bf9d0cd7..f205f775e3 100644
--- a/modules/@angular/examples/common/ngComponentOutlet/ts/e2e_test/ngComponentOutlet_spec.ts
+++ b/modules/@angular/examples/common/ngComponentOutlet/ts/e2e_test/ngComponentOutlet_spec.ts
@@ -31,5 +31,13 @@ describe('ngComponentOutlet', () => {
waitForElement('ng-component-outlet-complete-example');
expect(element.all(by.css('complete-component')).getText()).toEqual(['Complete: Ahoj Svet!']);
});
+
+ it('should render other module', () => {
+ browser.get(URL);
+ waitForElement('ng-component-outlet-other-module-example');
+ expect(element.all(by.css('other-module-component')).getText()).toEqual([
+ 'Other Module Component!'
+ ]);
+ });
});
});
diff --git a/modules/@angular/examples/common/ngComponentOutlet/ts/module.ts b/modules/@angular/examples/common/ngComponentOutlet/ts/module.ts
index f5586c50de..e56f528cea 100644
--- a/modules/@angular/examples/common/ngComponentOutlet/ts/module.ts
+++ b/modules/@angular/examples/common/ngComponentOutlet/ts/module.ts
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Component, Injectable, Injector, NgModule, ReflectiveInjector} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Compiler, Component, Injectable, Injector, NgModule, NgModuleFactory, ReflectiveInjector} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
@@ -60,12 +61,34 @@ class NgTemplateOutletCompleteExample {
}
// #enddocregion
+// #docregion NgModuleFactoryExample
+@Component({selector: 'other-module-component', template: `Other Module Component!`})
+class OtherModuleComponent {
+}
+
+@Component({
+ selector: 'ng-component-outlet-other-module-example',
+ template: `
+ `
+})
+class NgTemplateOutletOtherModuleExample {
+ // This field is necessary to expose OtherModuleComponent to the template.
+ OtherModuleComponent = OtherModuleComponent;
+ myModule: NgModuleFactory;
+
+ constructor(compiler: Compiler) { this.myModule = compiler.compileModuleSync(OtherModule); }
+}
+// #enddocregion
+
@Component({
selector: 'example-app',
template: `
- `
+
+
+ `
})
class ExampleApp {
}
@@ -73,11 +96,19 @@ class ExampleApp {
@NgModule({
imports: [BrowserModule],
declarations: [
- ExampleApp, NgTemplateOutletSimpleExample, NgTemplateOutletCompleteExample, HelloWorld,
- CompleteComponent
+ ExampleApp, NgTemplateOutletSimpleExample, NgTemplateOutletCompleteExample,
+ NgTemplateOutletOtherModuleExample, HelloWorld, CompleteComponent
],
entryComponents: [HelloWorld, CompleteComponent],
bootstrap: [ExampleApp]
})
export class AppModule {
}
+
+@NgModule({
+ imports: [CommonModule],
+ declarations: [OtherModuleComponent],
+ entryComponents: [OtherModuleComponent]
+})
+export class OtherModule {
+}
\ No newline at end of file
diff --git a/tools/public_api_guard/common/index.d.ts b/tools/public_api_guard/common/index.d.ts
index 6384324f1f..3419149a77 100644
--- a/tools/public_api_guard/common/index.d.ts
+++ b/tools/public_api_guard/common/index.d.ts
@@ -118,13 +118,14 @@ export declare class NgClass implements DoCheck {
}
/** @experimental */
-export declare class NgComponentOutlet implements OnChanges {
- componentRef: ComponentRef;
+export declare class NgComponentOutlet implements OnChanges, OnDestroy {
ngComponentOutlet: Type;
ngComponentOutletContent: any[][];
ngComponentOutletInjector: Injector;
- constructor(_cmpFactoryResolver: ComponentFactoryResolver, _viewContainerRef: ViewContainerRef);
+ ngComponentOutletNgModuleFactory: NgModuleFactory;
+ constructor(_viewContainerRef: ViewContainerRef);
ngOnChanges(changes: SimpleChanges): void;
+ ngOnDestroy(): void;
}
/** @stable */