I have an Angular 2 component which renders a nice table with kitten object data.
Since some of the columns are going to be reused in a different component, I'm looking for a way to extract the <td>
into a separate component (dynamic-kitten-tds
). I can not move <td>
s which render kitten.name
and kitten.lastWashed
since they are unique to cat-o-base
component:
<table>
<tbody>
<tr *ngFor="let kitten of kittenBasket">
<td>{{ kitten.name }}</td>
<dynamic-kitten-tds [value]="kitten"></dynamic-kitten-tds>
<td>{{ kitten.lastWashed | date }}</td>
</tr>
</tbody>
<table>
The entire template of the dynamic-kitten-tds
component looks like this:
<td *ngFor="let preference of kitten.preferences">{{ preference | json }}</td>
I may not use the *ngFor
like this:
<td>{{ kitten.name }}</td>
<td *ngFor="let preference of kitten.preferences" [value]="preference"></td>
<td>{{ kitten.lastWashed | date }}</td>
This limitation comes from the business logic that must be implemented as a part of dynamic-kitten-tds
component.
The code must result in a valid DOM emission.
How do I achieve it? Using auxiliary components is fine. Using special structural directives is fine too.
I looked through some other SO questions (like this one) however didn't find quite matching problem definition.
If using auxiliary components is fine here is my thought:
dynamic-outlet.ts
@Directive({selector: '[dynamicOutlet]'})
export class DynamicOutlet implements OnChanges, OnDestroy {
@Input() dynamicOutlet: Type<any>;
@Input() dynamicOutletModel: any;
private componentRef: ComponentRef<any> = null;
constructor(private vcRef: ViewContainerRef) {}
ngOnChanges(changes: SimpleChanges) {
this.vcRef.clear();
this.componentRef = null;
if (this.dynamicOutlet) {
const elInjector = this.vcRef.parentInjector;
const componentFactoryResolver = elInjector.get(ComponentFactoryResolver);
const componentFactory = componentFactoryResolver.resolveComponentFactory(this.dynamicOutlet);
this.componentRef = componentFactory.create(elInjector);
this.componentRef.changeDetectorRef.detectChanges();
this.componentRef.instance.model = this.dynamicOutletModel;
this.vcRef.createEmbeddedView(this.componentRef.instance.template, { $implicit: this.dynamicOutletModel });
}
}
ngOnDestroy() {
if(this.componentRef) {
this.vcRef.clear();
this.vcRef = null;
}
}
}
kitten.ts
@Component({
selector: 'kitten-component',
template: `
<ng-template let-model>
<td *ngFor="let preference of model.preferences">{{ preference | json }}</td>
</ng-template>
`
})
export class Kitten {
@ViewChild(TemplateRef) template: TemplateRef<any>;
model: any;
}
and then you can use it like
view
<ng-container *dynamicOutlet="kittenComp; model: kitten"></ng-container>
component
kittenComp = Kitten;
Don't forget to add Kitten
component to entryComponents
array.
Here is Plunker Example