*ngIf and *ngFor on same element causing error

garethdn picture garethdn · Jan 7, 2016 · Viewed 285.8k times · Source

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?

Answer

G&#252;nter Z&#246;chbauer picture Günter Zöchbauer · Sep 19, 2016

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>