* ngIf e * ngFor no mesmo elemento causando erro

473

Estou tendo um problema ao tentar usar o Angular *ngFore *ngIfno mesmo elemento.

Ao tentar fazer um loop pela coleção no *ngFor, a coleção é vista como nulle, consequentemente, falha ao tentar acessar suas propriedades no modelo.

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

}

Eu sei que a solução mais fácil é *ngIfsubir de nível, mas para cenários como repetir itens de lista em um ul, eu terminaria com um vazio lise a coleção estiver vazia ou com meus lielementos de contêiner redundantes.

Exemplo neste plnkr .

Observe o erro do console:

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

Estou fazendo algo errado ou isso é um bug?

garethdn
fonte
stackoverflow.com/questions/40529537/… eu usaria o ng-container
Robert King
1
Possível duplicata da tabela filtrada angular
Cobus Kruger

Respostas:

680

O Angular v2 não suporta mais de uma diretiva estrutural no mesmo elemento.
Como solução alternativa, use o <ng-container>elemento que permite usar elementos separados para cada diretiva estrutural, mas ele não é carimbado no DOM .

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

<ng-template>( <template>antes do Angular v4) permite fazer o mesmo, mas com uma sintaxe diferente, que é confusa e não é mais recomendada

<ng-template [ngIf]="show">
  <div *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
  </div>
</ng-template>
Günter Zöchbauer
fonte
5
Muito obrigado. Surpreendentemente, ainda não está documentado: github.com/angular/angular.io/issues/2303
Alex Fuentes
7
Como será o código quando precisarmos ter * ngIf dentro de * ngFor? Ou seja, a condição SE será baseada no valor de um elemento do loop.
Yuvraj Patil
22
Basta colocar ngForno <ng-container>elemento e ngIfno <div>. Você também pode ter dois aninhada <ng-container>envolvendo o <div>. <ng-container>é apenas um elemento auxiliar que não será adicionado ao DOM.
Günter Zöchbauer
3
Eu sugiro usar <ng-container>. Ele se comporta da mesma forma, <template>mas permite usar a sintaxe "normal" para diretivas estruturais.
Günter Zöchbauer
2
A documentação diz : "Uma diretiva estrutural por elemento host": "Existe uma solução fácil para este caso de uso: coloque o * ngIf em um elemento contêiner que agrupe o elemento * ngFor". - apenas reiterando
heringer
71

Como todos apontaram, apesar de ter várias diretivas de modelo em um único elemento funcionar no angular 1.x, não é permitido no Angular 2. Você pode encontrar mais informações aqui: https://github.com/angular/angular/issues/ 7315

2016 angular 2 beta

solução é usar o <template>como um espaço reservado, para que o código fique assim

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

mas, por algum motivo acima, não funciona. 2.0.0-rc.4Nesse caso, você pode usar este

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

Resposta atualizada 2018

Com atualizações, agora em 2018 a angular v6 recomenda usar em <ng-container>vez de<template>

então aqui está a resposta atualizada.

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

Como o @Zyzle mencionou, e o @ Günter mencionado em um comentário ( https://github.com/angular/angular/issues/7315 ), isso não é suportado.

Com

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

não há <li>elementos vazios quando a lista está vazia. Mesmo o <ul>elemento não existe (como esperado).

Quando a lista é preenchida, não há elementos de contêiner redundantes.

A discussão no github ( 4792) mencionada pelo @Zyzle em seu comentário também apresenta outra solução usando <template>(abaixo, estou usando sua marcação original - usando <div>s):

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

Essa solução também não apresenta nenhum elemento de contêiner extra / redundante.

Mark Rajcok
fonte
1
Não sei por que essa não é a resposta aceita. <template>é a maneira de adicionar um elemento pai que não será exibido na saída.
Evan Plaice
8

em html:

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

em css:

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

Você não pode ter ngFore ngIfno mesmo elemento. O que você pode fazer é adiar o preenchimento da matriz em que você está usando ngForaté clicar no botão de alternância do seu exemplo.

Aqui está uma maneira básica (não ótima) de fazê-lo: http://plnkr.co/edit/Pylx5HSWIZ7ahoC7wT6P

Zyzle
fonte
Por que ele não pode ter os dois? Elaborado por favor
maurycy
1
Há uma discussão em torno de que aqui github.com/angular/angular/issues/4792
Zyzle
1
Eu sei por que isso está acontecendo, é apenas para melhorar a qualidade da resposta, dizendo claramente que you can'tnão é realmente uma boa resposta, você não concorda?
Maurycy
Claro, eles não devem ser usados ​​juntos apenas porque colocá-los em certa ordem para modelar não garante que eles serão executados na mesma ordem. Mas isso não explica o que exatamente acontece quando é lançada 'Não é possível ler a propriedade' name 'of null'.
Estus Flask
* NgFor e * ngIf (com asterisco) são diretivas estruturais e geram a tag <template>. Diretivas estruturais, como ngIf, fazem sua mágica usando a tag de modelo HTML 5.
Pardeep Jain 04/04
5

Você não pode usar mais de um Structural Directiveno Angular no mesmo elemento, isso cria uma confusão e estrutura ruins; portanto, você deve aplicá-los em 2 elementos aninhados separados (ou pode usar ng-container). Leia esta declaração da equipe do Angular:

Uma diretiva estrutural por elemento host

Algum dia você desejará repetir um bloco de HTML, mas apenas quando uma condição específica for verdadeira. Você tentará colocar um * ngFor e um * ngIf no mesmo elemento host. Angular não vai deixar você. Você pode aplicar apenas uma diretiva estrutural a um elemento.

O motivo é a simplicidade. Diretivas estruturais podem fazer coisas complexas com o elemento host e seus descendentes. Quando duas diretivas reivindicam o mesmo elemento host, qual delas tem precedência? Qual deve ser o primeiro, o NgIf ou o NgFor? O NgIf pode cancelar o efeito do NgFor? Em caso afirmativo (e parece que deveria ser assim), como a Angular deve generalizar a capacidade de cancelar outras diretrizes estruturais?

Não há respostas fáceis para essas perguntas. Proibir várias diretivas estruturais os torna discutíveis. Existe uma solução fácil para esse caso de uso: coloque o * ngIf em um elemento contêiner que agrupe o elemento * ngFor . Um ou ambos os elementos podem ser um ng-container, para que você não precise introduzir níveis extras de HTML.

Portanto, você pode usar ng-container(Angular4) como o wrapper (será excluído do dom) ou um div ou span se você tiver classe ou alguns outros atributos, como abaixo:

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

Isso funcionará, mas o elemento ainda estará no DOM.

.hidden {
    display: none;
}

<div [class.hidden]="!show" *ngFor="let thing of stuff">
    {{log(thing)}}
    <span>{{thing.name}}</span>
</div>
ronald8192
fonte
Este é um muito fácil cortar para <select> <option> combinação, que eu simplesmente quero mostrar itens filtrados em vez da lista completa
davyzhang
Muito obrigado!
Abhishek Sharma
3

A tabela abaixo lista apenas os itens que têm um valor "iniciante" definido. Requer o *ngFore o *ngIfpara evitar linhas indesejadas em html.

Originalmente tinha *ngIfe *ngForna mesma <tr>tag, mas não funciona. Adicionado a <div>para o *ngForloop e colocado *ngIfna <tr>tag, funciona conforme o esperado.

<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
fonte
1
Eu não acho que <div>dentro de uma mesa seja uma idéia de gosma, especialmente quando existem alternativas melhores. Você verificou se, portanto, funciona no IE, o que é especialmente exigente quanto aos elementos em #<table>
Günter Zöchbauer
3

Atualizado para o angular2 beta 8

Agora, a partir do angular2 beta 8, podemos usar *ngIfe *ngForno mesmo componente, veja aqui .

Alternar:

Às vezes, não podemos usar tags HTML dentro de outro, como in tr, th( table) ou in li( ul). Não podemos usar outra tag HTML, mas precisamos executar alguma ação na mesma situação para podermos usar a tag HTML5<template> dessa maneira.

ngPara usar o modelo:

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

ngSe usando o modelo:

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

Para mais informações sobre diretivas estruturais em angular2, veja aqui .

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

Você também pode usar ng-template(em vez de modelo. Consulte a nota sobre o uso da marca de modelo) para aplicar * ngFor e ngIf no mesmo elemento HTML. Aqui está um exemplo em que você pode usar * ngIf e * ngFor para o mesmo elemento tr na tabela angular.

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

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

Nota:

A ressalva de usar apenas a templatetag em vez da ng-templatetag é que ela é lançada StaticInjectionErrorem alguns lugares.

Steffi Keran J Rani
fonte
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
fonte
Os itens li são exibidos apenas se tiver um nome.
Rajiv
3
Como essa resposta agrega valor aqui? Ele não fornece nada que não seja fornecido pelas outras respostas já ou eu perdi alguma coisa?
Günter Zöchbauer 2/11
0

Você não pode usar várias diretivas estruturais no mesmo elemento. Envolva seu elemento ng-templatee use uma diretiva estrutural lá

Pradip Patil
fonte
0

Você pode fazer isso de outra maneira, verificando o comprimento da matriz

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

Resultado

Meyers(21)
yogesh waghmare
fonte