Juliano
Juliano

Reputation: 463

Angular2 - How to dynamically create component and append to body's viewcontainer

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

Answers (2)

wawka
wawka

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

Milad
Milad

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

Related Questions