I have two components. The first one represents a table of items and the second one represents one item. The first one is repeating the second one many times.
The List Component (app-list
):
<table>
<tr *ngFor="let item of items" [item]="item" app-item></tr>
</table>
The Item Component (app-item
):
<td>
<img src="https://someimage.com/{{item.img}}.jpg">
</td>
<td>
<h3>{{item.name}}</h3>
</td>
<td>
{{item.description}}
</td>
In order for this to work, I had to use an attribute selector for the app-item
component:
@Component({
selector: '[app-item]'
})
This works perfectly.
Now I want to improve it and add a second row in each app-item
. My problem is that the tr
tag lies in the app-list
component instead of the app-item
component. I thought that if I move it to the app-item
component, I could add another tr
and be able to show two rows per one item. So this is what I did. After that I used ng-container
to repeat the items in my app-list
, in order to avoid adding a wrapper tag around my two rows:
<ng-container *ngFor="let item of items" [item]="item" app-item></ng-container>
This solution did not work. I got the following error:
ERROR TypeError: el.setAttribute is not a function
at EmulatedEncapsulationDomRenderer2.push../node_modules/@angular/platform-browser/fesm5/platform-browser.js.DefaultDomRenderer2.setAttribute (platform-browser.js:1089)
at EmulatedEncapsulationDomRenderer2.push../node_modules/@angular/platform-browser/fesm5/platform-browser.js.EmulatedEncapsulationDomRenderer2.applyToHost (platform-browser.js:1157)
at DomRendererFactory2.push../node_modules/@angular/platform-browser/fesm5/platform-browser.js.DomRendererFactory2.createRenderer (platform-browser.js:1015)
Can you help me resolve this error or suggest another implementation?
EDIT: SOLUTION
The better version @Serhiy is suggesting
The table:
<table>
<app-item *ngFor="let item of items" [item]="item" remove-component-tag></app-item>
</table>
The directive:
import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[remove-component-tag]'
})
export class RemoveComponentTagDirective {
constructor(private el: ElementRef) {
let element = el.nativeElement;
let children = el.nativeElement.childNodes;
setTimeout(()=>{
let reversedChildren = [];
children.forEach(child => {
reversedChildren.unshift(child);
});
reversedChildren.forEach(child => {
element.parentNode.insertBefore(child, element.nextSibling);
});
element.remove(element);
}, 0);
}
}
The timeout is necessary for some reason and works even with 0.
I can't see the right "angular" way to do it, but you should be able to use directives to clear your html during render.
Saw this approach in comments here: Angular2 : render a component without its wrapping tag
I tried that and it worked for me:
Parent component:
<table>
<div *ngFor="let item of items">
<app-item [item]="item" remove-wrapper></app-item>
</div>
</table>
Child component:
<tr>
<td>
<img src="https://someimage.com/{{item.img}}.jpg">
</td>
<td>
<h3>{{item.name}}</h3>
</td>
<td>
{{item.description}}
</td>
</tr>
<tr>
<td>
<img src="https://someimage.com/{{item.img}}.jpg">
</td>
<td>
<h3>{{item.name + ' 2'}}</h3>
</td>
<td>
{{item.description + ' 2'}}
</td>
</tr>
Directive:
@Directive({
selector: '[remove-wrapper]'
})
export class RemoveWrapperDirective {
constructor(private el: ElementRef) {
let parent = el.nativeElement.parentElement;
let children = el.nativeElement.childNodes;
setTimeout(()=>{
parent.parentNode.insertBefore(children[1], parent.nextSibling);
parent.parentNode.insertBefore(children[0], parent.nextSibling);
parent.remove(parent);
}, 10);
}
}
Without a timeout, it crashed for me. The code can be improved, but you can start from here.