Angular 2 - Ng для использования чисел вместо коллекций

191

...например...

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

Можно сделать что-то вроде ...

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

... без обращения к не элегантному решению, как:

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

?

Марко младший
источник
8
У меня та же проблема. Действительно огорченный, нельзя делать такие простые вещи с угловым 2.
albanx
1
Может быть, это может быть полезно: stackoverflow.com/questions/3895478/…
Pizzicato

Ответы:

198

Внутри вашего компонента вы можете определить массив чисел (ES6), как описано ниже:

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

Смотрите эту ссылку для создания массива: кратчайший способ создать массив целых чисел из 1..20 в JavaScript .

Затем вы можете перебрать этот массив с помощью ngFor:

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

Или коротко:

@Component({
  template: `
    <ul>
      <li *ngFor="let number of [0,1,2,3,4]">{{number}}</li>
    </ul>
  `
})
export class SampleComponent {
  (...)
}
Тьерри Темплиер
источник
5
Да, Тьерри! Это действительно не ваша вина, но все же в том же контексте :( Это совсем не элегантно. Но так как вы очень опытный разработчик A2, я могу предположить, что лучшего решения не существует. Это печально!
Марко-младший
Фактически, в Angular2 нет ничего для синтаксиса цикла. Вам нужно использовать то, что JavaScript предоставляет для создания массивов. Например: Array(5).fill(4)создать[4,4,4,4,4]
Тьерри Темплиер
3
PS: аннотация @View была удалена в angular2 beta 10 и выше.
Пардип Джейн
23
Использование Array.fill()в Angular 2 Typescript приводит к следующей ошибке Supplied parameters do not match any signature of call t arget.- Проверяя документы Array.prototype.fill, он говорит, что требуется 1 аргумент ... developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Джошуа Рассел
5
Array(5).fill(1).map((x, i) => i + 1); /*[1,2,3,4,5]*/это решает ошибку в TS
mshahbazm
90

@OP, вы были очень близки со своим «не элегантным» решением.

Как насчет:

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

Здесь я получаю Arrayконструктор из пустого массива: [].constructorпотому что Arrayне является распознанным символом в синтаксисе шаблона, и мне лень это делать Array=Arrayили counter = Arrayв машинописи, как @ pardeep-jain в его 4-м примере. И я называю это без, newпотому что newэто не обязательно для получения массива из Arrayконструктора.

Array(30)и new Array(30)эквивалентны.

Массив будет пустым, но это не имеет значения, потому что вы действительно хотите использовать ifrom ;let i = indexв вашем цикле.

jcairney
источник
13
Это лучший ответ.
kagronick
Это решение запускает обнаружение изменений. Я думаю, из-за нового массива.
Tobias81
1
@ Tobias81, не могли бы вы уточнить? Вы говорите, что каждый раз, когда приложение запускает обнаружение изменений, содержимое * ngFor перерисовывается, потому что массив воссоздается? Это определенно стоит отметить. Можно обойти это, фактически создав поле массива в TS для ссылки, чтобы оно было одинаковым при каждом обнаружении изменений. Но это было бы определенно менее элегантно, чем хотелось бы. Есть ли та же проблема обнаружения изменений во втором примере в выбранном ответе Тьерри Темплиера? <li *ngFor="let number of [0,1,2,3,4]">{{number}}</li>
Jcairney
это лучшее решение , найденное для этой проблемы
Khush
1
@ Tobias81, я проверял, чтобы убедиться, что обнаружение изменений не воссоздает содержимое ngFor несколько раз, помещая инструкцию print в конструктор компонента, который я создаю как дочерний элемент примерной директивы ngFor. Я не вижу воссоздания компонентов на каждой итерации обнаружения изменений, поэтому я не думаю, что на самом деле есть проблема (по крайней мере, в Angular 8).
jcairney
83

Нет, для NgFor пока нет метода использования чисел вместо коллекций. В настоящее время * ngFor принимает коллекцию только в качестве параметра, но вы можете сделать это следующими способами:

Используя трубу

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>

Использование массива чисел непосредственно в HTML (Просмотр)

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

Использование метода Split

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

Использование создания нового массива в компоненте

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

Рабочая демонстрация

Пардип Джайн
источник
4
Вы также можете использовать Array.fill()метод для создания массива вместо того, res.push()как показано в ответе Thierrys.
Гюнтер Цохбауэр
да, я могу, но есть ли что-то не так push? Я имею в виду, что оба метода верны, но все же, если есть разница. между ними.
Пардип Джейн
3
Нет, все еще хорошее решение +1. Я просто нахожу Array.fill()более элегантным, чем цикл, использующий push, и он также, вероятно, более эффективен.
Гюнтер Цохбауэр
1
Мне нравится это решение с counter = Arrayочень умным;)
Verri
11

Я не мог вынести идею выделения массива для простого повторения компонентов, поэтому я написал структурную директиву. В простейшей форме это не делает индекс доступным для шаблона, это выглядит так:

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
источник
Я согласен с тем, что подход с массивами уродлив, но мне кажется, что это преждевременная оптимизация.
Алуан Хаддад
3
Конечно, но и упражнение в написании директивы. С другой стороны, он не длиннее трубы, что было бы вторым вменяемым подходом.
pdudits
Это хороший момент, у вас не так много возможностей познакомиться с концепцией пользовательских структурных директив.
Алуан Хаддад
Хороший @pdudits - все еще работает с последними версиями: plnkr.co/edit/8wJtkpzre3cBNokHcDL7?p=preview [не стесняйтесь обновлять свой plnkr]
В
5

Я решил это, используя Angular 5.2.6 и 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);
}

Это может быть использовано в компоненте, как это:

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

Проверка ошибок и утверждения специально для краткости опущены (например, что происходит, если шаг отрицательный).

Пер Эдин
источник
2
Есть ли какие-либо способы в html, как это <div *ngfor="let i of 4, i++"></div>может быть
Нитила Шанмуганантан
5

Вы также можете использовать это

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>
Тонмой Нэнди
источник
4

Вы можете использовать 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);
}

Я не тестировал код, но он должен работать.

Алекс По
источник
Есть ли какие-либо способы в html, как это <div *ngfor="let i of 4, i++"></div>может быть
Нитила Шанмуганантан
Если вам нужно iили индексировать в коде, то вы можете сделать*ngFor="let i of range; let i = index"
Alex Po
3

Это также может быть достигнуто следующим образом:

HTML:

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

Машинопись:

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

Рабочая Демо

Адрита Шарма
источник
2

Поскольку метод fill () (упомянутый в принятом ответе) без аргументов выдает ошибку, я бы предложил что-то вроде этого (работает для меня, Angular 7.0.4, Typescript 3.1.6)

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

В коде компонента:

this.items = Array.from({length: 10}, (v, k) => k + 1);
Далибора
источник
1
<div *ngFor="let number of [].constructor(myCollection)">
    <div>
        Hello World
    </div>
</div>

Это хороший и быстрый способ повторить количество раз в myCollection.

Так что, если myCollection было 5, Hello World будет повторяться 5 раз.

Джейк Махи
источник
1

Используя пользовательскую Структурную Директиву с индексом:

Согласно угловой документации:

createEmbeddedView Создает встроенный вид и вставляет его в этот контейнер.

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.

Когда angular создает шаблон, вызывая createEmbeddedView, он также может передавать контекст, который будет использоваться внутри ng-template.

Используя необязательный параметр context, вы можете использовать его в компоненте, извлекая его из шаблона так же, как и в * 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));
  }
}
Рафи Хениг
источник
0

Пожалуйста, найдите мое динамическое решение, если вы хотите динамически увеличивать размер массива после нажатия кнопки (вот как я дошел до этого вопроса).

Распределение необходимых переменных:

  array = [1];
  arraySize: number;

Объявите функцию, которая добавляет элемент в массив:

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

Вызвать функцию в html

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

Итерация по массиву с помощью ngFor:

<div *ngFor="let i of array" >
  iterateThroughArray: {{ i }}
</div>
Ян Клеменс Стоффреген
источник
Есть ли какие-либо способы в html, как это <div *ngfor="let i of 4, i++"></div>может быть
Нитила Шанмуганантан
Вы должны перебрать массив. Если вам нужен скаляр, вы можете перебрать массив правильного размера и дополнительно создать его скаляр: * ngFor = "let item of array; let i = index"
Ян Клеменс Штоффреген,
0

Самый простой способ, который я пробовал

Вы также можете создать массив в файле компонента и вызвать его с помощью директивы * ngFor, вернув его в виде массива.

Что-то вроде этого ....

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

}

И эта функция может быть использована в вашем файле шаблона HTML

<p *ngFor="let a of arra(); let i= index">
value:{{a}} position:{{i}}
</p>
Alok Panday
источник
2
Есть ли какие-либо способы в html, как это <div *ngfor="let i of 4, i++"></div>может быть
Нитила Шанмуганантан
0

Мое решение:

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

В 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
источник
это создает новый массив при каждом получении. Может создать накладные расходы
Ремко Влиерман