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

473

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

При попытке перебрать коллекцию в *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уровень выше, но для сценариев, таких как циклическое перемещение по элементам списка в a ul, я бы в конечном итоге либо пуст, liесли коллекция пуста, либо мой lis обернут в избыточные элементы контейнера.

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

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

EXCEPTION: TypeError: Cannot read property 'name' of null in [{{thing.name}} in ShellComponent@5:12]

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

garethdn
источник
stackoverflow.com/questions/40529537/… я бы пошел с контейнером ng
Роберт
1
Возможная копия углового отфильтрованного стола
Cobus Kruger

Ответы:

680

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>
Гюнтер Цохбауэр
источник
5
Большое спасибо. Удивительно, но до сих пор нет документов: github.com/angular/angular.io/issues/2303
Алекс Фуэнтес,
7
Как будет выглядеть код, когда у нас должен быть * ngIf внутри * ngFor? Т.е. условие IF будет основано на значении элемента цикла.
Юврай Патил
22
Просто положите ngForна <ng-container>элемент и ngIfна <div>. Вы также можете иметь два вложенных <ng-container>упаковки <div>. <ng-container>это просто вспомогательный элемент, который не будет добавлен в DOM.
Гюнтер Цохбауэр
3
Я бы предложил использовать <ng-container>. Он ведет себя так же, как и <template>позволяет использовать «нормальный» синтаксис для структурных директив.
Гюнтер Цохбауэр
2
Документация гласит : «Одна структурная директива на элемент хоста»: «Для этого варианта использования есть простое решение: поместите * ngIf в элемент контейнера, который оборачивает элемент * ngFor». - просто повторюсь
Херингер
71

Как все отметили, несмотря на то, что наличие нескольких шаблонных директив в одном элементе работает в 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 году угловые v6 рекомендуют использовать <ng-container>вместо<template>

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

<ng-container *ngFor="let nav_link of defaultLinks" >
   <li *ngIf="nav_link.visible">
       .....
   </li> 
</ng-container>
Имал Хасаранга Перера
источник
29

Как упоминали @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>s):

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

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

Марк Райкок
источник
1
Я не уверен, почему это не принятый ответ. <template>это способ добавить родительский элемент, который не будет отображаться в выводе.
Эван Плейс,
8

в формате HTML:

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

в css:

.disabled-field {
    pointer-events: none;
    display: none;
}
мойтаба рамезани
источник
5

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

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

Zyzle
источник
Почему он не может иметь оба?
Уточните
1
Здесь обсуждается github.com/angular/angular/issues/4792
Zyzle
1
Я знаю, почему это происходит, просто для того, чтобы улучшить качество ответа, прямо говоря, you can'tэто не очень хороший ответ, вы согласны?
Maurycy
Конечно, их не следует использовать вместе только потому, что размещение их в определенном порядке в шаблоне не гарантирует, что они будут выполнены в одном и том же порядке. Но это не объясняет, что именно происходит, когда выбрасывается 'Cannot read property' name 'of null'.
Estus Flask
И * ngFor, и * ngIf (со звездочкой) являются структурными директивами, и они генерируют тег <template>. Структурные директивы, такие как ngIf, делают свое дело с помощью тега шаблона HTML 5.
Пардип Джайн
5

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

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

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

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

На эти вопросы нет простых ответов. Запрещение нескольких структурных директив делает их спорными. Для этого варианта использования есть простое решение: поместите * ngIf в элемент контейнера, который обернет элемент * ngFor . Один или оба элемента могут быть контейнером ng, поэтому вам не нужно вводить дополнительные уровни 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>
Алиреза
источник
4

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

.hidden {
    display: none;
}

<div [class.hidden]="!show" *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
</div>
ronald8192
источник
Это очень легкий взлом для комбинации <select> <option>, которую я просто хочу показать отфильтрованные элементы вместо полного списка
davyzhang
Большое спасибо!
Абхишек Шарма
3

В таблице ниже перечислены только те элементы, для которых установлено значение «новичок». Требует и, *ngForи *ngIfдля предотвращения нежелательных строк в HTML.

Первоначально имел *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>
charliebear240
источник
1
Я не думаю, что <div>внутренняя часть стола является идеей, особенно когда есть лучшие альтернативы. Вы проверяли, работает ли таким образом в IE, который особенно требователен к элементам<table>
Günter Zöchbauer
3

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

Теперь , как из angular2 бета-можно использовать *ngIfи *ngForна одном компоненте смотрите здесь .

Alternate:

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

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

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

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

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

Для получения дополнительной информации о структурных директивах angular2 смотрите здесь .

Пардип Джейн
источник
1
<div *ngFor="let thing of show ? stuff : []">
  {{log(thing)}}
  <span>{{thing.name}}</span>
</div>
Прашант Борде
источник
1

Вы также можете использовать ng-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в некоторых местах.

Штеффи Керан Рани Дж
источник
0

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

Раджив
источник
Элементы li отображаются только в том случае, если у них есть имя.
Раджив
3
Как этот ответ добавляет ценность здесь? Это не дает ничего, что уже не предусмотрено другими ответами, или я что-то упустил?
Гюнтер Цохбауэр
0

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

Прадип Патил
источник
0

Вы можете сделать это по-другому, проверив длину массива

<div *ngIf="stuff.length>0">
  <div *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </div>
</div>
смс
источник
-1

app.component.ts

import {NgModule, Component} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';

@Component({
  selector: 'ngif-example',
  template: `
<h4>NgIf</h4>
<ul *ngFor="let person of people">
  <li *ngIf="person.age < 30"> (1)
  {{ person.name }} ({{ person.age }})
  </li>
</ul>
`
})
class NgIfExampleComponent {

  people: any[] = [
    {
      "name": "yogesh ",
      "age": 35
    },
    {
      "name": "gamesh",
      "age": 32
    },
    {
      "name": " Meyers",
      "age": 21
    },
    {
      "name": " Ellis",
      "age": 34
    },
    {
      "name": " Tyson",
      "age": 32
    }
  ];
}

app.component.html

<ul *ngFor="let person of people" *ngIf="person.age < 30">
  <li>{{ person.name }}</li>
</ul>

Вывод

Meyers(21)
Йогеш Вагмаре
источник