7

I have an int property named "count" in my component.

I would like to display a p tag X amount of times, where X is the int my count property equals. Is there really no simple way to do this, besides messing with fake arrays?

Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
Amivit
  • 189
  • 2
  • 4
  • 15
  • Related to https://stackoverflow.com/questions/36095496/angular-2-how-to-write-a-for-loop-not-a-foreach-loop – Amivit Jun 13 '17 at 09:12
  • Not possible, you need an object or array for a loop. No way without that type of vars. You could also write an directive to make it work. – lin Jun 13 '17 at 09:13

5 Answers5

6

You could easily do it with an pipe filter which transforms an empty array to an number of childs depending on a filter param = number.

Pipe filter

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

@Pipe({  
    name: 'range',  
    pure: false  
})  

export class RangePipe implements PipeTransform {  
    transform(items: any[], quantity: number): any {  
      items.length = 0;
      for (let i = 0; i < quantity; i++) {
        items.push(i);
      }
      return items;
    }  
}  

View

<div *ngFor="let n of [] | range:100"></div>
lin
  • 17,956
  • 4
  • 59
  • 83
5

Plnkr: https://plnkr.co/edit/Yn775KSbBeUPeyaI9sep?p=preview

You can create another variable called countObservable

  countObservable = Observable.range(0, this.count).toArray();

Use async for in HTML

  <p *ngFor="let num of countObservable | async" >Hello {{num}}</h2>

Update

If we need to update the number we can use flatMap.

Instead of above code for countObservable, use this

count$= new BehaviorSubject(10);
countObservable$ = 
  this.count$.flatMap(count => Observable.range(0, count).toArray()) ;

To change the number value, just update count$

this.count$.next(newNum);
Vamshi
  • 9,194
  • 4
  • 38
  • 54
  • Funny, I wanted to achieve the functionality for a demo app, where I'm trying to show off a tiny bit of Rxjs knowledge. This is my favorite solution; thanks! – Amivit Jun 13 '17 at 09:53
  • This doesn't work when I change the count value afterwards. Any ideas? – Amivit Jun 13 '17 at 10:13
  • You will need to intercept the setting event of you variable and regenerate the observable – Jota.Toledo Jun 13 '17 at 10:28
4

I kind of disliked the approach of creating an empty array of size n every time that I wanted to render an element n times, so I created a custom structural directive:

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

export class ForNumberContext {
  constructor(public count: number, public index: number) { }
  get first(): boolean { return this.index === 0; }

  get last(): boolean { return this.index === this.count - 1; }

  get even(): boolean { return this.index % 2 === 0; }

  get odd(): boolean { return !this.even; }
}

@Directive({
  selector: '[ForNumber]'
})
export class ForNumberDirective {


 @Input() set forNumberOf(n: number) {
    this._forNumberOf = n;
    this.generate();
  }

  private _forNumberOf: number;

  constructor(private _template: TemplateRef<ForNumberContext>,
    private _viewContainer: ViewContainerRef) { }

  @Input()
  set ngForTemplate(value: TemplateRef<ForNumberContext>) {
    if (value) {
      this._template = value;
    }
  }

  private generate() {
    for (let i = 0; i < this._forNumberOf; i++) {
      this._viewContainer.createEmbeddedView(this._template, new ForNumberContext(this._forNumberOf, i));
    }
  }

}

And then u can use it as follows:

<ng-template ForNumber [forNumberOf]="count" let-index="index">
<span>Iteration: {{index}}!</span></ng-template>

Please note, I havent tested it extensively so I cant promise that its bulletproof :)

Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
  • 1
    This is the solution that fits best with the angular philosophy: it is reusable (unlike the `Observable` solution that has to be duplicated anywhere you want another counter). Does your directive also for with `...` as a shortcut for the `ng-template`? I think it should be possible to make that work but you might need to change a few things. – Duncan Jun 13 '17 at 10:44
  • 1
    yup, reusability is the reason that I wanted to capsulate this in a directive. I havent tried to use it with the * operator, and there are most likely things to add for change detection. Currently Im using it with an inmutable set of values and I was in a hurry to get the feature working, so I didnt payed attention to all the details – Jota.Toledo Jun 13 '17 at 11:51
  • @Duncan you can now find this and other reusable elements under https://github.com/ngx-extensions/ngx-extensions :) – Jota.Toledo Oct 22 '18 at 10:13
2

I solved it using :

In TS:

months = [...Array(12).keys()];

In Template:

<p *ngFor="let month of months">{{month+1}}</p>
Deb
  • 5,163
  • 7
  • 30
  • 45
0

According Angular documentation:

createEmbeddedView() Instantiates an embedded view and inserts it into this container. it accepts a context object as second parameter: abstract createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): EmbeddedViewRef.

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="randomNumber; let i = index; let first = first; let last = last; let even = even, let odd = odd; length = length">
   index :{{i}},
   length:{{length}},
   is first : {{first}},
   is last : {{last}},
   is even : {{even}},
   is odd : {{odd}}
 </p>

for.directive.ts:

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

@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,
        {
          index: i,
          odd: i % 2 == 1,
          even: i % 2 == 0,
          first: i == 0,
          last: i == num,
          length: num - 1,
        }
      );
  }
}
Rafi Henig
  • 5,950
  • 2
  • 16
  • 36