Обход объекта в Angular

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 он используется.
Cesar Jr Rodriguez
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
Я пытаюсь сделать то же самое с объектом keyas numberи valueas, stringно angular выдает ошибку expression has changed after it was checked? почему так возникла идея?
Pardeep Jain
Да, это тоже происходит со мной. И то же самое, если я тоже использую решение @obsur.
user2294382 01
1
См. Ответ Берслинга, потому что на последней версии angular 7 есть тривиальное решение
activedecay
156

Angular 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

Вам не нужно регистрировать это в каком-либо модуле, поскольку каналы Angular работают из коробки в любом шаблоне.

Это также работает для Javascript-Maps .

bersling
источник
Вы должны добавить implements PipeTransformопределение класса (см. Angular.io/guide/pipes#custom-pipes )
toioski 09
1
@toioski, спасибо, я добавил его и обновил синтаксис цикла for.
bersling 09
Отличный ответ, использовал это для моего словаря. Мне пришлось сделать keyValuePair.val [0], хотя мои значения оказались [{}], а не {}
jhhoff02
1
Есть ли в этом преимущество перед справедливостью return Object.keys(dict).map(key => ({key, val: dict[key]}))?
Джастин Морган
Я не вижу ничего, на самом деле я бы использовал ваш путь!
bersling
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>
Obscur
источник
5
Великолепно, и если я хочу сохранить ссылку на ключ, я просто сопоставлю объект с ключом и значением. Я хотел бы отметить несколько ответов как принятый, так как это решение моей проблемы, а отмеченный ответ - это ответ на мой вопрос.
Рикард Стааф
1
@obscur - Если я сделаю это сейчас, я получаю сообщение об ошибке «выражение было изменено после проверки» с использованием angular2.beta.0.0. Есть предположения?
user2294382 01
Это потому , что чистая ложь требует изменения detectionStrategy для инъекций
Джадсон Террелл
1
Зачем ставить его нечистым?
tom10271
У меня это сработало. Единственное, что я не мог использовать # в ngFor. Вместо этого использовал let.
MartinJH
19

В дополнение к ответу @ obscur вот пример того, как вы можете получить доступ как к @View, так keyи valueиз него.

Труба:

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

Сгенерированный результат будет:

Имя: Даарио
Фамилия: Нахарис

Имя: Виктарион
Фамилия: Грейджой

Имя: Квентин
Фамилия: Болл

SimonHawesome
источник
2
Стоит упомянуть только одну вещь: вам нужно изменить View: as <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 Хорошее замечание. Я обновил свой ответ. Я также возвращаю undefined вместо null.
Frederik Aalund
9

Вот вариант некоторых из приведенных выше ответов, который поддерживает несколько преобразований (keyval, key, value):

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>
t.888
источник
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;
  }
}

amcdnl
источник
1
Это работает хорошо, за исключением того, что в TypeScript вы должны добавить implements PipeTransformопределение класса
GeorgDangl
3

Angular 2.x && Angular 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>
MK
источник
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'»

«Для универсального массива типов требуется один аргумент»

Ошибки синтаксического анализа 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;

Честно говоря, я думаю, что одной из вещей, которые меня вызывали, было отсутствие обработки ошибок, добавив вызов `` return undefined '', я считаю, что теперь мы разрешаем отправку непредвиденных данных в канал, что, очевидно, происходило в моем случае ,

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

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

Некоторые примечания по созданию канала и страницы или компонента, использующего этот канал

я получал ошибки о том, что "name_of_my_pipe" не найдено

Используйте команду 'ionic generate pipe' из интерфейса командной строки, чтобы убедиться, что файлы pipe modules.ts созданы и указаны правильно. убедитесь, что вы добавили следующее на страницу mypage.module.ts.

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

(не уверен, что это изменится, если у вас также есть собственный custom_module, вам также может потребоваться добавить его в custommodule.module.ts)

если вы использовали команду 'ionic generate page' для создания своей страницы, но решили использовать эту страницу в качестве главной, не забудьте удалить ссылку на страницу из 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 вызова удобными для преобразования из объекта в строку и наоборот.

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

Я надеюсь, что эта подборка информации кому-то поможет.

Dreaser
источник
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.

basarat
источник
это неправда, извините. type script заставляет вас писать более чистый код. абстрактные классы гораздо проще. которого у вас просто нет. C ++ компилируется в некоторый asm - asm также не имеет классов или даже типов, тем не менее, вы пишете другой c ++, а затем код ur asm: P
YAMM
1

Словарь - это объект, а не массив. Я считаю, что ng-repeat требует массива в Angular 2.

Самым простым решением было бы создать канал / фильтр, который на лету преобразует объект в массив. Тем не менее, вы, вероятно, захотите использовать массив, как говорит @basarat.

Мартин
источник
1

Если у вас есть es6-shimили ваша tsconfig.jsonцель es6, вы можете использовать ES6 Map, чтобы сделать это.

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>
Sing
источник
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. Но затем, когда я добавлял его в свою функцию template * 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>
Мавзола
источник