* ngIf и * ngFor для одного элемента, вызывающего ошибку

У меня проблема с попыткой использовать Angular *ngFor и *ngIf на одном элементе.

При попытке *ngFor коллекцию в *ngFor коллекция считается null и поэтому происходит сбой при попытке доступа к ее свойствам в шаблоне.

@Component({
  selector: 'shell',
  template: '
    <h3>Shell</h3><button (click)="toggle()">Toggle!</button>

    <div *ngIf="show" *ngFor="let thing of stuff">
      {{log(thing)}}
      <span>{{thing.name}}</span>
    </div>
  '
})

export class ShellComponent implements OnInit {

  public stuff:any[] = [];
  public show:boolean = false;

  constructor() {}

  ngOnInit() {
    this.stuff = [
      { name: 'abc', id: 1 },
      { name: 'huo', id: 2 },
      { name: 'bar', id: 3 },
      { name: 'foo', id: 4 },
      { name: 'thing', id: 5 },
      { name: 'other', id: 6 },
    ]
  }

  toggle() {
    this.show = !this.show;
  }

  log(thing) {
    console.log(thing);
  }

}

Я знаю, что простое решение состоит в том, чтобы переместить *ngIf на уровень выше, но для таких сценариев, как зацикливание элементов списка в ul, я *ngIf либо пустой li если коллекция пустая, либо мой li обернутый в избыточные элементы контейнера.,

Пример на этом плнкр.

Обратите внимание на ошибку консоли:

EXCEPTION: TypeError: Cannot read property 'name' of null in [{{thing.name}} in [email protected]:12]

Я делаю что-то не так или это ошибка?

Ответ 1

Angular v2 не поддерживает более одной структурной директивы для одного и того же элемента.
В качестве обходного пути используйте элемент <ng-container> который позволяет вам использовать отдельные элементы для каждой структурной директивы, но он не проставляется в DOM.

<ng-container *ngIf="show">
  <div *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </div>
</ng-container>

<ng-template> (<template> до Angular v4) позволяет делать то же самое, но с другим синтаксисом, который сбивает с толку и больше не рекомендуется

<ng-template [ngIf]="show">
  <div *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </div>
</ng-template>

Ответ 2

Как все отметили, несмотря на то, что наличие нескольких шаблонных директив в одном элементе работает в Angular 1.x, это не разрешено в Angular 2. Вы можете найти дополнительную информацию здесь: https://github.com/angular/angular/issues/7315

2016 угловой 2 бета

Решение заключается в использовании <template> в качестве заполнителя, поэтому код выглядит так

<template *ngFor="let nav_link of defaultLinks"  >
   <li *ngIf="nav_link.visible">
       .....
   </li>
</template>

но по какой-то причине выше не работает в 2.0.0-rc.4 в этом случае вы можете использовать это

<template ngFor let-nav_link [ngForOf]="defaultLinks" >
   <li *ngIf="nav_link.visible">
       .....
   </li> 
</template>

Обновленный ответ 2018

С обновлениями, прямо сейчас в 2018 г. angular v6 рекомендуем использовать <ng-container> вместо <template>

так вот обновленный ответ.

<ng-container *ngFor="let nav_link of defaultLinks" >
   <li *ngIf="nav_link.visible">
       .....
   </li> 
</ng-container>

Ответ 3

У меня есть еще одно решение.

Используйте [hidden] вместо *ngIf

<div [hidden]="!show" *ngFor="let thing of stuff">

Разница в том, что *ngIf удалит элемент из DOM, в то время как [hidden] фактически играет со стилем CSS, установив display: none

Ответ 4

Как упоминал @Zyzle, и @Günter упомянул в комментарии (https://github.com/angular/angular/issues/7315), это не поддерживается.

С

<ul *ngIf="show">
  <li *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </li>
</ul>

нет пустых элементов <li>, когда список пуст. Даже элемент <ul> не существует (как и ожидалось).

Когда список заполняется, нет избыточных элементов контейнера.

обсуждение github (4792), которое @Zyzle упоминает в своем комментарии, также представляет другое решение с использованием <template> (ниже я использую вашу оригинальную разметку и тире; <div> а):

<template [ngIf]="show">
  <div *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </div>
</template>

Это решение также не содержит никаких дополнительных/избыточных элементов контейнера.

Ответ 5

Вы не можете иметь ngFor и ngIf на одном элементе. Что вы могли бы сделать, так это приостановить ngFor массива, который вы используете в ngFor до ngFor пор, пока в вашем примере не будет нажата ngFor переключения.

Вот основной (не отличный) способ, которым вы могли бы сделать это: http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P

Ответ 6

Это будет работать, но элемент все равно останется в DOM.

.hidden{
    display: none;
}

<div [class.hidden]="!show" *ngFor="let thing of stuff">
  {{log(thing)}}
  <span>{{thing.name}}</span>
</div>

Ответ 7

В таблице ниже перечислены только те элементы, которые имеют значение "beginner". Для предотвращения нежелательных строк в html требуется *ngFor и *ngIf.

Первоначально были теги *ngIf и *ngFor в одном теге <tr>, но не работают. Добавлен <div> для цикла *ngFor и помещен *ngIf в тег <tr>, работает как ожидалось.

<table class="table lessons-list card card-strong ">
  <tbody>
  <div *ngFor="let lesson of lessons" >
   <tr *ngIf="lesson.isBeginner">
    <!-- next line doesn't work -->
    <!-- <tr *ngFor="let lesson of lessons" *ngIf="lesson.isBeginner"> -->
    <td class="lesson-title">{{lesson.description}}</td>
    <td class="duration">
      <i class="fa fa-clock-o"></i>
      <span>{{lesson.duration}}</span>
    </td>
   </tr>
  </div>
  </tbody>

</table>

Ответ 8

Обновлено до angular2 beta 8

Теперь, начиная с angular2 beta 8, мы можем использовать *ngIf и *ngFor на том же компоненте здесь.

Альтернативные

Иногда мы не можем использовать теги HTML внутри другого, как в tr, th (table) или в li (ul). Мы не можем использовать другой тег HTML, но нам нужно выполнить некоторые действия в одной и той же ситуации, чтобы мы могли таким образом использовать тег .

ngДля использования шаблона:

<template ngFor #abc [ngForOf]="someArray">
    code here....
</template>

ngЕсли использовать шаблон:

<template [ngIf]="show">
    code here....
</template>    

Подробнее о структурных директивах в angular2 см. здесь.

Ответ 9

Вы не можете использовать более одного Structural Directive в Angular для одного и того же элемента, это создает плохую путаницу и структуру, поэтому вам нужно применять их в двух отдельных вложенных элементах (или вы можете использовать ng-container), прочитайте это выражение от команды Angular:

Одна структурная директива для каждого элемента узла

Когда-нибудь вам захочется повторить блок HTML, но только когда конкретное условие верно. Вы попытаетесь установить как * ngFor, так и a * ngIf на одном и том же элементе хоста. Angular не позволит вам. Вы можете примените только одну структурную директиву к элементу.

Причина проста. Структурные директивы могут делать сложные вещи с элементом-хозяином и его потомками. Когда лежат две директивы претендовать на тот же элемент хоста, который имеет приоритет? Который должен идти первым, NgIf или NgFor? Может ли NgIf отменить эффект из NgFor? Если да (и, похоже, должно быть так), как следует Angular обобщают возможность отмены для других структурных директивы?

На эти вопросы нет простых ответов. Запрещение нескольких структурные директивы делают их спорными. Там простое решение для этот прецедент: поставьте * ngIf на элемент контейнера, который обертывает * ngFor. Один или оба элемента могут быть ng-container, поэтому вам не нужно вводить дополнительные уровни HTML.

Таким образом, вы можете использовать ng-container (Angular4) в качестве обертки (будет удален из dom) или div или span, если у вас есть класс или некоторые другие атрибуты, как показано ниже:

<div class="right" *ngIf="show">
  <div *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </div>
</div>

Ответ 10

<div *ngFor="let thing of show ? stuff : []">
  {{log(thing)}}
  <span>{{thing.name}}</span>
</div>

Ответ 11

<!-- Since angular2 stable release multiple directives are not supported on a single element(from the docs) still you can use it like below -->


<ul class="list-group">
                <template ngFor let-item [ngForOf]="stuff" [ngForTrackBy]="trackBy_stuff">
                    <li *ngIf="item.name" class="list-group-item">{{item.name}}</li>
                </template>
   </ul>

Ответ 12

Вы не можете использовать несколько структурных директив для одного элемента. Оберните ваш элемент в ng-template и используйте там одну структурную директиву

Ответ 13

Вы также можете использовать ng-template (вместо шаблона. См. Примечание по поводу использования тега template) для применения * ngFor и ngIf к одному и тому же элементу HTML. Вот пример, где вы можете использовать как * ngIf, так и * ngFor для одного и того же элемента tr в угловой таблице.

<tr *ngFor = "let fruit of fruiArray">
    <ng-template [ngIf] = "fruit=='apple'>
        <td> I love apples!</td>
    </ng-template>
</tr>

где fruiArray = ['apple', 'banana', 'mango', 'pineapple'].

Замечания:

Предостережение об использовании только тега template вместо тега ng-template заключается в том, что в некоторых местах он вызывает StaticInjectionError.

Ответ 14

в формате HTML:

<div [ngClass]="{'disabled-field': !show}" *ngFor="let thing of stuff">
    {{thing.name}}
</div>

в css:

.disabled-field {
    pointer-events: none;
    display: none;
}