Angular 2 - NgFor using numbers instead collections
Asked Answered
L

17

287

...for example...

<div class="month" *ngFor="#item of myCollection; #i = index">
...
</div>

Is possible to do something like...

<div class="month" *ngFor="#item of 10; #i = index">
...
</div>

...without appeal to a non elegant solution like:

<div class="month" *ngFor="#item of ['dummy','dummy','dummy','dummy','dummy',
'dummy','dummy','dummy']; #i = index">
...
</div>

?

Lumumba answered 1/4, 2016 at 10:45 Comment(1)
Maybe this can be helpful: stackoverflow.com/questions/3895478/…Hekate
D
294

Within your component, you can define an array of number (ES6) as described below:

export class SampleComponent {
  constructor() {
    this.numbers = Array(5).fill().map((x,i)=>i); // [0,1,2,3,4]
    this.numbers = Array(5).fill(4); // [4,4,4,4,4]
  }
}

See this link for the array creation: Tersest way to create an array of integers from 1..20 in JavaScript.

You can then iterate over this array with ngFor:

@Component({
  template: `
    <ul>
      <li *ngFor="let number of numbers">{{number}}</li>
    </ul>
  `
})
export class SampleComponent {
  (...)
}

Or shortly:

@Component({
  template: `
    <ul>
      <li *ngFor="let number of [0,1,2,3,4]">{{number}}</li>
    </ul>
  `
})
export class SampleComponent {
  (...)
}
Diminution answered 1/4, 2016 at 10:47 Comment(12)
Yeah, Thierry ! It's not your fault, indeed, but still on the same context :( It's not elegant at all. But since you are a very skilled A2 developer, I can assume that there is no better solution. It's sad !Lumumba
In fact, there is nothing for this in Angular2 in the loop syntax. You need to leverage what JavaScript provides to build arrays. For example: Array(5).fill(4) to create [4,4,4,4,4]Diminution
PS: @View annotation has been removed in angular2 beta 10 and above.Carpous
Using Array.fill() in Angular 2 Typescript produces the following error Supplied parameters do not match any signature of call t arget. — Checking the Array.prototype.fill docs, it says it requires 1 argument... developer.mozilla.org/en/docs/Web/JavaScript/Reference/…Larimor
An alternative would be to use this: Array.apply(null, {length: 5}).map(Number.call, Number). The result is [0,1,2,3,4]Follett
In Angular 2 TypeScript you can use: Array(32).fill(1, 1, 32).map((x, i) => i); // monthdays from1 to 31Jolie
Array(5).fill(1).map((x, i) => i + 1); /*[1,2,3,4,5]*/ this resolves the error in TSMincing
Hi friend but if I want to use fill()map() and start from 0, because if I start limit 5 for exemplo its return to me 0,1,2,3,4 if I want 1,2,3,4,5 ?Banff
I don't know why would you use fill, I feel like the most readable alternative is Array.from({ length: N }); which in addition accept an optional second parameter that is a map function. example: for creating an array of numbers you would simply Array.from({length: N}, (_,k) => k ) which i find even more readable other than simpler, but This is my opinion.Heap
Can I use <li *ngFor="let tag of {{discussion.tags}}">{{tag}}</li> where discussion.tags is an array of tags. I am using typescript.Paranoiac
Had to use parseInt because my number was a string & had to add +1 at the end because I wanted to iterate from 1, not 0. Array(parseInt(this.product.size_1_maxqty)).fill().map((x,i)=>i+1)Evolution
Above does a tiny bit of extra work (fills with undefined) but is relatively minor vis-a-vis the speedup you can achieve by using a for loop, and if you forget the .fill you may be confused why your array is mysteriously [empty x 5]. You can encapsulate the above as a custom function, or alternatively use a somewhat more intended method: Array.from(Array(5),(x,i)=>i)Sharie
A
273

@OP, you were awfully close with your "non-elegant" solution.

How about:

<div class="month" *ngFor="let item of [].constructor(10); let i = index">
...
</div>

Here I'm getting the Array constructor from an empty array: [].constructor, because Array isn't a recognized symbol in the template syntax, and I'm too lazy to do Array=Array or counter = Array in the component typescript like @pardeep-jain did in his 4th example. And I'm calling it without new because new isn't necessary for getting an array out the Array constructor.

Array(30) and new Array(30) are equivalent.

The array will be empty, but that doesn't matter because you really just want to use i from ;let i = index in your loop.

Edit to respond to comments:

Q. How can I use a variable to set the length of the NgFor loop?

Here is an example on how to render a table with variable columns/rows

<table class="symbolTable">
  <tr *ngFor="let f of [].constructor(numRows); let r = index">
    <td class="gridCell" *ngFor="let col of [].constructor(numCols); let c = index">
      {{gridCards[r][c].name}}
    </td>
  </tr>
</table>
export class AppComponent implements OnInit {
  title = 'simbologia';
  numSymbols = 4;
  numCols = 5;
  numRows = 5;
  guessCards: SymbolCard[] = [];
  gridCards: SymbolCard[][] = [];

  ngOnInit(): void {
    for (let c = 0; c < this.numCols; c++) {
      this.guessCards.push(new SymbolCard());
    }

    for (let r = 0; r < this.numRows; r++) {
      let row: SymbolCard[] = [];

      for (let c = 0; c < this.numCols; c++) {
        row.push(
          new SymbolCard({
            name: '' + r + '_' + c
          }))
      }
      this.gridCards.push(row);
    }
  }
}
Apodaca answered 8/11, 2018 at 16:17 Comment(6)
This solution triggers change detection. I guess due to the new Array.Idalla
@Tobias81, could you elaborate? Are you saying that every time the app runs change detection, the contents of the *ngFor are redrawn because the array is recreated? That's definitely worth noting. One could get around it by actually creating an array field in the TS to reference so it's the same each time change detection runs. But that would be definitely less elegant than desired. Is the same change detection issue present in the 2nd example in Thierry Templier's selected answer? <li *ngFor="let number of [0,1,2,3,4]">{{number}}</li>Apodaca
@Tobias81, I've checked to make sure change detection doesn't recreate the contents of the ngFor repeatedly, by putting a print statement inside the constructor of a component that I create as a child of the example ngFor directive. I do not see the components being recreated on every Change Detection iteration, so I don't think there is actually a problem (at least in Angular 8).Apodaca
works great with a hardcoded value but when I try using a variable it didn't work as expected. If you can give an example of how to use a variable that would be greatMiraflores
It does work with variable values, no problem, I have updated the answer to show thatOnega
I don't understand how the fact that the array will be empty will have no impact on the let r = index; part.Vandalism
C
114

No there is no method yet for NgFor using numbers instead collections, At the moment, *ngFor only accepts a collection as a parameter, but you could do this by following methods:

Using pipe

demo-number.pipe.ts:

import {Pipe, PipeTransform} from 'angular2/core';

@Pipe({name: 'demoNumber'})
export class DemoNumber implements PipeTransform {
  transform(value, args:string[]) : any {
    let res = [];
    for (let i = 0; i < value; i++) {
        res.push(i);
      }
      return res;
  }
}

For newer versions you'll have to change your imports and remove args[] parameter:

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

@Pipe({name: 'demoNumber'})
export class DemoNumber implements PipeTransform {
  transform(value) : any {
    let res = [];
    for (let i = 0; i < value; i++) {
        res.push(i);
      }
      return res;
  }
}

html:

<ul>
  <li>Method First Using PIPE</li>
  <li *ngFor='let key of 5 | demoNumber'>
    {{key}}
  </li>
</ul>

Using number array directly in HTML(View)

<ul>
  <li>Method Second</li>
  <li *ngFor='let key of  [1,2]'>
    {{key}}
  </li>
</ul>

Using Split method

<ul>
  <li>Method Third</li>
  <li *ngFor='let loop2 of "0123".split("")'>{{loop2}}</li>
</ul>

Using creating New array in component

<ul>
  <li>Method Fourth</li>
  <li *ngFor='let loop3 of counter(5) ;let i= index'>{{i}}</li>
</ul>

export class AppComponent {
  demoNumber = 5 ;
  
  counter = Array;
  
  numberReturn(length){
    return new Array(length);
  }
}

#Working demo

Carpous answered 1/4, 2016 at 12:41 Comment(7)
You could also use the Array.fill() method for generating the array instead of res.push() like shown in Thierrys answer.Preceptive
yeah i can but is there anything wrong with push ? i mean both methods are correct but still if any diff. between them.Carpous
No, still a nice solution +1. I just find the Array.fill() more elegant than the loop using push and it's also probably more efficient.Preceptive
I like this solution with counter = Array, very smart ;)Pfennig
error TS2554: Expected 2 arguments, but got 1. when using your pipe.Martamartaban
have you changed the imports as per new versions?Carpous
Sure import { Pipe, PipeTransform } from '@angular/core';. Edit: Ok I got it, gonna edit your post for newer versions.Martamartaban
D
12

I couldn't bear the idea of allocating an array for plain repeat of components, so I've written a structural directive. In simplest form, that doesn't make the index available to the template, it looks like this:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({ selector: '[biRepeat]' })
export class RepeatDirective {

  constructor( private templateRef: TemplateRef<any>,
             private viewContainer: ViewContainerRef) { }

  @Input('biRepeat') set count(c:number) {
    this.viewContainer.clear();
    for(var i=0;i<c;i++) {
      this.viewContainer.createEmbeddedView(this.templateRef);
    }
  }
}

http://plnkr.co/edit/bzoNuL7w5Ub0H5MdYyFR?p=preview

Dogged answered 26/11, 2016 at 20:45 Comment(4)
I agree the array approach is ugly, but this seems like premature optimization to me.Phosphaturia
Of course, but also an exercise in writing a directive. On the other hand it is not longer than the pipe, which would be second sane approach.Dogged
That's a good point, there aren't a lot of opportunities to get some of your with the concept of custom structural directives.Phosphaturia
Nice one @Dogged - Still works with latest versions: plnkr.co/edit/8wJtkpzre3cBNokHcDL7?p=preview [feel free to update your plnkr]Write
C
11

you can also use like that

export class SampleComponent {
   numbers:Array<any> = [];
   constructor() {
      this.numbers = Array.from({length:10},(v,k)=>k+1);
   }
}

HTML

<p *ngFor="let i of numbers">
   {{i}}
</p>
Chrysalid answered 5/3, 2019 at 14:28 Comment(0)
I
10

Use a pipe to transform the number to an array.

@Pipe({
  name: 'enumerate',
})
export class EnumeratePipe implements PipeTransform {
  transform(n: number): number[] {
    return [...Array(n)].map((_,i) => i);
  }
}

Then use the pipe in your template.

<p *ngFor="let i of 5 | enumerate">
   Index: {{ i }}
</p>

https://stackblitz.com/edit/angular-ivy-pkwvyw?file=src/app/app.component.html

Intermigration answered 8/10, 2020 at 1:47 Comment(3)
value is not accessible in the ngForQualls
check the stackblitzIntermigration
I mean if you need to print 1,2,3,4,5Qualls
I
8

This can also be achieved like this:

HTML:

<div *ngFor="let item of fakeArray(10)">
     ...
</div>

Typescript:

fakeArray(length: number): Array<any> {
  if (length >= 0) {
    return new Array(length);
  }
}

Working Demo

Idyllic answered 10/9, 2019 at 9:57 Comment(2)
Do not do this, the method fakeArray will be called at each change detection. If you want to use methods in templates like this, pure pipes are the way to go instead.Norinenorita
fakeArray doesn't compile because there is no else statement.Hawkbill
A
5

I solved it like this using Angular 5.2.6 and TypeScript 2.6.2:

class Range implements Iterable<number> {
    constructor(
        public readonly low: number,
        public readonly high: number,
        public readonly step: number = 1
    ) {
    }

    *[Symbol.iterator]() {
        for (let x = this.low; x <= this.high; x += this.step) {
            yield x;
        }
    }
}

function range(low: number, high: number) {
    return new Range(low, high);
}

It can be used in a Component like this:

@Component({
    template: `<div *ngFor="let i of r">{{ i }}</div>`
})
class RangeTestComponent {
    public r = range(10, 20);
}

Error checking and assertions omitted on purpose for brevity (e.g. what happens if step is negative).

Allegorist answered 24/2, 2018 at 11:9 Comment(1)
Are there are any ways in html as of <div *ngfor="let i of 4, i++"></div> may beElmiraelmo
C
4

You can use lodash:

@Component({
  selector: 'board',
  template: `
<div *ngFor="let i of range">
{{i}}
</div>
`,
  styleUrls: ['./board.component.css']
})
export class AppComponent implements OnInit {
  range = _.range(8);
}

I didn't test code but it should work.

Coma answered 8/5, 2017 at 20:53 Comment(2)
Are there are any ways in html as of <div *ngfor="let i of 4, i++"></div> may beElmiraelmo
If you need i or index in a code then you can do *ngFor="let i of range; let i = index"Coma
L
3
<div *ngFor="let number of [].constructor(myCollection)">
    <div>
        Hello World
    </div>
</div>

This is a nice and quick way to repeat for the amount of times in myCollection.

So if myCollection was 5, Hello World would be repeated 5 times.

Lahey answered 15/10, 2019 at 14:49 Comment(1)
I liked this solution because do not touch in the component. Thanks!Mincemeat
M
3

My-component.ts

numbers: number[] = [];

constructor() {
  this.numbers = new Array<number>(10)
}

My-component.html

<div *ngFor="let num of numbers; let i = index">{{ i }}</div>
Mildamilde answered 24/9, 2021 at 15:39 Comment(0)
L
2

Since the fill() method (mentioned in the accepted answer) without arguments throw an error, I would suggest something like this (works for me, Angular 7.0.4, Typescript 3.1.6)

<div class="month" *ngFor="let item of items">
...
</div>

In component code:

this.items = Array.from({length: 10}, (v, k) => k + 1);
Lockage answered 30/3, 2019 at 9:13 Comment(0)
B
1

Using custom Structural Directive with index:

According Angular documentation:

createEmbeddedView Instantiates an embedded view and inserts it into this container.

abstract createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): EmbeddedViewRef.

Param          Type           Description
templateRef    TemplateRef    the HTML template that defines the view.
context        C              optional. Default is undefined.
index          number         the 0-based index at which to insert the new view into this container. If not specified, appends the new view as the last entry.

When angular creates template by calling createEmbeddedView it can also pass context that will be used inside ng-template.

Using context optional parameter, you may use it in the component, extracting it within the template just as you would with the *ngFor.

app.component.html:

<p *for="number; let i=index; let c=length; let f=first; let l=last; let e=even; let o=odd">
  item : {{i}} / {{c}}
  <b>
    {{f ? "First,": ""}}
    {{l? "Last,": ""}}
    {{e? "Even." : ""}}
    {{o? "Odd." : ""}}
  </b>
</p>

for.directive.ts:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

class Context {
  constructor(public index: number, public length: number) { }
  get even(): boolean { return this.index % 2 === 0; }
  get odd(): boolean { return this.index % 2 === 1; }
  get first(): boolean { return this.index === 0; }
  get last(): boolean { return this.index === this.length - 1; }
}

@Directive({
  selector: '[for]'
})
export class ForDirective {
  constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { }

  @Input('for') set loop(num: number) {
    for (var i = 0; i < num; i++)
      this.viewContainer.createEmbeddedView(this.templateRef, new Context(i, num));
  }
}
Bly answered 24/10, 2019 at 22:48 Comment(0)
A
1

I think the simple and short solution is here..

Your my.component.ts file

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

Your my.component.html file

<li *ngFor='let in of setArrayFromNumber(5); let i = index'>{{ i }}</li>

That's it!!

Abc answered 12/4, 2022 at 7:20 Comment(0)
J
0

Please find attached my dynamic solution if you want to increase the size of an array dynamically after clicking on a button (This is how I got to this question).

Allocation of necessary variables:

  array = [1];
  arraySize: number;

Declare the function that adds an element to the array:

increaseArrayElement() {
   this.arraySize = this.array[this.array.length - 1 ];
   this.arraySize += 1;
   this.array.push(this.arraySize);
   console.log(this.arraySize);
}

Invoke the function in html

  <button md-button (click)="increaseArrayElement()" >
      Add element to array
  </button>

Iterate through array with ngFor:

<div *ngFor="let i of array" >
  iterateThroughArray: {{ i }}
</div>
Jeremiad answered 26/7, 2017 at 13:18 Comment(2)
Are there are any ways in html as of <div *ngfor="let i of 4, i++"></div> may beElmiraelmo
you have to iterate over an array. If you need the scalar, you can iterate over an array with the right size and instantiate a scalar in addition: *ngFor="let item of array; let i = index"Jeremiad
P
0

A simplest way that i have tried

You can also create an array in your component file and you can call it with *ngFor directive by returning as an array .

Something like this ....

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-morning',
  templateUrl: './morning.component.html',
  styleUrls: ['./morning.component.css']
})
export class MorningComponent implements OnInit {

  arr = [];
  i: number = 0;
  arra() {
    for (this.i = 0; this.i < 20; this.i++) {
      this.arr[this.i]=this.i;
    }
    return this.arr;
  }

  constructor() { }

  ngOnInit() {
  }

}

And this function can be used in your html template file

<p *ngFor="let a of arra(); let i= index">
value:{{a}} position:{{i}}
</p>
Peggy answered 21/5, 2018 at 14:59 Comment(1)
Are there are any ways in html as of <div *ngfor="let i of 4, i++"></div> may beElmiraelmo
B
0

Here is something quite clean and simple for Angular:

In .ts:

max = 10;    

In .html:

<div *ngFor="let dummy of ','.repeat(max).split(','); index as ix">
   - {{ix + 1}}:
</div>
Blasting answered 30/11, 2021 at 20:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.