7

I'm creating dynamically a component with ngComponentOutlet. Sounds like:

import {Component, NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
  selector: 'alert-success',
  template: `
    <p>Alert success</p>
  `,
})
export class AlertSuccessComponent {  }

@Component({
  selector: 'alert-danger',
  template: `
    <p>Alert danger</p>
  `,
})
export class AlertDangerComponent {
  test = 'danger...';
}

@Component({
  selector: 'my-app',
  template: `
    <h1>Angular version 4</h1>
    <ng-container *ngComponentOutlet="alert"></ng-container>
    <button (click)="changeComponent()">Change component</button>
  `,
})
export class App {
  alert = AlertSuccessComponent;

  changeComponent() {
    this.alert = AlertDangerComponent;
    alert(this.alert.test);       <-- ???????
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, AlertSuccessComponent, AlertDangerComponent ],
  entryComponents: [AlertDangerComponent, AlertSuccessComponent],
  bootstrap: [ App ]
})
export class AppModule {}

In changeComponent(), I try (naively I guess) to get the reference of the current component to feed it with data, but it failed :(

Should I have to use ViewContainerRef, how?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Stef
  • 3,691
  • 6
  • 43
  • 58
  • I don't think there is a way to get the component. I saw a pull request to add that but it doesn't look like it was accepted. You can create and add the component yourself, then you get a reference to the created component instance and you can update its properties. See also https://github.com/angular/angular/blob/67dc970ce45f2867afefac6298f84c8140ad2c81/packages/common/src/directives/ng_component_outlet.ts and https://stackoverflow.com/questions/36325212/angular-2-dynamic-tabs-with-user-click-chosen-components/36325468#36325468 – Günter Zöchbauer Jun 16 '17 at 08:45
  • @GünterZöchbauer I missunderstood the OP, thought he just had to render a Component, didn't realize that had to do it dynamically. I changed approach, check it out and let me know what you think if you want/can! :D – SrAxi Jun 16 '17 at 09:08
  • I had a brief look at the examples in your answer, but it's all not what I understand he asks for. I think he should use something like demonstrated in the 2nd link in my comment above. – Günter Zöchbauer Jun 16 '17 at 09:11
  • 1
    @GünterZöchbauer Maybe I missed something, but in the second link you are suggesting `ngComponentOutlet`, and he already is using it. And, I believe we cannot change on demand the rendered Component with `ngComponentOutlet`. That's why after understanding the OP i chose the *directive like* approach. If we find a solution with `ngComponentOutlet` I will implement it on my own project! :P – SrAxi Jun 16 '17 at 09:23
  • 2
    `ngComponentOutlet` has this limitation that it doesn't provide a reference to the created component. See the RC.7 example in the linked answer instead. – Günter Zöchbauer Jun 16 '17 at 09:25

2 Answers2

2

You have to put the component name directly there:

<ng-container *ngComponentOutlet="AlertDangerComponent;
            ngModuleFactory: alertDangerModule;"></ng-container>

I took the liberty to add the Module, is used for when you render a component from a Module different from the current Module.

Also, for using the Module option, you'll need this in your current component:

private alertDangerModule: NgModuleFactory<any>;

constructor(private compiler: Compiler) {
      this.alertDangerModule = compiler.compileModuleSync(AlertDangerModule);
}

If you just want to load 1 component from current Module, this is what you need to do:

<ng-container *ngComponentOutlet="AlertDangerComponent"></ng-container>

NgComponentOutlet

For importing Module: NgModuleFactory

Update (Dynamic):

Create a vector, such as:

import AlertDangerComponent from './path';
import AlertSuccessComponent from './path';

export const MY_ALERTS = {
    'alertDanger': AlertDangerComponent,
    'alertSuccess': AlertSuccessComponent,
};

In your component, you import MY_ALERTS, and you could render as many components as MY_ALERTS has.

Or you could try render it dynamically, by creating a new ng-container (Haven't test this yet).

I'm using this to render components from a huge vector containing component classes with other values such as booleans so I know which component to load each time.

To render a component that is inside this vector you can:

<div *ngFor="let alert of MY_ALERTS | keys">
        <ng-container *ngComponentOutlet="MY_ALERTS[alert];
                 ngModuleFactory: commonAlertsModule;"></ng-container>
</div>

Where keys is just a @Pipe that returns me the keys of an object (instead of the value).

Update (Alternative approach):

I was thinking that maybe you could be interested on this other approach: Use a @Component as a 'Directive'. I'll explain myself:

Declare your Components with a directive like selector:

@Component({
  selector: '[alert-success]',
  template: `
    <p>Alert success</p>
  `,
})
export class AlertSuccessComponent {  }

@Component({
  selector: '[alert-danger]',
  template: `
    <p>Alert danger</p>
  `,
})
export class AlertDangerComponent {
  test = 'danger...';
}

Then, you just call one or the other, depending on occasion:

@Component({
  selector: 'my-app',
  template: `
    <h1>Angular version 4</h1>
    <div *ngIf="alertDanger" alert-danger></div>
    <div *ngIf="alertSuccess" alert-success></div>
    <button (click)="changeComponent()">Change component</button>
  `,
})
export class App {
  alertSuccess = true;
  alertDanger = false;

  changeComponent() {
    this.alertSuccess = !this.alertSuccess;
    this.alertDanger = !this.alertDanger;
  }
}

In my example (not tested though) I initialize the Success Alert. On click, It should set alertSuccess as false and set alertDanger as true.

SrAxi
  • 19,787
  • 11
  • 46
  • 65
  • Ok, if I understand, all the components are loaded, and I display only those I want... – Stef Jun 16 '17 at 09:15
  • 1
    @Stef Well, in my second approach you loop through the vector and from there you chose which component to load. But I believe won't allow you to change components on click. My third approach, then one with *directive like* selectors, I think will do the trick for you. – SrAxi Jun 16 '17 at 09:18
  • 1
    The third approach is pretty smart, @SrAxi... I think it gonna fit ! – Stef Jun 16 '17 at 09:37
  • @Stef I hope it does the job and solves your issue! Let me know how it goes! ;) – SrAxi Jun 16 '17 at 09:38
  • 1
    By the way, I'm -sadly- surprised that such an important stuff like dynamic component remains so tricky with Angular !! – Stef Jun 16 '17 at 09:40
  • And the "ngComponentOutlet" function doesn't really convinced me at all... – Stef Jun 16 '17 at 09:41
  • 1
    @Stef Yes, well Angular did make huge changes and are still doing them. This feature will come probably, but in the meanwhile we'll need to make some workarounds. There has been huge changes from version 2 to version4 already. We are on the right path! ;) – SrAxi Jun 16 '17 at 09:42
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146849/discussion-between-stef-and-sraxi). – Stef Jun 16 '17 at 09:42
  • @Stef Well, it saved me more than once. Check this, for example: https://stackoverflow.com/questions/43143956/angular2-use-pipe-to-render-templates-dynamically – SrAxi Jun 16 '17 at 09:43
  • One last thing, If I want to get the ref of the component, should I have to use @ViewChild(AlertDangerComponent) myref: AlertDangerComponent; (for instance ?) – Stef Jun 16 '17 at 09:52
0

I've had the same issue and did some reading. The Angular world changes fast. This seems like the most official approach in the Angular guide as of this writing. See this cookbook recipe:

Angular Dynamic Component Loader

It makes use of an 'anchor' directive to tell Angular where the dynamic components will go in the template. Then it goes on to use a component factory and a view container ref to create and insert the dynamic component and while saving the instance of the newly added component.

lostdorje
  • 6,150
  • 9
  • 44
  • 86