21

I have a question regarding dynamic component creation in Angular 2 rc5.

So let's assume that we have two plain angular components:

  @Component({
      template: `
        <div id="container">
          <h1>My Component</h1>
        </div>
       `,
      selector: 'my-app'
  })
  export class AppComponent { }

  @Component({
      template: '<p>{{text}}</p>',
      selector: 'simple-cmp'
  })
  export class SimpleComponent { public text='Hello World!' }

Then some external non-angular chunck of code modificates a DOM:

let newNode = document.createElement('div');
newNode.id = 'placeholder';
document.getElementById('container').appendChild(newNode);

Here is some presumable tree after manipulations:

 <div id="container">
      <h1>My Component</h1>
      <div id="placeholder"></div>
 </div>

So what I'm trying to do is just dynamically add SimpleComoponent instance into #placeholder div. How can I achieve this result?

I've been trying using ComponentFactory.createComponent(injector, [], newNode), it added the component though, but neither life cycle hooks nor binding not worked at all.

I believe there is some way to implement this using ViewContainerRef, but how can I link it with dynamically created node?

Here is the result I expect

 <div id="container">
      <h1>My Component</h1>
      <div id="placeholder">
        <simple-cmp>Hello world!</simple-cmp>
      </div>
 </div>

Thanks!

1

4 Answers 4

45

When creating a component you can pass the DOM node that will act as a host element of the created component:

create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any, ngModule?: NgModuleRef): ComponentRef

But since this component is not child of any other component, you have to manually attach it to ApplicationRef so you get change detection.

So here is what you need to do:

1) Create a component specifying the root node under which it should be added.
2) Attach the view to the ApplicationRef so that you get change detection. You will still have no Input and ngOnChanges operations, but the DOM update will be working fine.

  @Component({
      template: `
        <div id="container">
          <h1>My Component</h1>
        </div>
       `,
      selector: 'my-app'
  })
  export class AppComponent { 
      constructor(private resolver: ComponentFactoryResolver,
                  private injector: Injector,
                  private app: ApplicationRef) { 

      }

      addDynamicComponent() {
         let factory = this.resolver.resolveComponentFactory(SimpleComponent);

         let newNode = document.createElement('div');
         newNode.id = 'placeholder';
         document.getElementById('container').appendChild(newNode);

         const ref = factory.create(this.injector, [], newNode);
         this.app.attachView(ref.hostView);
      }
  }
Sign up to request clarification or add additional context in comments.

8 Comments

@Baconbeastnz, you're welcome) I also strongly recommend reading the article Here is what you need to know about dynamic components in Angular if you're working with dynamic components. Good luck!
This solution saved me. My use-case was passing in a component factory into a mat-table datasource and rendering the component inside the table. I opted to use getElementById over a viewContainerRef/@ViewChild so I could identify the cell's template based on a unique index-based ID, resulting in the following:
var container = document.getElementById("container-" + rowIndex); let componentRef= componentFactory.create(this.injector, [], container); this.applicationRef.attachView(componentRef.hostView);
Note: ComponentFactory is now deprecated as of angular 13.2. I have no idea what a nicer workaround is.
I would also like to know how to solve this since ComponentFactory is deprecated
|
9

You should never modify DOM outside Angular because it will lead to unpredictable behavior. Even if you append <simple-cmp> element manually it means nothing because it's not processed by Angular. All changes to DOM inside Angular app have to go through Angular methods.

Dynamically create a new component:

@Component({
    selector: 'my-component',
    template: '<div #element></div>',
})
export class MyComponent {
    @ViewChild('element', {read: ViewContainerRef}) private anchor: ViewContainerRef;

    constructor(private resolver: ComponentFactoryResolver) { }

    whatever() {
        let factory = this.resolver.resolveComponentFactory(ChildComponent);
        this.anchor.createComponent(factory);
    }
}

1 Comment

Thanks, man. That what I was afraid, actually the case is that I'm using jqWidgets library, which provide Angular 2 support. But they just wrapped widgets with angular 2 components. So I am trying to insert a component in a lazy-initilizing row detail of a grid (jqwidgets.com/jquery-widgets-demo/demos/angular2/#demos/…).
3

ComponentFactoryResolver is now deprecated. Here's an alternative approach:

For adding a dynamic component without an existing ViewContainerRef:

@Component({
    template: `
        <div id="container">
            <h1>My Component</h1>
        </div>
    `,
    selector: "my-app",
})
export class AppComponent {
    constructor(private injector: Injector, private app: ApplicationRef) {}

    addDynamicComponent() {
        let newNode = document.createElement("div");
        newNode.id = "placeholder";
        document.getElementById("container").appendChild(newNode);

        const componentRef = createComponent(SimpleComponent, {
            environmentInjector: this.app.injector,
            hostElement: newNode,
        });
        this.app.attachView(componentRef.hostView);
    }
}

For adding a dynamic component with an existing ViewContainerRef:

@Component({
    template: `
        <div #container></div>
    `,
    selector: "my-app",
})
export class AppComponent {
    @ViewChild("container", { read: ViewContainerRef }) private container: ViewContainerRef;


    addDynamicComponent() {
        const componentRef = this.container.createComponent(SimpleComponent);
    }
}

Resources:

Comments

0

I'm using it this way

        let div = document.getElementById([Dom Element]);
        const ref = factory.create(this.injector, [], div);
        ref.changeDetectorRef.detectChanges();

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.