2

I'm creating template components to be loaded in the parent component depending on what response I get from the server. I'll give you a brief example of what I'm trying to do in pseudo code.

This is my html parent component:

<div class="parent-container">
    <div *ngIf="template1"> (load template1.component) </div>
    <div *ngIf="template2"> (load template2.component) </div>
    etc...
</div>

then I would have the different components (for sake of brevity I will just list one)

<div class="child-container">
    <div> {{userName}} </div>
    <div> {{contactNo}} </div>
    <div> {{address}} </div>
</div>

so on ngInit the parent makes an http request to the server and gets a value. Depends on the value in the parent, I should be able to load the child template into the parent and display it. So, once loaded, the page would look like this:

<div class="parent-container">
    <div class="child-container">
        <div> {{userName}} </div>
        <div> {{contactNo}} </div>
        <div> {{address}} </div>
    </div>
</div>

Is it possible in angular? How can i create it? Thanks

[edit]

I implemented what dee zg suggested, this is the code:

@ViewChild(HostDirective) host: HostDirective;

OnNgInit(){}

//switch will be a response from a server 
selector(switch){
    switch(switch) { 
       case 'component1': { 
         this.loadComponent(Component1);
         break; 
      }  
      default: { 

         break; 
      } 
   } 

  }

  loadComponent(component){   


    var componentFactory = this._componentFactoryResolver.resolveComponentFactory(component);
    var viewContainerRef = this.host;
    this.viewContainerRef.clear();
    var componentRef = this.viewContainerRef.createComponent(componentFactory);

    (componentRef.instance);

  }

this is what is inside my host

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[host]',
})
export class HostDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}

and the html

<ng-template host></ng-template>

funny thing is that if whatever is inside loadComponent will be loaded into OnInit, then it will work. If I call it from loadComponent I will get that host is undefined.

Sivvio
  • 297
  • 2
  • 7
  • 22
  • Are you aware of which are the components you could actually load? Cause if so, then your current approach works. It's not the most elegant approach, but it works – Osman Cea Oct 05 '17 at 16:23
  • yes I am, but I would like to have one if statement in which I will load whichever component the backend is telling me to. The parent component, depending on the response, will choose which template component to use for that case, and then parse the information to the html and render the component – Sivvio Oct 05 '17 at 16:34
  • ***edit: look at dee zg answer***. ok, there's a way to load components dynamically inside a `ng-template` tag using `ViewContainerRef` and `ComponentFactoryResolver `, but you would still have to "declare" your dynamic components in you parent component decorator, give me some time and I can setup a plunker for you – Osman Cea Oct 05 '17 at 17:01
  • what triggers your `selector(switch)`? – dee zg Oct 06 '17 at 17:17
  • is basically a response from the server. so I do an http request to it, if I find the property of the response as "Component1" then it will go to switch, and so for the rest. For sake of brevity I omitted all the others, but it's just the same thing over and over – Sivvio Oct 06 '17 at 17:20
  • from erros you are mentioning, you are trying to instantiate the component even before the container exists and one potential reason might be that you just call it too early in components lifecycle. you should not do it before on init. at what time of component lifetime to you fire your http request which, if i understand correctly, fires this create method? – dee zg Oct 07 '17 at 03:50
  • it can be done in any istance, but usually done after few seconds, so I guess the init is already instantiated. – Sivvio Oct 09 '17 at 11:24
  • Could it be a solution, to store in an array all the instantiated components, and just call the rendering of them in the switch case? – Sivvio Oct 09 '17 at 11:24

2 Answers2

4

in template:

<ng-template #yourComponentHost></ng-template>

in component:

@ViewChild('yourComponentHost', { read: ViewContainerRef })
  yourComponentHost;
.
.
.
const componentFactory = this._componentFactoryResolver.resolveComponentFactory(YourComponentType1);
    const viewContainerRef = this.yourComponentHost;
    viewContainerRef.clear();
    const componentRef = viewContainerRef.createComponent(componentFactory);

    const yourComponentType1Instance = (<YourComponentType1>componentRef.instance);

From here, you have an access to your component through yourComponentType1Instance. Of course, you'll do your own switch logic in resolveComponentFactory to use component you need based on conditions you want.

dee zg
  • 13,793
  • 10
  • 42
  • 82
  • I'm facing an issue with this piece of code: (componentRef.instance); , I want it to be dynamic. I'm parsing the component through a method (so basically I will be parsing YourComponentType1, or 2 or 3), but if I parse it that way I get an error that says: Error: No component factory found for [object Object]. Did you add it to @NgModule.entryComponents? any idea how to resolve? – Sivvio Oct 06 '17 at 14:12
  • oh, yes, absolutely! components created with `componentFactory` must be added to `entryComponents`: https://angular.io/guide/dynamic-component-loader#resolving-components. Otherwise, angular would have no idea what `YourComponentType1` actually is. – dee zg Oct 06 '17 at 14:22
  • 1
    I did but same result. Debugging I found that whenever I parse yourcomponent1, it is an object (if instead I did it on ngInit it would be parsed as a component). so I parse yourcomponent1.constructor to initiate the component. what happens next is that this.yourComponentHost is undefined – Sivvio Oct 06 '17 at 14:36
  • hm...let me ask, just to be sure, what exactly do you mean by 'parse `yourcomponent1`? – dee zg Oct 06 '17 at 14:42
  • if you're talking about switching between components you want to instantiate by string, then this might be useful: https://stackoverflow.com/questions/40062970/how-to-use-resolvecomponentfactory-but-with-a-string-as-a-key – dee zg Oct 06 '17 at 14:49
  • also, full working example of `componentFactory`: https://netbasal.com/dynamically-creating-components-with-angular-a7346f4a982d – dee zg Oct 06 '17 at 14:50
  • so basically I have a switch statement. for each case I will call loadComponent(Component). loadComponent will have basically your code. when I arrive to the line: const viewContainerRef = this.yourComponentHost; it will find it as undefined and then throw this error Cannot read property 'viewContainerRef' of undefined – Sivvio Oct 06 '17 at 14:53
  • oh, then its another issue. did you add the part into your templare with `#yourComponentHost` and a `ViewChild` decorator pointing to it? because the error suggests you dont even have a reference to the template. it might be a good idea if you post the whole code into your question and we take a look. – dee zg Oct 06 '17 at 15:15
  • funny thing is that it works on ngOnInit(), but not on method call, that makes me think that after the view is loaded, it refreshes? – Sivvio Oct 06 '17 at 15:27
  • well, it depends on when do you call your method? perhaps in constructor? you should not call it before `OnInit`: https://angular.io/guide/lifecycle-hooks – dee zg Oct 06 '17 at 15:32
  • Nope, I have constructor(), ngOnInit() then loadComponent() – Sivvio Oct 06 '17 at 15:44
  • please show your whole component code in your initial question – dee zg Oct 06 '17 at 15:44
1

It's a little bit confusing what you are looking for. Because with your problem, why don't you create component for you child.

<div class="parent-container">
    <div *ngIf="template1"> <template1 [myData]="myData"></template1> </div>
    <div *ngIf="template2"> <template2 [myData]="myData"></template2> </div>
    etc...
</div>
Wandrille
  • 6,267
  • 3
  • 20
  • 43