21

In Angular 4 to dynamically create a component you can use ngComponentOutlet directive: https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html

something like this:

Dynamic component

@Component({
  selector: 'dynamic-component',
  template: `
     Dynamic component
  `
})
export class DynamicComponent {
  @Input() info: any;
}

App

@Component({
  selector: 'my-app',
  template: `
     App<br>
     <ng-container *ngComponentOutlet="component"></ng-container>
  `
})
export class AppComponent {
  this.component=DynamicComponent;
}

How do I pass @Input() info: any; information in this template <ng-container *ngComponentOutlet="component"></ng-container> ?

Doua Beri
  • 10,612
  • 18
  • 89
  • 138

3 Answers3

10

Such a feature was discussed in the pull request for ngComponentOutlet but was dropped for now. Even the componentRef shown currently in https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html is not public and therefore not available https://github.com/angular/angular/blob/3ef73c2b1945340ca6bd21f1790260c88698ae26/modules/%40angular/common/src/directives/ng_component_outlet.ts#L78

I'd suggest you create your own directive derived from https://github.com/angular/angular/blob/3ef73c2b1945340ca6bd21f1790260c88698ae26/modules/%40angular/common/src/directives/ng_component_outlet.ts#L72

and assign values to inputs like shown in Angular 2 dynamic tabs with user-click chosen components

this.compRef.instance.someProperty = 'someValue';
Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks. Is it possible to have a different ngComponentOutlet by the time v4.0 is released? I mean with some options that will deal with this problem? – Doua Beri Mar 01 '17 at 12:29
  • I don't know about the plans. The conclusion was that supporting inputs is too complex for the first version and that they want to release it as is and then eventually approach that problem again, but I haven't seen related discussions since then (which doesn't mean much because I didn't have much time to check) – Günter Zöchbauer Mar 01 '17 at 12:38
  • @GünterZöchbauer So, if I call a component dynamically with `ngComponentOutlet` the only way to send a `model` or `data` from my current template/component to the dynamic component is by creating a custom directive or using some kind of service right? `ngComponentOutlet`doesn't have any built in feature to communicate both components: the caller & the called? Not even `ngComponentOutletInjector`? – SrAxi Apr 04 '17 at 11:10
  • `ngComponentOutletInjector` is only to pass a customized injector. If you add a provider to your component where you use `ngComponentOutletContainer` you'll get the same result automatically for most use cases. I think they plan to support passing some context in Angular4. Not sure if this already landed. – Günter Zöchbauer Apr 04 '17 at 11:19
  • I haven't found anything yet. See also https://github.com/angular/angular/issues/15360, https://github.com/angular/angular/issues/8164 – Günter Zöchbauer Apr 04 '17 at 11:21
  • 1
    @GünterZöchbauer Thanks! I will try the `ngComponentOutletContainer` + `providers` option. And I will also check thank link. My alternative is a service that updates the models in both sides, but I really preffer to leave that as last option. – SrAxi Apr 04 '17 at 11:25
  • Services are really common to be used to communicate between components that are not direct parent-children statically added to a components template (for example components added by the router). – Günter Zöchbauer Apr 04 '17 at 11:41
  • @GünterZöchbauer Yes! I have already implemented a service to communicate between 3 components. Want to look up the information that you gave me, if it doesnt suit me I will go for the service option. I am creating a totally dynamic form where each fieldset of the form is dynamic and renders only if the backend sends that field. :P – SrAxi Apr 04 '17 at 12:01
7

With the help of the post of @Günter Zöchbauer I solved a similar problem this way - I hope you can adapt it somehow.

First I defined some interfaces:

// all dynamically loaded components should implement this guy
export interface IDynamicComponent { Context: object; }

// data from parent to dynLoadedComponent
export interface IDynamicComponentData {
  component: any;
  context?: object;
  caller?: any;
}

then I implemented them inside of the dynamically loaded component

dynamicLoadedComponentA.ts

// ...
export class DynamicLoadedComponentA implements IDynamicComponent {
// ...

// data from parent
public Context: object;

// ...

After that I built a new component which is responsible for the magic. Important here is that I had to register all dyn. loaded components as entryComponents.

dynamic.component.ts

@Component({
  selector: 'ngc-dynamic-component',
  template: ´<ng-template #dynamicContainer></ng-template>´,
  entryComponents: [ DynamicLoadedComponentA ]
})
export class DynamicComponent implements OnInit, OnDestroy, OnChanges {
  @ViewChild('dynamicContainer', { read: ViewContainerRef }) public dynamicContainer: ViewContainerRef;

  @Input() public componentData: IDynamicComponentData;

  private componentRef: ComponentRef<any>;
  private componentInstance: IDynamicComponent;

  constructor(private resolver: ComponentFactoryResolver) { }

  public ngOnInit() {
    this.createComponent();
  }

  public ngOnChanges(changes: SimpleChanges) {
    if (changes['componentData']) {
      this.createComponent();
    }
  }

  public ngOnDestroy() {
    if (this.componentInstance) {
      this.componentInstance = null;
    }
    if (this.componentRef) {
      this.componentRef.destroy();
    }
  }

  private createComponent() {
    this.dynamicContainer.clear();
    if (this.componentData && this.componentData.component) {
      const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(this.componentData.component);
      this.componentRef = this.dynamicContainer.createComponent(factory);
      this.componentInstance = this.componentRef.instance as IDynamicComponent;

      // fill context data
      Object.assign(this.componentInstance.Context, this.componentData.context || {});

      // register output events
      // this.componentRef.instance.outputTrigger.subscribe(event => console.log(event));
    }
  }
}

here the usage of this shiny new stuff:

app.html

<!-- [...] -->
<div>
  <ngc-dynamic-component [componentData]="_settingsData"></ngc-dynamic-component>
</div>
<!-- [...] -->

app.ts

// ...
  private _settingsData: IDynamicComponent = {
    component: DynamicLoadedComponentA,
    context: { SomeValue: 42 },
    caller: this
  };
// ...
tobias-kutter
  • 269
  • 2
  • 10
6

I think for now you can use

https://www.npmjs.com/package/ng-dynamic-component

It is made specifically for this issue

Dany Y
  • 6,833
  • 6
  • 46
  • 83