Repeat HTML element multiple times using ngFor based on a number
Asked Answered
H

17

175

How do I use *ngFor to repeat a HTML element multiple times?

For eg: If I have a member variable assigned to 20. How do I use the *ngFor directive to make a div repeat 20 times?

Honea answered 10/4, 2016 at 21:13 Comment(3)
See also #47587025Toadeater
There are four ways to achieve the same, read out here. https://mcmap.net/q/20989/-angular-2-ngfor-using-numbers-instead-collectionsWee
This is a great case for implementing a custom directive. You may find instructions how to do it, and in-depth bits about this case here: indepth.dev/tutorials/angular/… Implementing a custom directive means, that components will have less code, the HTML will be easier to read, and finally, there would be fewer code duplications. The logic in the directive will be easier to test and understand.Aurita
H
108

You could use the following:

@Component({
  (...)
  template: `
    <div *ngFor="let i of Arr(num).fill(1)"></div>
  `
})
export class SomeComponent {
  Arr = Array; //Array type captured in a variable
  num:number = 20;
}

Or implement a custom pipe:

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

@Pipe({
  name: 'fill'
})
export class FillPipe implements PipeTransform {
  transform(value) {
    return (new Array(value)).fill(1);
  }
}

@Component({
  (...)
  template: `
    <div *ngFor="let i of num | fill"></div>
  `,
  pipes: [ FillPipe ]
})
export class SomeComponent {
  arr:Array;
  num:number = 20;
}
Hialeah answered 10/4, 2016 at 21:22 Comment(9)
the FillPipe class must implements PipeTransformUmpteen
Yes, you're right! It's better ;-) Thanks for pointing this out. I updated my answer accordingly...Hialeah
i didn't know that I would have to create a custom pipe. Thought it would have been in built into angular 2 but your suggestion works perfectly. Thanks!Honea
In the first approach, I think you meant to say arr=Array; ?Trichocyst
can you make a codepen? it doesn't work: self.parentView.context.arr is not a functionAntinomian
Thanks for the first approach! I am not using the .fill(1) and is working ;)Soubrette
Love the pipe approach!Ascension
The pipe worked for me, although Angular 4 complained about 'pipes' not being a property of Component (something like that). Exporting FillPipe from an NgModule fixed that. I'm tempted to edit the answer, but since I'm not 100% knowledgeable about this, may I leave it to @ThierryTemplier ?Veterinary
This doesn't seem to work, the pipe returns the following array new Array(3).fill(1); (3) [1, 1, 1]Macarthur
D
256
<ng-container *ngFor="let _ of [].constructor(20)">🐱</ng-container>

generates 🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱

Dorcus answered 17/1, 2019 at 8:32 Comment(5)
This should be the correct answer.. It's by far the most concise!Emancipated
ERROR RangeError: Invalid array length. This will crash when the number of cats to draw is zero.Dreyer
Really like that simple approach!Indefatigable
It's actually better to use _ for the variable name since i ends up as undefined (because the array is empty). If you actually need the index you can use *ngFor="let _ of [].constructor(20); let catNumber=index" then Cat number {{ catNumber + 1 }} 🐱 will show you the cat number. If you have nested loops you can use _ for each and they won't conflict.Streusel
Doesn't anybody feel like this is somewhat hacky? I mean, it works well, of course, but creating an array just to be able to replicate content seems not very functional or extensible IMOGremial
H
108

You could use the following:

@Component({
  (...)
  template: `
    <div *ngFor="let i of Arr(num).fill(1)"></div>
  `
})
export class SomeComponent {
  Arr = Array; //Array type captured in a variable
  num:number = 20;
}

Or implement a custom pipe:

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

@Pipe({
  name: 'fill'
})
export class FillPipe implements PipeTransform {
  transform(value) {
    return (new Array(value)).fill(1);
  }
}

@Component({
  (...)
  template: `
    <div *ngFor="let i of num | fill"></div>
  `,
  pipes: [ FillPipe ]
})
export class SomeComponent {
  arr:Array;
  num:number = 20;
}
Hialeah answered 10/4, 2016 at 21:22 Comment(9)
the FillPipe class must implements PipeTransformUmpteen
Yes, you're right! It's better ;-) Thanks for pointing this out. I updated my answer accordingly...Hialeah
i didn't know that I would have to create a custom pipe. Thought it would have been in built into angular 2 but your suggestion works perfectly. Thanks!Honea
In the first approach, I think you meant to say arr=Array; ?Trichocyst
can you make a codepen? it doesn't work: self.parentView.context.arr is not a functionAntinomian
Thanks for the first approach! I am not using the .fill(1) and is working ;)Soubrette
Love the pipe approach!Ascension
The pipe worked for me, although Angular 4 complained about 'pipes' not being a property of Component (something like that). Exporting FillPipe from an NgModule fixed that. I'm tempted to edit the answer, but since I'm not 100% knowledgeable about this, may I leave it to @ThierryTemplier ?Veterinary
This doesn't seem to work, the pipe returns the following array new Array(3).fill(1); (3) [1, 1, 1]Macarthur
E
96
<div *ngFor="let dummy of ' '.repeat(20).split(''), let x = index">

Replace 20 with your variable

Escent answered 29/1, 2018 at 2:30 Comment(5)
This is definitely a great answerAleedis
repeat should be 19; length-1.Maloy
Elegant solution for a quite unconvenient problem. But I guess it has a performance impact for a big count of elements?Poss
working fine! thanksStillas
this is a great solution.. just that how will we get the value from that? Based on that.. I created <div *ngFor="let dummy of ' '.repeat(5).split(''), let x = index"><input type="text" name="myitem{{x}}" placeholder=" " /></span></div>, for example how will I get the value of myitem2 ?Bedight
D
69

There are two problems with the recommended solutions using Arrays:

  1. It's wasteful. In particular for large numbers.
  2. You have to define them somewhere which results in a lot of clutter for such a simple and common operation.

It seems more efficient to define a Pipe (once), returning an Iterable:

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

@Pipe({name: 'times'})
export class TimesPipe implements PipeTransform {
  transform(value: number): any {
    const iterable = <Iterable<any>> {};
    iterable[Symbol.iterator] = function* () {
      let n = 0;
      while (n < value) {
        yield ++n;
      }
    };
    return iterable;
  }
}

Usage example (rendering a grid with dynamic width / height):

<table>
    <thead>
      <tr>
        <th *ngFor="let x of colCount|times">{{ x }}</th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let y of rowCount|times">
        <th scope="row">{{ y }}</th>
        <td *ngFor="let x of colCount|times">
            <input type="checkbox" checked>
        </td>
      </tr>
    </tbody>
</table>
Deane answered 4/9, 2017 at 10:5 Comment(1)
So <ion-row *ngFor="let r of 10|times"> works! Thanks Andreas you've managed to simulate the well known for next loop in HTML :thumbsupL
G
30

You can simple do this in your HTML:

*ngFor="let number of [0,1,2,3,4,5...,18,19]"

And use the variable "number" to index.

Grith answered 15/3, 2017 at 6:12 Comment(6)
OP said he assigned 20 to a member variable.. so this will not help muchSprinkler
What if i want to iterate 200 times?Meek
@Meek You cant. :(Diastase
@MuhammadbinYusrat I know, I wanted to point out that this solution could only work with small number. I should have been more precise instead of using a rhetorical question... my bad :(Meek
@Meek What's wrong with 200? *ngFor="let number of [0,1,2,3,4,5...,199,200]" :-DRefrigeration
@StackUnderflow Sadly I'm not paid by characters. If I was, I would preach that it is the only way to acheive that :P (seriously just don't ;))Meek
E
14

A simpler and a reusable solution maybe to use custom structural directive like this.

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

@Directive({
  selector: '[appTimes]'
})
export class AppTimesDirective {

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

  @Input() set appTimes(times: number) {
    for (let i = 0 ; i < times ; i++) {
      this.viewContainer.createEmbeddedView(this.templateRef);
    }
  }

}

And use it like this :

<span *appTimes="3" class="fa fa-star"></span>
Emphasis answered 24/11, 2017 at 14:4 Comment(2)
for more info : netbasal.com/…Emphasis
For this solution, you need to add this.viewContainer.clear(); before the for-loop.Noncontributory
E
9

Best and simple way of doing nth time repetition is [].constructor(nth)

Example for 5 times loop

 <ng-container *ngFor="let _ of [].constructor(5); let i = index">
    <b>{{ i }}</b>
 </ng-container>
Exsert answered 14/10, 2021 at 8:51 Comment(0)
S
4

The most efficient and concise way to achieve this is by adding an iterator utility. Don't bother yielding values. Don't bother setting a variable in the ngFor directive:

function times(max: number) {
  return {
    [Symbol.iterator]: function* () {
      for (let i = 0; i < max; i++, yield) {
      }
    }
  };
}

@Component({
  template: ```
<ng-template ngFor [ngForOf]="times(6)">
  repeats 6 times!
</ng-template>

```
})
export class MyComponent {
  times = times;
}
Steviestevy answered 27/12, 2018 at 10:37 Comment(0)
D
3

You don't need to fill the array like suggested in most answers. If you use index in your ngFor loop all you need to create is an empty array with the correct length:

const elements = Array(n); // n = 20 in your case

and in your view:

<li *ngFor="let element in elements; let i = index">
  <span>{{ i }}</span>
</li>
Dobby answered 25/11, 2019 at 17:1 Comment(0)
E
3

I know you specifically asked to do it using *ngFor, but i wanted to share the way i solved this using an structural directive:

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

@Directive({ selector: '[appRepeat]' })
export class RepeatDirective {
  constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef) {
  }

  @Input() set appRepeat(loops: number) {
    for (let index = 0; index < loops; ++index) {
      this.viewContainerRef.createEmbeddedView(this.templateRef);
    }
  }
}

With that you can use it just like this:

<div *appRepeat="15">
  Testing
</div>
Ellene answered 8/5, 2020 at 17:31 Comment(3)
That's what I was looking for!Impel
Code could be shorter though...here's my version @Input() set smpClone(loops: number) { while (--loops > 0) { this._viewContainerRef.createEmbeddedView(this._templateRef); } } Impel
but since we are producing readable code and do not play code golf I would prefer the former solution, although you are technically correct :DCara
B
2

If you are using Lodash, you can do the following:

Import Lodash into your component.

import * as _ from "lodash";

Declare a member variable within the component to reference Lodash.

lodash = _;

Then in your view, you can use the range function. 20 can be replaced by any variable in your component.

*ngFor="let number of lodash.range(20)"

It must be said that binding to functions in the view might be costly, depending on the complexity of the function you are calling as Change Detection will call the function repeatedly.

Byroad answered 19/6, 2018 at 7:54 Comment(0)
L
2

You can use this simply:

HTML

<div *ngFor="let i of Count">

TS

export class Component implements OnInit {
  Count = [];

  constructor() {
    this.Count.length = 10; //you can give any number
  }

  ngOnInit(): void {}
}
Lexington answered 9/5, 2020 at 18:17 Comment(0)
P
1

Super simple solution if you want a number from ts file so you can later on add it into the input is:

In your component.ts file:

amountOfRepetetions = new Array(20);

And in your html template (component.html) component place something like:

<ng-container *ngFor="let oneComponent of amountOfRepetetions">
    <component-i-want-multiplied></component-i-want-multiplied>
</ng-container>

Note: works in Angular 15 (I didn't try it for other versions)

Prescript answered 24/8, 2023 at 14:7 Comment(0)
S
0

Simpler approach:

Define a helperArray and instantiate it dynamically (or static if you want) with the length of the count that you want to create your HTML elements. For example, I want to get some data from server and create elements with the length of the array that is returned.

export class AppComponent {
  helperArray: Array<any>;

  constructor(private ss: StatusService) {
  }

  ngOnInit(): void {
    this.ss.getStatusData().subscribe((status: Status[]) => {
      this.helperArray = new Array(status.length);
    });
  }
}

Then use the helperArray in my HTML template.

<div class="content-container" *ngFor="let i of helperArray">
  <general-information></general-information>
  <textfields></textfields>
</div>
Shantel answered 14/7, 2017 at 16:19 Comment(0)
C
0

Here's a slightly improved version of Ilyass Lamrani's structural directive that allows you to use the index in your template:

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

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

  @Input()
  set appRepeatOf(times: number) {
    const initialLength = this.viewContainer.length;
    const diff = times - initialLength;

    if (diff > 0) {
      for (let i = initialLength; i < initialLength + diff; i++) {
        this.viewContainer.createEmbeddedView(this.templateRef, {
          $implicit: i
        });
      }
    } else {
      for (let i = initialLength - 1; i >= initialLength + diff ; i--) {
      this.viewContainer.remove(i);
    }
  }

}

Usage:

<li *appRepeat="let i of myNumberProperty">
    Index: {{i}}
</li>
Colorist answered 3/5, 2018 at 14:14 Comment(0)
L
0

You can do this :

Ts:

   CreateTempArray(number){
   var arr=[];
   for(let i=0;i<number;i++){
     arr[i]="";
   }
   return arr;
  }

Html:

<div *ngFor="let i of CreateTempArray(20);">
    cycle 20 times
</div>
Leeannaleeanne answered 5/10, 2021 at 21:26 Comment(0)
L
0

A variation of the top answer

@Component({
  selector: 'app-loop',
  template: `
    <ng-template ngFor [ngForOf]="repeat(20)">🌭</ng-template>
  `
})
export class LoopComponent {
  protected readonly repeat = Array;
}

This displays: 🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭🌭

Lynching answered 18/7, 2023 at 23:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.