diff --git a/modules/@angular/common/src/common.ts b/modules/@angular/common/src/common.ts
index 014de76d8a..74cd089c05 100644
--- a/modules/@angular/common/src/common.ts
+++ b/modules/@angular/common/src/common.ts
@@ -14,7 +14,7 @@
export * from './location/index';
export {NgLocaleLocalization, NgLocalization} from './localization';
export {CommonModule} from './common_module';
-export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
+export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
export {VERSION} from './version';
export {Version} from '@angular/core';
diff --git a/modules/@angular/common/src/directives/index.ts b/modules/@angular/common/src/directives/index.ts
index 5984efc0a1..38ba2878d8 100644
--- a/modules/@angular/common/src/directives/index.ts
+++ b/modules/@angular/common/src/directives/index.ts
@@ -9,6 +9,7 @@
import {Provider} from '@angular/core';
import {NgClass} from './ng_class';
+import {NgComponentOutlet} from './ng_component_outlet';
import {NgFor} from './ng_for';
import {NgIf} from './ng_if';
import {NgPlural, NgPluralCase} from './ng_plural';
@@ -18,6 +19,7 @@ import {NgTemplateOutlet} from './ng_template_outlet';
export {
NgClass,
+ NgComponentOutlet,
NgFor,
NgIf,
NgPlural,
@@ -29,12 +31,14 @@ export {
NgTemplateOutlet
};
+
/**
* A collection of Angular directives that are likely to be used in each and every Angular
* application.
*/
export const COMMON_DIRECTIVES: Provider[] = [
NgClass,
+ NgComponentOutlet,
NgFor,
NgIf,
NgTemplateOutlet,
diff --git a/modules/@angular/common/src/directives/ng_component_outlet.ts b/modules/@angular/common/src/directives/ng_component_outlet.ts
new file mode 100644
index 0000000000..9ad6c3988f
--- /dev/null
+++ b/modules/@angular/common/src/directives/ng_component_outlet.ts
@@ -0,0 +1,86 @@
+/**
+ * @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 {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, OnChanges, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
+
+
+/**
+ * Instantiates a single {@link Component} type and inserts its Host View into current View.
+ * `NgComponentOutlet` provides a declarative approach for dynamic component creation.
+ *
+ * `NgComponentOutlet` requires a component type, if a falsy value is set the view will clear and
+ * any existing component will get destroyed.
+ *
+ * ### Fine tune control
+ *
+ * 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.
+ *
+ * * `ngOutletProviders`: 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}).
+ *
+ *
+ * ### Syntax
+ *
+ * Simple
+ * ```
+ *
+ * ```
+ *
+ * Customized
+ * ```
+ *
+ *
+ * ```
+ *
+ * # Example
+ *
+ * {@example common/ngComponentOutlet/ts/module.ts region='SimpleExample'}
+ *
+ * A more complete example with additional options:
+ *
+ * {@example common/ngComponentOutlet/ts/module.ts region='CompleteExample'}
+ *
+ * @experimental
+ */
+@Directive({selector: '[ngComponentOutlet]'})
+export class NgComponentOutlet implements OnChanges {
+ @Input() ngComponentOutlet: Type;
+ @Input() ngComponentOutletInjector: Injector;
+ @Input() ngComponentOutletContent: any[][];
+
+ componentRef: ComponentRef;
+
+ constructor(
+ private _cmpFactoryResolver: ComponentFactoryResolver,
+ private _viewContainerRef: ViewContainerRef) {}
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (this.componentRef) {
+ this._viewContainerRef.remove(this._viewContainerRef.indexOf(this.componentRef.hostView));
+ }
+ this._viewContainerRef.clear();
+ 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);
+ }
+ }
+}
diff --git a/modules/@angular/common/test/directives/ng_component_outlet_spec.ts b/modules/@angular/common/test/directives/ng_component_outlet_spec.ts
new file mode 100644
index 0000000000..58e91d6317
--- /dev/null
+++ b/modules/@angular/common/test/directives/ng_component_outlet_spec.ts
@@ -0,0 +1,184 @@
+/**
+ * @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 {CommonModule} from '@angular/common';
+import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
+import {Component, ComponentRef, Inject, Injector, NO_ERRORS_SCHEMA, NgModule, OpaqueToken, Optional, Provider, QueryList, ReflectiveInjector, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
+import {TestBed, async} from '@angular/core/testing';
+import {expect} from '@angular/platform-browser/testing/matchers';
+
+export function main() {
+ describe('insert/remove', () => {
+
+ beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
+
+ it('should do nothing if component is null', async(() => {
+ const template = ``;
+ TestBed.overrideComponent(TestComponent, {set: {template: template}});
+ let fixture = TestBed.createComponent(TestComponent);
+
+ fixture.componentInstance.currentComponent = null;
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement).toHaveText('');
+ }));
+
+ it('should insert content specified by a component', async(() => {
+ let fixture = TestBed.createComponent(TestComponent);
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('');
+
+ fixture.componentInstance.currentComponent = InjectedComponent;
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('foo');
+ }));
+
+ it('should emit a ComponentRef once a component was created', async(() => {
+ let fixture = TestBed.createComponent(TestComponent);
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('');
+
+ fixture.componentInstance.cmpRef = null;
+ fixture.componentInstance.currentComponent = InjectedComponent;
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('foo');
+ expect(fixture.componentInstance.cmpRef).toBeAnInstanceOf(ComponentRef);
+ expect(fixture.componentInstance.cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
+ }));
+
+
+ it('should clear view if component becomes null', async(() => {
+ let fixture = TestBed.createComponent(TestComponent);
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('');
+
+ fixture.componentInstance.currentComponent = InjectedComponent;
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('foo');
+
+ fixture.componentInstance.currentComponent = null;
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('');
+ }));
+
+
+ it('should swap content if component changes', async(() => {
+ let fixture = TestBed.createComponent(TestComponent);
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('');
+
+ fixture.componentInstance.currentComponent = InjectedComponent;
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('foo');
+
+ fixture.componentInstance.currentComponent = InjectedComponentAgain;
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('bar');
+ }));
+
+ it('should use the injector, if one supplied', async(() => {
+ let fixture = TestBed.createComponent(TestComponent);
+
+ const uniqueValue = {};
+ fixture.componentInstance.currentComponent = InjectedComponent;
+ fixture.componentInstance.injector = ReflectiveInjector.resolveAndCreate(
+ [{provide: TEST_TOKEN, useValue: uniqueValue}], fixture.componentRef.injector);
+
+ fixture.detectChanges();
+ let cmpRef: ComponentRef = fixture.componentInstance.cmpRef;
+ expect(cmpRef).toBeAnInstanceOf(ComponentRef);
+ expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
+ expect(cmpRef.instance.testToken).toBe(uniqueValue);
+
+ }));
+
+ it('should resolve a with injector', async(() => {
+ let fixture = TestBed.createComponent(TestComponent);
+
+ fixture.componentInstance.cmpRef = null;
+ fixture.componentInstance.currentComponent = InjectedComponent;
+ fixture.detectChanges();
+ let cmpRef: ComponentRef = fixture.componentInstance.cmpRef;
+ expect(cmpRef).toBeAnInstanceOf(ComponentRef);
+ expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
+ expect(cmpRef.instance.testToken).toBeNull();
+ }));
+
+ it('should render projectable nodes, if supplied', async(() => {
+ const template = `projected foo${TEST_CMP_TEMPLATE}`;
+ TestBed.overrideComponent(TestComponent, {set: {template: template}})
+ .configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
+
+ TestBed
+ .overrideComponent(InjectedComponent, {set: {template: ``}})
+ .configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
+
+ let fixture = TestBed.createComponent(TestComponent);
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('');
+
+ fixture.componentInstance.currentComponent = InjectedComponent;
+ fixture.componentInstance.projectables =
+ [fixture.componentInstance.vcRef
+ .createEmbeddedView(fixture.componentInstance.tplRefs.first)
+ .rootNodes];
+
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement).toHaveText('projected foo');
+ }));
+ });
+}
+
+const TEST_TOKEN = new OpaqueToken('TestToken');
+@Component({selector: 'injected-component', template: 'foo'})
+class InjectedComponent {
+ constructor(@Optional() @Inject(TEST_TOKEN) public testToken: any) {}
+}
+
+
+@Component({selector: 'injected-component-again', template: 'bar'})
+class InjectedComponentAgain {
+}
+
+const TEST_CMP_TEMPLATE =
+ ``;
+@Component({selector: 'test-cmp', template: TEST_CMP_TEMPLATE})
+class TestComponent {
+ currentComponent: Type;
+ injector: Injector;
+ projectables: any[][];
+
+ get cmpRef(): ComponentRef { return this.ngComponentOutlet.componentRef; }
+ set cmpRef(value: ComponentRef) { this.ngComponentOutlet.componentRef = value; }
+
+ @ViewChildren(TemplateRef) tplRefs: QueryList>;
+ @ViewChild(NgComponentOutlet) ngComponentOutlet: NgComponentOutlet;
+
+ constructor(public vcRef: ViewContainerRef) {}
+}
+
+@NgModule({
+ imports: [CommonModule],
+ declarations: [TestComponent, InjectedComponent, InjectedComponentAgain],
+ exports: [TestComponent, InjectedComponent, InjectedComponentAgain],
+ entryComponents: [InjectedComponent, InjectedComponentAgain]
+})
+export class TestModule {
+}
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
new file mode 100644
index 0000000000..57bf9d0cd7
--- /dev/null
+++ b/modules/@angular/examples/common/ngComponentOutlet/ts/e2e_test/ngComponentOutlet_spec.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 {$, ExpectedConditions, browser, by, element} from 'protractor';
+import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
+
+function waitForElement(selector: string) {
+ const EC = ExpectedConditions;
+ // Waits for the element with id 'abc' to be present on the dom.
+ browser.wait(EC.presenceOf($(selector)), 20000);
+}
+
+describe('ngComponentOutlet', () => {
+ const URL = 'common/ngComponentOutlet/ts/';
+ afterEach(verifyNoBrowserErrors);
+
+ describe('ng-component-outlet-example', () => {
+ it('should render simple', () => {
+ browser.get(URL);
+ waitForElement('ng-component-outlet-simple-example');
+ expect(element.all(by.css('hello-world')).getText()).toEqual(['Hello World!']);
+ });
+
+ it('should render complete', () => {
+ browser.get(URL);
+ waitForElement('ng-component-outlet-complete-example');
+ expect(element.all(by.css('complete-component')).getText()).toEqual(['Complete: Ahoj Svet!']);
+ });
+ });
+});
diff --git a/modules/@angular/examples/common/ngComponentOutlet/ts/module.ts b/modules/@angular/examples/common/ngComponentOutlet/ts/module.ts
new file mode 100644
index 0000000000..ff1cba7824
--- /dev/null
+++ b/modules/@angular/examples/common/ngComponentOutlet/ts/module.ts
@@ -0,0 +1,83 @@
+/**
+ * @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, Injectable, Injector, NgModule, ReflectiveInjector} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+
+
+
+// #docregion SimpleExample
+@Component({selector: 'hello-world', template: 'Hello World!'})
+class HelloWorld {
+}
+
+@Component({
+ selector: 'ng-component-outlet-simple-example',
+ template: ``
+})
+class NgTemplateOutletSimpleExample {
+ // This field is necessary to expose HelloWorld to the template.
+ HelloWorld = HelloWorld;
+}
+// #enddocregion
+
+// #docregion CompleteExample
+@Injectable()
+class Greeter {
+ suffix = '!'
+}
+
+@Component({
+ selector: 'complete-component',
+ template: `Complete: {{ greeter.suffix }}`
+})
+class CompleteComponent {
+ constructor(public greeter: Greeter) {}
+}
+
+@Component({
+ selector: 'ng-component-outlet-complete-example',
+ template: `
+ `
+})
+class NgTemplateOutletCompleteExample {
+ // This field is necessary to expose CompleteComponent to the template.
+ CompleteComponent = CompleteComponent;
+ myInjector: Injector;
+
+ myContent = [[document.createTextNode('Ahoj')], [document.createTextNode('Svet')]];
+
+ constructor(injector: Injector) {
+ this.myInjector = ReflectiveInjector.resolveAndCreate([Greeter], injector);
+ }
+}
+// #enddocregion
+
+
+@Component({
+ selector: 'example-app',
+ template: `
+
+ `
+})
+class ExampleApp {
+}
+
+@NgModule({
+ imports: [BrowserModule],
+ declarations: [
+ ExampleApp, NgTemplateOutletSimpleExample, NgTemplateOutletCompleteExample, HelloWorld,
+ CompleteComponent
+ ],
+ entryComponents: [HelloWorld, CompleteComponent],
+ bootstrap: [ExampleApp]
+})
+export class AppModule {
+}
diff --git a/tools/public_api_guard/common/index.d.ts b/tools/public_api_guard/common/index.d.ts
index 595497fc86..eb2bff554f 100644
--- a/tools/public_api_guard/common/index.d.ts
+++ b/tools/public_api_guard/common/index.d.ts
@@ -117,6 +117,16 @@ export declare class NgClass implements DoCheck {
ngDoCheck(): void;
}
+/** @experimental */
+export declare class NgComponentOutlet implements OnChanges {
+ componentRef: ComponentRef;
+ ngComponentOutlet: Type;
+ ngComponentOutletContent: any[][];
+ ngComponentOutletInjector: Injector;
+ constructor(_cmpFactoryResolver: ComponentFactoryResolver, _viewContainerRef: ViewContainerRef);
+ ngOnChanges(changes: SimpleChanges): void;
+}
+
/** @stable */
export declare class NgFor implements DoCheck, OnChanges {
ngForOf: any;