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>
forwardRefhere is redundantforwardRef(() => SimpleComponent)- read here why What isforwardRefin Angular and why we need itMultiContainerItemDirectiveis redundant here, you could use@ContentChildren(SimpleComponent) public items. Now the question is why don't use simply useng-contentlike this<div <ng-content select="app-simple"></ng-content></div>?forwaredRefand it still works - I will have a look at that article! For your second question: In the end, I don't know aboutSimpleComponent- I want to support just any component that implements the Interface.<div <ng-content select="app-simple"></ng-content></div>?app-simpleas 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 ofmulti-containercould just throw in aSimpleComponent, someOtherComponentand then again anotherSimpleComponent. The multi-container would render them (with some additional markup around each component) and also do something with them (through the interface).