angular 2 *ngFor, when iterable hasn't be instantiated yet
Asked Answered
R

4

5

I have a couple of *ngFor loops depending on iterables which might not be instantiated yet. (e.g. they are waiting for an observable)

is there any expression like this I could use in the view?

UPDATE: Here's the part thats throwing error

Component

activeLectureContent:LectureContent;

View

  <div class="filter-units">
    <div>Units</div>
    <a (click)="setUnit(i)" class="btn btn-icon-only blue filter-unit-btn" *ngFor="#unit of activeLectureContent.content; #i = index">
      <span>{{i+1}}</span>
    </a>
  </div>

Cannot read property 'content' of undefined in [null]

Lecture Content looks like this

export class LectureContent{
  constructor(
    public name:string = '',
    public filter:LectureFilter[]=[],
    public show:boolean = true,
    public hover:boolean = false,
    public content:any[]=[]
  ){}
}

cheers

Rosewood answered 29/4, 2016 at 21:44 Comment(2)
Is this causing an error? Often, you don't have to do anything if the iterable doesn't exist yet.Coseismal
yes, that iterable contains large objects and elements inside that div need to access thoseRosewood
M
3

If it's "simply" an iterable (array, ... but not observable, promise), I think that there is nothing to do. ngFor will check for updates of the iterable. When the value will become not null, ngFor will update the corresponding content:

See this sample:

@Component({
  selector: 'app'
  template: `
    <div>
      <div *ngFor="#category of categories;#i=index">{{i}} - {{category.name}}</div>
    </div>
  `
})
export class App {
  constructor() {
    Observable.create((observer) => {
      setTimeout(() => {
        observer.next();
      }, 2000);
    }).subscribe(() => {
      this.categories = [
        { name: 'Cat1', value: 'cat1' },
        { name: 'Cat2', value: 'cat2' },
        { name: 'Cat3', value: 'cat3' },
        { name: 'Cat4', value: 'cat4' }
      ];
    });
  }
}

See this plunkr: https://plnkr.co/edit/J1aVclVcjJPqm1Qx9j0j?p=preview.

With beta 17, you need to replace # by let:

<div *ngFor="let category of categories;let i=index">{{i}} - (...)

It doesn't seem that ngIf with ngFor works well. See thisp plunkr: https://plnkr.co/edit/aE3umzzSZ5U9BocEE9l6?p=preview

If "iterable" is an observable or a promise, que the async pipe.

Edit

You could try something like that:

<template [ngIf]=" activeLectureContent ">
    <a (click)="setUnit(i)" class="btn btn-icon-only blue filter-unit-btn" *ngFor="#unit of activeLectureContent.content; #i = index">
      <span>{{i+1}}</span>
    </a>
</template>

I use the expanded syntax for ngIf since ngFor and ngIf can't be used on the same element.

Megalith answered 30/4, 2016 at 8:13 Comment(5)
Hi Thierry, i've updated my question. Maybe my iterable isn't really an simple iterable? Because it's an array inside an object that hasn't be instantiated yet? If I get you correcty, ngFor doesn't throw an error if its hooked directly to an iterable?Rosewood
Hey! You need to check that activeLectureContent isn't undefined. I updated my answer. Perhaps the Elvis operator could be used: "activeLectureContent?.content". But I don't know if it works in this case (ngFor).Megalith
THANKS! Elvis really safed they day ;)Rosewood
Elvis really works for activeLectureContent?.content. however it won't work for activeLectureContent?.content[activeUnit]?.tasks since content is an array and not an object i guess? as for the ngIf = false, means the whole block won't be rendered at all, so any uninstantiated variables inside won't cause error.Rosewood
Great! Perhaps you could try this: "activeLectureContent?.content?[activeMent].tasks"Megalith
B
3

You could use an ngIf directive, so it is only rendered if iterable is thruthy.

<div *ngIf="iterable" *ngFor="let item of iterable"><div>
Breadth answered 29/4, 2016 at 21:49 Comment(1)
Hi! thanks for the reply, i've updated my question because i think i'm looping not directly over an iterable but rather it's an array inside an Object which hasn't be instantiated.Rosewood
M
3

If it's "simply" an iterable (array, ... but not observable, promise), I think that there is nothing to do. ngFor will check for updates of the iterable. When the value will become not null, ngFor will update the corresponding content:

See this sample:

@Component({
  selector: 'app'
  template: `
    <div>
      <div *ngFor="#category of categories;#i=index">{{i}} - {{category.name}}</div>
    </div>
  `
})
export class App {
  constructor() {
    Observable.create((observer) => {
      setTimeout(() => {
        observer.next();
      }, 2000);
    }).subscribe(() => {
      this.categories = [
        { name: 'Cat1', value: 'cat1' },
        { name: 'Cat2', value: 'cat2' },
        { name: 'Cat3', value: 'cat3' },
        { name: 'Cat4', value: 'cat4' }
      ];
    });
  }
}

See this plunkr: https://plnkr.co/edit/J1aVclVcjJPqm1Qx9j0j?p=preview.

With beta 17, you need to replace # by let:

<div *ngFor="let category of categories;let i=index">{{i}} - (...)

It doesn't seem that ngIf with ngFor works well. See thisp plunkr: https://plnkr.co/edit/aE3umzzSZ5U9BocEE9l6?p=preview

If "iterable" is an observable or a promise, que the async pipe.

Edit

You could try something like that:

<template [ngIf]=" activeLectureContent ">
    <a (click)="setUnit(i)" class="btn btn-icon-only blue filter-unit-btn" *ngFor="#unit of activeLectureContent.content; #i = index">
      <span>{{i+1}}</span>
    </a>
</template>

I use the expanded syntax for ngIf since ngFor and ngIf can't be used on the same element.

Megalith answered 30/4, 2016 at 8:13 Comment(5)
Hi Thierry, i've updated my question. Maybe my iterable isn't really an simple iterable? Because it's an array inside an object that hasn't be instantiated yet? If I get you correcty, ngFor doesn't throw an error if its hooked directly to an iterable?Rosewood
Hey! You need to check that activeLectureContent isn't undefined. I updated my answer. Perhaps the Elvis operator could be used: "activeLectureContent?.content". But I don't know if it works in this case (ngFor).Megalith
THANKS! Elvis really safed they day ;)Rosewood
Elvis really works for activeLectureContent?.content. however it won't work for activeLectureContent?.content[activeUnit]?.tasks since content is an array and not an object i guess? as for the ngIf = false, means the whole block won't be rendered at all, so any uninstantiated variables inside won't cause error.Rosewood
Great! Perhaps you could try this: "activeLectureContent?.content?[activeMent].tasks"Megalith
T
3

Resurrecting this post as I recently had this same question.

This is a perfect case for Angular's (safe navigation operator).

From the docs:

The Angular safe navigation operator (?.) is a fluent and convenient way to guard against null and undefined values in property paths.

So the example view from the question would be:

  <div class="filter-units">
    <div>Units</div>
    <a (click)="setUnit(i)" class="btn btn-icon-only blue filter-unit-btn" 
      *ngFor="let unit of activeLectureContent?.content; let i = index">
      <span>{{i+1}}</span>
    </a>
  </div>

Adding the ? after activeLectureContent means the ngFor will be ignored if activeLectureContent is null or undefined.

Thirion answered 31/5, 2017 at 21:21 Comment(0)
D
1

You are looking for AsyncPipe:

<div *ngFor="#item of iterable | async"></div>

It works with Observables, Promises and EventEmitters. You can get more information about it in the guide (chapter "The impure AsyncPipe") and documentation.

Denna answered 29/4, 2016 at 22:19 Comment(1)
Hi! thanks for the reply, i've updated my question because i think i'm looping not directly over an iterable but rather it's an array inside an Object which hasn't be instantiated.Rosewood

© 2022 - 2024 — McMap. All rights reserved.