Iteruj po obiekcie w Angular

130

Próbuję zrobić kilka rzeczy w Angular 2 Alpha 28 i mam problem ze słownikami i NgFor.

Mam interfejs w TypeScript wyglądający tak:

interface Dictionary {
    [ index: string ]: string
}

W JavaScript przełoży się to na obiekt, który z danymi może wyglądać tak:

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

Chcę to powtórzyć i wypróbowałem to:

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

Ale bezskutecznie, żadne z poniższych też nie zadziałało:

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

We wszystkich przypadkach pojawiają się błędy, takie jak „Nieoczekiwany token” lub „Nie można znaleźć obiektu obsługującego potok„ iterableDiff ””

Czego tu brakuje? Czy to już nie jest możliwe? (Pierwsza składnia działa w Angular 1.x) czy składnia jest inna w przypadku iteracji po obiekcie?

Rickard Staaf
źródło
Co to jest „słownik”? Nigdy nie widziałem ani nie słyszałem tego terminu w kontekście JavaScript, Angular lub TypeScript. Y
Słownik oznacza mapę. Myślę, że termin ten nie jest używany w ogóle w kontekście JS, ale w Pythonie lub Ruby jest używany.
Cesar Jr Rodriguez
2
Myślę, że odpowiedź @bersling jest teraz poprawną odpowiedzią na to pytanie.
Joshua Kissoon
1
Proszę lepiej zaznaczyć poprawną odpowiedź. bersling jest poprawna
activedecay

Odpowiedzi:

87

Wygląda na to, że nie chcą obsługiwać składni z ng1.

Według Miško Hevery ( odniesienie ):

Mapy nie mają kolejności w kluczach, dlatego ich iteracja jest nieprzewidywalna. Było to obsługiwane w ng1, ale uważamy, że to był błąd i nie będzie obsługiwane w NG2

Plan zakłada utworzenie potoku mapToIterable

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

Aby więc iterować po obiekcie, będziesz musiał użyć „potoku”. Obecnie nie ma zaimplementowanego potoku, który to robi.

Aby obejść ten problem, oto mały przykład, który iteruje po klawiszach:

Składnik:

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
}
Jesse Good
źródło
1
próbuję to samo na obiekcie z keyjak numberi valuejak, stringale kątowy rzuca błąd expression has changed after it was checked? skąd taki pomysł?
Pardeep Jain
Tak, to się też dzieje ze mną. I to samo, jeśli użyję również rozwiązania @ obsur.
user2294382
1
Zapoznaj się z odpowiedzią firmy Bersling, ponieważ w najnowszym Angular 7 istnieje trywialne rozwiązanie
activedecay
156

Odpowiedź Angular 6.1.0+

Użyj wbudowanej keyvaluerury w następujący sposób:

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

lub tak:

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

gdzie mySortingFunctionjest w twoim .tspliku, na przykład:

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

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

Nie musisz tego rejestrować w żadnym module, ponieważ rury kątowe działają po wyjęciu z pudełka w każdym szablonie.

Działa również z mapami Javascript .

bersling
źródło
Powinieneś dodać implements PipeTransformw definicji klasy (patrz angular.io/guide/pipes#custom-pipes )
toioski
1
@toioski dzięki, dodałem go i zaktualizowałem do nowej składni pętli for.
bersling
Świetna odpowiedź, użyłem tego do ngFor my Dictionary. Musiałem jednak zrobić keyValuePair.val [0], ponieważ moje wartości zakończyły się [{}], a nie {}
jhhoff02
1
Czy jest to przewaga nad sprawiedliwą return Object.keys(dict).map(key => ({key, val: dict[key]}))?
Justin Morgan,
Nie widzę żadnych, właściwie użyłbym twojego sposobu!
bersling
72

spróbuj użyć tej rury

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
źródło
5
Świetnie, a jeśli chcę zachować odniesienie do klucza, po prostu zamapuję obiekt z kluczem i wartością. Chciałbym móc oznaczyć kilka odpowiedzi jako zaakceptowaną, ponieważ jest to rozwiązanie mojego problemu, podczas gdy zaznaczona odpowiedź jest odpowiedzią na moje pytanie.
Rickard Staaf
1
@obscur - Jeśli wykonam powyższe teraz, otrzymuję błąd „wyrażenie zmieniło się po sprawdzeniu” przy użyciu angular2.beta.0.0. jakieś pomysły?
user2294382
To dlatego, że pure: false wymaga wstrzyknięcia
strategii
1
Dlaczego ustawiasz go na nieczystość?
tom10271
To działało dobrze dla mnie. Jedyną rzeczą było to, że nie mogłem użyć # w ngFor. Zamiast tego używano let.
MartinJH,
19

Oprócz odpowiedzi @ obscur, oto przykład, w jaki sposób można uzyskać dostęp zarówno do, jak keyi valuez @View.

Rura:

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

}

Widok:

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

Gdyby więc obiekt miał wyglądać tak:

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

Wygenerowany wynik byłby:

Imię: Daario
Nazwisko: Naharis

Imię: Victarion
Nazwisko: Greyjoy

Imię: Quentyn
Nazwisko: Ball

Simon Wspaniale
źródło
2
tylko jedna rzecz, o której należy wspomnieć, musisz zmienić widok: jako <li *ngFor="let u of myObject | keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>. +1 ode mnie.
sib10
Kod wewnątrz funkcji mapy można uprościć jako: return { 'key' : key, 'value' : value[key] };
Makotosan
17

Zaktualizowano: Angular zapewnia teraz rurkę do przecinania obiektu json za pośrednictwem keyvalue:

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

PRACA DEMO i więcej szczegółów Przeczytaj


Wcześniej (dla starszej wersji): Do tej pory najlepszą / najkrótszą odpowiedzią, jaką znalazłem, jest (bez żadnego filtra rury lub funkcji niestandardowej po stronie komponentu)

Strona komponentu:

objectKeys = Object.keys;

Strona szablonu:

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

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

</div>

PRACA DEMO

Vivek Doshi
źródło
1
let item of myDict | keyvalueto rozwiązało mój problem.
Silambarasan RD
13

Dodając do doskonałej odpowiedzi SimonHawesome . Stworzyłem zwięzłą wersję, która wykorzystuje niektóre z nowych funkcji maszynopisu. Zdaję sobie sprawę, że wersja SimonHawesome jest celowo rozwlekła, aby wyjaśnić podstawowe szczegóły. Dodałem również wczesne sprawdzenie, aby potok działał dla fałszywych wartości. Np. Jeśli mapa jest null.

Zauważ, że użycie transformacji iteratora (tak jak tutaj) może być bardziej wydajne, ponieważ nie musimy przydzielać pamięci na tymczasową tablicę (jak to zrobiono w niektórych innych odpowiedziach).

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] }));
    }
}
Frederik Aalund
źródło
3
uwielbiam ten wątek, z jednym komentarzem tworzącym inny! Już miałem napisać to samo, kiedy zobaczyłem twój kod
David
3
jedyna rzecz w tym rozwiązaniu: powinno zaimplementowaćPipeTransform
iRaS
@iRaS Słuszna uwaga. Zaktualizowałem moją odpowiedź. Zwracam również undefined zamiast null.
Frederik Aalund
9

Oto odmiana niektórych z powyższych odpowiedzi, która obsługuje wiele przekształceń (wartość klucza, klucz, wartość):

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

Stosowanie

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
źródło
1
pure: falsejest naprawdę ważny dla natychmiastowych refleksji.
Fırat KÜÇÜK
4

Miałem podobny problem, zbudowałem coś dla obiektów i map.

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
źródło
1
Działa to dobrze, z wyjątkiem tego, że w TypeScript należy dodać implements PipeTransformdo definicji klasy
GeorgDangl
3

Angular 2.x i Angular 4.x nie obsługują tego po wyjęciu z pudełka

Możesz użyć tych dwóch potoków do iteracji według klucza lub wartości .

Klucze rurowe:

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

Rura wartości:

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

Jak używać:

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

<div *ngFor="let key of data | keys"></div>
<div *ngFor="let value of data | values"></div>
MK
źródło
2

Jeśli ktoś się zastanawia, jak pracować z wielowymiarowym obiektem, oto rozwiązanie.

załóżmy, że mamy następujący obiekt w serwisie

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

w komponencie dodaj następującą funkcję

challenges;

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

wreszcie w celu wykonania

<div *ngFor="#key of keys();">
    <h4 class="heading">{{challenges[key].title}}</h4>
    <p class="description">{{challenges[key].description}}</p>
</div>
Chyngyz Sydykov
źródło
2

Wyrywałem sobie włosy, próbując przeanalizować i wykorzystać dane zwrócone z zapytania JSON / wywołania API. Nie jestem pewien, dokąd poszedłem źle, czuję, że krążyłem odpowiedź od dni, ścigając różne kody błędów, takie jak:

„Nie można znaleźć obiektu obsługującego potok„ iterableDiff ””

„Ogólna tablica typu wymaga jednego argumentu (ów)”

Błędy analizy JSON i jestem pewien, że inni

Zakładam, że po prostu miałem niewłaściwą kombinację poprawek.

Oto krótkie podsumowanie problemów i rzeczy, na które należy zwrócić uwagę.

Najpierw sprawdź wynik wywołań interfejsu API, wyniki mogą mieć postać obiektu, tablicy lub tablicy obiektów.

nie będę się w to zbytnio zagłębiał, wystarczy powiedzieć, że pierwotny błąd OP, że nie jest iterowalny, jest generalnie spowodowany próbą iteracji obiektu, a nie tablicy.

Oto niektóre z moich wyników debugowania pokazujących zmienne zarówno tablic, jak i obiektów

Ponieważ generalnie chcielibyśmy iterować nasz wynik JSON, musimy upewnić się, że ma on postać tablicy. Wypróbowałem wiele przykładów i być może wiedząc, co teraz wiem, niektóre z nich faktycznie zadziałałyby, ale podejście, które zastosowałem, polegało na zaimplementowaniu potoku, a kod, którego użyłem, był taki, że opublikowany przez 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;

Szczerze mówiąc, myślę, że jedną z rzeczy, które mnie dopadły, był brak obsługi błędów, dodając wywołanie `` return undefined ''. Myślę, że teraz pozwalamy na wysyłanie niespodziewanych danych do potoku, co oczywiście miało miejsce w moim przypadku .

jeśli nie chcesz zajmować się argumentem do potoku (i nie sądzę, że jest to konieczne w większości przypadków), możesz po prostu zwrócić następujące polecenie

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

Kilka uwag na temat tworzenia rury i strony lub komponentu używającego tej rury

czy otrzymywałem błędy dotyczące nie znalezienia „name_of_my_pipe”

Użyj polecenia „ionic generowanie potoku” w interfejsie wiersza polecenia, aby upewnić się, że moduły potokowe. upewnij się, że dodałeś następujące elementy do strony mypage.module.ts.

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

(nie jestem pewien, czy to się zmieni, jeśli masz również własny moduł niestandardowy, może być konieczne dodanie go do custommodule.module.ts)

jeśli użyłeś polecenia „jonowa generuj stronę”, aby utworzyć swoją stronę, ale zdecydujesz się użyć tej strony jako strony głównej, pamiętaj, aby usunąć odwołanie do strony z app.module.ts (oto kolejna odpowiedź, którą opublikowałem, dotyczącą tego https: / /forum.ionicframework.com/t/solved-pipe-not-found-in-custom-component/95179/13?u=dreaser

W moich poszukiwaniach odpowiedzi, gdzie jest wiele sposobów wyświetlania danych w pliku html i nie rozumiem wystarczająco, aby wyjaśnić różnice. W niektórych scenariuszach może się okazać, że lepiej jest używać jednego nad drugim.

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

Jednak to, co zadziałało, pozwoliło mi wyświetlić zarówno wartość, jak i klucz, było następujące:

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

aby wywołać API, wygląda na to, że musisz zaimportować HttpModule do app.module.ts

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

i potrzebujesz Http na stronie, z której dzwonisz

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

Podczas wykonywania wywołania API wydaje się, że jesteś w stanie dostać się do danych podrzędnych (obiektów lub tablic w tablicy) na 2 różne sposoby, które wydają się działać

albo podczas rozmowy

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

lub kiedy przypisujesz dane do zmiennej lokalnej

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

(nie jestem pewien, czy ta zmienna musi być ciągiem tablicy, ale to jest to, co mam teraz. Może działać jako zmienna bardziej ogólna)

I ostatnia uwaga, nie było konieczne korzystanie z wbudowanej biblioteki JSON, jednak te 2 wywołania mogą być przydatne do konwersji z obiektu na ciąg i odwrotnie

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

Mam nadzieję, że ta kompilacja informacji komuś pomoże.

Dreaser
źródło
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>
Rohit Jangid
źródło
1

W JavaScript przełoży się to na obiekt, który z danymi może wyglądać tak

Interfejsy w języku TypeScript są konstrukcjami deweloperskimi (wyłącznie dla narzędzi ... 0 wpływu na środowisko wykonawcze). Powinieneś napisać ten sam TypeScript, co JavaScript.

basarat
źródło
to nieprawda, sry. skrypt typu zmusza cię do pisania czystszego kodu. o wiele łatwiej jest abstrakcyjne klasy. których po prostu nie masz. C ++ kompiluje się do jakiegoś asm - asm również nie ma klas ani nawet typów, niemniej jednak piszesz inny kod c ++ niż ur asm: P
YAMM
1

Słownik jest obiektem, a nie tablicą. Uważam, że ng-repeat wymaga tablicy w Angular 2.

Najprostszym rozwiązaniem byłoby utworzenie potoku / filtra, który przekształca obiekt w tablicę w locie. To powiedziawszy, prawdopodobnie chcesz użyć tablicy, jak mówi @basarat.

Jaskółka oknówka
źródło
1

Jeśli masz es6-shimlub twój tsconfig.jsoncel es6, możesz użyć mapy ES6, aby to zrobić.

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>
Śpiewać
źródło
0

Zdefiniuj MapValuesPipei zaimplementuj 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;
    }
}

Dodaj rurę do modułu rur. Jest to ważne, jeśli musisz użyć tej samej rury w więcej niż jednym komponencie :

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

Następnie po prostu użyj potoku w swoim html z *ngFor:

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

Pamiętaj, że będziesz musiał zadeklarować swój PipesModule w komponencie, w którym chcesz użyć potoku:

@NgModule({
  imports: [
    CommonModule,
    PipesAggrModule
  ],
...
}
export class MyModule {}
Menelaos Kotsollaris
źródło
0

Zamierzałem więc zaimplementować własną funkcję pomocniczą, objLength (obj), która zwraca po prostu Object (obj) .keys.length. Ale kiedy dodawałem go do funkcji szablonu * ngIf, moje IDE zasugerowało obiektKeys (). Spróbowałem i zadziałało. Zgodnie z deklaracją wydaje się, że jest oferowany przez lib.es5.d.ts, więc proszę bardzo!

Oto jak to zaimplementowałem (mam niestandardowy obiekt, który używa kluczy wygenerowanych po stronie serwera jako indeksu dla przesłanych plików):

        <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>
mauzole
źródło