Почему этот цикл *ngFor отображает данные дважды?

У меня есть элемент 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 под карточками контактов, он отобразит полный набор контактов, а затем все остальные строки (и все правильно), затем снова визуализируйте контакты. Однако он не отображает ни одну из других строк.

Можете ли вы отредактировать и показать нам шаблон карты-контакта приложения?

Kaperto 26.06.2024 03:47

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

joed4no 26.06.2024 03:57
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
5
2
72
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Под телом аккордеона все содержимое должно быть заключено в 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>

Демо-версия Stackblitz

Ты восхитителен. Спасибо. Можете немного объяснить, зачем там нужен шаблон? Что оно делает?

joed4no 26.06.2024 22:46

@joed4no По умолчанию ng-template не может отображаться сам по себе, поэтому в моем примере ngtemplate используется ng-bootstrap, но содержимое не отображается за пределами начальной загрузки из-за ng-шаблона. Если ng-шаблона нет, то контент используется как есть, но поскольку ng-шаблона нет, контент все равно отображается, поэтому он отображается дважды: один раз, потому что мы определили его без ng-шаблона, и еще один. потому что ng-bootstrap вставил его программно.

Naren Murali 27.06.2024 06:11

В дополнение к вашему ответу:
Вы также можете использовать новое значение @, чтобы избежать всех этих оберток 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>
    }

Другие вопросы по теме