Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass a component type to render to another component in Angular?

I have a base component (MyBaseComponent) receiving data from it's invoker (App).

I want to know if it's possible for my base component to be told by it's invoker what 'display' component it should use -- MyDisplayComponent or MyOtherDisplayComponent.

I.e., I want to do something along the lines of:

<my-base [data]="names">
    <my-other-display></my-other-display>
</my-base>

and in a different location, something like:

<my-base [data]="names">
    <my-display></my-display>
</my-base>

But I'm unable to figure out how to do this. I've tried different use of <ng-content>, but I don't think it provides for this level of flexibility. Additionally ComponentFactoryResolver sounds like it could work, but again I'm not sure how it could work in an iterative fashion, as well as pass data to the child controls.

Here's a working Plunker, with the code outlined below, which hopefully conveys what I'm attempting.

app.ts

import {Component, NgModule, VERSION} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {MyBaseComponent} from './my-base.component'
import {MyDisplayComponent} from './my-display.component'
import {MyOtherDisplayComponent} from './my-other-display.component'


@Component({
  selector: 'my-app',
  template: `
    <div>
      <my-base [data]="names">

      <!-- Is it possible to specify the 'display component' type to use here? -->

      </my-base>
    </div>
  `,
})
export class App {

  names: string[] = ["bill", "bob", "ben"];

  constructor() {
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, MyBaseComponent, MyDisplayComponent, MyOtherDisplayComponent ],
  bootstrap: [ App ]
})
export class AppModule {}

my-base.component.ts

import {Component, Input} from '@angular/core'

@Component({
  selector: 'my-base',
  template: `
  <div *ngFor="let datum of data">

    <!-- Instead of specifying the display components here,
    how can I specify the components type to use for display
    at the 'app.ts' level? -->

    <!--<my-display [value]="datum"></my-display>-->
    <my-other-display [value]="datum"></my-other-display>
  </div>
  `,
})
export class MyBaseComponent {

  @Input()
  data: string[];

  constructor() {
  }
}

my-display.component.ts

import {Component, Input} from '@angular/core'

@Component({
  selector: 'my-display',
  template: `
  <div>{{value}}</div>
  `,
})
export class MyDisplayComponent {
  @Input()
  value:string;

  constructor() {
  }
}

my-other-display.component.ts

//our root app component
import {Component, Input} from '@angular/core'

@Component({
  selector: 'my-other-display',
  template: `
  <div>{{value}}! {{value}}! {{value}}!</div>
  `,
})
export class MyOtherDisplayComponent {

  @Input()
  value:string;

  constructor() {
  }
}

Again, the big 'ask' here is whether it's possible to specify the display components for use at the app.ts (invoker) level so that the my-base.component.ts file can be ignorant of the specific implementation used in a given situation.

It may be that the my-base.component.ts file needs to pass a class name or a type to use that adheres to some kind of interface...?

Thanks in advance for any help.

like image 896
Cuga Avatar asked Jun 01 '17 18:06

Cuga


3 Answers

You may be able to use ngComponentOutlet to choose the component and probably ngIf or switch for which to show, I'm not sure what your going for.

Plunker

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

 // in module
entryComponents : [ MyDisplayComponent, MyOtherDisplayComponent],
like image 159
Dylan Avatar answered Oct 12 '22 15:10

Dylan


1) You can try using ngTemplateOutlet directive like

my-base.component.ts

@Component({
  selector: 'my-base',
  template: `
    <div *ngFor="let datum of data">
      <ng-container *ngTemplateOutlet="tmpl; context: { $implicit: datum }"></ng-container>
    </div>
  `,
})
export class MyBaseComponent {
  @ContentChild(TemplateRef) tmpl: TemplateRef<any>;

  @Input() data: string[];
}

app.component.ts

@Component({
  selector: 'my-app',
  template: `
    <my-base [data]="names">
      <ng-template let-item>
        <my-other-display [value]="item"></my-other-display>
      </ng-template>
    </my-base>

    <my-base [data]="names">
      <ng-template let-item>
        <my-display [value]="item"></my-display>
      </ng-template>
    </my-base>
  `,
})
export class App {
  names: string[] = ["bill", "bob", "ben"];
}

Forked Plunker

2) Using ngForTemplate. Almost the same approach with only one difference in

my-base.component.ts

@Component({
  selector: 'my-base',
  template: `<div *ngFor="let datum of data; template: tmpl"></div>`
})
export class MyBaseComponent {
  @ContentChild(TemplateRef) tmpl: TemplateRef<any>;

  @Input() data: string[];
}

Forked Plunker ngForTemplate

like image 34
yurzui Avatar answered Oct 12 '22 15:10

yurzui


instead of ng-content you can simply solve using if and else.

base component will pass true or false value to render

app.ts

 <my-base [data]="names" [isValid]=false>

MyBaseComponent.ts

 <div *ngIf="isValid;then content else other_content"></div>

<ng-template #content> <div *ngFor="let datum of data"> 
    <my-display [value]="datum"></my-display>
  </div></ng-template>
<ng-template #other_content> <div *ngFor="let datum of data">

    <my-other-display [value]="datum"></my-other-display>
  </div></ng-template>
like image 1
CharanRoot Avatar answered Oct 12 '22 16:10

CharanRoot