* ngIf et * ngFor sur le même élément provoquant une erreur


473

J'ai du mal à essayer d'utiliser Angular *ngForet *ngIfsur le même élément.

Lorsque vous essayez de parcourir la collection dans le *ngFor, la collection est vue comme nullet échoue par conséquent lorsque vous essayez d'accéder à ses propriétés dans le modèle.

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

}

Je sais que la solution simple est de *ngIfmonter d'un niveau, mais pour des scénarios tels que le bouclage des éléments de liste dans un ul, je me retrouverais soit avec un vide lisi la collection est vide, soit avec mon lis enveloppé dans des éléments de conteneur redondants.

Exemple à ce plnkr .

Notez l'erreur de console:

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

Suis-je en train de faire quelque chose de mal ou est-ce un bug?



1
Copie possible de la table filtrée angulaire
Cobus Kruger

Réponses:


680

Angular v2 ne prend pas en charge plus d'une directive structurelle sur le même élément.
Comme solution de contournement, utilisez l' <ng-container>élément qui vous permet d'utiliser des éléments distincts pour chaque directive structurelle, mais il n'est pas tamponné dans le DOM .

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

<ng-template>( <template>avant Angular v4) permet de faire de même mais avec une syntaxe différente qui prête à confusion et n'est plus recommandée

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

5
Merci beaucoup. Étonnamment, il n'y a toujours pas de documents: github.com/angular/angular.io/issues/2303
Alex Fuentes

7
À quoi ressemblera le code lorsque nous devons avoir * ngIf dans * ngFor? C'est-à-dire que la condition IF sera basée sur la valeur d'un élément de boucle.
Yuvraj Patil

22
Il suffit de mettre ngForl' <ng-container>élément et le ngIfau <div>. Vous pouvez également avoir deux <ng-container>enveloppements imbriqués <div>. <ng-container>est juste un élément d'assistance qui ne sera pas ajouté au DOM.
Günter Zöchbauer

3
Je suggère d'utiliser <ng-container>. Il se comporte de la même manière que <template>mais permet d'utiliser la syntaxe "normale" pour les directives structurelles.
Günter Zöchbauer

2
La documentation indique : "Une directive structurelle par élément hôte": "Il existe une solution simple pour ce cas d'utilisation: placez * ngIf sur un élément conteneur qui enveloppe l'élément * ngFor." - réitérant
heringer

71

Comme tout le monde l'a souligné, même si plusieurs directives de modèle dans un seul élément fonctionnent dans angular 1.x, cela n'est pas autorisé dans Angular 2. vous pouvez trouver plus d'informations ici: https://github.com/angular/angular/issues/ 7315

2016 angular 2 beta

la solution consiste à utiliser le <template>comme un espace réservé, de sorte que le code va comme ceci

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

mais pour une raison quelconque ci-dessus ne fonctionne pas 2.0.0-rc.4dans ce cas, vous pouvez utiliser ce

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

Réponse mise à jour 2018

Avec les mises à jour, en ce moment en 2018, angulaire v6 recommande d'utiliser <ng-container>au lieu de<template>

voici donc la réponse mise à jour.

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

29

Comme @Zyzle l'a mentionné et @ Günter mentionné dans un commentaire ( https://github.com/angular/angular/issues/7315 ), cela n'est pas pris en charge.

Avec

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

il n'y a aucun <li>élément vide lorsque la liste est vide. Même l' <ul>élément n'existe pas (comme prévu).

Lorsque la liste est remplie, il n'y a aucun élément de conteneur redondant.

La discussion github (4792) que @Zyzle a mentionnée dans son commentaire présente également une autre solution en utilisant <template>(ci-dessous j'utilise votre balisage d'origine - en utilisant <div>s):

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

Cette solution n'introduit pas non plus d'éléments de conteneur supplémentaires / redondants.


1
Je ne sais pas pourquoi ce n'est pas la réponse acceptée. <template>est le moyen d'ajouter un élément parent qui n'apparaîtra pas dans la sortie.
Evan Plaice

8

en html:

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

en css:

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

5

Vous ne pouvez pas avoir ngForet ngIfsur le même élément. Ce que vous pourriez faire, c'est de ne pas remplir le tableau que vous utilisez ngForjusqu'à ce que vous cliquiez sur la bascule de votre exemple.

Voici une manière basique (pas géniale) de le faire: http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P


Pourquoi il ne peut pas avoir les deux? Elaborate please
maurycy

1
Il y a une discussion à ce sujet ici github.com/angular/angular/issues/4792
Zyzle

1
Je sais pourquoi cela se produit, c'est juste pour améliorer la qualité de la réponse, en disant clairement que ce you can'tn'est pas vraiment une bonne réponse, n'est-ce pas?
maurycy

Bien sûr, ils ne devraient pas être utilisés ensemble simplement parce que les mettre dans un certain ordre dans un modèle ne garantit pas qu'ils seront exécutés dans le même ordre. Mais cela n'explique pas ce qui se passe exactement lorsque 'Impossible de lire la propriété' nom 'de null' est levée.
Estus Flask

* NgFor et * ngIf (avec un astérisque) sont des directives structurelles et génèrent la balise <template>. Les directives structurelles, comme ngIf, font leur magie en utilisant la balise de modèle HTML 5.
Pardeep Jain

5

Vous ne pouvez pas utiliser plus d'un Structural DirectiveAngular sur le même élément, cela crée une mauvaise confusion et structure, vous devez donc les appliquer dans 2 éléments imbriqués distincts (ou vous pouvez utiliser ng-container), lisez cette déclaration de l'équipe Angular:

Une directive structurelle par élément hôte

Un jour, vous voudrez répéter un bloc de code HTML, mais uniquement lorsqu'une condition particulière est vraie. Vous allez essayer de mettre à la fois un * ngFor et un * ngIf sur le même élément hôte. Angular ne vous laissera pas. Vous ne pouvez appliquer qu'une seule directive structurelle à un élément.

La raison en est la simplicité. Les directives structurelles peuvent faire des choses complexes avec l'élément hôte et ses descendants. Lorsque deux directives revendiquent le même élément hôte, laquelle a priorité? Lequel devrait aller en premier, le NgIf ou le NgFor? Le NgIf peut-il annuler l'effet du NgFor? Si tel est le cas (et il semble qu'il devrait en être ainsi), comment Angular devrait-il généraliser la possibilité d'annuler pour d'autres directives structurelles?

Il n'y a pas de réponses faciles à ces questions. L'interdiction de plusieurs directives structurelles les rend sans objet. Il existe une solution simple pour ce cas d'utilisation: placez le * ngIf sur un élément conteneur qui enveloppe l' élément * ngFor . Un ou les deux éléments peuvent être un conteneur ng , vous n'avez donc pas à introduire de niveaux supplémentaires de HTML.

Vous pouvez donc utiliser ng-container(Angular4) comme wrapper (sera supprimé du dom) ou div ou span si vous avez une classe ou d'autres attributs comme ci-dessous:

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

4

Cela fonctionnera mais l'élément sera toujours dans le DOM.

.hidden {
    display: none;
}

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

C'est un hack très facile pour la combinaison <select> <option>, que je veux simplement afficher les éléments filtrés au lieu de la liste complète
davyzhang

Merci beaucoup!
Abhishek Sharma

3

Le tableau ci-dessous répertorie uniquement les éléments dont la valeur "débutant" est définie. Nécessite à la fois le *ngForet le *ngIfpour empêcher les lignes indésirables en html.

A l'origine *ngIfet *ngForsur la même <tr>balise, mais ne fonctionne pas. Ajout d'un <div>pour la *ngForboucle et placé *ngIfdans la <tr>balise, fonctionne comme prévu.

<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
Je ne pense pas que l' <div>intérieur d'une table soit une idée de goos, surtout quand il existe de meilleures alternatives. Avez-vous vérifié si cela fonctionne donc dans IE, qui est particulièrement pointilleux sur les éléments dans<table>
Günter Zöchbauer

3

Mise à jour vers angular2 beta 8

Maintenant, depuis angular2 beta 8, nous pouvons utiliser *ngIfet *ngForsur le même composant voir ici .

Alterner:

Parfois, nous ne pouvons pas utiliser de balises HTML dans une autre comme dans tr, th( table) ou dans li( ul). Nous ne pouvons pas utiliser une autre balise HTML, mais nous devons effectuer une action dans la même situation afin que nous puissions utiliser la balise de fonctionnalité HTML5 <template>de cette manière.

ngPour utiliser le modèle:

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

ngSi vous utilisez un modèle:

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

Pour plus d'informations sur les directives structurelles dans angular2, voir ici .


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

1

Vous pouvez également utiliser ng-template(au lieu du modèle. Voir la remarque pour la mise en garde de l'utilisation de la balise de modèle) pour appliquer à la fois * ngFor et ngIf sur le même élément HTML. Voici un exemple où vous pouvez utiliser à la fois * ngIf et * ngFor pour le même élément tr dans le tableau angulaire.

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

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

Remarque:

La mise en garde d'utiliser uniquement la templatebalise au lieu de la ng-templatebalise est qu'elle jette StaticInjectionErrorà certains endroits.


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>


Les éléments li ne s'affichent que s'ils ont un nom.
Rajiv

3
Comment cette réponse ajoute-t-elle de la valeur ici? Il ne fournit pas quelque chose qui n'est pas déjà fourni par les autres réponses ou ai-je oublié quelque chose?
Günter Zöchbauer

0

Vous ne pouvez pas utiliser plusieurs directives structurelles sur le même élément. Enveloppez votre élément ng-templateet utilisez une directive structurelle


0

Vous pouvez le faire d'une autre manière en vérifiant la longueur du tableau

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

Production

Meyers(21)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.