ChatGPT解决这个技术问题 Extra ChatGPT

*ngIf and *ngFor on same element causing error

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 as null 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 a ul, I'd end up with either an empty li if the collection is empty, or my lis 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?

stackoverflow.com/questions/40529537/… i'd go with ng-container
Possible duplicate of Angular filtered table

E
Edric

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>

Thanks a lot. Surprisingly is still undocumented: github.com/angular/angular.io/issues/2303
How 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.
Just put ngFor at the <ng-container> element and the ngIf 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.
I'd suggest using <ng-container>. It behaves the same as <template> but allows to use the "normal" syntax for structural directives.
Documentation 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
i
imal hasaranga perera

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>

You saved my day, thank you. one question why ng-container doesn't show HTML impact with styling and all
M
Mark Rajcok

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.


I'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.
m
mojtaba ramezani

in html:

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

in css:

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

A
Alireza

You can not use more than one Structural Directive in Angular on the same element, it makes a bad confusion and structure, so you need to apply them in 2 separate nested elements(or you can use ng-container), read this statement from Angular team:

One structural directive per host element Someday you'll want to repeat a block of HTML but only when a particular condition is true. You'll try to put both an *ngFor and an *ngIf on the same host element. Angular won't let you. You may apply only one structural directive to an element. The reason is simplicity. Structural directives can do complex things with the host element and its descendents. When two directives lay claim to the same host element, which one takes precedence? Which should go first, the NgIf or the NgFor? Can the NgIf cancel the effect of the NgFor? If so (and it seems like it should be so), how should Angular generalize the ability to cancel for other structural directives? There are no easy answers to these questions. Prohibiting multiple structural directives makes them moot. There's an easy solution for this use case: put the *ngIf on a container element that wraps the *ngFor element. One or both elements can be an ng-container so you don't have to introduce extra levels of HTML.

So you can use ng-container (Angular4) as the wrapper (will be deleted from the dom) or a div or span if you have class or some other attributes as below:

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

C
C_Ogoo

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>

This is a very easy hack for