Reputation: 463
I've been trying to dynamically create a component and append it to the document tag. I've been having a hard time figuring out how to select the body's ViewContainterRef so I can just append a new component using the ComponentFactoryResolver.
I tried to obtain a reference to the body container using the code below, but it does not work. Does anybody know how to do it? Thanks!
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);
}
}
Upvotes: 2
Views: 13037
Reputation: 5144
I resolved this with separate service:
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();
}
}
Then in your component:
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 })
});
}
}
Upvotes: 10
Reputation: 28592
Angular2 material team are doing something similar to this for the tooltip and other dynamic components that the view might be outside of the current container ref.
This is the class : https://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts What they're doing :
First injecting bunch of useful classes :
constructor(
private _viewRef: ViewContainerRef,
private _hostDomElement: Element,
private _componentFactoryResolver: ComponentFactoryResolver,
private _appRef: ApplicationRef,
private _defaultInjector: Injector) {
}
Then : Creating the component in the current viewContainerRef , which in your case is your modal.
ngOnInit() {
// Calls the factory to crate a brand new instance
let componentFactory = this._componentFactoryResolver.resolveComponentFactory(ModalComponent);
let componentRef = this._viewRef.createComponent(componentFactory);
}
Then attaching it to the appRef
(this._appRef as any).attachView(componentRef.hostView);
this.setDisposeFn(() => {
(this._appRef as any).detachView(componentRef.hostView);
componentRef.destroy();
});
I've never done this , so you might need to give it some effort , but I assume this is the way.
Upvotes: 6