У меня есть элемент NG Bootstrap Accordion, который при открытии отображает список карточек контактов. По какой-то причине список контактов, выводимый ngFor
, дублируется и отображается дважды. Данные не дублируются (проверял и источник данных, и отладчиком).
<div ngbAccordion class = "accordion accordion-flush">
<div ngbAccordionItem class = "accordion-item">
<h3 ngbAccordionHeader class = "accordion-header">
<button
ngbAccordionButton
class = "accordion-button text-secondary fs-5 ps-0"
>
Contacts
</button>
</h3>
<div
ngbAccordionBody
ngbAccordionCollapse
class = "accordion-body row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3"
>
<ng-container
*ngIf = "card.contacts && card.contacts.length; else noContacts"
>
<div *ngFor = "let contact of card.contacts" class = "col">
<app-contact-card [contact] = "contact"></app-contact-card>
</div>
</ng-container>
<ng-template #noContacts>
<div class = "text-secondary">No contacts</div>
</ng-template>
</div>
</div>
</div>
Если я не включу тег ng-container
в оператор ngIf
, то компонент Accordion выдаст ошибку, поскольку ngFor
изначально возвращает ноль перед отображением данных. Однако при таком коде список контактов отображается дважды (один полный список за другим полным списком, а не дублирование контактов подряд).
Я попытался добавить функцию trackBy в ngFor, но это не устранило дублирование. Любые предложения или идеи будут с благодарностью приняты. Спасибо.
Шаблон карточки контакта выглядит так
<div class = "card h-100">
<div class = "card-header d-flex">
<i class = "bi bi-person-circle fs-5 me-3"></i>
<div class = "d-flex flex-column align-self-center">
<ng-container *ngIf = "contact.first_name || contact.last_name; else noName">
<div>{{ contact.first_name + ' ' }} {{ contact.last_name }}</div>
<small>{{ contact.phone }}</small>
</ng-container>
<ng-template #noName>
<div>{{ contact.phone }}</div>
</ng-template>
</div>
</div>
<div class = "card-body">
<ng-container *ngIf = "contact.fieldsToShow.length; else noExtraData">
<div *ngFor = "let field of contact.fieldsToShow" class = "mini-card">
<div class = "field text-secondary">{{ field }}:</div>
<div class = "value text-truncate ms-1">{{ contact[field] }}</div>
</div>
<div *ngIf = "contact.totalFields > contact.fieldsToShow.length" class = "text-secondary text-center mb-n2">
<small>More</small>
</div>
</ng-container>
<ng-template #noExtraData>
<div class = "text-secondary">No extra data</div>
</ng-template>
</div>
</div>
Однако я не думаю, что это имеет какое-либо отношение к компоненту карточки контакта, потому что, если я добавлю больше строк в разделе AccordionBody
под карточками контактов, он отобразит полный набор контактов, а затем все остальные строки (и все правильно), затем снова визуализируйте контакты. Однако он не отображает ни одну из других строк.
Обновлено. Я почти уверен, что дело не в карточке контакта. Если я добавлю дополнительные строки данных под строкой, содержащей карточки контактов, будут отображены контакты, затем другие строки данных, а затем снова контакты.
Под телом аккордеона все содержимое должно быть заключено в ng-template
, при этом дублирование будет удалено.
Еще одна вещь: ngbAccordionCollapse
должен находиться в отдельном div, за которым следует другой div с директивой ngbAccordionBody
.
<div ngbAccordion class = "accordion accordion-flush">
<div ngbAccordionItem class = "accordion-item">
<h3 ngbAccordionHeader class = "accordion-header">
<button
ngbAccordionButton
class = "accordion-button text-secondary fs-5 ps-0"
>
Contacts
</button>
</h3>
<div
ngbAccordionCollapse
class = "accordion-body row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3"
>
<div ngbAccordionBody>
<ng-template> <!-- changed here! -->
<ng-container
*ngIf = "card.contacts && card.contacts.length; else noContacts"
>
<div *ngFor = "let contact of card.contacts" class = "col">
<app-contact-card [contact] = "contact"></app-contact-card>
</div>
</ng-container>
<ng-template #noContacts>
<div class = "text-secondary">No contacts</div>
</ng-template>
</ng-template> <!-- changed here! -->
</div>
</div>
</div>
</div>
Ты восхитителен. Спасибо. Можете немного объяснить, зачем там нужен шаблон? Что оно делает?
@joed4no По умолчанию ng-template не может отображаться сам по себе, поэтому в моем примере ngtemplate используется ng-bootstrap, но содержимое не отображается за пределами начальной загрузки из-за ng-шаблона. Если ng-шаблона нет, то контент используется как есть, но поскольку ng-шаблона нет, контент все равно отображается, поэтому он отображается дважды: один раз, потому что мы определили его без ng-шаблона, и еще один. потому что ng-bootstrap вставил его программно.
В дополнение к вашему ответу:
Вы также можете использовать новое значение @
, чтобы избежать всех этих оберток ng-template
и ng-container
.
Он был представлен в Angular 17 как часть нового синтаксиса потока управления
Это будет выглядеть примерно так:
@if (card.contacts && card.contacts.length) {
// You could also leave the *ngFor instead of @for
@for (contact of card.contacts; track contact ) {
<div class = "col">
<app-contact-card [contact] = "contact"></app-contact-card>
</div>
}
} @else {
<div class = "text-secondary">No contacts</div>
}
Можете ли вы отредактировать и показать нам шаблон карты-контакта приложения?