3

In my component, I have a list of components that I received via @ContentChildren. Now I want to render these components in my view, but I am stuck with how to do so. I experimented with ng-container and ng-content, but I had no success with them. I was thinking of something along the lines:

<component-renderer [component]="myComponent"></component-renderer>

But I have not found anything that offers this functionality. I put together a full (as minimal as I could) example to get the picture (maybe I already went wrong some step before?). It is also important that I get the reference to those components in my component, as they will all implement a specific interface that I want to use:

MyInterface.ts

import { InjectionToken } from "@angular/core";

export const MyInterfaceToken = new InjectionToken<MyInterface>('MyInterfaceToken');

// This interface will be implemented by the "child" components.
export interface MyInterface {
    doSomething(): void;
}

simple.component.ts

import { Component, OnInit, Input, forwardRef } from '@angular/core';
import { MyInterface, MyInterfaceToken } from '../MyInterface';

@Component({
  selector: 'app-simple',
  template: '<div>{{ value }}</div>',
  providers: [{
    provide: MyInterfaceToken, useExisting: SimpleComponent
  }]
})
export class SimpleComponent implements MyInterface {
  @Input() public value: string;

  doSomething(): void {
    alert(this.value);
  }
}

multi-container.component.ts

import { Component, OnInit, Self, Inject, Optional, Directive, ContentChildren, AfterContentInit, QueryList } from '@angular/core';
import { MyInterface, MyInterfaceToken } from '../MyInterface';

// Directive to retreive the components, partially taken from:
// https://github.com/angular/angular/issues/8277
@Directive({ selector: '[multi-container-item]' })
export class MultiContainerItemDirective {
  constructor(@Inject(MyInterfaceToken) @Self() @Optional() public component: MyInterface) {}
}

@Component({
  selector: 'app-multi-container',
  templateUrl: './multi-container.component.html'
})
export class MultiContainerComponent implements AfterContentInit {
  @ContentChildren(MultiContainerItemDirective) public items: QueryList<MultiContainerItemDirective>;

  public ngAfterContentInit() {
    console.log(this.items); // Inspect in console => got both items.
    this.items.first.component.doSomething(); // works.
  }

  public getComponents() {
    return this.items.map(x => x.component);
  }
}

usage example

<app-multi-container>
  <app-simple [value]="'Test1'" multi-container-item></app-simple>
  <app-simple [value]="'Test2'" multi-container-item></app-simple>
</app-multi-container>

To put the question short: What do I put into the html of multi-container to render the items? Something like:

<div *ngFor="let component of getComponents()">
  <component-renderer [component]="component"></component-renderer>
</div>
11
  • forwardRef here is redundant forwardRef(() => SimpleComponent) - read here why What is forwardRef in Angular and why we need it Commented Nov 23, 2017 at 7:41
  • also MultiContainerItemDirective is redundant here, you could use @ContentChildren(SimpleComponent) public items. Now the question is why don't use simply use ng-content like this <div <ng-content select="app-simple"></ng-content></div>? Commented Nov 23, 2017 at 7:46
  • @AngularInDepth.com thanks, you're right, I can remove forwaredRef and it still works - I will have a look at that article! For your second question: In the end, I don't know about SimpleComponent - I want to support just any component that implements the Interface. Commented Nov 23, 2017 at 7:48
  • I see, cool, right strategy :). So what about using <div <ng-content select="app-simple"></ng-content></div>? Commented Nov 23, 2017 at 7:57
  • Using app-simple as the selector in my view would (if I understand correclty) restrict the supported child components to be SimpleComponents. I want to support any component that implements the interface, as well as any combination of them. So the user of multi-container could just throw in a SimpleComponent, some OtherComponent and then again another SimpleComponent. The multi-container would render them (with some additional markup around each component) and also do something with them (through the interface). Commented Nov 23, 2017 at 8:00

1 Answer 1

1

You can simply use ng-content in the MultiContainerComponent component:

<div><ng-content select="[multi-container-item]"></ng-content></div>
Sign up to request clarification or add additional context in comments.

1 Comment

What about if you need to dynamically set which of the elements in ContentChildren to display? As opposed to a static class selector like this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.