* ngIf และ * ngFor สำหรับองค์ประกอบเดียวกันที่ทำให้เกิดข้อผิดพลาด


473

ฉันมีปัญหากับการพยายามใช้ Angular's *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ถ้าคอลเลกชันว่างเปล่าหรือฉันliถูกห่อด้วยองค์ประกอบคอนเทนเนอร์ที่ซ้ำซ้อน

ตัวอย่างที่plnkrนี้

หมายเหตุข้อผิดพลาดคอนโซล:

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

ฉันกำลังทำอะไรผิดหรือเป็นข้อผิดพลาดหรือไม่?


stackoverflow.com/questions/40529537/… ฉันจะไปกับ ng-container
robert king

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

7
โค้ดจะมีลักษณะเป็นอย่างไรเมื่อเราต้องมี * ngIf inside * ngFor เงื่อนไข Ie IF จะยึดตามค่าขององค์ประกอบลูป
Yuvraj Patil

22
เพียงแค่ใส่ngForที่<ng-container>องค์ประกอบและที่ngIf <div>คุณยังสามารถ<ng-container>ตัดคำซ้อนสอง<div>อัน <ng-container>เป็นเพียงองค์ประกอบตัวช่วยที่จะไม่ถูกเพิ่มใน DOM
GünterZöchbauer

3
<ng-container>ผมขอแนะนำให้ใช้ มันทำงานเหมือนกับ<template>แต่อนุญาตให้ใช้ไวยากรณ์ "ปกติ" สำหรับคำสั่งโครงสร้าง
GünterZöchbauer

2
เอกสารอธิบายว่า : "หนึ่งคำสั่งโครงสร้างต่อองค์ประกอบโฮสต์": "มีทางออกที่ง่ายสำหรับกรณีการใช้งานนี้: ใส่ * ngIf ลงในองค์ประกอบภาชนะที่ล้อมรอบองค์ประกอบ * ngFor" - เพียงแค่ย้ำ
heringer

71

ในขณะที่ทุกคนชี้ให้เห็นถึงแม้ว่าจะมีคำสั่งเทมเพลตหลายรายการในองค์ประกอบเดียวทำงานในเชิงมุม 1.x ไม่อนุญาตให้ใช้ใน Angular 2 คุณสามารถค้นหาข้อมูลเพิ่มเติมได้จากที่นี่: https://github.com/angular/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/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>):

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

วิธีการแก้ปัญหานี้ยังไม่แนะนำองค์ประกอบภาชนะพิเศษ / ซ้ำซ้อนใด ๆ


1
ฉันไม่แน่ใจว่าทำไมนี่ไม่ใช่คำตอบที่ยอมรับได้ <template>เป็นวิธีการเพิ่มองค์ประกอบหลักที่จะไม่ปรากฏในผลลัพธ์
Evan Plaice


5

คุณไม่สามารถngForและngIfองค์ประกอบเดียวกัน สิ่งที่คุณสามารถทำได้คือระงับการเติมอาร์เรย์ที่คุณใช้ngForจนกว่าจะมีการคลิกสลับในตัวอย่างของคุณ

นี่เป็นวิธีพื้นฐาน (ไม่ยอดเยี่ยม) ที่คุณสามารถทำได้: http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P


ทำไมเขาถึงมีทั้งคู่ไม่ได้? โปรดบรรจง

1
มีการอภิปรายเกี่ยวกับสิ่งที่นี่github.com/angular/angular/issues/4792
Zyzle

1
ฉันรู้ว่าทำไมมันถึงเกิดขึ้นมันเป็นเพียงการปรับปรุงคุณภาพของคำตอบเพียงแค่พูดว่าyou can'tไม่ใช่คำตอบที่ดีคุณจะเห็นด้วยหรือไม่?
maurycy

แน่นอนว่าพวกเขาไม่ควรใช้ร่วมกันเพียงเพราะวางไว้ในลำดับที่แน่นอนเพื่อแม่แบบไม่รับประกันว่าพวกเขาจะถูกดำเนินการในลำดับเดียวกัน แต่นี่ไม่ได้อธิบายว่าเกิดอะไรขึ้นอย่างแน่นอนเมื่อ 'โยนไม่สามารถอ่านคุณสมบัติ' ชื่อ 'เป็นโมฆะได้
Estus Flask

ทั้ง * ngFor และ * ngIf (พร้อมเครื่องหมายดอกจัน) เป็นคำสั่งโครงสร้างและสร้างแท็ก <template> คำสั่งโครงสร้างเช่น ngIf ทำได้โดยใช้แท็กเทมเพลต HTML 5
Pardeep Jain

5

คุณไม่สามารถใช้มากกว่าหนึ่งStructural Directiveในแองกูลาร์ในองค์ประกอบเดียวกันมันทำให้เกิดความสับสนและโครงสร้างที่ไม่ดีดังนั้นคุณต้องใช้มันในองค์ประกอบซ้อนกัน 2 องค์ประกอบแยกกัน (หรือคุณสามารถใช้ng-container) อ่านคำแถลงนี้จากทีมแองกูลาร์:

หนึ่งคำสั่งโครงสร้างต่อองค์ประกอบโฮสต์

สักวันคุณจะต้องการบล็อก HTML ซ้ำอีกครั้ง แต่เมื่อเงื่อนไขเฉพาะเป็นจริง คุณจะพยายามใส่ทั้ง* ngForและ* ngIfในองค์ประกอบโฮสต์เดียวกัน เชิงมุมจะไม่ยอมให้คุณ คุณอาจใช้คำสั่งโครงสร้างเดียวกับองค์ประกอบ

เหตุผลก็คือความเรียบง่าย คำสั่งโครงสร้างสามารถทำสิ่งที่ซับซ้อนด้วยองค์ประกอบโฮสต์และลูกหลานของมัน เมื่อทั้งสองคำสั่งอ้างสิทธิ์ในองค์ประกอบโฮสต์เดียวกันอันไหนจะมีผลเหนือกว่า? สิ่งใดควรไปก่อนคือ NgIf หรือ NgFor NgIf สามารถยกเลิกผลของ NgFor ได้หรือไม่? ถ้าเป็นเช่นนั้น (และดูเหมือนว่ามันควรจะเป็นเช่นนั้น) Angular ควรสรุปความสามารถในการยกเลิกคำสั่งโครงสร้างอื่น ๆ อย่างไร

ไม่มีคำตอบที่ง่ายสำหรับคำถามเหล่านี้ การห้ามใช้คำสั่งโครงสร้างหลายคำสั่งทำให้เกิดข้อโต้แย้ง มีวิธีแก้ปัญหาที่ง่ายสำหรับกรณีการใช้งานนี้: วาง* ngIfลงในองค์ประกอบคอนเทนเนอร์ที่ล้อม รอบองค์ประกอบ* ngFor องค์ประกอบหนึ่งหรือทั้งสองรายการสามารถเป็นng-containerดังนั้นคุณไม่จำเป็นต้องแนะนำ HTML ระดับพิเศษเพิ่มเติม

ดังนั้นคุณสามารถใช้ng-container(Angular4) เป็น wrapper (จะถูกลบออกจาก 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>แท็กเดียวกันแต่ไม่ได้ผล เพิ่ม a <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>
GünterZöchbauer

3

อัปเดตเป็น angular2 เบต้า 8

ขณะนี้เป็นจาก angular2 เบต้า 8 เราสามารถใช้*ngIfและ*ngForในส่วนเดียวกันดูที่นี่

สำรอง:

บางครั้งเราไม่สามารถใช้แท็ก HTML ภายในแท็กอื่นเช่นในtr, th( table) หรือในli( ul) เราไม่สามารถใช้แท็ก HTML อื่น แต่เราต้องดำเนินการบางอย่างในสถานการณ์เดียวกันดังนั้นเราจึงสามารถติดแท็กคุณสมบัติ HTML5 <template>ด้วยวิธีนี้

ngFor การใช้แม่แบบ:

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

ถ้าใช้แม่แบบ:

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคำสั่งของโครงสร้างใน angular2 ดูที่นี่



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 จะแสดงเฉพาะในกรณีที่มีชื่อ
Rajiv

3
คำตอบนี้จะเพิ่มมูลค่าที่นี่ได้อย่างไร มันไม่ได้ให้คำตอบอื่น ๆ ที่ไม่ได้ให้ไว้แล้วหรือฉันพลาดอะไรไปหรือเปล่า?
GünterZö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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.