【问题标题】:*ngIf and *ngFor on same element causing error*ngIf 和 *ngFor 在同一元素上导致错误
【发布时间】:2016-04-12 00:01:35
【问题描述】:

我在尝试在同一元素上使用 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,或者我的lis 包裹在冗余容器元素中。

plnkr 的示例。

注意控制台错误:

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

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

【问题讨论】:

标签: angular ngfor angular-ng-if


【解决方案1】:

Angular v2 不支持在同一个元素上使用多个结构指令。
作为一种解决方法,请使用 &lt;ng-container&gt; 元素,该元素允许您为每个结构指令使用单独的元素,但它未标记到 DOM

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

&lt;ng-template&gt;(Angular v4 之前的&lt;template&gt;)允许做同样的事情,但语法不同,这令人困惑,不再推荐

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

【讨论】:

  • 非常感谢。令人惊讶的是仍然没有记录:github.com/angular/angular.io/issues/2303
  • 当我们必须在 *ngFor 中有 *ngIf 时,代码会是什么样子? IE。 IF 条件将基于循环元素的值。
  • 只需将ngFor 放在&lt;ng-container&gt; 元素上,将ngIf 放在&lt;div&gt; 上。您还可以有两个嵌套的&lt;ng-container&gt; 包裹&lt;div&gt;&lt;ng-container&gt; 只是一个辅助元素,不会添加到 DOM 中。
  • 我建议使用&lt;ng-container&gt;。它的行为与&lt;template&gt; 相同,但允许对结构指令使用“正常”语法。
  • Documentation says: "每个宿主元素一个结构指令": "这个用例有一个简单的解决方案:将 *ngIf 放在包装 *ngFor 元素的容器元素上。" - 只是重申
【解决方案2】:

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

2016 角度 2 测试版

解决方案是使用&lt;template&gt;作为占位符,所以代码是这样的

<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 中建议使用 &lt;ng-container&gt; 而不是 &lt;template&gt;

所以这是更新的答案。

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

【讨论】:

  • 你拯救了我的一天,谢谢。一个问题为什么ng-container 没有显示 HTML 对样式和所有的影响
【解决方案3】:

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

当列表为空时,没有空的&lt;li&gt; 元素。甚至&lt;ul&gt; 元素也不存在(如预期的那样)。

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

@Zyzle 在他的评论中提到的github discussion (4792) 还提供了另一个使用&lt;template&gt; 的解决方案(下面我使用的是您的原始标记&dash; 使用&lt;div&gt;s):

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

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

【讨论】:

  • 我不确定为什么这不是公认的答案。 &lt;template&gt; 是添加不会出现在输出中的父元素的方法。
【解决方案4】:

在 html 中:

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

在css中:

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

【讨论】:

    【解决方案5】:

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

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

    总有一天你会想要重复一段 HTML,但只有当一个 特定条件为真。您将尝试同时放置 *ngFor*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>
    

    【讨论】:

      【解决方案6】:

      这会起作用,但元素仍会在 DOM 中。

      .hidden {
          display: none;
      }
      

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

      【讨论】:

      • 这是
      【解决方案7】:

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

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

      【讨论】:

      • 为什么他不能两者兼得?请详细说明
      • 我知道为什么会这样,只是为了提高答案的质量,直白地说you can't并不是一个很好的答案,你同意吗?
      • 当然,它们不应该一起使用,仅仅因为将它们按特定顺序放置到模板中并不能保证它们将以相同的顺序执行。但这并不能解释抛出“Cannot read property 'name' of null”时究竟会发生什么。
      • *ngFor 和 *ngIf(带星号)都是结构指令,它们会生成
      【解决方案8】:

      我用这个解决了我的问题

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

      【讨论】:

        【解决方案9】:

        下表仅列出了设置了“初学者”值的项目。 需要 *ngFor*ngIf 以防止 html 中出现不需要的行。

        原来*ngIf*ngFor 在同一个&lt;tr&gt; 标签上,但不起作用。 为*ngFor 循环添加了&lt;div&gt; 并将*ngIf 放在&lt;tr&gt; 标记中,按预期工作。

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

        【讨论】:

        • 我不认为在表格中使用&lt;div&gt; 是个好主意,尤其是当有更好的选择时。您是否检查过因此是否适用于 IE,它对 &lt;table&gt; 中的元素特别挑剔
        【解决方案10】:

        更新到 angular2 beta 8

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

        替代:

        有时我们不能在另一个内部使用 HTML 标记,例如 trth (table) 或 li (ul)。我们不能使用另一个 HTML 标签,但我们必须在相同的情况下执行一些操作,这样我们才能以这种方式 HTML5 特征标签 &lt;template&gt;

        ngFor 使用模板:

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

        ngIf 使用模板:

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

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

        【讨论】:

          【解决方案11】:

          您也可以使用 ng-template(而不是模板。有关使用模板标签的注意事项,请参阅注释)同时应用 *ngForngIf 在同一个 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

          【讨论】:

          • 看起来不错的解决方案!
          • 谢谢@ankush981
          【解决方案12】:

          我不想用*ngIf 将我的*ngFor 包装到另一个div 或使用[ngClass],所以我创建了一个名为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>
          

          【讨论】:

            【解决方案13】:
            <div *ngFor="let thing of show ? stuff : []">
              {{log(thing)}}
              <span>{{thing.name}}</span>
            </div>
            

            【讨论】:

              【解决方案14】:

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

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

              其中“stuff”是“事物”数组,“显示”布尔值以显示或不显示内容

              【讨论】:

                【解决方案15】:

                <!-- 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 项只有在有名称时才会显示。
                • 这个答案如何在这里增加价值?它没有提供其他答案没有提供的任何东西,还是我错过了什么?
                【解决方案16】:

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

                【讨论】:

                  【解决方案17】:

                  您可以通过检查数组长度来实现此目的

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-07-16
                    • 2017-04-13
                    • 2019-10-01
                    • 2019-12-24
                    • 2018-01-11
                    • 2018-08-17
                    • 1970-01-01
                    相关资源
                    最近更新 更多