34

I'm new to angular in general and to angular2 specifically. I'm trying to write a container component, which should have child components in it.

For example, container component:

@Component({
  selector: 'my-list',
  template: `
    <ul>
      <ng-content></ng-content>
    </ul>
  `
})
export class MyList {
}

Child component:

import { Component } from 'angular2/core'

@Component({
  selector: 'my-item',
  template: `
    <li>
      <ng-content></ng-content>
    </li>
  `
})
export class MyItem {
}

I'd like to make this structure:

<my-list>
    <my-item>One</my-item>
    <my-item>Two</my-item>
</my-list>

To be rendered to the following one:

<my-list>
    <ul>
        <li>One</li>
        <li>Two</li>
    </ul>
</my-list>

But instead, I have the host element of the container and the items preserved as well:

<my-list>
    <ul>
        <my-item>
            <li>One</li>
        </my-item>
        <my-item>
            <li>Two</li>
        </my-item>
    </ul>
 </my-list>

Plunk is available here

Question: is there a way to eliminate the host elements and to leave only the rendered template?

yankee
  • 38,872
  • 15
  • 103
  • 162
Denis Itskovich
  • 4,383
  • 3
  • 32
  • 53
  • see https://plnkr.co/edit/7nMTnH?p=preview – Eric Martinez Mar 01 '16 at 21:36
  • 2
    @EricMartinez, in your plunk I see that you replaced the usage of `my-list` and `my-item` by direct usage of `ul` and `li`. This is pointless. I meant `my-list` and `my-item` to be the components, which render themselves to `ul` and `li` accordingly. Of course, this is just a very simplified example. The real case is much more complex, but the main point is still the same: to eliminate (replace) host elements – Denis Itskovich Mar 01 '16 at 21:46

4 Answers4

24

This you should get what you want:

@Component({
  selector: 'ul[my-list]',
  template: `
    <ng-content></ng-content>
  `
})
export class MyList {
}
@Component({
  selector: 'li[my-item]',
  template: `
    <ng-content></ng-content>
  `
})
export class MyItem {
...
}
<ul my-list>
    <li my-item>One</li my-item>
    <li my-item>Two</li my-item>
</ul my-list>
Fmerco
  • 1,160
  • 5
  • 20
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 3
    This will work, but it's not exactly what I need. This causes the user of my component to be aware of my implementation DOM structure (ul/li) - making me much less flexible to change this structure in future (e.g. to divs) – Denis Itskovich Mar 01 '16 at 21:25
  • 1
    What you can do is to not use `ul` and `li` but `my-item` with CSS `my-item: { display: list-item;}`, then it behaves like a list item but doesn't have to be one and you can use the element selectors you had in your question (but with the template content of my answer) – Günter Zöchbauer Mar 01 '16 at 21:27
  • With CSS there might be another problem: I would like to use existing `bootstrap` CSS and to be able to update it smoothly. Is there a way to reuse `bootstrap`'s styles for `ul` and `li` and to apply them on custom elements? – Denis Itskovich Mar 01 '16 at 21:31
  • Sure, but only when they are actually `
      ` and `
    • ` elements like in my answer not when used like in your question or my previous comment.
    – Günter Zöchbauer Mar 01 '16 at 21:37
  • @GünterZöchbauer - Is there an alternative to this solution? I tried copying in the code that you supplied and it does not work. Thanks. – VtoCorleone May 22 '17 at 23:35
  • It throws this error The selector should be kebab-cased and include a dash (https://angular.io/guide/styleguide#style-05-02) (component-selector)tslint(1) – Don Dilanga Jan 08 '20 at 17:35
  • That's just a style rule that you can decide to follow, ignore, or comply with. – Günter Zöchbauer Jan 08 '20 at 19:25
  • Does this work in 2022 ? I tried it & got an error & the Angular interpreter did not expect the `my-item` of the closing tag. – Nice Books Apr 17 '22 at 12:23
13

Finally I found solution: injecting ElementRef to MyItem and using its nativeElement.innerHTML:

MyList:

import { Component, ContentChildren, QueryList } from 'angular2/core'
import { MyItem } from './myitem'

@Component({
  selector: 'my-list',
  template: `
    <ul>
      <li *ngFor="#item of items" [innerHTML]="item.innerHTML"></li>
    </ul>
  `
})
export class MyList {
  @ContentChildren(MyItem) items: QueryList<MyItem>
}

MyItem:

import { Directive, Inject, ElementRef } from 'angular2/core'

@Directive({selector: 'my-item'})
export class MyItem {
  constructor(@Inject(ElementRef) element: ElementRef) {
    this.innerHTML = element.nativeElement.innerHTML
  }
}

Working plunk is here

yankee
  • 38,872
  • 15
  • 103
  • 162
Denis Itskovich
  • 4,383
  • 3
  • 32
  • 53
  • @Natanael, you can find a working plunk here: https://plnkr.co/edit/um5BC7?p=preview – Denis Itskovich Jun 12 '16 at 12:33
  • @Natanael you have to access innerHTML inside ngAfterContentInit callback. It is empty if you access it inside constructor. I edited answer to reflect this... – hendrix Aug 01 '16 at 12:25
  • 7
    Fair warning: Do not use InnerHTML for anything other than **static** HTML. This includes bindings, other components, whatever - it will mess things up –  Jun 12 '19 at 15:07
4

New angular versions have really cool directive, which may be used also for your use case. Tadaa: NgComponentOutlet. Happy coding ;)

Example:

@Component({selector: 'hello-world', template: 'Hello World!'})
class HelloWorld {
}

@Component({
  selector: 'ng-component-outlet-simple-example',
  template: `<ng-container *ngComponentOutlet="HelloWorld"></ng-container>`
})
class NgTemplateOutletSimpleExample {
  // This field is necessary to expose HelloWorld to the template.
  HelloWorld = HelloWorld;
}
Luckylooke
  • 4,061
  • 4
  • 36
  • 49
-3

In the latest angular you can even avoid adding <ng-content></ng-content>

I've applied is like:

import {Component} from '@angular/core';

@Component({
    selector: 'div[app-root]',
    templateUrl: 'app.component.html',
    styleUrls: ['app.component.css']
})
export class AppComponent {
    title = 'Delegates Presence Reporting';
}

and the template:

<div class="centered">
<h1>{{title}}</h1>
</div>

index.html

<body>
<div app-root></div>
</body>
Vladyn
  • 437
  • 1
  • 5
  • 14