получить доступ к ключу и значению объекта, используя * ngFor

426

Я немного озадачен тем, как получить keyи valueобъекта в angular2 при использовании *ngForдля перебора объекта. Я знаю в angular 1.x есть синтаксис вроде

ng-repeat="(key, value) in demo"

но я не знаю, как сделать то же самое в angular2. Я пробовал что-то подобное, но безуспешно

<ul>
  <li *ngFor='#key of demo'>{{key}}</li>
</ul>

demo = {
    'key1': [{'key11':'value11'}, {'key12':'value12'}],
    'key2': [{'key21':'value21'}, {'key22':'value22'}],
  }

Вот plnkr с моей попыткой: http://plnkr.co/edit/mIj619FncOpfdwrR0KeG?p=preview

Как я могу получить key1и key2динамически использовать *ngFor? После тщательного поиска я нашел идею использования каналов, но я не знаю, как это сделать. Есть ли встроенная труба для того же в angular2?

Пардип Джейн
источник
2
в настоящее время key, valueв angular2 нет синтаксиса парной поддержки ngFor, вы должны посмотреть на этот ответ
Pankaj Parkar
@PankajParkar да, уже читал этот ответ. какой-нибудь альтернативный сейчас?
Пардип Джейн
@Pradeep Я не думаю о каком-либо другом пути для этого сейчас, вы должны пойти для создания собственного Pipeдля этого ..
Pankaj Parkar
хм, но я понятия не имею, как создать трубу для того же.
Пардип Джейн
Ответ @Pradeep, который я дал вам для справки, имеет такую ​​реализацию. они должны работать ..
Панкадж Паркар

Ответы:

400

Иметь Object.keysдоступ в шаблоне и использовать его в *ngFor.

@Component({
  selector: 'app-myview',
  template: `<div *ngFor="let key of objectKeys(items)">{{key + ' : ' + items[key]}}</div>`
})

export class MyComponent {
  objectKeys = Object.keys;
  items = { keyOne: 'value 1', keyTwo: 'value 2', keyThree: 'value 3' };
  constructor(){}
}
tomtastico
источник
1
@PardeepJain - это метод ES5 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
tomtastico
25
Это лучшее и более эффективное решение
Aous1000
1
@tomtastico Как бы вы отобразили это для 3D-массива? Например, {"1": {"1.1": ["1.1.1", "1.1.2"]}}. И затем вложение 3 ngFor's
Фрэнк
@ Черт, ты только что сказал это сам. Гнездо *ngForс. Первые два, используя objectKeys, внутренне не нужно (так как это просто массив).
tomtastico
1
Потрясающие. Установка objectKeys = Object.keys - это самый простой метод, который я видел, чтобы иметь возможность проверить длину объекта из HTML.
JAC
394

Как и в последнем выпуске Angular (v6.1.0) , Angular Team добавила новый встроенный канал для канала с тем же именем, что и keyvalueканал, чтобы помочь вам перебирать объекты, карты и массивы в commonмодуле углового пакета. Например -

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

Рабочий разветвленный пример

проверьте это здесь для более полезной информации -

Если вы используете Angular v5 или ниже или хотите добиться использования pipe, следуйте этому ответу

Пардип Джейн
источник
1
LOL Я должен был сделать обновление ng6 только для доступа к этой трубе - отличная
вещь
36
Вы можете сохранить исходный порядок ключей, используя собственный компаратор: *ngFor="let item of testObject | keyvalue:keepOriginalOrder" и в своем классе определите: public keepOriginalOrder = (a, b) => a.key
mike-shtil
2
public keepOriginalOrder = (a, b) => a.key, спасибо большое за это
Kumaresan Perumal
1
это должен быть ответ - хорошо работает на угловых 7
Calios
1
Невероятно, что это не было там с первой версии
Карлос Пинсон
227

Вы можете создать пользовательский канал для возврата списка ключей для каждого элемента. Что-то такое:

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

@Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
  transform(value, args:string[]) : any {
    let keys = [];
    for (let key in value) {
      keys.push(key);
    }
    return keys;
  }
}

и используйте это так:

<tr *ngFor="let c of content">           
  <td *ngFor="let key of c | keys">{{key}}: {{c[key]}}</td>
</tr>

редактировать

Вы также можете вернуть запись, содержащую ключ и значение:

@Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
  transform(value, args:string[]) : any {
    let keys = [];
    for (let key in value) {
      keys.push({key: key, value: value[key]});
    }
    return keys;
  }
}

и используйте это так:

<span *ngFor="let entry of content | keys">           
  Key: {{entry.key}}, value: {{entry.value}}
</span>
Тьерри Темплиер
источник
1
обратите внимание на отсутствующую заключительную скобку вkeys.push({key: key, value: value[key]);
Мартон Паллаги
49
На самом деле я никому не рекомендую использовать каналы для создания коллекций внутри *ngForвыражения. Это создает огромное узкое место в производительности, потому что оно должно генерировать коллекцию каждый раз, когда детектор изменений проверяет наличие изменений.
мартин
3
Спасибо за решение ... проблема в том, что всякий раз, когда объект изменяется, канал не обновляется. Если я добавлю pure:falseв трубу, она станет очень неэффективной. Есть ли у вас решение обновить канал вручную, когда я меняю объект (удаляю элемент)?
ncohen
4
Ответ немного устарел. Строка * ngFor = "# запись содержимого | ключей" не работает должным образом, и цикл for ... in лучше изменить на "for (const key of Object.keys (value))"
Experimenter
2
@RachChen Не в шаблонах: common: NgFor has been removed as it was deprecated since v4. Use NgForOf instead. This does not impact the use of*ngFor in your templates.( jaxenter.com/road-to-angular-5-133253.html )
mwld
49

Обновить

В 6.1.0-beta.1 KeyValuePipe был введен https://github.com/angular/angular/pull/24319

<div *ngFor="let item of {'b': 1, 'a': 1} | keyvalue">
  {{ item.key }} - {{ item.value }}
</div>

Пример плунжера

Предыдущая версия

Другой подход заключается в создании NgForInдирективы, которая будет использоваться следующим образом:

<div *ngFor="let key in obj">
   <b>{{ key }}</b>: {{ obj[key] }}
</div>

Пример плунжера

ngforin.directive.ts

@Directive({
  selector: '[ngFor][ngForIn]'
})
export class NgForIn<T> extends NgForOf<T> implements OnChanges {

  @Input() ngForIn: any;

  ngOnChanges(changes: NgForInChanges): void {
    if (changes.ngForIn) {
      this.ngForOf = Object.keys(this.ngForIn) as Array<any>;

      const change = changes.ngForIn;
      const currentValue = Object.keys(change.currentValue);
      const previousValue = change.previousValue ? Object.keys(change.previousValue) : undefined;
      changes.ngForOf =  new SimpleChange(previousValue, currentValue, change.firstChange);

      super.ngOnChanges(changes);
    }
  }
}
yurzui
источник
43

Из Angular 6.1 вы можете использовать keyvalue pipe:

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

Но есть неудобство, которое сортирует результирующий список по значению ключа. Если вам нужно что-то нейтральное:

@Pipe({ name: 'keyValueUnsorted', pure: false  })
export class KeyValuePipe implements PipeTransform {
  transform(input: any): any {
    let keys = [];
    for (let key in input) {
      if (input.hasOwnProperty(key)) {
        keys.push({ key: key, value: input[key]});
      }
    }
    return keys;
  }
}

Не забудьте указать атрибут pure: false pipe. В этом случае канал вызывается при каждом цикле обнаружения изменений, даже если входная ссылка не изменилась (как в случае добавления свойств к объекту).

Жерар Карбо
источник
Уже поделился тем же ответом выше stackoverflow.com/a/51491848/5043867
Pardeep Jain
26

Разработка ответа @ Тьерри с примером.

В key and valueцикле * ngFor нет встроенного канала или метода . поэтому мы должны создать собственную трубу для того же самого. как сказал тьерри, вот ответ с кодом.

** Класс pipe реализует метод преобразования интерфейса PipeTransform, который принимает входное значение и необязательный массив строк параметров и возвращает преобразованное значение.

** Метод преобразования имеет важное значение для трубы. Интерфейс PipeTransform определяет этот метод и управляет как инструментами, так и компилятором. Это необязательно; Angular ищет и выполняет метод преобразования независимо. для более подробной информации смотрите здесь

import {Component, Pipe, PipeTransform} from 'angular2/core';
import {CORE_DIRECTIVES, NgClass, FORM_DIRECTIVES, Control, ControlGroup, FormBuilder, Validators} from 'angular2/common';

@Component({
    selector: 'my-app',
    templateUrl: 'mytemplate.html',
    directives: [CORE_DIRECTIVES, FORM_DIRECTIVES],
    pipes: [KeysPipe]
})
export class AppComponent { 

  demo = {
    'key1': 'ANGULAR 2',
    'key2': 'Pardeep',
    'key3': 'Jain',
  }
}


@Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
  transform(value, args:string[]) : any {
    let keys = [];
    for (let key in value) {
      keys.push({key: key, value: value[key]});
    }
    return keys;
  }
}

а HTML часть это:

<ul>
  <li *ngFor='#key of demo | keys'>
   Key: {{key.key}}, value: {{key.value}}
  </li>
</ul>

Рабочая Plnkr http://plnkr.co/edit/50LlK0k6OnMnkc2kNHM2?p=preview

обновить до RC

как предложено user6123723 (спасибо) в комментарии здесь обновление.

<ul>
  <li *ngFor='let key of demo | keys'>
   Key: {{key.key}}, value: {{key.value}}
  </li>
</ul>
Пардип Джейн
источник
Это должно быть обновлено: вот предупреждение, которое я получаю "#" внутри выражений не рекомендуется. Используйте вместо этого «пусть»! ("</ li> -> <ul * ngIf =" demo "> <li [ERROR ->] * ngFor = '# ключ демонстрации | keys'> Key: {{key.key}}, значение: { {key.value}} </ li> "): myComponent @ 56: 6
user6123723
Не уверен, что это ново, но процитировать из документов:> Мы должны включить наш канал в массив объявлений AppModule.
Akzidenzgrotesk
18

У @Marton было важное возражение против принятого ответа на том основании, что канал создает новую коллекцию при каждом обнаружении изменений. Вместо этого я бы создал HtmlService, который предоставляет ряд служебных функций, которые представление может использовать следующим образом:

@Component({
  selector: 'app-myview',
  template: `<div *ngFor="let i of html.keys(items)">{{i + ' : ' + items[i]}}</div>`
})
export class MyComponent {
  items = {keyOne: 'value 1', keyTwo: 'value 2', keyThree: 'value 3'};
  constructor(private html: HtmlService){}
}

@Injectable()
export class HtmlService {
  keys(object: {}) {
    return Object.keys(object);
  }
  // ... other useful methods not available inside html, like isObject(), isArray(), findInArray(), and others...
}
Стивен Пол
источник
2
и как это лучше, чем просто Object.keys(...)внутри * ngFor?
Simon_Weaver
8
Потому что это скинет TypeError: Cannot read property 'keys' of undefined. Кажется, он не поддерживается в шаблоне.
Стивен Пол
1
Это очень хорошо работает как решение и позволяет избежать проблем с производительностью, указанных выше. stackoverflow.com/questions/35534959/…
Дж. Адам Коннор
привет, можно ли это использовать не в templateопции, а в фактическом HTML-коде шаблона? спасибо
Скарамуш
16

Если вы уже используете Lodash, вы можете использовать этот простой подход, который включает в себя как ключ, так и значение:

<ul>
  <li *ngFor='let key of _.keys(demo)'>{{key}}: {{demo[key]}}</li>
</ul>

В файле машинописи включите:

import * as _ from 'lodash';

и в экспортируемый компонент, включают:

_: any = _;
Джереми Мориц
источник
извините, но нет необходимости использовать дополнительные библиотеки, такие как Lodash для таких вещей. В любом случае всегда приветствуются новые методы :)
Pardeep Jain
8

Спасибо за трубу, но мне пришлось внести некоторые изменения, прежде чем я смог использовать ее в угловых 2 RC5. Изменена строка импорта Pipe, а также добавлен тип any в инициализацию массива ключей.

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

 @Pipe({name: 'keys'})
 export class KeysPipe implements PipeTransform {
 transform(value) {
   let keys:any = [];
   for (let key in value) {
      keys.push( {key: key, value: value[key]} );
    }
     return keys;
  }
}
Адам Виннипасс
источник
да, импорт был изменен
Pardeep Jain
7

Ни один из ответов здесь не работал для меня из коробки, вот что сработало для меня:

Создать pipes/keys.tsс содержанием:

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

@Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform
{
    transform(value:any, args:string[]): any {
        let keys:any[] = [];
        for (let key in value) {
            keys.push({key: key, value: value[key]});
        }
        return keys;
    }
}

Добавить в app.module.ts(Ваш основной модуль):

import { KeysPipe } from './pipes/keys';

и затем добавьте в ваш массив объявлений модуля что-то вроде этого:

@NgModule({
    declarations: [
        KeysPipe
    ]
})
export class AppModule {}

Тогда в вашем шаблоне представления вы можете использовать что-то вроде этого:

<option *ngFor="let entry of (myData | keys)" value="{{ entry.key }}">{{ entry.value }}</option>

Вот хорошая ссылка, которую я нашел, если вы хотите узнать больше.

cjohansson
источник
Могу ли я узнать, в чем разница между вашим ответом и другим ответом (используя только трубу), представленным выше? похоже, что выше
Пардип Джейн
1
Конечно 1. В приведенных выше примерах используется * ngFor = "# entry" вместо * ngFor = "let entry of", и мой компилятор не принял синтаксис #entry, ссылка также не использует #. "let entry of (myData | keys)" кажется лучшим решением. 2. Мой компилятор также не проверял пример класса трубы, потому что в нем отсутствовали явные типы данных, поэтому я добавил это. 3. В приведенных выше примерах не показано, как интегрировать Pipe в проект, как это делает мой ответ, вам нужно импортировать его в основной модуль.
cjohansson
ха-ха, да, конечно, потому что, когда был дан ответ в то время, включая синтаксис #и т. д. Кстати, ваш ответ также верен, без сомнения
Pardeep Jain
6

Есть действительно хорошая библиотека, которая делает это среди других хороших каналов. Это называется ngx-pipe .

Например, pipe pipe возвращает ключи для объекта, а pipe values ​​возвращает значения для объекта:

ключи трубы

<div *ngFor="let key of {foo: 1, bar: 2} | keys">{{key}}</div> 
<!-- Output: 'foo' and 'bar -->

труба ценностей

<div *ngFor="let value of {foo: 1, bar: 2} | values">{{value}}</div>
<!-- Output: 1 and 2 -->

Не нужно создавать свои собственные трубы :)

RichieRock
источник
2
хорошая альтернатива, но главное - зачем использовать внешнюю библиотеку для простого кода, если мы можем сделать это с помощью простого куска кода, такого как pipe
Pardeep Jain
2
Ммм ... но это труба? Это всего лишь одна строка в вашем package.json и еще две строки в вашем модуле при импорте библиотеки. С другой стороны, для пользовательского канала требуется отдельный файл с 10-20 строками кода, а также строки импорта в вашем модуле. Мы находим использование ngx-pipe очень простым в наших проектах. Почему мы должны изобретать велосипед? :)
RichieRock
да, без сомнения, на самом деле это мнение основано, вы можете выбрать любой из этих двух, никто не ошибается.
Пардип Джейн
2
Не забывайте, что если вы пишете пользовательскую трубу, вы должны проверить , что пользовательские трубы , а также . Таким образом, это 10-20 строк кода канала, а затем, вероятно, 20-40 строк кода для тестирования канала.
Данвелман
4

Используйте индекс:

<div *ngFor="let value of Objects; index as key">

Применение:

{{key}} -> {{value}}
Адоний Васкес
источник
1
Это что-то новое для меня, лучше, если бы вы могли добавить пример вместе с вашим ответом :) Также можете ли вы указать мне любую документацию для того же?
Пардип Джайн
Какой тип объектов? Массив или Карта? Пожалуйста, сделайте это ясно. Заранее спасибо
Василий Мухаммед
3

Вот простое решение

Вы можете использовать итераторы машинописи для этого

import {Component} from 'angular2/core';
declare var Symbol;
@Component({
    selector: 'my-app',
    template:`<div>
    <h4>Iterating an Object using Typescript Symbol</h4><br>
Object is : <p>{{obj | json}}</p>
</div>
============================<br>
Iterated object params are:
<div *ngFor="#o of obj">
{{o}}
</div>

`
})
export class AppComponent {
  public obj: any = {
    "type1": ["A1", "A2", "A3","A4"],
    "type2": ["B1"],
    "type3": ["C1"],
    "type4": ["D1","D2"]
  };

  constructor() {
    this.obj[Symbol.iterator] =  () => {
          let i =0;

          return {
            next: () => {
              i++;
              return {
                  done: i > 4?true:false,
                  value: this.obj['type'+i]
              }
            }
          }
    };
  }
}

http://plnkr.co/edit/GpmX8g?p=info

Судхир КБ
источник
3

измените демонстрационный тип на массив или переберите свой объект и нажмите на другой массив

public details =[];   
Object.keys(demo).forEach(key => {
      this.details.push({"key":key,"value":demo[key]);
    });

и из HTML:

<div *ngFor="obj of details">
  <p>{{obj.key}}</p>
  <p>{{obj.value}}</p>
  <p></p>
</div>
Мохаммад Реза Мрг
источник
Это не подходящий метод, это может легко сделать любой.
Пардип Джейн
1

Я думаю, что Object.keys - лучшее решение этой проблемы. Для любого, кто сталкивается с этим ответом и пытается выяснить, почему Object.keys дает им ['0', '1'] вместо ['key1', 'key2'], предостерегающую историю - остерегайтесь разницы между " из "и" в ":

Я уже использовал Object.keys, что-то похожее на это:

interface demo {
    key: string;
    value: string;
}

createDemo(mydemo: any): Array<demo> {
    const tempdemo: Array<demo> = [];

    // Caution: use "of" and not "in"
    for (const key of Object.keys(mydemo)) {
        tempdemo.push(
            { key: key, value: mydemo[key]}
        );
    }

    return tempdemo;
}

Однако вместо

for (const key OF Object.keys(mydemo)) {

Я случайно написал

for (const key IN Object.keys(mydemo)) {

который "работал" совершенно нормально без каких-либо ошибок и вернулся

[{key: '0', value: undefined}, {key: '1', value: undefined}]

Это стоило мне около 2 часов, гуглил и ругался ..

(шлепает по лбу)

Кьяран Бруен
источник
1

Вы можете получить ключ динамического объекта с помощью этого

myObj['key']
Ризо
источник
0

Вы должны сделать это сейчас, я знаю, что это не очень эффективно, так как вы не хотите конвертировать объект, который вы получаете от Firebase.

    this.af.database.list('/data/' + this.base64Email).subscribe(years => {
        years.forEach(year => {

            var localYears = [];

            Object.keys(year).forEach(month => {
                localYears.push(year[month])
            });

            year.months = localYears;

        })

        this.years = years;

    });
kevinius
источник