* ngIf y * ngFor en el mismo elemento que causa error

473

Tengo un problema al tratar de usar Angular's *ngFory *ngIfen el mismo elemento.

Al intentar recorrer la colección en el *ngFor, la colección se ve como nully, en consecuencia, falla al intentar acceder a sus propiedades en la plantilla.

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

}

Sé que la solución fácil es subir el *ngIfnivel, pero para escenarios como recorrer los elementos de la lista en un ul, terminaría con un vacío lisi la colección está vacía, o mi lis envuelto en elementos contenedores redundantes.

Ejemplo en este plnkr .

Tenga en cuenta el error de la consola:

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

¿Estoy haciendo algo mal o es un error?

garethdn
fuente
stackoverflow.com/questions/40529537/… iría con ng-container
robert king
1
Posible duplicado de la tabla angular filtrada
Cobus Kruger

Respuestas:

680

Angular v2 no admite más de una directiva estructural sobre el mismo elemento.
Como solución alternativa, use el <ng-container>elemento que le permite usar elementos separados para cada directiva estructural, pero no está estampado en el DOM .

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

<ng-template>( <template>antes de Angular v4) permite hacer lo mismo pero con una sintaxis diferente que es confusa y ya no se recomienda

<ng-template [ngIf]="show">
  <div *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </div>
</ng-template>
Günter Zöchbauer
fuente
55
Muchas gracias. Sorprendentemente aún no está documentado: github.com/angular/angular.io/issues/2303
Alex Fuentes
77
¿Cómo será el código cuando tengamos que tener * ngIf dentro * ngFor? Es decir, la condición IF se basará en el valor de un elemento de bucle.
Yuvraj Patil
22
Simplemente ponga ngForel <ng-container>elemento y el ngIfen el <div>. También puede tener dos anidados <ng-container>envolviendo el <div>. <ng-container>es solo un elemento auxiliar que no se agregará al DOM.
Günter Zöchbauer
3
Sugeriría usar <ng-container>. Se comporta igual <template>pero permite utilizar la sintaxis "normal" para directivas estructurales.
Günter Zöchbauer
2
La documentación dice : "Una directiva estructural por elemento host": "Hay una solución fácil para este caso de uso: coloque * ngIf en un elemento contenedor que envuelva el elemento * ngFor". - solo reiterando
heringer
71

Como todos señalaron, aunque tener múltiples directivas de plantilla en un solo elemento funciona en angular 1.x, no está permitido en Angular 2. Puede encontrar más información aquí: https://github.com/angular/angular/issues/ 7315

2016 angular 2 beta

la solución es usar el <template>como marcador de posición, por lo que el código es así

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

pero por alguna razón anterior no funciona 2.0.0-rc.4en ese caso, puede usar esto

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

Respuesta actualizada 2018

Con actualizaciones, ahora en 2018 angular v6 se recomienda usar en <ng-container>lugar de<template>

Así que aquí está la respuesta actualizada.

<ng-container *ngFor="let nav_link of defaultLinks" >
   <li *ngIf="nav_link.visible">
       .....
   </li> 
</ng-container>
imal hasaranga perera
fuente
29

Como @Zyzle mencionó y @ Günter mencionó en un comentario ( https://github.com/angular/angular/issues/7315 ), esto no es compatible.

Con

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

no hay <li>elementos vacíos cuando la lista está vacía. Incluso el <ul>elemento no existe (como se esperaba).

Cuando se llena la lista, no hay elementos contenedores redundantes.

La discusión de github (4792) que @Zyzle mencionó en su comentario también presenta otra solución usando <template>(a continuación estoy usando su marcado original - usando <div>s):

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

Esta solución tampoco introduce ningún elemento contenedor adicional / redundante.

Mark Rajcok
fuente
1
No estoy seguro de por qué esta no es la respuesta aceptada. <template>es la forma de agregar un elemento padre que no aparecerá en la salida.
Evan Plaice el
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;
}
mojtaba ramezani
fuente
5

No se puede tener ngFory ngIfen el mismo elemento. Lo que podría hacer es esperar para completar la matriz en la que está utilizando ngForhasta que se haga clic en la palanca de su ejemplo.

Aquí hay una forma básica (no excelente) de hacerlo: http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P

Zyzle
fuente
¿Por qué no puede tener ambos? Elaborado por favor
maurycy
1
Hay una discusión sobre eso aquí github.com/angular/angular/issues/4792
Zyzle
1
Sé por qué está sucediendo, es solo para mejorar la calidad de la respuesta, simplemente decir you can'tque no es realmente una buena respuesta, ¿no está de acuerdo?
maurycy
Claro, no deben usarse juntos solo porque ponerlos en cierto orden en la plantilla no garantiza que se ejecutarán en el mismo orden. Pero esto no explica qué sucede exactamente cuando se arroja 'No se puede leer la propiedad' nombre 'de nulo'.
Estus Flask
Tanto * ngFor como * ngIf (con asterisco) son directivas estructurales y generan la etiqueta <template>. Las directivas estructurales, como ngIf, hacen su magia usando la etiqueta de plantilla HTML 5.
Pardeep Jain
5

No puede usar más de uno Structural Directiveen Angular en el mismo elemento, crea una mala confusión y estructura, por lo que debe aplicarlos en 2 elementos anidados separados (o puede usar ng-container), lea esta declaración del equipo de Angular:

Una directiva estructural por elemento host

Algún día querrás repetir un bloque de HTML, pero solo cuando una condición particular sea verdadera. Intentará poner un * ngFor y un * ngIf en el mismo elemento host. Angular no te dejará. Puede aplicar solo una directiva estructural a un elemento.

La razón es la simplicidad. Las directivas estructurales pueden hacer cosas complejas con el elemento host y sus descendientes. Cuando dos directivas reclaman el mismo elemento host, ¿cuál tiene prioridad? ¿Cuál debería ir primero, el NgIf o el NgFor? ¿Puede el NgIf cancelar el efecto del NgFor? Si es así (y parece que debería ser así), ¿cómo debería Angular generalizar la capacidad de cancelar para otras directivas estructurales?

No hay respuestas fáciles para esas preguntas. Prohibir múltiples directivas estructurales los hace discutibles. Hay una solución fácil para este caso de uso: coloque * ngIf en un elemento contenedor que envuelva el elemento * ngFor . Uno o ambos elementos pueden ser un contenedor ng para que no tenga que introducir niveles adicionales de HTML.

Por lo tanto, puede usar ng-container(Angular4) como el contenedor (se eliminará del dom) o un div o span si tiene clase u otros atributos como se muestra a continuación:

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

Esto funcionará pero el elemento seguirá en el DOM.

.hidden {
    display: none;
}

<div [class.hidden]="!show" *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
</div>
ronald8192
fuente
Este es un truco muy fácil para la combinación <select> <option>, que simplemente quiero mostrar elementos filtrados en lugar de la lista completa
davyzhang
¡Muchas gracias!
Abhishek Sharma
3

La siguiente tabla solo enumera los elementos que tienen un conjunto de valores "principiante". Requiere tanto el *ngFory el *ngIfpara evitar filas no deseadas en html.

Originalmente tenía *ngIfy *ngForen la misma <tr>etiqueta, pero no funciona. Se agregó un <div>para el *ngForbucle y se colocó *ngIfen la <tr>etiqueta, funciona como se esperaba.

<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>
charliebear240
fuente
1
No creo que <div>dentro de una mesa sea una buena idea, especialmente cuando hay mejores alternativas. ¿Ha comprobado si funciona tanto en IE que es especialmente exigente con elementos en<table>
Günter Zöchbauer
3

Actualizado a angular2 beta 8

Ahora a partir de angular2 beta 8 podemos usar *ngIfy *ngForen el mismo componente ver aquí .

Alterno:

A veces no podemos usar etiquetas HTML dentro de otra como in tr, th( table) o in li( ul). No podemos usar otra etiqueta HTML, pero tenemos que realizar alguna acción en la misma situación para que podamos etiquetar la función HTML5 <template>de esta manera.

ng Para usar la plantilla:

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

ng Si usa la plantilla:

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

Para obtener más información sobre las directivas estructurales en angular2, consulte aquí .

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

También puede usar ng-template(en lugar de plantilla. Consulte la nota sobre la advertencia de usar la etiqueta de plantilla) para aplicar tanto * ngFor como ngIf en el mismo elemento HTML. Aquí hay un ejemplo donde puede usar tanto * ngIf como * ngFor para el mismo elemento tr en la tabla angular.

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

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

Nota:

La advertencia de usar solo la templateetiqueta en lugar de la ng-templateetiqueta es que arroja StaticInjectionErroren algunos lugares.

Steffi Keran Rani J
fuente
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>

Rajiv
fuente
Los elementos li solo se muestran si tiene un nombre.
Rajiv el
3
¿Cómo esta respuesta agrega valor aquí? ¿No proporciona nada que no haya sido proporcionado por las otras respuestas o me perdí algo?
Günter Zöchbauer el
0

No puede usar directivas estructurales múltiples en el mismo elemento. Envuelva su elemento ng-templatey use una directiva estructural allí

Pradip Patil
fuente
0

Puede hacer esto de otra manera comprobando la longitud de la matriz

<div *ngIf="stuff.length>0">
  <div *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </div>
</div>
SMS
fuente
-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>

Salida

Meyers(21)
Yogesh Waghmare
fuente