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

*ngIf and *ngFor on same element causing error

我在尝试对同一元素使用 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]

我是不是做错了什么或者这是一个错误?

您不能在同一元素上同时使用 ngForngIf。您可以做的是推迟填充您在 ngFor 中使用的数组,直到您的示例中的切换被单击。

这里有一个基本的(不是很好)方法:http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P

正如@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>

此解决方案也不引入任何 extra/redundant 容器元素。

已更新至 angular2 beta 8

现在,从 angular2 beta 8 开始,我们可以在同一组件 see here.

上使用 *ngIf*ngFor

备用:

有时我们不能在另一个标签中使用 HTML 标签,例如 trth (table) 或 li (ul).我们不能使用另一个 HTML 标签,但我们必须在相同情况下执行一些操作,因此我们可以 HTML5 特征标签 <template> 以这种方式。

ng使用模板:

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

ng如果使用模板:

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

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

正如每个人所指出的,即使在单个元素中有多个模板指令在 angular 1.x 中有效,但在 Angular 2 中是不允许的。 你可以从这里找到更多信息:https://github.com/angular/angular/issues/7315

2016 angular 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>

这将起作用,但该元素仍将位于 DOM。

.hidden {
    display: none;
}

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

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> before Angular v4)允许做同样的事情,但使用不同的语法,这令人困惑,不再推荐

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

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

下面的

Table 仅列出设置了 "beginner" 值的项目。 需要 *ngFor*ngIf 以防止 html.

中不需要的行

最初在同一个 <tr> 标签上有 *ngIf*ngFor,但不起作用。 为 *ngFor 循环添加了一个 <div> 并在 <tr> 标签中放置了 *ngIf,按预期工作。

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

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

One structural directive per host element

Someday you'll want to repeat a block of HTML but only when a particular condition is true. You'll try to put both an *ngFor and an *ngIf on the same host element. Angular won't let you. You may apply only one structural directive to an element.

The reason is simplicity. Structural directives can do complex things with the host element and its descendents. When two directives lay claim to the same host element, which one takes precedence? Which should go first, the NgIf or the NgFor? Can the NgIf cancel the effect of the NgFor? If so (and it seems like it should be so), how should Angular generalize the ability to cancel for other structural directives?

There are no easy answers to these questions. Prohibiting multiple structural directives makes them moot. There's an easy solution for this use case: put the *ngIf on a container element that wraps the *ngFor element. One or both elements can be an ng-container so you don't have to introduce extra levels of HTML.

因此您可以使用 ng-container (Angular4) 作为包装器(将从 dom 中删除)或 div 或跨度,如果您有 class 或以下一些其他属性:

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

不能在同一元素上使用多个结构指令。将您的元素包裹在 ng-template 中并在那里使用一个结构指令

您也可以使用 ng-template(而不是模板。请参阅注意事项以了解使用模板标签的注意事项)来同时应用 *ngFor ngIf 在同一个 HTML 元素上。这是一个示例,您可以在 angular table 中对同一 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

在html中:

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

在 css 中:

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

您可以通过检查数组长度的另一种方式来做到这一点

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

其他解决方案可能是在您的 for 循环中放置一个空数组,以防您不想显示它

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

其中“stuff”是“thing”的数组,“show”是显示或不显示内容的布尔值

我不想用 *ngIf 或使用 [ngClass] 将我的 *ngFor 包装到另一个 div 中,所以我创建了一个名为 show 的管道:

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>

我用这个解决了我的问题

<ng-container *ngFor="let item of menuItems">
  <li *ngIf="canShowItem(item)"></li>
</ng-container>