+ `,
+})
+class ProjectionTestApp {
+}
+
+
+
+const TEST_COMPONENTS = [ProjectionTestApp, ProjectionTestComponent];
+@NgModule({
+ imports: [ProjectionModule],
+ exports: TEST_COMPONENTS,
+ declarations: TEST_COMPONENTS,
+ entryComponents: TEST_COMPONENTS,
+})
+class ProjectionTestModule { }
+
diff --git a/src/lib/core/projection/projection.ts b/src/lib/core/projection/projection.ts
new file mode 100644
index 000000000000..e6616d88be6c
--- /dev/null
+++ b/src/lib/core/projection/projection.ts
@@ -0,0 +1,87 @@
+import {Injectable, Directive, ModuleWithProviders, NgModule, ElementRef} from '@angular/core';
+
+
+// "Polyfill" for `Node.replaceWith()`.
+// cf. https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/replaceWith
+function _replaceWith(toReplaceEl: HTMLElement, otherEl: HTMLElement) {
+ toReplaceEl.parentElement.replaceChild(otherEl, toReplaceEl);
+}
+
+
+@Directive({
+ selector: 'dom-projection-host'
+})
+export class DomProjectionHost {
+ constructor(public ref: ElementRef) {}
+}
+
+
+@Injectable()
+export class DomProjection {
+ /**
+ * Project an element into a host element.
+ * Replace a host element by another element. This also replaces the children of the element
+ * by the children of the host.
+ *
+ * It should be used like this:
+ *
+ * ```
+ * @Component({
+ * template: `
+ *
+ *
other
+ *
+ *
+ *
`
+ * })
+ * class Cmpt {
+ * constructor(private _projector: DomProjection, private _el: ElementRef) {}
+ * ngOnInit() { this._projector.project(this._el, this._projector); }
+ * }
+ * ```
+ *
+ * This component will move the content of the element it's applied to in the outer div. Because
+ * `project()` also move the children of the host inside the projected element, the element will
+ * contain the `
other
` HTML as well as its own children.
+ *
+ * Note: without `` the projection will project an empty element.
+ */
+ project(ref: ElementRef, host: DomProjectionHost): void {
+ const projectedEl = ref.nativeElement;
+ const hostEl = host.ref.nativeElement;
+ const childNodes = projectedEl.childNodes;
+ let child = childNodes[0];
+
+ // We hoist all of the projected element's children out into the projected elements position
+ // because we *only* want to move the projected element and not its children.
+ _replaceWith(projectedEl, child);
+ let l = childNodes.length;
+ while (l--) {
+ child.parentNode.insertBefore(childNodes[0], child.nextSibling);
+ child = child.nextSibling; // nextSibling is now the childNodes[0].
+ }
+
+ // Insert all host children under the projectedEl, then replace host by component.
+ l = hostEl.childNodes.length;
+ while (l--) {
+ projectedEl.appendChild(hostEl.childNodes[0]);
+ }
+ _replaceWith(hostEl, projectedEl);
+
+ // At this point the host is replaced by the component. Nothing else to be done.
+ }
+}
+
+
+@NgModule({
+ exports: [DomProjectionHost],
+ declarations: [DomProjectionHost],
+})
+export class ProjectionModule {
+ static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: ProjectionModule,
+ providers: [DomProjection]
+ };
+ }
+}
diff --git a/src/lib/module.ts b/src/lib/module.ts
index 754226b7f0c6..31d18001e089 100644
--- a/src/lib/module.ts
+++ b/src/lib/module.ts
@@ -6,6 +6,7 @@ import {
PortalModule,
OverlayModule,
A11yModule,
+ ProjectionModule,
StyleCompatibilityModule,
} from './core/index';
@@ -59,6 +60,7 @@ const MATERIAL_MODULES = [
PortalModule,
RtlModule,
A11yModule,
+ ProjectionModule,
StyleCompatibilityModule,
];
@@ -78,6 +80,7 @@ const MATERIAL_MODULES = [
MdTabsModule.forRoot(),
MdToolbarModule.forRoot(),
PortalModule.forRoot(),
+ ProjectionModule.forRoot(),
RtlModule.forRoot(),
// These modules include providers.