50

I have a collection of items, where each item have a rank attribute, which is a number. I wan to loop over this number, here is what I've tried:

<div class="col-md-3 col-sm-4 item-container" *ngFor="let item of items">
    <div class="item">
        <p class="rank">
            <img src="" class="img-responsive" *ngFor="let rank of item.rank"/>
        </p>
    </div>
</div>

typescript:

export class MonthpicksComponent{

    items: Item[] = [];
    //...
}

export class Item{
  id: number;
  name: string;
  img: string;
  desc: string;
  rank: number;
  voters: number;
}

but unlucky the first loop shows only one result and the second loop showed nothing.

Witold Tkaczyk
  • 695
  • 8
  • 18
Mohammad
  • 3,449
  • 6
  • 48
  • 75
  • You'll probably need to add code for how the `items` object looks like. Html is not sufficient enough to solve this. – Sajal Oct 18 '17 at 08:00
  • @Sajal check update please – Mohammad Oct 18 '17 at 08:04
  • Provide items structure in controller @mohammad – Vignesh Oct 18 '17 at 08:05
  • You cannot loop over a number if its not an array. `rank` is a number. – Sajal Oct 18 '17 at 08:05
  • @Sajal I want to print `` tags as how much rank the item has, how I can do that? – Mohammad Oct 18 '17 at 08:11
  • 1
    this is a high frequency question here, please refer to : https://stackoverflow.com/questions/36535629/repeat-html-element-multiple-times-using-ngfor-based-on-a-number – kite.js.org Oct 18 '17 at 08:13
  • Possible duplicate of [Repeat HTML element multiple times using ngFor based on a number](https://stackoverflow.com/questions/36535629/repeat-html-element-multiple-times-using-ngfor-based-on-a-number) – wscourge Oct 18 '17 at 12:05

9 Answers9

110

Maybe the simplest solution without any TS code:

<div *ngFor="let item of [].constructor(10); let i = index">
   {{i}}
</div>
NJoco
  • 1,407
  • 2
  • 10
  • 10
  • 9
    Why isn't this the preferred solutions, as this is clearly _exactly_ what most of us have in mind? Like – it certainly does require a temporary array to loop over, but in this case generated without a single line typescript. Kudos! – GerZah May 18 '20 at 11:06
52

You can use javascript inside *ngFor so the solution could look like this:

my.component.ts

counter(i: number) {
    return new Array(i);
}

my.component.html

<li *ngFor='let in of counter(5) ;let i = index'>{{i}}</li>
Witold Tkaczyk
  • 695
  • 8
  • 18
  • 2
    Doesn't that rebuild on every check, because it's an impure call? – Wouter Lievens Jan 09 '20 at 09:04
  • I don't think so, maybe. Easy to check that. The point of that answer is that you can execute js code from `*ngFor` body. The rest of the answer is just an example. There are literally thousands of ways to do that. – Witold Tkaczyk Jan 09 '20 at 10:24
  • 3
    Sure, but my impression was that executing arbitrary functions or code in an Angular template is a huge anti-pattern because Angular cannot determine whether the code is pure or impure. That's what pipes are for. – Wouter Lievens Jan 10 '20 at 11:06
15

If you want to implement this in the canonical Angular way, you could create a range pipe which produces the array on the spot.

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'range'
})
export class RangePipe implements PipeTransform {
  transform(length: number, offset: number = 0): number[] {
    if (!length) {
      return [];
    }
    const array = [];
    for (let n = 0; n < length; ++n) {
      array.push(offset + n);
    }
    return array;
  }
}

And then you would use it in a template like this:

<ul>
  <li *ngFor="let n of item.rank | range">
    <img src="star.png">
  </li>
</ul>

The offset parameter is useful when you don't want the array to start at 0, of course.

A massive upside of this approach is that this is a pure pipe, meaning it will only be executed if item.rank changes. Other approaches may update spuriously on any arbitrary page check phase (potentially many times per second).

Wouter Lievens
  • 4,019
  • 5
  • 41
  • 66
  • 3
    Not sure why this doesn't have more up votes. When passing a number into my component via @Input() every other way described here left me with an `Invalid array length` error in my template. This was the only thing that worked for me. – doubleya Jan 15 '20 at 22:52
  • 2
    I'm glad I could be of help! My answer is fairly new, perhaps that's why it doesn't rank high. I'm convinced it's the most Angular-spirited solution. – Wouter Lievens Jan 16 '20 at 23:53
  • 1
    this is the most optimal solution! Thank you for sharing. I did not realize how simple this was to implement. – d0rf47 Aug 25 '21 at 18:33
5

You can do like this

<li *ngFor='let in of [0,1,2,3,4];let i = index'>{{i}}</li>
phani indra
  • 243
  • 1
  • 10