web-dev-qa-db-fra.com

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

J'ai du mal à utiliser les codes *ngFor et *ngIf d'Angular sur le même élément. 

Lorsque vous essayez de parcourir la collection dans le *ngFor, celle-ci est vue sous le nom null et échoue par conséquent lorsque vous tentez 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 la plus simple consiste à déplacer le *ngIf vers le haut, mais pour des scénarios tels que le bouclage d'éléments de liste dans un ul, je finirais avec un li si la collection est vide, ou mon lis entouré de conteneurs redondants.

Exemple à cette plnkr .

Notez l'erreur de la console:

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

Est-ce que je fais quelque chose de mal ou est-ce un bug?

299
garethdn

Angular v2 ne prend pas en charge plus d’une directive structurelle sur le même élément.
Comme solution de contournement, utilisez l’élément <ng-container> qui vous permet d’utiliser des éléments distincts pour chaque directive structurelle, mais il n’apparaît pas 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 la même chose, mais avec une syntaxe différente qui est source de confusion et n'est plus recommandé 

<ng-template [ngIf]="show">
  <div *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </div>
</ng-template>
479
Günter Zöchbauer

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

2016 angulaire 2 bêta

la solution consiste à utiliser le <template> comme espace réservé. 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 dans 2.0.0-rc.4 dans ce cas, vous pouvez utiliser cette 

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

Réponse mise à jour 2018

Avec les mises à jour, dès maintenant en 2018, angular v6 recommande d'utiliser <ng-container> au lieu de <template>

alors voici la réponse mise à jour.

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

Comme @Zyzle l'a mentionné, et @ Günter a 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 pas d'éléments <li> vides lorsque la liste est vide. Même l'élément <ul> n'existe pas (comme prévu).

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

La discussion github (4792) que @Zyzle mentionnée dans son commentaire présente également une autre solution utilisant <template> (ci-dessous, j'utilise votre balise 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.

27
Mark Rajcok

J'ai une autre solution.

Utilisez [hidden] au lieu de *ngIf

<div [hidden]="!show" *ngFor="let thing of stuff">

La différence est que *ngIf supprimera l'élément du DOM, alors que [hidden] joue réellement avec le style CSS en définissant display: none

27
Ajmal sha

Vous ne pouvez pas avoir ngFor et ngIf sur le même élément. Ce que vous pouvez faire est de ne pas remplir le tableau que vous utilisez dans ngFor jusqu'à ce que l'utilisateur clique sur la bascule de votre exemple.

Voici une méthode de base (pas terrible): http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P

4
Zyzle

Le tableau ci-dessous répertorie uniquement les éléments pour lesquels la valeur "débutant" est définie . Nécessite à la fois le *ngFor et le *ngIf afin d'éviter les lignes indésirables dans html.

A l'origine, *ngIf et *ngFor se trouvaient sur la même balise <tr>, mais ne fonctionnaient pas . Ajout d'un <div> pour la boucle *ngFor et placer *ngIf dans la balise <tr>, 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>
3
charliebear240

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

Mise à jour vers angular2 beta 8

Maintenant, à partir de angular2 beta 8, nous pouvons utiliser *ngIf et *ngFor sur le même composant voir ici .

Alterner:

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

ngPour utiliser un modèle:

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

si vous utilisez un modèle:

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

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

2
Pardeep Jain

Vous ne pouvez pas utiliser plus d'un Structural Directive dans Angular sur le même élément, cela crée une confusion et une structure. Vous devez donc les appliquer à deux é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 peut-être répéter un bloc HTML, mais seulement quand condition particulière est vraie. Vous allez essayer de mettre un * ngFor et un * ngIf sur le même élément Host. Angular ne vous laissera pas faire. Tu peux appliquer une seule directive structurelle à un élément.

La raison est la simplicité. Les directives structurelles peuvent faire des choses complexes avec l'élément Host et ses descendants. Quand deux directives sont posées réclamer le même élément Host, lequel est prioritaire? Lequel devrait aller en premier, le NgIf ou le NgFor? Est-ce que le NgIf peut annuler l'effet du NgFor? Si tel est le cas (et cela semble être le cas), comment devrait-on Angular généraliser la possibilité d'annuler pour autre structurel directives?

Il n'y a pas de réponse facile à ces questions. Interdire plusieurs directives structurelles les rend discutables. Il y a une solution facile pour ce cas d'utilisation: mettez le * ngIf sur un élément conteneur qui enveloppe le * ngFor element. Un ou les deux éléments peuvent être un ng-container afin que vous n’ayez pas à introduire de niveaux supplémentaires de HTML.

Vous pouvez donc utiliser ng-container (Angular4) comme enveloppe (sera supprimé du dom) ou un div ou un span si vous avez la 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>
2
Alireza
<div *ngFor="let thing of show ? stuff : []">
  {{log(thing)}}
  <span>{{thing.name}}</span>
</div>
1
Prashant Borde

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

0
Rajiv

en html:

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

en css:

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

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

0
Pradip Patil

Vous pouvez également utiliserng-template(au lieu de template. Voir la remarque relative à l'utilisation du tag template) pour appliquer les deux * ngFor et ngIf au même élément HTML. Voici un exemple où vous pouvez utiliser les deux * 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:

L'utilisation de la balise template au lieu deng-templateest conditionnée par le fait qu'elle jette StaticInjectionError à certains endroits.

0
Steffi Keran Rani J