Ітерація над об'єктом у кутовій


130

Я намагаюся зробити деякі речі в Angular 2 Alpha 28, і у мене виникають проблеми зі словниками та NgFor.

У мене інтерфейс в TypeScript виглядає так:

interface Dictionary {
    [ index: string ]: string
}

У JavaScript це буде перекладати на об’єкт, який з даними може виглядати так:

myDict={'key1':'value1','key2':'value2'}

Я хочу перебрати це і спробувати це:

<div *ngFor="(#key, #value) of myDict">{{key}}:{{value}}</div>

Але безрезультатно, ні один із наведених нижче не працював:

<div *ngFor="#value of myDict">{{value}}</div>
<div *ngFor="#value of myDict #key=index">{{key}}:{{value}}</div>

У всіх випадках я отримую помилки на кшталт "Несподіваний маркер" або "Неможливо знайти" iterableDiff "підтримуючий об'єкт"

Що я тут пропускаю? Це вже неможливо? (Перший синтаксис працює у Angular 1.x) чи синтаксис відрізняється для ітерації над об'єктом?


Що таке "словник"? Я ніколи не бачив і не чув цього терміна в контексті JavaScript, Angular або TypeScript. Y

Словник означає карту, я думаю, цей термін взагалі не використовується в контексті JS, але в Python або Ruby він використовується.
Сезар-молодший Родрігес

2
Я думаю, що відповідь @bersling - це правильна відповідь на це питання.
Джошуа Кіссон

1
Будь ласка, позначте правильну відповідь краще. bersling правильний
activedecay

Відповіді:


87

Здається, вони не хочуть підтримувати синтаксис від ng1.

За словами Мішко Гевери ( довідка ):

Карти не мають замовлень у ключах, тому їх ітерація непередбачувана. Це було підтримано в ng1, але ми вважаємо, що це було помилкою і не буде підтримуватися в NG2

План полягає в тому, щоб мати трубу mapToIterable

<div *ngFor"var item of map | mapToIterable">

Тож для того, щоб перебрати об’єкт, вам потрібно буде використовувати "трубу". В даний час не існує жодної труби, яка б це робила.

В якості вирішення ось невеликий приклад, який повторюється над клавішами:

Компонент:

import {Component} from 'angular2/core';

@Component({
  selector: 'component',
  templateUrl: `
       <ul>
       <li *ngFor="#key of keys();">{{key}}:{{myDict[key]}}</li>
       </ul>
  `
})
export class Home {
  myDict : Dictionary;
  constructor() {
    this.myDict = {'key1':'value1','key2':'value2'};
  }

  keys() : Array<string> {
    return Object.keys(this.myDict);
  }
}

interface Dictionary {
    [ index: string ]: string
}

1
Я намагаюся те ж саме на об'єкті з keyяк numberі valueяк, stringале кутова помилка кидання expression has changed after it was checked? чому так будь-яка ідея?
Pardeep Jain

Так, це теж відбувається для мене. І те саме, якщо я теж використовую рішення @ obsur.
користувач2294382

1
Будь ласка, дивіться відповідь Берслінга, оскільки є тривіальне рішення останнього кутового 7
activedecay

156

Кутова 6.1.0+ відповідь

Використовуйте вбудовану keyvalue-pipe так:

<div *ngFor="let item of myObject | keyvalue">
    Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>

або так:

<div *ngFor="let item of myObject | keyvalue:mySortingFunction">
    Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>

де mySortingFunctionу вашому .tsфайлі, наприклад:

mySortingFunction = (a, b) => {
  return a.key > b.key ? -1 : 1;
}

Stackblitz: https://stackblitz.com/edit/angular-iterate-key-value

Вам не потрібно буде реєструвати це в будь-якому модулі, оскільки кутові труби працюють з коробки в будь-якому шаблоні.

Він також працює для Javascript-Maps .


Ви повинні додати implements PipeTransformдо визначення класу (див. Angular.io/guide/pipes#custom-pipes )
toioski

1
@toioski спасибі, я додав його та оновив до нового синтаксису циклу for.
берслінг

Чудова відповідь, використовував це для ngДля мого словника. Мені довелося зробити keyValuePair.val [0], хоча мої значення закінчилися [{}], а не {}
jhhoff02

1
Чи є перевага в цьому над просто return Object.keys(dict).map(key => ({key, val: dict[key]}))?
Джастін Морган

Я не бачу жодного, насправді я б використовував ваш шлях!
берслінг

72

спробуйте використати цю трубу

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

@Pipe({ name: 'values',  pure: false })
export class ValuesPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value).map(key => value[key]);
  }
}

<div *ngFor="#value of object | values"> </div>

5
Блискуче, і якщо я хочу зберегти посилання на ключ, я просто нанесіть на карті об'єкт із клавішею та значенням. Я б хотів, щоб я міг відзначити кілька відповідей як прийняту відповідь, оскільки це рішення моєї проблеми, тоді як позначена відповідь - це відповідь на моє запитання.
Рікард Штаф

1
@obscur - Якщо я зараз зроблю вище, я отримаю помилку "вираз змінився після його перевірки", використовуючи angular2.beta.0.0. Будь-які думки?
користувач2294382

Це тому, що чисто: false вимагає введення стратегії введення змін
Джадсон Террелл

1
Навіщо встановлювати це нечисто?
tom10271

Це добре спрацювало для мене. Єдине, що я не міг використовувати # в ngFor. Використовується нехай замість цього.
MartinJH

19

Окрім відповіді @ obscur, ось приклад того, як можна отримати доступ keyі valueдо @View, і від нього.

Труба:

@Pipe({
   name: 'keyValueFilter'
})

export class keyValueFilterPipe {
    transform(value: any, args: any[] = null): any {

        return Object.keys(value).map(function(key) {
            let pair = {};
            let k = 'key';
            let v = 'value'


            pair[k] = key;
            pair[v] = value[key];

            return pair;
        });
    }

}

Вид:

<li *ngFor="let u of myObject | 
keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>

Так якби об'єкт мав виглядати так:

myObject = {
    Daario: Naharis,
    Victarion: Greyjoy,
    Quentyn: Ball
}

Отриманий результат буде таким:

Ім'я: Дааріо
Прізвище Дааріо: Нахаріс

Ім'я: Вікаріон
: Грейджой

Ім'я: Квентін
Прізвище: Бал


2
лише одне, що потрібно згадати, вам потрібно змінити Вид: як <li *ngFor="let u of myObject | keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>. +1 від мене.
sib10

Код у вашій карті функції може бути спрощений як: return { 'key' : key, 'value' : value[key] };
Макотосан

17

Оновлено: Angular тепер надає трубу для переходу через об'єкт json через keyvalue:

<div *ngFor="let item of myDict | keyvalue">
  {{item.key}}:{{item.value}}
</div>

РОБОТА ДЕМО , і для більш детальної інформації Прочитайте


Раніше (для старшої версії): Досі найкраща / найкоротша відповідь, яку я знайшов, є (Без жодного фільтру труби чи спеціальної функції з боку компонента)

Сторона компонента:

objectKeys = Object.keys;

Сторона шаблону:

<div *ngFor='let key of objectKeys(jsonObj)'>
   Key: {{key}}

    <div *ngFor='let obj of jsonObj[key]'>
        {{ obj.title }}
        {{ obj.desc }}
    </div>

</div>

РОБОТА ДЕМО


1
let item of myDict | keyvalueце вирішило мою проблему.
Silambarasan RD

13

Додаючи відмінну відповідь SimonHawesome . Я створив стисну версію, яка використовує деякі нові функції машинопису. Я розумію, що версія SimonHawesome навмисно багатослівна, щоб пояснити основні деталі. Я також додав перевірку на ранньому етапі, щоб труба працювала на хибні значення. Наприклад, якщо карта є null.

Зауважте, що використання перетворення ітератора (як це зроблено тут) може бути більш ефективним, оскільки нам не потрібно виділяти пам'ять для тимчасового масиву (як це робиться в деяких інших відповідях).

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

@Pipe({
    name: 'mapToIterable'
})
export class MapToIterable implements PipeTransform {
    transform(map: { [key: string]: any }, ...parameters: any[]) {
        if (!map)
            return undefined;
        return Object.keys(map)
            .map((key) => ({ 'key': key, 'value': map[key] }));
    }
}

3
люблю цю нитку, одним коментарем будуючи поверх іншої! Я збирався написати те саме, коли побачив ваш код
Девід

3
єдине в цьому рішенні: воно має реалізовуватисяPipeTransform
iRaS

@iRaS Добрий момент. Я оновив свою відповідь. Я також повертаю невизначений замість null.
Фредерік Олунд

9

Ось варіант щодо деяких з вищезазначених відповідей, який підтримує кілька перетворень (ключ, ключ, значення):

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

type Args = 'keyval'|'key'|'value';

@Pipe({
  name: 'mapToIterable',
  pure: false
})
export class MapToIterablePipe implements PipeTransform {
  transform(obj: {}, arg: Args = 'keyval') {
    return arg === 'keyval' ?
        Object.keys(obj).map(key => ({key: key, value: obj[key]})) :
      arg === 'key' ?
        Object.keys(obj) :
      arg === 'value' ?
        Object.keys(obj).map(key => obj[key]) :
      null;
  }
}

Використання

map = {
    'a': 'aee',
    'b': 'bee',
    'c': 'see'
}

<div *ngFor="let o of map | mapToIterable">{{o.key}}: {{o.value}}</div>
  <div>a: aee</div>
  <div>b: bee</div>
  <div>c: see</div>

<div *ngFor="let o of map | mapToIterable:'keyval'">{{o.key}}: {{o.value}}</div>
  <div>a: aee</div>
  <div>b: bee</div>
  <div>c: see</div>

<div *ngFor="let k of map | mapToIterable:'key'">{{k}}</div>
  <div>a</div>
  <div>b</div>
  <div>c</div>

<div *ngFor="let v of map | mapToIterable:'value'">{{v}}</div>
  <div>aee</div>
  <div>bee</div>
  <div>see</div>

1
pure: falseдійсно важливо для миттєвих роздумів.
Fırat KÜÇÜK

4

У мене було подібне питання, побудували щось для об’єктів та Карт.

import { Pipe } from 'angular2/core.js';

/**
 * Map to Iteratble Pipe
 * 
 * It accepts Objects and [Maps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
 * 
 * Example:
 * 
 *  <div *ngFor="#keyValuePair of someObject | mapToIterable">
 *    key {{keyValuePair.key}} and value {{keyValuePair.value}}
 *  </div>
 * 
 */
@Pipe({ name: 'mapToIterable' })
export class MapToIterable {
  transform(value) {
    let result = [];
    
    if(value.entries) {
      for (var [key, value] of value.entries()) {
        result.push({ key, value });
      }
    } else {
      for(let key in value) {
        result.push({ key, value: value[key] });
      }
    }

    return result;
  }
}


1
Це добре, за винятком того, що в TypeScript вам слід додати implements PipeTransformвизначення класу
GeorgDangl

3

Кутовий 2.x && Кутовий 4.x не підтримує це поза коробкою

Ви можете використовувати ці дві труби для ітерації або за ключем, або за значенням .

Ключі труби:

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

@Pipe({
  name: 'keys',
  pure: false
})
export class KeysPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value)
  }
}

Значення труби:

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

@Pipe({
  name: 'values',
  pure: false
})
export class ValuesPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value).map(key => value[key])
  }
}

Як використовувати:

let data = {key1: 'value1', key2: 'value2'}

<div *ngFor="let key of data | keys"></div>
<div *ngFor="let value of data | values"></div>

2

Якщо хтось задається питанням, як працювати з багатовимірним об’єктом, ось рішення.

припустимо, ми маємо наступний об’єкт в сервісі

getChallenges() {
    var objects = {};
    objects['0'] = { 
        title: 'Angular2', 
        description : "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
    };

    objects['1'] = { 
        title: 'AngularJS', 
        description : "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
    };

    objects['2'] = { 
        title: 'Bootstrap',
        description : "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
    };
    return objects;
}

в компоненті додайте наступну функцію

challenges;

constructor(testService : TestService){
    this.challenges = testService.getChallenges();
}
keys() : Array<string> {
    return Object.keys(this.challenges);
}

нарешті з огляду зробити наступне

<div *ngFor="#key of keys();">
    <h4 class="heading">{{challenges[key].title}}</h4>
    <p class="description">{{challenges[key].description}}</p>
</div>

2

Я розривав волосся, намагаючись розібратися і використовувати дані, повернуті з JSON-запиту / виклику api. Я не впевнений, де саме я пішов не так, я відчуваю, що цілий день кружляв відповідь, переслідуючи різні коди помилок, як-от:

"Неможливо знайти" iterableDiff "підтримуючий об'єкт"

"Для загального масиву TYpe потрібен один аргумент (и)"

JSON аналізує помилки та впевнений, що інші

Я припускаю, що у мене просто неправильна комбінація виправлень.

Отже, ось короткий підсумок роботи та речі, на які слід звернути увагу.

По-перше, перевірте результат своїх api-дзвінків, результати можуть бути у вигляді об’єкта, масиву чи масиву об’єктів.

я не хочу занадто сильно вникати в нього, достатньо сказати, що початкова помилка OP в тому, що вона не була ітерабельною, як правило, викликана тим, що ви намагаєтесь перебрати об'єкт, а не масив.

Ось деякі мої результати налагодження, що показують змінні як масивів, так і об'єктів

Тому, як правило, ми хотіли б повторити наш результат JSON, нам потрібно забезпечити його у формі масиву. Я спробував численні приклади, і, можливо, знаючи, що я знаю зараз, деякі з них насправді спрацюють, але підхід, з яким я пішов, справді був для впровадження труби, і використовуваний код був у тому, що розміщено t.888

   transform(obj: {[key: string]: any}, arg: string) {
if (!obj)
        return undefined;

return arg === 'keyval' ?
    Object.keys(obj).map((key) => ({ 'key': key, 'value': obj[key] })) :
  arg === 'key' ?
    Object.keys(obj) :
  arg === 'value' ?
    Object.keys(obj).map(key => obj[key]) :
  null;

Чесно кажучи, я думаю, що однією з речей, яка мене викликала, було відсутність обробки помилок, додавши дзвінок "повернути невизначений", я вважаю, що ми зараз дозволяємо надсилати в трубу не очікувані дані, що, очевидно, відбувалося в моєму випадку .

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

       if (!obj)
          return undefined;
       return Object.keys(obj);

Деякі примітки щодо створення вашої труби та сторінки або компонента, який використовує цю трубу

це я отримував помилки про те, що "name_of_my_pipe" не знайдено

Використовуйте команду "Іонна генерація труби" від CLI, щоб переконатися, що трубові модулі.ts створені та посилаються правильно. переконайтеся, що ви додали наступне на сторінку mypage.module.ts.

import { PipesModule } from ‘…/…/pipes/pipes.module’;

(не впевнений, що це зміниться, якщо у вас також є власний custom_module, можливо, вам також доведеться додати його до custommodule.module.ts)

якщо ви використовували команду "Іонна генерувати сторінку" для створення своєї сторінки, але вирішили використовувати цю сторінку як свою головну сторінку, не забудьте видалити посилання на сторінку з app.module.ts (ось ще одна відповідь, яку я опублікував, що стосується цього https: / /forum.ionicframework.com/t/solved-pipe-not-found-in-custom-component/95179/13?u=dreaser

У моєму пошуку відповідей там, де існує декілька способів відображення даних у html-файлі, і я недостатньо розумію, щоб пояснити відмінності. Можливо, вам буде краще використовувати одне за іншим у певних сценаріях.

            <ion-item *ngFor="let myPost of posts">
                  <img src="https://somwhereOnTheInternet/{{myPost.ImageUrl}}"/>
                  <img src="https://somwhereOnTheInternet/{{posts[myPost].ImageUrl}}"/>
                  <img [src]="'https://somwhereOnTheInternet/' + myPost.ImageUrl" />
            </ion-item>

Однак, що працювало, що дозволило мені відобразити і значення, і ключ, було наступне:

    <ion-list>  
      <ion-item *ngFor="let myPost of posts  | name_of_pip:'optional_Str_Varible'">

        <h2>Key Value = {{posts[myPost]}} 

        <h2>Key Name = {{myPost}} </h2>

      </ion-item>
   </ion-list>  

щоб дзвінок API виглядав так, що вам потрібно імпортувати HttpModule в app.module.ts

import { HttpModule } from '@angular/http';
 .
 .  
 imports: [
BrowserModule,
HttpModule,

і вам потрібна Http на сторінці, з якої ви телефонуєте

import {Http} from '@angular/http';

Здійснюючи виклик API, вам здається, що ви можете отримати дітям дані (об’єкти або масиви в масиві) двома різними способами, або, здається, працює

або під час дзвінка

this.http.get('https://SomeWebsiteWithAPI').map(res => res.json().anyChildren.OrSubChildren).subscribe(
        myData => {

або коли ви призначаєте дані до локальної змінної

posts: Array<String>;    
this.posts = myData['anyChildren'];

(не впевнений, чи повинна ця змінна бути рядком масиву, але ось у мене це є. Можливо, вона працює як більш загальна змінна)

І підсумкове зауваження: не потрібно було використовувати вбудовану бібліотеку JSON, однак ви можете виявити ці 2 виклики зручними для перетворення об’єкта в рядок і vica навпаки

        var stringifiedData = JSON.stringify(this.movies);                  
        console.log("**mResults in Stringify");
        console.log(stringifiedData);

        var mResults = JSON.parse(<string>stringifiedData);
        console.log("**mResults in a JSON");
        console.log(mResults);

Я сподіваюся, що ця збірка інформації допоможе комусь.


2
//Get solution for ng-repeat    
//Add variable and assign with Object.key

    export class TestComponent implements OnInit{
      objectKeys = Object.keys;
      obj: object = {
        "test": "value"
        "test1": "value1"
        }
    }
    //HTML
      <div *ngFor="let key of objectKeys(obj)">
        <div>
          <div class="content">{{key}}</div>
          <div class="content">{{obj[key]}}</div>
        </div>

1

У JavaScript це буде перекладати на об’єкт, який з даними може виглядати так

Інтерфейси в TypeScript - це розроблена конструкція часу (суто для інструментального ... 0 часу виконання). Ви повинні написати той же TypeScript, що і ваш JavaScript.


це неправда, сири. сценарій типу змушує писати чистіший код. набагато простіше до абстрактних класів. яких у вас просто немає. C ++ компілюється в деяку ASM - ASM також не має класів або навіть типів, проте ви пишете різні c ++, а потім ур ASM код: P
YAMM

1

Словник - це об'єкт, а не масив. Я вважаю, що для ng-повтору потрібен масив у Angular 2.

Найпростішим рішенням було б створити трубу / фільтр, який перетворює об'єкт на масив на льоту. Однак, ймовірно, ви хочете використовувати масив, як говорить @basarat.


1

Якщо у вас є es6-shimабо ваша tsconfig.jsonмета es6, ви могли б використовувати ES6 карту , щоб зробити це.

var myDict = new Map();
myDict.set('key1','value1');
myDict.set('key2','value2');

<div *ngFor="let keyVal of myDict.entries()">
    key:{{keyVal[0]}}, val:{{keyVal[1]}}
</div>

0

Визначте MapValuesPipeта застосуйте PipeTransform :

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

@Pipe({name: 'mapValuesPipe'})
export class MapValuesPipe implements PipeTransform {
    transform(value: any, args?: any[]): Object[] {
        let mArray: 
        value.forEach((key, val) => {
            mArray.push({
                mKey: key,
                mValue: val
            });
        });

        return mArray;
    }
}

Додайте трубу в модуль труб. Це важливо, якщо вам потрібно використовувати одну й ту саму трубу у кількох компонентах :

@NgModule({
  imports: [
    CommonModule
  ],
  exports: [
    ...
    MapValuesPipe
  ],
  declarations: [..., MapValuesPipe, ...]
})
export class PipesAggrModule {}

Потім просто використовуйте трубу у своєму html за допомогою *ngFor:

<tr *ngFor="let attribute of mMap | mapValuesPipe">

Пам'ятайте, що вам потрібно буде оголосити PipesModule в компоненті, де ви хочете використовувати трубу:

@NgModule({
  imports: [
    CommonModule,
    PipesAggrModule
  ],
...
}
export class MyModule {}

0

Тому я збирався реалізувати власну помічну функцію, objLength (obj), яка повертає просто Object (obj) .keys.length. Але тоді, коли я додавав його до функції * ngIf, мій IDE запропонував objectKeys (). Я спробував це, і це спрацювало. Після цього до своєї декларації, схоже, пропонується lib.es5.d.ts, тож ви йдете!

Ось як я його реалізував (у мене є власний об’єкт, який використовує створені на сервері ключі як індекс для завантажених файлів):

        <div *ngIf="fileList !== undefined && objectKeys(fileList).length > 0">
          <h6>Attached Files</h6>
          <table cellpadding="0" cellspacing="0">
            <tr *ngFor="let file of fileList | keyvalue">
              <td><a href="#">{{file.value['fileName']}}</a></td>
              <td class="actions">
                <a title="Delete File" (click)="deleteAFile(file.key);">
                </a>
              </td>
            </tr>
          </table>
        </div>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.