* ngIf和* ngFor在同一元素上导致错误


473

我在与试图使用角度的问题*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中的列表项之类的方案ulli如果集合为空,则最终将为空,或者li将其包装在冗余容器元素中。

plnkr的示例

注意控制台错误:

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

我是在做错什么还是错误?



Answers:


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
非常感谢。令人惊讶的是仍未记录在案
Alex Fuentes

7
当我们必须在* ngFor中包含* ngIf时,代码将如何显示?即,IF条件将基于循环元素的值。
Yuvraj Patil

22
只要把ngFor<ng-container>元素和ngIf<div>。您还可以有两个嵌套的<ng-container>包装<div><ng-container>只是一个辅助元素,不会添加到DOM中。
君特Zöchbauer

3
我建议使用<ng-container>。它的行为与<template>结构指令相同,但允许使用“常规”语法。
君特Zöchbauer

2
文档说:“每个主机元素一个结构指令”:“此用例有一个简单的解决方案:将* ngIf放在包装* ngFor元素的容器元素上。” -只是重申
heringer

71

每个人都指出,即使单个元素中包含多个模板指令也可以在angular 1.x中使用,但在Angular 2中是不允许的。您可以在此处找到更多信息:https : //github.com/angular/angular/issues/ 7315

2016角2 Beta

解决方案是使用<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>

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>元素也不存在(按预期)。

填充列表后,将没有多余的容器元素。

@Zyzle在评论中提到的github讨论(4792)也提出了使用的另一种解决方案<template>(在下面,我使用的是您的原始标记-using <div>s):

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

此解决方案也不会引入任何多余/冗余的容器元素。


1
我不确定为什么这不是公认的答案。<template>是添加不会在输出中显示的父元素的方法。
Evan Plaice

8

在html中:

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

在CSS中:

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

5

您不能在同一元素上使用ngForngIf。您可以做的是推迟填充正在使用的阵列,ngFor直到单击示例中的切换按钮为止。

这是您可以执行的基本(不太好)方法:http : //plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P


为什么他不能同时拥有两者?请细说
maurycy

1
有围绕一个讨论这里github.com/angular/angular/issues/4792
Zyzle

1
我知道为什么会这样,只是为了提高答案的质量,简单地说you can't这不是一个很好的答案,您是否同意?
maurycy '16

当然,不应仅将它们按一定顺序放到模板中并不能保证它们将以相同顺序执行,所以不能将它们一起使用。但这并不能解释当抛出“无法读取null的属性'名称'”时到底发生了什么。
Estus Flask,

* ngFor和* ngIf(带星号)都是结构性指令,它们生成<template>标记。诸如ngIf之类的结构化指令通过使用HTML 5模板标记来发挥作用。
Pardeep Jain

5

您不能Structural Directive在Angular中对同一个元素使用多个元素,这会造成混乱和结构混乱,因此您需要将它们应用于2个单独的嵌套元素中(或者可以使用ng-container),请从Angular团队阅读以下声明:

每个主机元素一个结构指令

总有一天,您只想在满足特定条件时才重复HTML块。您将尝试将* ngFor* ngIf放在同一宿主元素上。Angular不会让你。您只能对一个元素应用一个结构指令。

原因是简单。结构化指令可以对宿主元素及其后代进行复杂的处理。当两个指令对同一宿主元素提出要求时,哪个优先?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>

对于<select> <option>组合来说,这是一个非常简单的技巧,我只想显示过滤后的项目而不是完整列表
davyzhang

非常感谢你!
Abhishek Sharma

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>

1
我不认为<div>在桌子内放东西不是一个好主意,特别是当有更好的选择时。你检查,如果这样在IE中这是特别挑剔的元素<table>
君特Zöchbauer

3

更新到angular2 beta 8

现在从angular2 beta 8开始,我们可以在同一组件上使用*ngIf参见此处*ngFor

备用:

有时我们无法在trthtable)或liul)中的另一个内部使用HTML标签。我们不能使用其他HTML标签,但必须在相同情况下执行某些操作,因此我们可以<template>以这种方式使用HTML5功能标签。

ng对于使用模板:

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

ng如果使用模板:

<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(而不是模板。有关使用模板标签的注意事项,请参见注释)在同一HTML元素上同时应用* ngForngIf。这是一个示例,您可以将* 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
这个答案如何在这里增加价值?它没有提供其他答案未提供的任何内容,还是我错过了某些事情?
君特Zöchbauer

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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.