Scorrere sull'oggetto in Angolare


130

Sto cercando di fare alcune cose in Angular 2 Alpha 28 e sto riscontrando un problema con dizionari e NgFor.

Ho un'interfaccia in TypeScript simile a questa:

interface Dictionary {
    [ index: string ]: string
}

In JavaScript questo si tradurrà in un oggetto che con i dati potrebbe apparire così:

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

Voglio iterare su questo e ho provato questo:

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

Ma invano, nessuno dei seguenti ha funzionato neanche:

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

In tutti i casi ricevo errori come "Token imprevisto" o "Impossibile trovare l'oggetto di supporto della pipe 'iterableDiff'"

Cosa mi sto perdendo qui? Non è più possibile? (La prima sintassi funziona in Angular 1.x) o la sintassi è diversa per iterare su un oggetto?


Che cos'è un "dizionario"? Non ho mai visto o sentito quel termine in un contesto JavaScript, angolare o TypeScript. Y

Dizionario significa una mappa penso, il termine non è affatto usato nel contesto JS ma in Python o Ruby lo fa.
Cesar Jr Rodriguez,

2
Penso che la risposta di @bersling sia ora la risposta corretta a questa domanda.
Joshua Kissoon,

1
Si prega di contrassegnare meglio la risposta corretta. il bersling è corretto
activedecay

Risposte:


86

Sembra che non vogliano supportare la sintassi di ng1.

Secondo Miško Hevery ( riferimento ):

Le mappe non hanno ordini in chiavi e quindi l'iterazione è imprevedibile. Questo è stato supportato in ng1, ma pensiamo che sia stato un errore e non sarà supportato in NG2

Il piano è di avere una pipe mapToIterable

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

Quindi, per iterare sul tuo oggetto dovrai usare una "pipe". Attualmente non esiste alcuna pipe implementata che lo faccia.

Per ovviare al problema, ecco un piccolo esempio che scorre le chiavi:

Componente:

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
sto provando lo stesso sull'oggetto con keyas numbere valueas stringma angolare sta gettando errore expression has changed after it was checked? perché così qualche idea?
Pardeep Jain,

Sì, sta succedendo anche per me. E lo stesso se uso anche la soluzione di @ obsur.
user2294382

1
Si prega di vedere la risposta di bersling perché esiste una soluzione banale sull'ultimo 7 angolare
activedecay

156

Risposta angolare 6.1.0+

Usa il keyvalue-pipe integrato in questo modo:

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

o in questo modo:

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

dove si mySortingFunctiontrova il tuo .tsfile, ad esempio:

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

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

Non sarà necessario registrarlo in nessun modulo, poiché i tubi angolari funzionano immediatamente in qualsiasi modello.

Funziona anche con Javascript-Maps .


Devi aggiungere implements PipeTransformla definizione della classe (vedi angular.io/guide/pipes#custom-pipes )
toioski

1
@toioski grazie, l'ho aggiunto e aggiornato alla nuova sintassi del ciclo for.
bersling

Ottima risposta, usata per ngPer il mio dizionario. Ho dovuto fare keyValuePair.val [0] anche se i miei valori sono finiti [{}] e non {}
jhhoff02

1
C'è un vantaggio rispetto a questo return Object.keys(dict).map(key => ({key, val: dict[key]}))?
Justin Morgan,

Non ne vedo nessuno, in realtà userei la tua strada!
bersling

72

prova a usare questa pipa

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
Fantastico, e se voglio mantenere il riferimento alla chiave, mapperò invece un oggetto con chiave e valore. Vorrei poter contrassegnare diverse risposte come risposta accettata, poiché questa è la soluzione al mio problema mentre la risposta contrassegnata è la risposta alla mia domanda.
Rickard Staaf,

1
@obscur - Se faccio quanto sopra adesso, ricevo un errore "espressione è cambiata dopo che è stata controllata" usando angular2.beta.0.0. qualche idea?
user2294382

Questo perché puro: falso richiede un rilevamento del cambiamento. La strategia deve essere iniettata
Judson Terrell,

1
Perché impostarlo su impuro?
tom10271,

Questo ha funzionato bene per me. L'unica cosa era che non potevo usare # in ngFor. Usato invece.
MartinJH,

19

Oltre alla risposta di @ obscur, ecco un esempio di come è possibile accedere sia a keyche valueda @View.

Tubo:

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

}

Visualizza:

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

Quindi se l'oggetto dovesse apparire come:

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

Il risultato generato sarebbe:

Nome: Daario
Cognome: Naharis

Nome: Victarion
Cognome: Greyjoy

Nome: Quentyn
Cognome: Ball


2
solo una cosa da menzionare è necessario modificare la vista: come <li *ngFor="let u of myObject | keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>. +1 da me.
sib10,

Il codice all'interno della funzione della mappa potrebbe essere semplificato come: return { 'key' : key, 'value' : value[key] };
Makotosan

17

Aggiornato: Angular sta ora fornendo la pipe per scavare attraverso l'oggetto json tramite keyvalue:

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

DEMO DI LAVORO , e per maggiori dettagli Leggi


Precedentemente (per la versione precedente): fino ad ora la risposta migliore / più breve che ho trovato è (senza alcun filtro del tubo o funzione personalizzata dal lato componente)

Lato componente:

objectKeys = Object.keys;

Lato modello:

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

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

</div>

DEMO DI LAVORO


1
let item of myDict | keyvaluequesto ha risolto il mio problema.
Silambarasan RD

13

Aggiungendo alla risposta eccellente di SimonHawesome . Ho realizzato una versione sintetica che utilizza alcune delle nuove funzionalità dattiloscritte. Mi rendo conto che la versione di SimonHawesome è intenzionalmente dettagliata per spiegare i dettagli sottostanti. Ho anche aggiunto un controllo anticipato in modo che la pipe funzioni per valori falsi . Ad esempio, se la mappa è null.

Si noti che l'utilizzo di una trasformazione iteratore (come fatto qui) può essere più efficiente poiché non è necessario allocare memoria per un array temporaneo (come fatto in alcune delle altre risposte).

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
Adoro questa discussione, con un commento che si basa su un altro! Stavo per scrivere la stessa cosa quando ho visto il tuo codice
David

3
l'unica cosa in questa soluzione: dovrebbe implementarePipeTransform
iRaS il

@iRaS Un buon punto. Ho aggiornato la mia risposta. Restituisco anche indefinito anziché null.
Frederik Aalund,

9

Ecco una variazione di alcune delle risposte sopra che supporta trasformazioni multiple (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;
  }
}

uso

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è davvero importante per le riflessioni istantanee.
Fırat KÜÇÜK,

4

Ho avuto un problema simile, ho creato qualcosa per oggetti e mappe.

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
Funziona bene, tranne per il fatto che in TypeScript è necessario aggiungere implements PipeTransformalla definizione della classe
GeorgDangl

3

Angular 2.x && Angular 4.x non supporta questo immediatamente

È possibile utilizzare queste due pipe per iterare per chiave o per valore .

Tubo chiavi:

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

Valori pipe:

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

Come usare:

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

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

2

Se qualcuno si chiede come lavorare con un oggetto multidimensionale, ecco la soluzione.

supponiamo di avere il seguente oggetto in servizio

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

nel componente aggiungere la seguente funzione

challenges;

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

finalmente in vista fai quanto segue

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

2

Mi sono strappato i capelli cercando di analizzare e utilizzare i dati restituiti da una chiamata JSON query / api. Non sono sicuro di dove stavo sbagliando, mi sento come se stessi cercando la risposta da giorni, inseguendo vari codici di errore come:

"Impossibile trovare l'oggetto di supporto pipe 'iterableDiff'"

"L'array TYPE generico richiede un argomento (i)"

JSON analizza gli errori e ne sono sicuro altri

Immagino di aver appena avuto la combinazione sbagliata di correzioni.

Quindi ecco un piccolo riassunto di gotcha e cose da cercare.

Innanzitutto controlla il risultato delle tue chiamate api, i risultati potrebbero essere sotto forma di un oggetto, un array o un array di oggetti.

non ci entrerò troppo, basti dire che l'errore originale dell'OP di non essere iterabile è generalmente causato dal tentativo di iterare un oggetto, non una matrice.

Ecco alcuni dei miei risultati di debug che mostrano variabili di matrici e oggetti

Pertanto, poiché in genere vorremmo ripetere il nostro risultato JSON, dobbiamo assicurarci che sia sotto forma di array. Ho provato numerosi esempi, e forse sapendo quello che so ora alcuni di questi avrebbero effettivamente funzionato, ma l'approccio che ho seguito è stato davvero quello di implementare una pipe e il codice che ho usato è stato quello pubblicato da 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;

Sinceramente penso che una delle cose che mi ha procurato sia stata la mancanza di gestione degli errori, aggiungendo la chiamata 'return indefined' credo che ora stiamo permettendo l'invio di dati non previsti alla pipe, che ovviamente si stavano verificando nel mio caso .

se non vuoi trattare l'argomento con la pipe (e guarda non penso che sia necessario nella maggior parte dei casi) puoi semplicemente restituire quanto segue

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

Alcune note sulla creazione della pipe e della pagina o del componente che utilizza quella pipe

sto ricevendo errori su "name_of_my_pipe" non trovato?

Utilizzare il comando 'ionic generate pipe' dalla CLI per assicurarsi che i moduli pipe.ts vengano creati e referenziati correttamente. assicurati di aggiungere quanto segue alla pagina mypage.module.ts.

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

(non sono sicuro che questo cambi se hai anche il tuo custom_module, potresti anche doverlo aggiungere a custommodule.module.ts)

se hai usato il comando 'ionic generate page' per creare la tua pagina, ma decidi di usare quella pagina come tua pagina principale, ricordati di rimuovere il riferimento della pagina da app.module.ts (ecco un'altra risposta che ho postato trattando quella https: / /forum.ionicframework.com/t/solved-pipe-not-found-in-custom-component/95179/13?u=dreaser

Nella mia ricerca di risposte là dove un certo numero di modi per visualizzare i dati nel file html, e non capisco abbastanza per spiegare le differenze. Potrebbe essere meglio usarne uno sopra l'altro in determinati scenari.

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

Tuttavia ciò che ha funzionato che mi ha permesso di visualizzare sia il valore che la chiave è stato il seguente:

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

per chiamare l'API sembra che sia necessario importare HttpModule in app.module.ts

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

e hai bisogno di Http nella pagina da cui effettui la chiamata

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

Quando si effettua la chiamata API, sembra che si riesca a ottenere i dati secondari (gli oggetti o le matrici all'interno dell'array) in 2 modi diversi, entrambi sembrano funzionare

sia durante la chiamata

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

o quando assegni i dati alla tua variabile locale

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

(non sono sicuro se quella variabile debba essere una stringa di array, ma questo è quello che ho adesso. Potrebbe funzionare come una variabile più generica)

E nota finale, non è stato necessario utilizzare la libreria JSON integrata, tuttavia è possibile trovare utili queste 2 chiamate per la conversione da un oggetto a una stringa e viceversa

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

Spero che questa raccolta di informazioni aiuti qualcuno.


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

In JavaScript questo si tradurrà in un oggetto che con i dati potrebbe apparire così

Le interfacce in TypeScript sono un costrutto del tempo di sviluppo (puramente per gli strumenti ... 0 impatto di runtime). Dovresti scrivere lo stesso TypeScript del tuo JavaScript.


non è vero, sry. type script ti costringe a scrivere codice più pulito. è molto più facile astrarre le classi. che semplicemente non hai. Il C ++ si compila in alcuni asm - asm non ha nemmeno classi o tipi, tuttavia scrivi c ++ diverso quindi il tuo codice asm: P
YAMM

1

Il dizionario è un oggetto, non un array. Credo che ng-repeat richieda un array in Angular 2.

La soluzione più semplice sarebbe quella di creare un pipe / filtro che converta l'oggetto al volo in un array. Detto questo, probabilmente vorrai usare un array come dice @basarat.


1

Se hai es6-shimo il tuo tsconfig.jsonobiettivo es6, puoi usare ES6 Map per realizzarlo.

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

Definire MapValuesPipee implementare 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;
    }
}

Aggiungi la tua pipe nel modulo pipe. Ciò è importante se è necessario utilizzare la stessa pipe in più di un componente :

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

Quindi usa semplicemente la pipe nel tuo html con *ngFor:

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

Ricorda, dovrai dichiarare il tuo PipesModule nel componente in cui vuoi usare la pipe:

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

0

Quindi avrei implementato la mia funzione di aiuto, objLength (obj), che restituisce solo Object (obj) .keys.length. Ma quando l'ho aggiunto alla mia funzione template * ngIf, il mio IDE mi ha suggerito objectKeys (). L'ho provato e ha funzionato. A seguito della sua dichiarazione, sembra essere offerto da lib.es5.d.ts, quindi eccovi!

Ecco come l'ho implementato (ho un oggetto personalizzato che utilizza chiavi generate sul lato server come indice per i file che ho caricato):

        <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>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.