Angular2: заменить элемент хоста шаблоном компонента

Я новичок в angular вообще и в angular2 в частности. Я пытаюсь написать компонент контейнера, в котором должны быть дочерние компоненты.

Например, компонент-контейнер:

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

Дочерний компонент:

import { Component } from 'angular2/core'

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

Я хотел бы сделать эту структуру:

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

Для перевода в следующий:

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

Но вместо этого у меня есть хост-элемент контейнера и сохраненные элементы:

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

Plunk доступен здесь

Вопрос: есть ли способ удалить основные элементы и оставить только визуализированный шаблон?


person Denis Itskovich    schedule 01.03.2016    source источник
comment
см. plnkr.co/edit/7nMTnH?p=preview.   -  person Eric Martinez    schedule 02.03.2016
comment
@EricMartinez, в вашем прыжке я вижу, что вы заменили использование my-list и my-item прямым использованием ul и li. Это бессмысленно. Я имел в виду, что my-list и my-item являются компонентами, которые преобразуются в ul и li соответственно. Конечно, это всего лишь очень упрощенный пример. Реальный случай намного сложнее, но суть все та же: устранить (заменить) элементы-хозяева   -  person Denis Itskovich    schedule 02.03.2016


Ответы (5)


Это вы должны получить то, что хотите:

@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>
person Günter Zöchbauer    schedule 01.03.2016
comment
Это будет работать, но это не совсем то, что мне нужно. Это заставляет пользователя моего компонента знать о моей реализации DOM-структуры (ul/li), что делает меня менее гибким для изменения этой структуры в будущем (например, для div) - person Denis Itskovich; 02.03.2016
comment
Что вы можете сделать, так это не использовать ul и li, а my-item с CSS my-item: { display: list-item;}, тогда он ведет себя как элемент списка, но не обязательно должен быть им, и вы можете использовать селекторы элементов, которые у вас были в вашем вопросе (но с содержимым шаблона моего ответа) - person Günter Zöchbauer; 02.03.2016
comment
С CSS может быть другая проблема: я хотел бы использовать существующий bootstrap CSS и иметь возможность плавно его обновлять. Есть ли способ повторно использовать стили bootstrap для ul и li и применить их к пользовательским элементам? - person Denis Itskovich; 02.03.2016
comment
Конечно, но только тогда, когда они на самом деле являются элементами <ul> и <li>, как в моем ответе, а не когда они используются, как в вашем вопросе или моем предыдущем комментарии. - person Günter Zöchbauer; 02.03.2016
comment
@GünterZöchbauer - Есть ли альтернатива этому решению? Я попытался скопировать код, который вы предоставили, и он не работает. Спасибо. - person VtoCorleone; 23.05.2017
comment
Выдает эту ошибку. Селектор должен иметь форму кебаба и включать тире (angular.io/ guide/styleguide#style-05-02) (селектор компонентов)tslint(1) - person Don Dilanga; 08.01.2020
comment
Это просто правило стиля, которому вы можете следовать, игнорировать или соблюдать его. - person Günter Zöchbauer; 08.01.2020

Наконец я нашел решение: ввести ElementRef в MyItem и использовать его nativeElement.innerHTML:

Мой список:

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>
}

Мой элемент:

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

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

Рабочий планк здесь

person Denis Itskovich    schedule 03.03.2016
comment
@Natanael, вы можете найти рабочий планк здесь: plnkr.co/edit/um5BC7?p=preview - person Denis Itskovich; 12.06.2016
comment
@Natanael, вам нужно получить доступ к innerHTML внутри обратного вызова ngAfterContentInit. Он пуст, если вы обращаетесь к нему внутри конструктора. Я отредактировал ответ, чтобы отразить это... - person hendrix; 01.08.2016
comment
Справедливое предупреждение: не используйте InnerHTML ни для чего, кроме статического HTML. Это включает в себя привязки, другие компоненты, что угодно - это все испортит. - person ; 12.06.2019

В новых версиях angular есть действительно крутая директива, которую можно использовать и для вашего варианта использования. Тадаа: NgComponentOutlet. Удачного кодирования ;)

Пример:

@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;
}
person Luckylooke    schedule 19.12.2017
comment
А также интересный вариант NgTemplateOutlet.. этот мне помог :) - person Luckylooke; 20.12.2017
comment
Пожалуйста, приведите пример использования - person Steven Liekens; 19.02.2018
comment
Пример @StevenLiekens был в документах, но я скопировал его здесь. Используется элемент ng-container, который заменяется метаэлементом во время компиляции. - person Luckylooke; 03.08.2018
comment
Я имею в виду, можете ли вы показать, как решить проблему спрашивающего с помощью NgComponentOutlet? - person Steven Liekens; 03.08.2018

Существует хакерское обходное решение, которое может не поддерживаться в старых версиях браузера, но оно работало для моего проекта, и я думаю, что его довольно легко интегрировать без изменения или добавления большого количества кода.

Сначала вам нужно изменить селектор класса/компонента MyItem с селектора элемента.

selector: 'custom-element-name'

к селектору атрибутов

selector: '[customAttributeName]'

и, наконец, используйте элемент html slot для переноса MyItem внутри HTML-шаблона MyList

<slot customAttributeName></slot>

Полный код:

import { Component } from 'angular2/core'

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


@Component({
  selector: '[myItemAsAtribute]',
  template: `
      <li *ngFor="let item of items">{{item}}</li>

  `
})
export class MyItem {
}
person N.Nonkovic    schedule 30.01.2019
comment
когда я проверяю DOM в Chorme, я все еще вижу элемент <slot />. - person Quang Van; 20.12.2019

В последнем угловом вы можете даже не добавлять <ng-content></ng-content>

Я подал заявку, как:

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';
}

и шаблон:

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

index.html

<body>
<div app-root></div>
</body>
person Vladyn    schedule 07.03.2018
comment
Ничего не заменяется. Вывод будет div class=centered внутри корня приложения div. - person MoonStom; 22.05.2018