24

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

Khabir
  • 5,370
  • 1
  • 21
  • 33
Draftsman
  • 303
  • 1
  • 3
  • 15
  • For future readers; you might also want to consider the Dynamic Component Loader. https://angular.io/guide/dynamic-component-loader – Kildareflare Aug 16 '18 at 10:19

1 Answers1

27

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-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>
  <test-widget class="grid-item-content-002" #widget></test>
</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{}
    
Khabir
  • 5,370
  • 1
  • 21
  • 33
Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
  • Thanks for the answer. But can I add the components to the template by code? I have to store the components and display them on some kind of event. Also it would be much better if the gridComponent doesn't know anything about the testComponent (no import tag) – Draftsman May 12 '17 at 09:42
  • The problem with dynamic adding stuff, is that it kinda prevents you from using AoT, and it's also not seen as the angular way. Perhaps you should try to find another way to achieve what you want. I'll update my answer a little, to show how can you dynamically turn on and off a widget. But I'm not exactly sure what you want to achieve – Poul Kruijt May 12 '17 at 09:52
  • As you can see in my picture above, I use some kind of a widgetstore (purple boxes). After clicking them, a black box is added to the white space below. The clicked widget needs information about which component will be rendered in a black box. So I need to store the content. Little confusing I know. But thanks for your help – Draftsman May 12 '17 at 09:57
  • 2
    I've updated my answer. Hopes it makes sense a bit. But no, it's not really possible to do what you want: 'store the content'. You will lose the angular bindings if you use `[innerHtml]` for instance. I hope my answer gives you ideas to do it another way – Poul Kruijt May 12 '17 at 10:05
  • Thanks for the effort. Think I can't even get a better solution for this problem. – Draftsman May 12 '17 at 10:10
  • I came across this post because Im trying to do something like this: Place a component (or component's html) into the MatDialog's content area (Angular's material dialog). @PierreDuc In theory, can this approach be applied the same way? – chitgoks Jan 25 '18 at 02:43
  • another way could perhaps to be to use # and the componentElement: thoughts (would probably need a display: none in there somewhere)? – Rusty Rob Mar 16 '18 at 00:30
  • 1
    Why this use case isn't mentioned in the angular documentation is beyond me. Thanks for the clear explaination! – Kirk Sefchik May 12 '21 at 16:01
  • How GridTestWidgetComponent places the widget it receives in the @Input. Please explain – Mohan Ram Jan 26 '22 at 14:51