* ngIf e * ngFor sullo stesso elemento causa errore


473

Sto riscontrando un problema con il tentativo di utilizzare Angular *ngFore *ngIfsullo stesso elemento.

Quando si tenta di scorrere la raccolta in *ngFor, la raccolta viene vista come nulle di conseguenza non riesce quando si tenta di accedere alle sue proprietà nel modello.

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

}

So che la soluzione semplice è quella di passare al *ngIflivello superiore, ma per scenari come il looping degli elementi dell'elenco in a ul, finirei con uno vuoto lise la raccolta è vuota o con i miei lielementi avvolti in contenitori ridondanti.

Esempio in questo plnkr .

Nota l'errore della console:

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

Sto facendo qualcosa di sbagliato o è un bug?



1
Possibile duplicato della tabella filtrata angolare
Cobus Kruger,

Risposte:


680

Angular v2 non supporta più di una direttiva strutturale sullo stesso elemento.
Come soluzione alternativa, utilizzare l' <ng-container>elemento che consente di utilizzare elementi separati per ciascuna direttiva strutturale, ma non è timbrato sul DOM .

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

<ng-template>( <template>prima di Angular v4) consente di fare lo stesso ma con una sintassi diversa che è confusa e non è più consigliata

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

5
Molte grazie. Sorprendentemente non è ancora documentato: github.com/angular/angular.io/issues/2303
Alex Fuentes,

7
Come sarà il codice quando dovremo avere * ngIf all'interno di * ngFor? Vale a dire la condizione IF sarà basata sul valore di un elemento loop.
Yuvraj Patil,

22
Metti solo ngForl' <ng-container>elemento e ngIfil <div>. Puoi anche avere due nidificati che <ng-container>avvolgono il file <div>. <ng-container>è solo un elemento helper che non verrà aggiunto al DOM.
Günter Zöchbauer,

3
Suggerirei di usare <ng-container>. Si comporta allo stesso modo <template>ma consente di utilizzare la sintassi "normale" per le direttive strutturali.
Günter Zöchbauer,

2
La documentazione dice : "Una direttiva strutturale per elemento host": "C'è una soluzione semplice per questo caso d'uso: mettere * ngIf su un elemento contenitore che avvolge l'elemento * ngFor." - solo ribadendo
Heringer

71

Come tutti hanno sottolineato, anche se avere più direttive di template in un singolo elemento funziona in Angular 1.x non è consentito in Angular 2. puoi trovare maggiori informazioni da qui: https://github.com/angular/angular/issues/ 7315

2016 angular 2 beta

la soluzione è utilizzare il <template>segnaposto, quindi il codice va così

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

ma per qualche motivo sopra non funziona 2.0.0-rc.4in quel caso puoi usarlo

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

Risposta aggiornata 2018

Con gli aggiornamenti, ora nel 2018 Angular v6 consiglia di utilizzare <ng-container>invece di<template>

quindi ecco la risposta aggiornata.

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

29

Come menzionato @Zyzle e @ Günter in un commento ( https://github.com/angular/angular/issues/7315 ), questo non è supportato.

Con

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

non ci sono <li>elementi vuoti quando l'elenco è vuoto. Anche l' <ul>elemento non esiste (come previsto).

Quando l'elenco viene popolato, non ci sono elementi contenitore ridondanti.

La discussione github (4792) che @Zyzle ha menzionato nel suo commento presenta anche un'altra soluzione usando <template>(sotto sto usando il tuo markup originale - usando <div>s):

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

Questa soluzione non introduce inoltre alcun elemento contenitore extra / ridondante.


1
Non sono sicuro del perché questa non sia la risposta accettata. <template>è il modo per aggiungere un elemento padre che non verrà visualizzato nell'output.
Evan Plaice,

8

in html:

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

in css:

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

5

Non puoi avere ngFore ngIfsullo stesso elemento. Quello che potresti fare è trattenere il popolamento dell'array in uso ngForfino a quando non viene cliccato l'interruttore nel tuo esempio.

Ecco un modo di base (non eccezionale) per farlo: http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P


Perché non può avere entrambi? Elaborate per favore
Maurycy

1
C'è una discussione al
riguardo

1
So perché sta accadendo, è solo per migliorare la qualità della risposta, chiaramente dicendo che you can'tnon è davvero una buona risposta, non sei d'accordo?
Maurycy

Certo, non dovrebbero essere usati insieme solo perché metterli in un certo ordine al modello non garantisce che saranno eseguiti nello stesso ordine. Ma questo non spiega cosa succede esattamente quando viene lanciato "Impossibile leggere la proprietà" nome "di null".
Estus Flask

Sia * ngFor che * ngIf (con asterisco) sono direttive strutturali e generano tag <template>. Le direttive strutturali, come ngIf, fanno la loro magia usando il tag template HTML 5.
Pardeep Jain,

5

Non puoi usare più di uno Structural Directivein Angolare sullo stesso elemento, crea una cattiva confusione e struttura, quindi devi applicarli in 2 elementi nidificati separati (o puoi usare ng-container), leggi questa affermazione dal team Angular:

Una direttiva strutturale per elemento host

Un giorno vorrai ripetere un blocco di HTML ma solo quando una determinata condizione è vera. Proverai a mettere sia un * ngFor che un * ngIf sullo stesso elemento host. Angular non te lo permetterà. È possibile applicare una sola direttiva strutturale a un elemento.

Il motivo è la semplicità. Le direttive strutturali possono fare cose complesse con l'elemento host e i suoi discendenti. Quando due direttive rivendicano lo stesso elemento host, quale ha la precedenza? Quale dovrebbe essere il primo, NgIf o NgFor? NgIf può cancellare l'effetto di NgFor? Se è così (e sembra che dovrebbe essere così), come dovrebbe Angular generalizzare la possibilità di annullare per altre direttive strutturali?

Non ci sono risposte facili a queste domande. Proibire più direttive strutturali le rende discutibili. C'è una soluzione semplice per questo caso d'uso: metti * ngIf su un elemento contenitore che avvolge l' elemento * ngFor . Uno o entrambi gli elementi possono essere un ng-container, quindi non è necessario introdurre livelli extra di HTML.

Quindi puoi usare ng-container(Angular4) come wrapper (verrà eliminato dalla dom) o div o span se hai classe o altri attributi come di seguito:

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

4

Funzionerà ma l'elemento sarà ancora nel DOM.

.hidden {
    display: none;
}

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

Questo è un trucco molto semplice per la combinazione <select> <option>, che voglio semplicemente mostrare elementi filtrati invece dell'elenco completo
davyzhang,

Grazie mille!
Abhishek Sharma,

3

La tabella seguente elenca solo gli elementi che hanno un valore "principiante" impostato. Richiede sia il *ngForche il *ngIfper impedire righe indesiderate in HTML.

Originariamente aveva *ngIfe *ngForsullo stesso <tr>tag, ma non funziona. Aggiunto a <div>per il *ngForciclo e inserito *ngIfnel <tr>tag, funziona come previsto.

<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
Non penso che <div>all'interno di un tavolo sia un'idea goos, soprattutto quando ci sono alternative migliori. Hai controllato se funziona così in IE, che è particolarmente esigente sugli elementi in<table>
Günter Zöchbauer,

3

Aggiornato a angular2 beta 8

Ora da angular2 beta 8 possiamo usare *ngIfe *ngForsullo stesso componente vedere qui .

Alternato:

A volte non possiamo usare i tag HTML all'interno di un altro come in tr, th( table) o in li( ul). Non possiamo usare un altro tag HTML ma dobbiamo eseguire alcune azioni nella stessa situazione, quindi possiamo tag tag HTML5 <template>in questo modo.

Per l'utilizzo del modello:

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

ngSe si utilizza il modello:

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

Per ulteriori informazioni sulle direttive strutturali in angular2 vedere qui .


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

1

Puoi anche usare ng-template(invece del modello. Vedi la nota per usare il tag template) per applicare sia * ngFor che ngIf sullo stesso elemento HTML. Ecco un esempio in cui è possibile utilizzare sia * ngIf che * ngFor per lo stesso elemento tr nella tabella angolare.

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

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

Nota:

L'avvertenza di usare solo il templatetag anziché il ng-templatetag è che si lancia StaticInjectionErrorin alcuni punti.


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>


le voci vengono visualizzate solo se ha un nome.
Rajiv,

3
In che modo questa risposta aggiunge valore qui? Non fornisce nulla che non sia già stato fornito dalle altre risposte o ho perso qualcosa?
Günter Zöchbauer,

0

Non è possibile utilizzare più direttive strutturali sullo stesso elemento. Avvolgi il tuo elemento ng-templatee usa una direttiva strutturale lì


0

È possibile farlo in un altro modo controllando la lunghezza dell'array

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

Produzione

Meyers(21)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.