*ngIf and *ngFor on same element causing error
Solution 1
Angular v2 doesn't support more than one structural directive on the same element.
As a workaround use the <ng-container>
element that allows you to use separate elements for each structural directive, but it is not stamped to the DOM.
<ng-container *ngIf="show">
<div *ngFor="let thing of stuff">
{{log(thing)}}
<span>{{thing.name}}</span>
</div>
</ng-container>
<ng-template>
(<template>
before Angular v4) allows to do the same but with a different syntax which is confusing and no longer recommended
<ng-template [ngIf]="show">
<div *ngFor="let thing of stuff">
{{log(thing)}}
<span>{{thing.name}}</span>
</div>
</ng-template>
Solution 2
As everyone pointed out even though having multiple template directives in a single element works in angular 1.x it is not allowed in Angular 2. you can find more info from here : https://github.com/angular/angular/issues/7315
2016 angular 2 beta
solution is to use the <template>
as a placeholder, so the code goes like this
<template *ngFor="let nav_link of defaultLinks" >
<li *ngIf="nav_link.visible">
.....
</li>
</template>
but for some reason above does not work in 2.0.0-rc.4
in that case you can use this
<template ngFor let-nav_link [ngForOf]="defaultLinks" >
<li *ngIf="nav_link.visible">
.....
</li>
</template>
Updated Answer 2018
With updates, right now in 2018 angular v6 recommend to use <ng-container>
instead of <template>
so here is the updated answer.
<ng-container *ngFor="let nav_link of defaultLinks" >
<li *ngIf="nav_link.visible">
.....
</li>
</ng-container>
Solution 3
As @Zyzle mentioned, and @Günter mentioned in a comment (https://github.com/angular/angular/issues/7315), this is not supported.
With
<ul *ngIf="show">
<li *ngFor="let thing of stuff">
{{log(thing)}}
<span>{{thing.name}}</span>
</li>
</ul>
there are no empty <li>
elements when the list is empty. Even the <ul>
element does not exist (as expected).
When the list is populated, there are no redundant container elements.
The github discussion (4792) that @Zyzle mentioned in his comment also presents another solution using <template>
(below I'm using your original markup ‐ using <div>
s):
<template [ngIf]="show">
<div *ngFor="let thing of stuff">
{{log(thing)}}
<span>{{thing.name}}</span>
</div>
</template>
This solution also does not introduce any extra/redundant container elements.
Solution 4
in html:
<div [ngClass]="{'disabled-field': !show}" *ngFor="let thing of stuff">
{{thing.name}}
</div>
in css:
.disabled-field {
pointer-events: none;
display: none;
}
Solution 5
This will work but the element will still in the DOM.
.hidden {
display: none;
}
<div [class.hidden]="!show" *ngFor="let thing of stuff">
{{log(thing)}}
<span>{{thing.name}}</span>
</div>
garethdn
Updated on July 20, 2022Comments
-
garethdn almost 2 years
I'm having a problem with trying to use Angular's
*ngFor
and*ngIf
on the same element.When trying to loop through the collection in the
*ngFor
, the collection is seen asnull
and consequently fails when trying to access its properties in the template.@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); } }
I know the easy solution is to move the
*ngIf
up a level but for scenarios like looping over list items in aul
, I'd end up with either an emptyli
if the collection is empty, or myli
s wrapped in redundant container elements.Example at this plnkr.
Note the console error:
EXCEPTION: TypeError: Cannot read property 'name' of null in [{{thing.name}} in ShellComponent@5:12]
Am I doing something wrong or is this a bug?
-
maurycy over 8 yearsWhy he cant have both? Elaborate please
-
Zyzle over 8 yearsThere's a discussion around that here github.com/angular/angular/issues/4792
-
maurycy over 8 yearsI know why that's happening, it's just to improve quality of the answer, plainly saying
you can't
is not really a good answer, wont you agree? -
Estus Flask over 8 yearsSure, they shouldn't be used together just because putting them in certain order to template doesn't guarantee that they will be executed in the same order. But this does not explain what exactly happens when 'Cannot read property 'name' of null' is thrown.
-
Evan Plaice over 8 yearsI'm not sure why this isn't the accepted answer.
<template>
is the way to add a parent element that won't show up in the output. -
Pardeep Jain about 8 yearsBoth *ngFor and *ngIf (with asterisk) are structural directives and they generate <template> tag. Structural directives, like ngIf, do their magic by using the HTML 5 template tag.
-
Rajiv over 7 yearsli items are only displayed if it has a name.
-
Günter Zöchbauer over 7 yearsHow does this answer add value here? It doesn't provide anything that's not provided by the other answers already or did I miss something?
-
Günter Zöchbauer over 7 yearsI don't think a
<div>
inside a table is a goos idea, especially when there are better alternatives. Have you checked if thus works in IE which is especially picky about elements in<table>
-
Alex Fuentes over 7 yearsThanks a lot. Surprisingly is still undocumented: github.com/angular/angular.io/issues/2303
-
Yuvraj Patil over 7 yearsHow will code look like when we have to have *ngIf inside *ngFor ? I.e. IF condition will be based on value of a loop element.
-
Günter Zöchbauer over 7 yearsJust put
ngFor
at the<ng-container>
element and thengIf
at the<div>
. You can also have two nested<ng-container>
wrapping the<div>
.<ng-container>
is just a helper element that will not be added to the DOM. -
Alex Fuentes over 7 years@user3640967 I agree with both. There is a lot of documentation but ng-container seems too much-needed functionality for everyone, should be better documented, Don't you think?
-
davyzhang about 7 yearsThis is a very easy hack for <select> <option> combination, which I simply want to show filtered items instead of the full list
-
MrCroft about 7 years
<md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn"> <template *ngIf="!selectedCategories.includes(category)"> <md-option *ngFor="let category of categories" [value]="category" (onSelectionChange)="selectCategory($event, category.id)"> {{ category.title }} </md-option> </template> </md-autocomplete>
It shows nothing in my case. If I just ngFor over md-option (without the <template *ngIf="">, I actually have options... Sorry, but it doesn't do multiline here -
Günter Zöchbauer about 7 years
*ngIf
doesn't work on<template>
. Either use[ngIf]="..."
or<ng-container *ngIf="..."
-
MrCroft about 7 yearsYup, it's a single ` and no space after it. I've just realized, used [ngIf]. It works if I explicitly put
true
orfalse
. So, there must be a problem with my!selectedCategories.includes(category)
condition (selectedCategories is an array of objects, of thecategory
form -{"id": 1, "title": "Some title"}
Anyway, I'm already off topic. Using <template> should work. -
Günter Zöchbauer about 7 yearsI'd suggest using
<ng-container>
. It behaves the same as<template>
but allows to use the "normal" syntax for structural directives. -
heringer about 6 yearsDocumentation says: "One structural directive per host element": "There's an easy solution for this use case: put the *ngIf on a container element that wraps the *ngFor element." - just reiterating
-
Günter Zöchbauer about 6 years@heringer thanks for the link. Such docs didn't exist back then when I posted this answer though :D
-
heringer about 6 years@GünterZöchbauer Yeah, i realized that. I'm just updating the thread for future reference :)
-
Hidayt Rahman almost 4 yearsYou saved my day, thank you. one question why
ng-container
doesn't show HTML impact with styling and all -
ankush981 over 2 yearsLooks like a neat solution!
-
Steffi Keran Rani J over 2 yearsThank you @ankush981