* ngIf i * ngFor dla tego samego elementu powodującego błąd


473

Mam problem z próbą użycia Angulara *ngFori *ngIftego samego elementu.

Podczas próby przechodzenia przez kolekcję w *ngForkolekcji kolekcja jest postrzegana jako nullkonsekwentnie nieudana podczas próby uzyskania dostępu do jej właściwości w szablonie.

@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);
  }

}

Wiem, że łatwym rozwiązaniem jest przejście *ngIfo jeden poziom wyżej, ale w scenariuszach takich jak zapętlanie elementów listy w a ulskończy się albo pustym, lijeśli kolekcja jest pusta, albo liopakowaniem w zbędne elementy kontenera.

Przykład na tym plnkr .

Zwróć uwagę na błąd konsoli:

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

Czy robię coś źle, czy to błąd?


stackoverflow.com/questions/40529537 /... wybrałbym z ng-container
— Robert King

Odpowiedzi:


680

Angular v2 nie obsługuje więcej niż jednej dyrektywy strukturalnej dla tego samego elementu.
Aby obejść ten <ng-container>problem, należy użyć elementu, który pozwala na użycie osobnych elementów dla każdej dyrektywy strukturalnej, ale nie jest on stemplowany na DOM .

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

<ng-template>( <template>przed Angular v4) pozwala robić to samo, ale z inną składnią, która jest myląca i nie jest już zalecana

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

5
Wielkie dzięki. Zaskakujące jest to wciąż nieudokumentowane: github.com/angular/angular.io/issues/2303
— Alex Fuentes

7
Jak będzie wyglądał kod, gdy będziemy musieli mieć * ngIf wewnątrz * ngFor? Czyli warunek IF będzie oparty na wartości elementu pętli.
— Yuvraj Patil

22
Wystarczy umieścić ngForna <ng-container>elemencie i ngIfna <div>. Możesz także mieć dwa zagnieżdżone <ng-container>zawijanie pliku <div>. <ng-container>jest tylko elementem pomocniczym, który nie zostanie dodany do DOM.
— Günter Zöchbauer

3
Sugerowałbym użycie <ng-container>. Zachowuje się tak samo, <template>ale pozwala na stosowanie „normalnej” składni dla dyrektyw strukturalnych.
— Günter Zöchbauer

2
Dokumentacja mówi : „Jedna dyrektywa strukturalna na element hosta”: „Istnieje łatwe rozwiązanie dla tego przypadku użycia: umieść * ngIf na elemencie kontenera, który otacza element * ngFor.” - tylko powtarzając
— heringer

71

Jak wszyscy zauważyli, mimo że posiadanie wielu dyrektyw szablonów w jednym elemencie działa w wersji kątowej 1.x, nie jest to dozwolone w wersji kątowej 2. Więcej informacji można znaleźć tutaj: https://github.com/angular/angular/issues/ 7315

2016 kątowa 2 beta

rozwiązaniem jest użycie <template>symbolu zastępczego, więc kod wygląda tak

<template *ngFor="let nav_link of defaultLinks"  >
   <li *ngIf="nav_link.visible">
       .....
   </li>
</template>

ale z jakiegoś powodu powyższe nie działa 2.0.0-rc.4w takim przypadku możesz użyć tego

<template ngFor let-nav_link [ngForOf]="defaultLinks" >
   <li *ngIf="nav_link.visible">
       .....
   </li> 
</template>

Zaktualizowana odpowiedź 2018

Dzięki aktualizacjom, teraz w 2018 r. Zaleca się stosowanie <ng-container>zamiast wersji kątowej v6<template>

oto zaktualizowana odpowiedź.

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

29

Jak wspomniano @Zyzle i @ Günter wspomniane w komentarzu ( https://github.com/angular/angular/issues/7315 ), nie jest to obsługiwane.

Z

<ul *ngIf="show">
  <li *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </li>
</ul>

<li>gdy lista jest pusta, nie ma pustych elementów. Nawet <ul>element nie istnieje (zgodnie z oczekiwaniami).

Gdy lista jest zapełniona, nie ma zbędnych elementów kontenera.

Dyskusja github (4792) , które @Zyzle wspomniano w swoim komentarzu przedstawiono również inne rozwiązanie używając <template>(poniżej używam oryginalnego znaczników - używając <div>s):

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

To rozwiązanie nie wprowadza również żadnych dodatkowych / zbędnych elementów kontenera.


1
Nie jestem pewien, dlaczego nie jest to akceptowana odpowiedź. <template>to sposób na dodanie elementu nadrzędnego, który nie pojawi się w danych wyjściowych.
— Evan Plaice,

8

w html:

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

w css:

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

5

Nie można mieć ngFori ngIfna tym samym elemencie. Co możesz zrobić, to wstrzymać się z zapełnianiem tablicy, w której używasz, ngFordopóki nie klikniesz przełącznika w twoim przykładzie.

Oto podstawowy (niezbyt świetny) sposób: http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P


Dlaczego nie może mieć obu? Proszę opracować
— Maurycy

1
Dyskusja wokół tego jest tutaj github.com/angular/angular/issues/4792
— Zyzle

1
Wiem, dlaczego tak się dzieje, tylko po to, aby poprawić jakość odpowiedzi. Mówiąc wprost, you can'tnie jest to naprawdę dobra odpowiedź, prawda?
— Maurycy

Jasne, nie należy ich używać razem, ponieważ umieszczenie ich w określonej kolejności w szablonie nie gwarantuje, że zostaną wykonane w tej samej kolejności. Ale to nie wyjaśnia, co dokładnie się dzieje, gdy wyrzucane jest „Nie można odczytać właściwości” nazwa „null”.
— Estus Flask

Zarówno * ngFor, jak i * ngIf (z gwiazdką) są dyrektywami strukturalnymi i generują znacznik <template>. Dyrektywy strukturalne, takie jak ngIf, wykonują swoją magię za pomocą znacznika szablonu HTML 5.
— Pardeep Jain

5

Nie możesz użyć więcej niż jednego Structural Directivew Angular na tym samym elemencie, robi to nieporozumienia i strukturę, więc musisz zastosować je w 2 osobnych zagnieżdżonych elementach (lub możesz użyć ng-container), przeczytaj to zdanie od zespołu Angular:

Jedna dyrektywa strukturalna na element hosta

Pewnego dnia będziesz chciał powtórzyć blok HTML, ale tylko wtedy, gdy spełniony jest określony warunek. Spróbujesz umieścić zarówno * ngFor, jak i * ngIf na tym samym elemencie hosta. Angular ci na to nie pozwoli. Do elementu można zastosować tylko jedną dyrektywę strukturalną.

Powodem jest prostota. Dyrektywy strukturalne mogą robić złożone rzeczy z elementem nadrzędnym i jego potomkami. Kiedy dwie dyrektywy zgłaszają roszczenia do tego samego elementu hosta, która z nich ma pierwszeństwo? Który powinien iść pierwszy, NgIf lub NgFor? Czy NgIf może anulować działanie NgFor? Jeśli tak (i ​​wydaje się, że tak powinno być), w jaki sposób Angular powinien uogólnić możliwość anulowania w przypadku innych dyrektyw strukturalnych?

Nie ma łatwych odpowiedzi na te pytania. Zakaz stosowania wielu dyrektyw strukturalnych powoduje, że stają się one dyskusyjne. Istnieje proste rozwiązanie dla tego przypadku użycia: umieść * ngIf na elemencie kontenera, który otacza element * ngFor . Jeden lub oba elementy mogą być kontenerami ng, więc nie musisz wprowadzać dodatkowych poziomów HTML.

Możesz więc użyć ng-container(Angular4) jako opakowania (zostanie usunięte z dom) lub div lub span, jeśli masz klasę lub niektóre inne atrybuty, jak poniżej:

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

4

To zadziała, ale element nadal będzie w DOM.

.hidden {
    display: none;
}

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

Jest to bardzo łatwy hack dla kombinacji <select> <option>, w której po prostu chcę wyświetlać filtrowane elementy zamiast pełnej listy
— davyzhang

Dziękuję Ci bardzo!
— Abhishek Sharma

3

Poniższa tabela zawiera tylko pozycje z ustawioną wartością „dla początkujących”. Wymaga zarówno *ngFori, *ngIfaby zapobiec niechcianym wierszom w html.

Pierwotnie miał *ngIfi *ngForna tym samym <tr>tagu, ale nie działa. Dodano <div>do *ngForpętli i umieszczone *ngIfw <tr>tagu, działa zgodnie z oczekiwaniami.

<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
Nie sądzę, żeby <div>wnętrze stołu było pomysłem goosowym, zwłaszcza gdy istnieją lepsze alternatywy. Czy sprawdziłeś, czy tak działa w IE, co jest szczególnie wybredne w przypadku elementów w<table>
— Günter Zöchbauer,

3

Zaktualizowano do angular2 beta 8

Od wersji angular2 beta 8 możemy teraz używać *ngIfi*ngFor na tym samym komponencie patrz tutaj .

Alternatywny:

Czasami nie możemy używać tagów HTML wewnątrz innych, takich jak in tr, th( table) lub in li(ul ). Nie możemy użyć innego tagu HTML, ale musimy wykonać pewne czynności w tej samej sytuacji, abyśmy mogli tag funkcji HTML5<template> w ten sposób .

ng Do korzystania z szablonu:

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

ng Jeśli używasz szablonu:

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

Aby uzyskać więcej informacji na temat dyrektyw strukturalnych w angular2, zobacz tutaj .


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

1

Możesz także użyć ng-template(zamiast szablonu. Zobacz uwaga dotycząca używania znacznika szablonu) do zastosowania zarówno * ngFor, jak i ngIf na tym samym elemencie HTML. Oto przykład, w którym możesz użyć zarówno * ngIf, jak i * ngFor dla tego samego elementu tr w tabeli kątowej.

<tr *ngFor = "let fruit of fruiArray">
    <ng-template [ngIf] = "fruit=='apple'>
        <td> I love apples!</td>
    </ng-template>
</tr>

gdzie fruiArray = ['apple', 'banana', 'mango', 'pineapple'].

Uwaga:

Jedynym zastrzeżeniem używania templatetagu zamiast ng-templatetagu jest to, że rzuca StaticInjectionErrorw niektórych miejscach.


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>


pozycje li są wyświetlane tylko wtedy, gdy ma nazwę.
— Rajiv

3
Jak ta odpowiedź wnosi tutaj wartość dodaną? Nie zawiera niczego, co nie zostało już zawarte w innych odpowiedziach, czy coś przeoczyłem?
— Günter Zöchbauer

0

Nie można użyć wielu dyrektyw strukturalnych dla tego samego elementu. Zawiń swój element ng-templatei użyj tam jednej dyrektywy strukturalnej


0

Możesz to zrobić w inny sposób, sprawdzając długość tablicy

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

Wynik

Meyers(21)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.