1- import { ComponentFactoryResolver , ComponentRef , EmbeddedViewRef } from '@angular/core' ;
1+ import {
2+ ComponentFactoryResolver ,
3+ ComponentRef ,
4+ EmbeddedViewRef ,
5+ ApplicationRef ,
6+ Injector ,
7+ } from '@angular/core' ;
28import { BasePortalHost , ComponentPortal , TemplatePortal } from './portal' ;
3- import { MdComponentPortalAttachedToDomWithoutOriginError } from './portal-errors' ;
49
510
611/**
@@ -12,27 +17,60 @@ import {MdComponentPortalAttachedToDomWithoutOriginError} from './portal-errors'
1217export class DomPortalHost extends BasePortalHost {
1318 constructor (
1419 private _hostDomElement : Element ,
15- private _componentFactoryResolver : ComponentFactoryResolver ) {
20+ private _componentFactoryResolver : ComponentFactoryResolver ,
21+ private _appRef : ApplicationRef ,
22+ private _defaultInjector : Injector ) {
1623 super ( ) ;
1724 }
1825
1926 /** Attach the given ComponentPortal to DOM element using the ComponentFactoryResolver. */
2027 attachComponentPortal < T > ( portal : ComponentPortal < T > ) : ComponentRef < T > {
21- if ( portal . viewContainerRef == null ) {
22- throw new MdComponentPortalAttachedToDomWithoutOriginError ( ) ;
23- }
24-
2528 let componentFactory = this . _componentFactoryResolver . resolveComponentFactory ( portal . component ) ;
26- let ref = portal . viewContainerRef . createComponent (
27- componentFactory ,
28- portal . viewContainerRef . length ,
29- portal . injector || portal . viewContainerRef . parentInjector ) ;
29+ let componentRef : ComponentRef < T > ;
30+
31+ // If the portal specifies a ViewContainerRef, we will use that as the attachment point
32+ // for the component (in terms of Angular's component tree, not rendering).
33+ // When the ViewContainerRef is missing, we use the factory to create the component directly
34+ // and then manually attach the ChangeDetector for that component to the application (which
35+ // happens automatically when using a ViewContainer).
36+ if ( portal . viewContainerRef ) {
37+ componentRef = portal . viewContainerRef . createComponent (
38+ componentFactory ,
39+ portal . viewContainerRef . length ,
40+ portal . injector || portal . viewContainerRef . parentInjector ) ;
41+
42+ this . setDisposeFn ( ( ) => componentRef . destroy ( ) ) ;
43+ } else {
44+ componentRef = componentFactory . create ( portal . injector || this . _defaultInjector ) ;
45+
46+ // When creating a component outside of a ViewContainer, we need to manually register
47+ // its ChangeDetector with the application. This API is unfortunately not yet published
48+ // in Angular core. The change detector must also be deregistered when the component
49+ // is destroyed to prevent memory leaks.
50+ //
51+ // See https://github.com/angular/angular/pull/12674
52+ let changeDetectorRef = componentRef . changeDetectorRef ;
53+ ( this . _appRef as any ) . registerChangeDetector ( changeDetectorRef ) ;
54+
55+ this . setDisposeFn ( ( ) => {
56+ ( this . _appRef as any ) . unregisterChangeDetector ( changeDetectorRef ) ;
3057
31- let hostView = < EmbeddedViewRef < any > > ref . hostView ;
32- this . _hostDomElement . appendChild ( hostView . rootNodes [ 0 ] ) ;
33- this . setDisposeFn ( ( ) => ref . destroy ( ) ) ;
58+ // Normally the ViewContainer will remove the component's nodes from the DOM.
59+ // Without a ViewContainer, we need to manually remove the nodes.
60+ let componentRootNode = this . _getComponentRootNode ( componentRef ) ;
61+ if ( componentRootNode . parentNode ) {
62+ componentRootNode . parentNode . removeChild ( componentRootNode ) ;
63+ }
3464
35- return ref ;
65+ componentRef . destroy ( ) ;
66+ } ) ;
67+ }
68+
69+ // At this point the component has been instantiated, so we move it to the location in the DOM
70+ // where we want it to be rendered.
71+ this . _hostDomElement . appendChild ( this . _getComponentRootNode ( componentRef ) ) ;
72+
73+ return componentRef ;
3674 }
3775
3876 attachTemplatePortal ( portal : TemplatePortal ) : Map < string , any > {
@@ -58,4 +96,9 @@ export class DomPortalHost extends BasePortalHost {
5896 this . _hostDomElement . parentNode . removeChild ( this . _hostDomElement ) ;
5997 }
6098 }
99+
100+ /** Gets the root HTMLElement for an instantiated component. */
101+ private _getComponentRootNode ( componentRef : ComponentRef < any > ) : HTMLElement {
102+ return ( componentRef . hostView as EmbeddedViewRef < any > ) . rootNodes [ 0 ] as HTMLElement ;
103+ }
61104}
0 commit comments