2016-12-16 88 views
1

我一直在試圖動態創建一個組件並將其附加到文檔標籤。我一直很難弄清楚如何選擇主體的ViewContainterRef,所以我可以使用ComponentFactoryResolver追加一個新組件。Angular2 - 如何動態創建組件並追加到body的viewcontainer

我試圖使用下面的代碼獲取對身體容器的引用,但它不起作用。有人知道該怎麼做嗎?謝謝!

import { 
    Component, 
    ComponentRef, 
    ApplicationRef, 
    Injector, 
    Input, 
    ViewContainerRef, 
    ComponentFactoryResolver, 
    ViewChild, 
    OnInit, 
    OnDestroy 
} from '@angular/core'; 

import { 
    ModalComponent 
} from './modal.component'; 

@Component({ 
    selector: 'my-modal' 
}) 
export class MyModalComponent { 

    private _bodyRef: ViewContainerRef; 


    constructor(private resolver: ComponentFactoryResolver, private app: ApplicationRef) { 

     // Does not work! 
     this._bodyRef = app['_rootComponents'][0]['_hostElement'].vcRef; 

    } 


    ngOnInit() { 

     // Calls the factory to crate a brand new instance 
     let componentFactory = this.resolver.resolveComponentFactory(ModalComponent); 
     this._bodyRef.createComponent(componentFactory); 


    } 
} 
+0

你可以在我的例子來看看類似的t興googlemap infoWindow http://stackoverflow.com/questions/40922224/angular2-component-into-dynamicaly-created-element/40926110#40926110 – yurzui

回答

6

Angular2材料的團隊正在做類似這樣的提示,並且認爲可能是當前容器裁判之外的其他動態組件的東西。

這是類:https://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts 他們在做什麼:

第一注射一堆有用的類:

constructor(
     private _viewRef: ViewContainerRef, 
     private _hostDomElement: Element, 
     private _componentFactoryResolver: ComponentFactoryResolver, 
     private _appRef: ApplicationRef, 
     private _defaultInjector: Injector) { 
    } 

然後: 創建當前viewContainerRef組件,而你的情況是你的模式。

ngOnInit() { 

     // Calls the factory to crate a brand new instance 
     let componentFactory = this._componentFactoryResolver.resolveComponentFactory(ModalComponent); 
     let componentRef = this._viewRef.createComponent(componentFactory); 


    } 

然後將其連接到appRef

(this._appRef as any).attachView(componentRef.hostView); 

     this.setDisposeFn(() => { 
      (this._appRef as any).detachView(componentRef.hostView); 
      componentRef.destroy(); 
     }); 

我從來沒有這樣做,所以你可能需要給它一些努力,但我認爲這是這樣的。

+0

雖然你指向正確的方向......這個答案是更明確的http: //stackoverflow.com/a/40687392/5583283 – j3ff

1

我解決了這個與單獨的服務:

import { 
 
    Injectable, 
 
    ComponentFactoryResolver, 
 
    ApplicationRef, 
 
    Injector, 
 
    EmbeddedViewRef, 
 
    ComponentRef 
 
} from '@angular/core'; 
 

 
@Injectable() 
 
export class DOMService { 
 

 
    constructor(
 
    private componentFactoryResolver: ComponentFactoryResolver, 
 
    private applicationRef: ApplicationRef, 
 
    private injector: Injector, 
 
) {} 
 

 
    appendComponentToBody(component: any) { 
 
    //create a component reference 
 
    const componentRef = this.componentFactoryResolver.resolveComponentFactory(component) 
 
     .create(this.injector); 
 

 
    // attach component to the appRef so that so that it will be dirty checked. 
 
    this.applicationRef.attachView(componentRef.hostView); 
 

 
    // get DOM element from component 
 
    const domElem = (componentRef.hostView as EmbeddedViewRef <any>) 
 
     .rootNodes[0] as HTMLElement; 
 

 
    document.body.appendChild(domElem); 
 

 
    return componentRef; 
 
    } 
 

 
    removeComponentFromBody(componentRef: ComponentRef <any>) { 
 
    this.applicationRef.detachView(componentRef.hostView); 
 
    componentRef.destroy(); 
 
    } 
 
}

在組件

然後:

import { 
 
    Component, 
 
    AfterContentInit 
 
} from '@angular/core'; 
 
import { 
 
    ComponentToInject 
 
} from 'path/to/component'; 
 
import { 
 
    DOMService 
 
} from 'path/to/service'; 
 

 
@Component({ 
 
    selector: 'my-component', 
 
    template: '....' 
 
}) 
 
export class MyComponent implements AfterContentInit { 
 

 
    constructor(
 
    private DOMService: DOMService, 
 
) {} 
 

 
    ngAfterContentInit() { 
 
     // to prevent ExpressionChangedAfterItHasBeenCheckedError 
 
     setTimeout(() => { 
 
      const cmp = this.DOMService.appendComponentToBody(ComponentToInject); 
 

 
      // if you need to get access to input of injected component. Let's say ComponentToInject has public property title 
 
      const instance = cmp.instance; 
 
      instance.title = 'Some title'; 
 

 
      // if you need to get access to output of injected component. Let's say ComponentToInject assings EventEmitter to onClick property 
 
      instance.onClick.subscribe(() => { // do somethis }) 
 
      }); 
 
     } 
 

 
     }