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