Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create repeatable Angular component with two rows for tables

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.

like image 422
Stefanos Kargas Avatar asked Nov 02 '25 04:11

Stefanos Kargas


1 Answers

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.

like image 147
Serhiy Avatar answered Nov 03 '25 18:11

Serhiy