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

时间:2016-01-07 14:37:31

标签: angular ngfor angular-ng-if

我在尝试在同一元素上使用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提升到一个级别,但对于像ul中的列表项循环这样的情况,我最终会得到一个空的li如果集合为空,或者我的li包裹在冗余容器元素中。

plnkr的示例。

请注意控制台错误:

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

我做错了什么还是这个错误?

18 个答案:

答案 0 :(得分:537)

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>(Angular v4之前的<template>)允许执行相同的操作,但语法不同,令人困惑且不再推荐

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

答案 1 :(得分:45)

正如大家所指出的那样,即使在单个元素中有多个模板指令也在角度1.x中工作,但Angular 2中不允许这样做。 您可以在此处找到更多信息:https://github.com/angular/angular/issues/7315

2016 angular 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年,角度v6建议使用<ng-container>代替<template>

所以这是更新的答案。

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

答案 2 :(得分:32)

我还有一个解决方案。

使用[hidden]代替*ngIf

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

不同之处在于,*ngIf会从DOM中删除元素,而[hidden]实际上会使用CSS样式播放,方法是设置display:none

答案 3 :(得分: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 discussion (4792)也提出了使用<template>的另一种解决方案(下面我使用的是原始标记 - 使用<div> s):

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

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

答案 4 :(得分:5)

您不能在同一元素上拥有ngForngIf。您可以做的是暂停填充您在ngFor中使用的阵列,直到您点击示例中的切换为止。

这是一种基本(不太好)的方法:http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P

答案 5 :(得分:3)

这会有效,但元素仍然在DOM中。

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

答案 6 :(得分:3)

下表仅列出了具有&#34;初学者&#34;价值集。 需要*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>

答案 7 :(得分:2)

更新为angular2 beta 8

现在从angular2 beta 8开始,我们可以在同一个组件see here上使用*ngIf*ngFor

替代:

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

ng使用模板:

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

ngIf使用模板:

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

有关angular2 see here中结构指令的更多信息。

答案 8 :(得分:2)

你不能在同一元素的Angular中使用多个Structural Directive,它会产生一个错误的混淆和结构,所以你需要在两个独立的嵌套元素中应用它们(或者你可以使用{{1} }),阅读Angular团队的这篇声明:

  

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

     

有一天你会想要重复一段HTML,但只有当一个   特殊情况是真的。你会试着把 * ngFor 和   同一主机元素上的 * ngIf 。 Angular不会让你。你可以   仅对元素应用一个结构指令。

     

原因是简单。结构指令可以做复杂的事情   与宿主元素及其后代。当两个指令存在时   声称拥有相同的主机元素,哪一个优先?哪一个   应该先行,NgIf还是NgFor? NgIf能否取消效果   NgFor?如果是这样(似乎应该如此),应该怎么做   Angular概括了取消其他结构的能力   指令?

     

这些问题没有简单的答案。禁止多重   结构指令使他们没有实际意义。有一个简单的解决方案   这个用例:将 * ngIf 放在包装的容器元素上    * ngFor 元素。一个或两个元素可以是 ng-container ,因此您不必引入额外的HTML级别。

所以你可以使用ng-container(Angular4)作为包装器(将从dom中删除)或div或span,如果你有类或其他属性如下所示:

ng-container

答案 9 :(得分:1)

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

答案 10 :(得分:1)

在html中:

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

在CSS中:

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

答案 11 :(得分:0)

您不能在同一元素上使用多个结构指令。在ng-template中包装元素,并在其中使用一个结构指令

答案 12 :(得分:0)

您还可以使用 ng-template (而不是模板。有关使用模板标记的注意事项,请参见注释)同时应用 * ngFor ngIf 。这是一个示例,您可以在角度表中为同一 tr 元素同时使用 * ngIf和* ngFor

<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

答案 13 :(得分:0)

self.renderer = [SCNRenderer rendererWithContext:_openGLContext options:nil];
self.renderer.autoenablesDefaultLighting = YES;
self.renderer.playing = YES;
self.renderer.scene = myAnimatedScene;

答案 14 :(得分:0)

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)

答案 15 :(得分:0)

您可以通过检查数组长度来另一种方式

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

答案 16 :(得分:0)

在另一种解决方案上,可能是在您不想显示它的情况下在您的for循环中放置一个空数组

<div *ngFor="let thing of show ? stuff : []">

其中“东西”是“事物”的数组,而“显示”要显示内容的布尔值

答案 17 :(得分:0)

我不想使用*ngFordiv包裹到另一个*ngIf或使用[ngClass],所以我创建了一个名为show的管道: / p>

show.pipe.ts

export class ShowPipe implements PipeTransform {    
  transform(values: any[], show: boolean): any[] {
    if (!show) {
      return[];
    }
    return values;
  }
}

any.page.html

<table>
  <tr *ngFor="let arr of anyArray | show : ngIfCondition">
    <td>{{arr.label}}</td>
  </tr>
</table>