angulartypescriptinputnestedcomponents

Pass Component to a Component in Angular


I have this small gridComponent:

@Component({
    selector: 'moving-grid',
    templateUrl: './grid.component.html',
    styleUrls: ['./grid.component.css']
})
  export class GridComponent {
     @Input('widgets') extComponents: Array<Component>;
  }

And a second testComponent

@Component({
  selector: 'test',
  template: `
    <div #content>I say hello<li>i</li><li>u</li><button (click)="test()">click me</button> world</div>
  `
})

export class TestComponent {
  @ViewChild('content') content: HTMLElement;

  getContent() {
    return this.content;
  }
  test() {
    console.log("test");
  }
}

Now I'm trying to pass multiple instances of testComponent to the gridComponent. Therefore I have a third Component which looks like this one:

selector: 'moving-grid-container',
template: `
        <moving-grid [widgets]="[z1,z2]"> 
          <test  class="grid-item-content-001" #z1></test>
          <test  class="grid-item-content-002" #z2></test>
        </moving-grid>
      `

Until this point, everything works like expected. But how can I render the Components from @Input in the gridComponent? My first approach was to declare a @ViewChild in the testComponent and return it with a getContent()-function. But it won't work. Can I use the ng-content directive in some way or is there a better solution?

The GridComponent looks like this. I want to display the templates of a @Input-Component inside one of the black boxes. Is it possible?

Moving-Grid Component

Thanks for any kind of help


Solution

  • You should not use an @Input to pass in the components. You can use @ContentChildren for that and an abstract WidgetComponent:

    @Component({
        selector: 'moving-grid',
        template: `
          <div class="widget-wrapper">
             <ng-container *ngFor="let widget of widgets">
                 <!-- use a ngSwitchCase here for different types-->
                 <grid-test-widget [widget]="widget" *ngIf="widget.active && widget.type === 'test'"></grid-test-widget>
             </ng-container>          
          </div>
        `,
        styleUrls: ['./grid.component.css']
    })
    export class GridComponent implements AfterContentInit {
         @ContentChildren('widget')
         widgets: QueryList<WidgetComponent>;
    
         ngAfterContentInit() {
            //your components will be available here
         }
    }
    

    The template of your third component [moving-grid-container] will stay the same, but without the [widgets] and an added #widget name:

    <moving-grid> 
      <test-widget class="grid-item-content-001" #widget [active]="false"></test-widget>
      <test-widget class="grid-item-content-002" #widget></test-widget>
    </moving-grid>
    

    Your TestWidgetComponent which will extend an abstract WidgetComponent :

    @Component({
      selector: 'test-widget',
      // ...
    })
    export class TestWidgetComponent extends WidgetComponent {
        public type: string = 'test';
    }
    

    And your WidgetComponent:

    @Directive()
    export abstract class WidgetComponent {
    
       @Input()
       public active: boolean;
    
       public type: string;
    
    }
    

    And you'll have several grid widgets based on the type of the widget:

    @Component({
      selector: 'grid-test-widget',
      template: `<div #content>I say hello<li>i</li><li>u</li><button (click)="test()">click me</button> world</div>`
    })
    export class GridTestWidgetComponent{}