klucz dostępu i wartość obiektu za pomocą * ngFor


426

Jestem trochę zdezorientowany, jak uzyskać keyi valueobiektu w angular2 podczas korzystania *ngForz iteracji po obiekcie. Wiem, że w wersji kątowej 1.x istnieje taka składnia

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

ale nie wiem jak to zrobić w angular2. Próbowałem czegoś podobnego, bez powodzenia:

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

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

Oto plnkr z moją próbą: http://plnkr.co/edit/mIj619FncOpfdwrR0KeG?p=preview

Jak mogę uzyskać key1i key2dynamicznie korzystać *ngFor? Po intensywnych poszukiwaniach wpadłem na pomysł użycia rur, ale nie wiem, jak sobie z tym poradzić. Czy jest jakaś wbudowana rura do robienia tego samego w angular2?


2
obecnie key, valuew angular2 nie ma żadnej pary składni ngFor, powinieneś spojrzeć na tę odpowiedź
Pankaj Parkar

@PankajParkar tak, już przeczytałem tę odpowiedź. jakiś alternatywny na razie?
Pardeep Jain

@ Pradeep Nie myślę o tym w żaden inny sposób, powinieneś zacząć tworzyć własne Pipedo tego ..
Pankaj Parkar

hmm, ale nie mam pojęcia, jak stworzyć fajkę dla tego samego.
Pardeep Jain

Odpowiedź @ Pradeep, którą dałem ci w celach informacyjnych, ma tę implementację. powinni pracować ...
Pankaj Parkar

Odpowiedzi:


398

Uzyskaj Object.keysdostęp do szablonu i użyj go w *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(){}
}


25
To lepsze i bardziej wydajne rozwiązanie
Aous1000

1
@tomtastico Jak pokazałbyś to dla tablicy 3D? Na przykład {„1”: {„1.1”: [„1.1.1”, „1.1.2”]}}. A potem zagnieżdżanie 3 ng Forów
Frank

@Frank właśnie powiedziałeś to sam. Nest the *ngFors. Pierwsze dwa przy użyciu objectKeys, najbardziej nie ma potrzeby (ponieważ to tylko tablica).
tomtastico

1
Niesamowite. Ustawienie objectKeys = Object.keys to najprostsza metoda, jaką widziałem, aby móc sprawdzić długość obiektu z HTML.
JAC

392

Podobnie jak w najnowszej wersji Angular (v6.1.0) , Angular Team dodał nową wbudowaną potok dla tej samej nazwy co keyvaluepotok, aby pomóc Ci iterować po obiektach, mapach i tablicach w commonmodule pakietu kątowego. Na przykład -

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

Przykład rozwidlonego działania

sprawdź tutaj, aby uzyskać więcej przydatnych informacji -

Jeśli używasz Angulara w wersji 5 lub starszej lub chcesz osiągnąć za pomocą potoku, zastosuj się do tej odpowiedzi


1
lol Musiałem zrobić aktualizację ng6 ​​tylko po to, aby uzyskać dostęp do tej fajki - świetne rzeczy - thx
danday74

36
Możesz zachować oryginalną kolejność kluczy za pomocą niestandardowego komparatora: *ngFor="let item of testObject | keyvalue:keepOriginalOrder" a w swojej klasie zdefiniuj: public keepOriginalOrder = (a, b) => a.key
mike-shtil

2
public keepOriginalOrder = (a, b) => a.key dużo za to
Kumaresan Perumal

1
taka powinna być odpowiedź - działająca dobrze na kątownikach 7
kalios

1
To niewiarygodne, że nie było tego od pierwszej wersji
Carlos Pinzón

227

Możesz utworzyć niestandardowy potok, aby zwrócić listę kluczy dla każdego elementu. Coś w tym stylu:

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

i użyj go w ten sposób:

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

Edytować

Możesz także zwrócić wpis zawierający zarówno klucz, jak i wartość:

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

i użyj go w ten sposób:

<span *ngFor="let entry of content | keys">           
  Key: {{entry.key}}, value: {{entry.value}}
</span>

1
zwróć uwagę na brakujący wspornik zamykający wkeys.push({key: key, value: value[key]);
Marton Pallagi,

49
Właściwie to zniechęcam kogokolwiek do używania potoków do tworzenia kolekcji wewnątrz *ngForwyrażeń. Stwarza ogromne wąskie gardło wydajności, ponieważ musi generować kolekcję za każdym razem, gdy wykrywacz zmian sprawdza zmiany.
martin

3
Dzięki za rozwiązanie ... problem polega na tym, że przy zmianie obiektu potok nie jest aktualizowany. Jeśli dodam pure:falsedo rury, staje się ona bardzo nieefektywna. Czy masz rozwiązanie, aby ręcznie zaktualizować potok za każdym razem, gdy zmieniam obiekt (usuwam element)?
ncohen

4
Odpowiedź jest nieco przestarzała. Wiersz * ngFor = "# entry content | keys" nie działa poprawnie, a pętlę for ... lepiej zmienić na "for (klucz klucza Object.keys (wartość))"
Experimenter

2
@RachChen Nie w szablonach: 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

Aktualizacja

W wersji 6.1.0-beta.1 wprowadzono KeyValuePipe https://github.com/angular/angular/pull/24319

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

Przykład plunkera

Poprzednia wersja

Innym podejściem jest stworzenie NgForIndyrektywy, która będzie stosowana w następujący sposób:

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

Przykład plunkera

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

43

Z Angular 6.1 możesz użyć potoku wartości klucza :

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

Ale ma tę niedogodność, że sortuje wynikową listę według wartości klucza. Jeśli potrzebujesz czegoś neutralnego:

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

Nie zapomnij podać atrybutu pure: false pipe. W takim przypadku potok jest wywoływany w każdym cyklu wykrywania zmian, nawet jeśli referencja wejściowa nie uległa zmianie (tak jest w przypadku dodawania właściwości do obiektu).


Udostępniłem już tę samą odpowiedź powyżej stackoverflow.com/a/51491848/5043867
Pardeep Jain

26

Opracowanie odpowiedzi @ Thierry z przykładem.

Nie ma wbudowanej rury ani metody pobierania key and valuez pętli * ngFor. więc musimy stworzyć niestandardową potok dla tego samego. jak powiedział Thierry, tutaj jest odpowiedź z kodem.

** Klasa pipe implementuje metodę transformacji interfejsu PipeTransform, która pobiera wartość wejściową i opcjonalną tablicę ciągów parametrów i zwraca wartość przekształconą.

** Metoda transformacji jest niezbędna dla rury. Interfejs PipeTransform definiuje tę metodę i prowadzi zarówno narzędzia, jak i kompilator. Jest opcjonalny; Angular szuka i wykonuje metodę transformacji niezależnie. więcej informacji na temat rury znajdziesz tutaj

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

a część HTML to:

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

Working Plnkr http://plnkr.co/edit/50LlK0k6OnMnkc2kNHM2?p=preview

aktualizacja do RC

jak sugeruje użytkownik6123723 (dzięki) w komentarzu tutaj jest aktualizacja.

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

To musi zostać zaktualizowane: Oto ostrzeżenie, które dostaję „#” w wyrażeniach, jest przestarzałe. Zamiast tego użyj „let”! ("</li> -> <ul * ngIf =" demo "> <li [ERROR ->] * ngFor = '# key of demo | keys'> Klucz: {{key.key}}, wartość: { {key.value}} </li> "): myComponent @ 56: 6
user6123723,

Nie jestem pewien, czy to jest nowe, ale cytowanie z dokumentów:> Musimy uwzględnić naszą potok w tablicy deklaracji AppModule.
Akzidenzgrotesk

18

@Marton miał istotny sprzeciw wobec zaakceptowanej odpowiedzi, uzasadniając to tym, że potok tworzy nową kolekcję przy każdym wykryciu zmiany. Zamiast tego utworzę HtmlService, który zapewnia szereg funkcji narzędziowych, z których widok może korzystać w następujący sposób:

@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
i jak to jest lepsze niż tylko Object.keys(...)w * ngFor?
Simon_Weaver,

8
Ponieważ będzie rzucać: TypeError: Cannot read property 'keys' of undefined. Wygląda na to, że nie jest obsługiwany w szablonie.
Stephen Paul,

1
Działa to bardzo dobrze jako rozwiązanie i pozwala uniknąć problemów z wydajnością wskazanych powyżej. stackoverflow.com/questions/35534959/…
J. Adam Connor

cześć, czy to b nie jest używane w templateopcji, ale w rzeczywistym kodzie HTML szablonu? dzięki
Scaramouche,

16

Jeśli już korzystasz z Lodash, możesz zastosować to proste podejście, które obejmuje zarówno klucz, jak i wartość:

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

W pliku maszynopisu dołącz:

import * as _ from 'lodash';

aw eksportowanym składniku uwzględnij:

_: any = _;

przepraszam, ale nie trzeba do tego używać dodatkowej biblioteki, takiej jak Lodash. w każdym razie nowe metody są zawsze mile widziane :)
Pardeep Jain

8

Dzięki za fajkę, ale musiałem wprowadzić pewne zmiany, zanim mogłem użyć jej w kanciastym 2 RC5. Zmieniono linię importu potoku, a także dodano typ dowolnej do inicjalizacji tablicy kluczy.

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

tak, import został zmieniony
Pardeep Jain

7

Żadna z odpowiedzi tutaj nie działała dla mnie od razu po wyjęciu z pudełka, oto, co zadziałało dla mnie:

Twórz pipes/keys.tsz zawartością:

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

Dodaj do app.module.ts(Twój główny moduł):

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

a następnie dodaj do tablicy deklaracji modułu coś takiego:

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

Następnie w szablonie widoku możesz użyć czegoś takiego:

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

Oto dobre referencje, które znalazłem, jeśli chcesz przeczytać więcej.


czy mogę wiedzieć, jaka jest różnica między twoją odpowiedzią a innymi odpowiedziami (używając tylko potoku) podanymi powyżej? wygląda tak samo jak powyżej
Pardeep Jain

1
Pewnie 1. Powyższe przykłady używają * ngFor = "# entry" zamiast * ngFor = "let entry of", a mój kompilator nie zaakceptował składni #entry, odwołanie również nie używa #. „let entry of (myData | keys)” wydaje się lepszym rozwiązaniem. 2. Mój kompilator również nie sprawdził poprawności przykładowej klasy rur, ponieważ brakowało w niej jawnych typów danych, więc to dodałem. 3. Powyższe przykłady nie pokazują, jak zintegrować Pipe z projektem, co robi moja odpowiedź, musisz zaimportować go do modułu głównego.
cjohansson

haha tak offcourese, ponieważ kiedy odpowiedź została podana w tym czasie, w tym składnia #itp. btw, twoja odpowiedź również jest poprawna bez wątpienia
Pardeep Jain

6

Jest naprawdę fajna biblioteka, która robi to wśród innych fajnych fajek. Nazywa się to ngx-pipe .

Na przykład klucz potok zwraca klucze do obiektu, a potok wartości zwraca wartość dla obiektu:

klucze do fajki

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

rura wartości

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

Nie musisz tworzyć własnej rury :)


2
dobra alternatywa, ale ważne jest, dlaczego używać biblioteki zewnętrznej dla prostego spokoju kodu, jeśli możemy to zrobić za pomocą prostego fragmentu kodu, takiego jak potok
Pardeep Jain

2
Umm ... ale to jest fajka? To tylko jeden wiersz w pakiecie.json i kolejne dwa wiersze w module podczas importowania biblioteki. Z drugiej strony niestandardowy potok potrzebuje osobnego pliku zawierającego około 10-20 linii kodu, a także linii importu w module. Używanie rur ngx jest bardzo łatwe w naszych projektach. Dlaczego powinniśmy wynaleźć koło na nowo? :)
RichieRock

tak, bez wątpienia, tak naprawdę opiera się na opinii, możesz wybrać między tymi dwoma, nikt nie jest w niewłaściwy sposób.
Pardeep Jain

2
Nie zapomnij, jeśli piszesz rury niestandardowej, należy przetestować niestandardową rurę jak dobrze . To 10-20 linii kodu potoku, a następnie prawdopodobnie 20-40 linii kodu testu do przetestowania potoku.
danwellman

4

Użyj indeksu:

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

Stosowanie:

{{key}} -> {{value}}

1
To dla mnie coś nowego, lepiej, jeśli mógłbyś dodać przykład wraz z odpowiedzią :) Czy możesz też wskazać mi jakąkolwiek dokumentację dotyczącą tego samego?
Pardeep Jain

Jaki jest typ obiektów? Tablica czy mapa? Proszę to wyjaśnić. Z góry dziękuję
Basil Mohammed

3

Oto proste rozwiązanie

W tym celu można użyć iteratorów maszynopisu

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

zmień typ prezentacji na tablicę lub iteruj po obiekcie i prześlij do innej tablicy

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

i z HTML:

<div *ngFor="obj of details">
  <p>{{obj.key}}</p>
  <p>{{obj.value}}</p>
  <p></p>
</div>

To nie jest odpowiednia metoda, każdy może to łatwo zrobić.
Pardeep Jain

1

Myślę, że Object.keys jest najlepszym rozwiązaniem tego problemu. Dla każdego, kto spotka się z tą odpowiedzią i spróbuje dowiedzieć się, dlaczego Object.keys daje im [„0”, „1”] zamiast [„key1”, „key2”], przestroga - strzeż się różnicy między „ z „i” w „:

Już korzystałem z Object.keys, coś podobnego do tego:

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

Jednak zamiast

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

Nieumyślnie napisałem

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

który „działał” idealnie dobrze bez żadnego błędu i zwrócił

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

Kosztowało mnie to około 2 godzin googlingu i przekleństw.

(klepie się w czoło)


1

możesz uzyskać klucz obiektu dynamicznego, próbując tego

myObj['key']

0

Na razie musisz to zrobić w ten sposób, wiem, że nie jest to zbyt wydajne, ponieważ nie chcesz konwertować obiektu otrzymanego z bazy ogniowej.

    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;

    });
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.