Angular 2 - NgPara usar números em vez de coleções

191

...por exemplo...

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

É possível fazer algo como ...

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

... sem apelar para uma solução não elegante como:

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

?

Marco Jr
fonte
8
Eu tenho o mesmo problema. Realmente chateado não se pode fazer coisas tão simples com angular 2.
albanx
1
Talvez isso possa ser útil: stackoverflow.com/questions/3895478/…
Pizzicato

Respostas:

197

Dentro do seu componente, você pode definir uma matriz de número (ES6), conforme descrito abaixo:

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]
  }
}

Consulte este link para a criação da matriz: Maneira resumida de criar uma matriz de números inteiros de 1 a 20 em JavaScript .

Você pode iterar sobre essa matriz com ngFor:

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

Ou brevemente:

@Component({
  template: `
    <ul>
      <li *ngFor="let number of [0,1,2,3,4]">{{number}}</li>
    </ul>
  `
})
export class SampleComponent {
  (...)
}
Thierry Templier
fonte
5
Sim, Thierry! Não é sua culpa, de fato, mas ainda no mesmo contexto :( Não é nada elegante. Mas, como você é um desenvolvedor A2 muito qualificado, posso assumir que não há solução melhor. É triste!
Marco Jr
De fato, não há nada para isso no Angular2 na sintaxe do loop. Você precisa aproveitar o que o JavaScript fornece para criar matrizes. Por exemplo: Array(5).fill(4)para criar[4,4,4,4,4]
Thierry Templier
3
A anotação PS: @View foi removida no angular2 beta 10 e superior.
Pardeep Jain #
23
O uso Array.fill()do Angular 2 Typescript produz o seguinte erro Supplied parameters do not match any signature of call t arget.- Verificando os documentos Array.prototype.fill, ele exige 1 argumento ... developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Joshua Russell
5
Array(5).fill(1).map((x, i) => i + 1); /*[1,2,3,4,5]*/isso resolve o erro no TS
mshahbazm 27/03
90

@OP, você estava muito perto da sua solução "não elegante".

E se:

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

Aqui estou obtendo o Arrayconstrutor a partir de uma matriz vazia:, [].constructorporque Arraynão é um símbolo reconhecido na sintaxe do modelo, e estou com preguiça de fazer Array=Arrayou counter = Arrayno componente texto datilografado como @ pardeep-jain fez no seu quarto exemplo. E eu estou chamando isso de fora newporque newnão é necessário para obter uma matriz do Arrayconstrutor.

Array(30)e new Array(30)são equivalentes.

A matriz estará vazia, mas isso não importa, porque você realmente quer apenas para usar ia partir ;let i = indexde seu loop.

jcairney
fonte
13
Esta é a melhor resposta.
Kagronick
Esta solução aciona a detecção de alterações. Eu acho que devido à nova matriz.
Tobias81
1
@ Tobias81, você poderia elaborar? Você está dizendo que toda vez que o aplicativo executa a detecção de alterações, o conteúdo do * ngFor é redesenhado porque a matriz é recriada? Definitivamente vale a pena notar. Pode-se contornar isso criando um campo de matriz no TS para referência, para que seja o mesmo sempre que a detecção de alterações for executada. Mas isso seria definitivamente menos elegante do que o desejado. O mesmo problema de detecção de alterações está presente no segundo exemplo da resposta selecionada de Thierry Templier? <li *ngFor="let number of [0,1,2,3,4]">{{number}}</li>
precisa saber é
esta é a melhor solução encontrada para esse problema
khush 16/02
1
@ Tobias81, verifiquei para garantir que a detecção de alterações não recrie o conteúdo do ngFor repetidamente, colocando uma instrução print no construtor de um componente que eu criei como filho da diretiva ngFor de exemplo. Eu não vejo os componentes sendo recriados em todas as iterações de detecção de alterações, então não acho que exista realmente um problema (pelo menos no Angular 8).
jcairney 29/03
83

Ainda não existe um método para o NgFor usar números em vez de coleções. No momento, * ngFor aceita apenas uma coleção como parâmetro, mas você pode fazer isso seguindo os seguintes métodos:

Usando tubo

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;
  }
}


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

Usando matriz numérica diretamente em HTML (Exibir)

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

Usando o método Split

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

Usando a criação de Nova matriz no componente

<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);
  }
}

Demonstração de trabalho

Pardeep Jain
fonte
4
Você também pode usar o Array.fill()método para gerar a matriz em vez de res.push()como mostrado na resposta de Thierrys.
Günter Zöchbauer 01/04
Sim, eu posso, mas há algo de errado com push? Quero dizer que ambos os métodos estão corretos, mas ainda assim, se houver alguma diferença. entre eles.
Pardeep Jain #
3
Não, ainda é uma boa solução +1. Eu acho o Array.fill()mais elegante que o loop usando push e também é provavelmente mais eficiente.
Günter Zöchbauer 01/04
1
Eu gosto desta solução, com counter = Array, muito inteligente;)
Verri
11

Não suportava a idéia de alocar uma matriz para repetição simples de componentes, por isso escrevi uma diretiva estrutural. Na forma mais simples, que não disponibiliza o índice para o modelo, é assim:

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

pdudits
fonte
Concordo que a abordagem de matriz é feia, mas isso parece otimização prematura para mim.
Aluan Haddad 27/02
3
Claro, mas também um exercício de redação de uma diretiva. Por outro lado, não é maior que o tubo, o que seria uma segunda abordagem sã.
Pdudits 01/03/19
Esse é um bom ponto, não há muitas oportunidades para você se familiarizar com o conceito de diretivas estruturais personalizadas.
Aluan Haddad
Nice one @pdudits - Ainda funciona com as versões mais recentes: plnkr.co/edit/8wJtkpzre3cBNokHcDL7?p=preview [sinta-se livre para atualizar seu plnkr]
AT
5

Resolvi assim usando o Angular 5.2.6 e o ​​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);
}

Pode ser usado em um componente como este:

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

Verificação de erros e afirmações omitidas de propósito por questões de brevidade (por exemplo, o que acontece se a etapa for negativa).

Per Edin
fonte
2
Existe alguma maneira em html a partir de <div *ngfor="let i of 4, i++"></div>pode ser?
Nithila Shanmugananthan
5

você também pode usar assim

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>
Tonmoy Nandy
fonte
4

Você pode usar o 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);
}

Não testei o código, mas deve funcionar.

Alex Po
fonte
Existem algumas maneiras em html como de <div *ngfor="let i of 4, i++"></div>podem ser
Nithila Shanmugananthan
Se você precisar iou indexar um código, poderá fazê-lo #*ngFor="let i of range; let i = index"
Alex Po
3

Isso também pode ser alcançado assim:

HTML:

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

Texto datilografado:

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

Demonstração de trabalho

Adrita Sharma
fonte
2

Como o método fill () (mencionado na resposta aceita) sem argumentos gera um erro, eu sugeriria algo assim (funciona para mim, Angular 7.0.4, Typescript 3.1.6)

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

No código do componente:

this.items = Array.from({length: 10}, (v, k) => k + 1);
Dalibor
fonte
1
<div *ngFor="let number of [].constructor(myCollection)">
    <div>
        Hello World
    </div>
</div>

Esta é uma maneira rápida e agradável de repetir a quantidade de vezes em myCollection.

Portanto, se myCollection fosse 5, o Hello World seria repetido 5 vezes.

Jake Mahy
fonte
1

Usando diretiva estrutural personalizada com índice:

De acordo com a documentação angular:

createEmbeddedView Instancia uma exibição incorporada e a insere nesse contêiner.

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.

Quando angular cria um modelo chamando createEmbeddedView, ele também pode passar o contexto que será usado dentro ng-template.

Usando o parâmetro opcional de contexto, você pode usá-lo no componente, extraindo-o no modelo da mesma forma que faria com o * 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));
  }
}
Rafi Henig
fonte
0

Por favor, encontre em anexo minha solução dinâmica se você quiser aumentar dinamicamente o tamanho de uma matriz depois de clicar em um botão (foi assim que cheguei a essa pergunta).

Alocação de variáveis ​​necessárias:

  array = [1];
  arraySize: number;

Declare a função que adiciona um elemento à matriz:

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

Chame a função em html

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

Iterar através da matriz com ngFor:

<div *ngFor="let i of array" >
  iterateThroughArray: {{ i }}
</div>
Jan Clemens Stoffregen
fonte
Existe alguma maneira em html a partir de <div *ngfor="let i of 4, i++"></div>pode ser?
Nithila Shanmugananthan
você precisa iterar sobre uma matriz. Se você precisar a escalar, você pode iterar sobre uma matriz com o tamanho certo e instanciar um escalar além: * ngFor = "vamos peça de matriz; deixe i = index"
Jan Clemens Stoffregen
0

Uma maneira mais simples que eu tentei

Você também pode criar uma matriz no seu arquivo de componente e chamá-la com a diretiva * ngFor retornando como uma matriz.

Algo assim ....

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() {
  }

}

E essa função pode ser usada no seu arquivo de modelo html

<p *ngFor="let a of arra(); let i= index">
value:{{a}} position:{{i}}
</p>
Alok Panday
fonte
2
Existe alguma maneira de html a partir de <div *ngfor="let i of 4, i++"></div>pode ser?
Nithila Shanmugananthan
0

Minha solução:

export class DashboardManagementComponent implements OnInit {
  _cols = 5;
  _rows = 10;
  constructor() { }

  ngOnInit() {
  }

  get cols() {
    return Array(this._cols).fill(null).map((el, index) => index);
  }
  get rows() {
    return Array(this._rows).fill(null).map((el, index) => index);
  }

Em html:

<div class="charts-setup">
  <div class="col" *ngFor="let col of cols; let colIdx = index">
    <div class="row" *ngFor="let row of rows; let rowIdx = index">
      Col: {{colIdx}}, row: {{rowIdx}}
    </div>
  </div>
</div>
cuddlemeister
fonte
isso cria uma nova matriz a cada get. Poderia criar despesas gerais
Remco Vlierman