I am experimenting with angular2 2.0.0-beta.0
I have a table and the line content is generated by angular2 this way:
<table>
<tr *ngFor="#line of data">
.... content ....
</tr>
</table>
Now this works and I want to encapsulate the content into a component "table-line".
<table>
<table-line *ngFor="#line of data" [data]="line">
</table-line>
</table>
And in the component, the template has the
But now the table does no more work. Which means, the content is no longer shown in columns. In the browser, the inspector shows me that the DOM elements look like this:
<table>
<table-line ...>
<tbody>
<tr> ....
How can I make this work?
use existing table elements as selector
The table element doesn't allow <table-line>
elements as children and the browser just removes them when it finds them. You can wrap it in a component and still use the allowed <tr>
tag. Just use "tr"
as selector.
using <template>
<template>
should be allowed as well but doesn't yet work in all browsers. Angular2 actually never adds a <template>
element to the DOM, but only processes them internally, therefore this can be used in all browsers with Angular2 as well.
Attribute selectors
Another way is to use attribute selectors
@Component({
selector: '[my-tr]',
...
})
to be used like
<tr my-tr>
I found the example very usefull but it didn't work in the 2,2.3 build, so after much head scratching made it work again with a few small changes.
import {Component, Input} from '@angular/core'
@Component({
selector: "[my-tr]",
template: `<td *ngFor='let item of row'>{{item}}</td>`
})
export class MyTrComponent {
@Input("line") row:any;
}
@Component({
selector: "my-app",
template: `<h1>{{title}}</h1>
<table>
<tr *ngFor="let line of data" my-tr [line]="line"></tr>
</table>`
})
export class AppComponent {
title = "Angular 2 - tr attribute selector!";
data = [ [1,2,3], [11, 12, 13] ];
constructor() { console.clear(); }
}
MyTrComponent
to the app.module.ts
and works fine.
Here's an example using a component with an attribute selector:
import {Component, Input} from '@angular/core';
@Component({
selector: '[myTr]',
template: `<td *ngFor="let item of row">{{item}}</td>`
})
export class MyTrComponent {
@Input('myTr') row;
}
@Component({
selector: 'my-app',
template: `{{title}}
<table>
<tr *ngFor="let line of data" [myTr]="line"></tr>
</table>
`
})
export class AppComponent {
title = "Angular 2 - tr attribute selector";
data = [ [1,2,3], [11, 12, 13] ];
}
Output:
1 2 3
11 12 13
Of course the template in the MyTrComponent would be more involved, but you get the idea.
Old (beta.0) plunker.
Error: Uncaught (in promise): Error: Template parse errors: Can't bind to 'myTr' since it isn't a known property of 'tr'.
Adding 'display: contents' to the component style worked out for me.
CSS:
.table-line {
display: contents;
}
HTML:
<table>
<table-line class="table-line" [data]="line">
</table-line>
</table>
Why this works?
When instancing a component, angular (after compiling) wraps the content of the component in the DOM as follows:
<table>
<table-line>
<tr></tr>
</table-line>
</table>
But in order for the table to display properly, the tr
tags can't be wrapped by anything.
So, we add display: contents
, to this new element. As I understand, what this does is to tell the explorer that this tag should not be rendered, and display the inner content as if there was no wrapping around. So, while the tag still exists, it doesn't affect visually to the table, and the tr
tags are treated as if they were direct children of the table
tag.
If you'd like to investigate further on how contents works: https://bitsofco.de/how-display-contents-works/
contents
works, so I didn't want to put there information based on false assumptions... I added the explanation as I understand it, but if someone notice that there's an error on it please feel free to point it out ^^. I also added a link so people can investigate further... One thing I noticed, while it says that this doesn't work on IE11, I tried it out on IE 11.657.18362.0 and it worked fine (but, since I'm using angular, there might be a polyfill somewhere, so I wouldn't guarantee it 100% on other environments)
try this
@Component({
selecctor: 'parent-selector',
template: '<table><body><tra></tra></body></table>'
styles: 'tra{ display:table-row; box-sizing:inherit; }'
})
export class ParentComponent{
}
@Component({
selecctor: 'parent-selector',
template: '<td>Name</td>Date<td></td><td>Stackoverflow</td>'
})
export class ChildComponent{}
Success story sharing
tr
tag it will be used, otherwise it's default browser behavior takes place. You can also add attributes or classes to your component selector like<tr line-item>
with a component selector"tr[line-item] "
or<tr class="line-item">
with component selectortr.line-item
. This way you have full control. I haven't tried any of this myself yet in Angular2, but I'm pretty sure this works.tslint
direct me to the style guide recommending against this. I dislike turning off recommended linting rules, but as far as I can tell, it seems a fairly arbitrarily style rule and unavoidable in some situations (such as OP's problem)<li>
or similar, I don't see any reason why not to use it.