Iterar sobre objeto en angular

130

Estoy tratando de hacer algunas cosas en Angular 2 Alpha 28, y tengo un problema con los diccionarios y NgFor.

Tengo una interfaz en TypeScript que se ve así:

interface Dictionary {
    [ index: string ]: string
}

En JavaScript, esto se traducirá en un objeto que con datos podría verse así:

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

Quiero repetir esto y probé esto:

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

Pero en vano, ninguno de los siguientes funcionó tampoco:

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

En todos los casos recibo errores como "token inesperado" o "No se puede encontrar el objeto de soporte de tubería 'iterableDiff'"

¿Que me estoy perdiendo aqui? ¿Ya no es posible? (La primera sintaxis funciona en Angular 1.x) o ¿es diferente la sintaxis para iterar sobre un objeto?

Rickard Staaf
fuente
¿Qué es un "diccionario"? Nunca he visto ni escuchado ese término en un contexto JavaScript, Angular o TypeScript. Y
Diccionario significa un mapa, creo, el término no se usa en absoluto en el contexto JS, pero sí en Python o Ruby.
Cesar Jr Rodriguez
2
Creo que la respuesta @bersling es ahora la respuesta correcta a esta pregunta.
Joshua Kissoon el
1
Por favor marque la respuesta correcta mejor. bersling es correcto
activedecay

Respuestas:

86

Parece que no quieren admitir la sintaxis de ng1.

Según Miško Hevery ( referencia ):

Los mapas no tienen órdenes en las claves y, por lo tanto, su iteración es impredecible. Esto se admitió en ng1, pero creemos que fue un error y no se admitirá en NG2

El plan es tener una tubería mapToIterable

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

Entonces, para iterar sobre su objeto, necesitará usar una "tubería". Actualmente no hay una tubería implementada que haga eso.

Como solución alternativa, aquí hay un pequeño ejemplo que itera sobre las claves:

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
}
Jesse Good
fuente
1
Estoy intentando lo mismo en un objeto con keyas numbery valuecomo stringpero angular está arrojando un error expression has changed after it was checked? ¿Por qué tan alguna idea?
Pardeep Jain
Sí, esto también me está pasando a mí. Y lo mismo si uso la solución de @ obsur también.
user2294382
1
Vea la respuesta de Bersling porque hay una solución trivial en el último angular 7
activedecay
156

Angular 6.1.0+ Respuesta

Use el keyvaluetubo incorporado de esta manera:

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

o así:

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

dónde mySortingFunctionestá en su .tsarchivo, por ejemplo:

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

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

No necesitará registrar esto en ningún módulo, ya que las tuberías angulares funcionan de fábrica en cualquier plantilla.

También funciona para Javascript-Maps .

bersling
fuente
Debe agregar implements PipeTransformla definición de clase (ver angular.io/guide/pipes#custom-pipes )
toioski
1
@toioski gracias, lo he agregado y actualizado a la nueva sintaxis del bucle for.
Bersling
Gran respuesta, usé esto para ngFor my Dictionary. Sin embargo, tuve que hacer keyValuePair.val [0] ya que mis valores terminaron [{}] y no {}
jhhoff02
1
¿Hay alguna ventaja en esto sobre solo return Object.keys(dict).map(key => ({key, val: dict[key]}))?
Justin Morgan
¡No veo ninguno, en realidad usaría tu camino!
Bersling
72

intenta usar esta 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>
oscurecer
fuente
55
Brillante, y si quiero mantener la referencia a la clave, solo asignaré un objeto con clave y valor. Desearía poder marcar varias respuestas como respuestas aceptadas, ya que esta es la solución a mi problema, mientras que la respuesta marcada es la respuesta a mi pregunta.
Rickard Staaf
1
@obscur: si hago lo anterior ahora, aparece el error "la expresión ha cambiado después de haber sido verificada" usando angular2.beta.0.0. ¿Alguna idea?
user2294382
Eso es porque puro: falso requiere una detección de cambio Estrategia para ser inyectado
Judson Terrell
1
¿Por qué configurarlo como impuro?
tom10271
Esto funcionó bien para mí. Lo único fue que no pude usar # en el ngFor. Usado let en su lugar.
MartinJH
19

Además de la respuesta de @ obscur, aquí hay un ejemplo de cómo puede acceder tanto a @View keycomo valuea @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;
        });
    }

}

Ver:

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

Entonces, si el objeto se viera así:

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

El resultado generado sería:

Nombre: Daario
Apellido: Naharis

Nombre: Victarion
Apellido: Greyjoy

Nombre: Quentyn
Apellido: Ball

SimonHawesome
fuente
2
Solo una cosa para mencionar que necesita cambiar la Vista: como <li *ngFor="let u of myObject | keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>. +1 de mi parte
sib10
El código dentro de su función de mapa podría simplificarse como: return { 'key' : key, 'value' : value[key] };
Makotosan
17

Actualizado: Angular ahora proporciona la tubería para recorrer el objeto json a través de keyvalue:

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

DEMO DE TRABAJO , y para más detalles Leer


Anteriormente (para la versión anterior): hasta ahora la respuesta mejor / más corta que encontré es (Sin ningún filtro de tubería o función personalizada desde el lado del componente)

Lado componente:

objectKeys = Object.keys;

Lado de la plantilla:

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

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

</div>

DEMO DE TRABAJO

Vivek Doshi
fuente
1
let item of myDict | keyvalueEsto resolvió mi problema.
Silambarasan RD
13

Agregando a la excelente respuesta de SimonHawesome . He creado una versión sucinta que utiliza algunas de las nuevas funciones de mecanografía. Me doy cuenta de que la versión de SimonHawesome es intencionalmente detallada para explicar los detalles subyacentes. También he agregado una verificación de salida anticipada para que la tubería funcione para valores falsos . Por ejemplo, si el mapa es null.

Tenga en cuenta que el uso de una transformación de iterador (como se hace aquí) puede ser más eficiente ya que no necesitamos asignar memoria para una matriz temporal (como se hizo en algunas de las otras respuestas).

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
fuente
3
¡Me encanta este hilo, con un comentario construyendo encima de otro! Estaba a punto de escribir lo mismo cuando vi su código
David
3
lo único en esta solución: debe implementarsePipeTransform
iRaS
@iRaS Buen punto. He actualizado mi respuesta. También devuelvo indefinido en lugar de nulo.
Frederik Aalund
9

Aquí hay una variación de algunas de las respuestas anteriores que admite múltiples transformaciones (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>
t.888
fuente
1
pure: falseEs realmente importante para reflexiones instantáneas.
Fırat KÜÇÜK
4

Tuve un problema similar, construí algo para objetos y mapas.

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
fuente
1
Esto funciona bien, excepto que en TypeScript debe agregar implements PipeTransforma la definición de clase
GeorgDangl
3

Angular 2.x && Angular 4.x no admite esto fuera de la caja

Puede usar estos dos tubos para iterar por clave o por valor .

Tubo de llaves:

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

Tubería de valores:

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

Cómo utilizar:

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

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

Si alguien se pregunta cómo trabajar con objetos multidimensionales, aquí está la solución.

supongamos que tenemos el siguiente objeto en servicio

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

en componente agregar siguiente función

challenges;

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

finalmente a la vista hacer lo siguiente

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

Me he estado arrancando el pelo al tratar de analizar y usar los datos devueltos de una consulta JSON / llamada de API. No estoy seguro exactamente de dónde estaba yendo mal, siento que he estado circulando la respuesta durante días, persiguiendo varios códigos de error como:

"No se puede encontrar el objeto de soporte de tubería 'iterableDiff'"

"La matriz de tipos genérica requiere uno o más argumentos"

Errores de análisis JSON, y estoy seguro de que otros

Supongo que acabo de tener la combinación incorrecta de correcciones.

Así que aquí hay un resumen de las trampas y cosas que debe buscar.

Primero verifique el resultado de sus llamadas a la API, sus resultados pueden ser en forma de un objeto, una matriz o una matriz de objetos.

No voy a profundizar demasiado, basta con decir que el error original del OP de no ser iterable generalmente es causado por tratar de iterar un objeto, no una matriz.

He aquí algunos de mis resultados de depuración que muestran variables de matrices y objetos.

Entonces, como generalmente nos gustaría iterar sobre nuestro resultado JSON, debemos asegurarnos de que tenga la forma de una matriz. Intenté numerosos ejemplos, y tal vez sabiendo lo que sé ahora, algunos de ellos funcionarían, pero el enfoque que utilicé fue implementar una tubería y el código que utilicé fue el publicado por 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;

Honestamente, creo que una de las cosas que me atrapó fue la falta de manejo de errores, al agregar la llamada 'devolver indefinido' creo que ahora estamos permitiendo que se envíen datos no esperados a la tubería, lo que obviamente estaba ocurriendo en mi caso .

si no desea lidiar con el argumento de la tubería (y mira, no creo que sea necesario en la mayoría de los casos) puede devolver lo siguiente

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

Algunas notas sobre cómo crear su tubería y página o componente que usa esa tubería

es que estaba recibiendo errores sobre 'nombre_de_mi_pipe' no encontrado

Use el comando 'iónico generar tubería' de la CLI para asegurarse de que los módulos de tubería.ts se creen y hagan referencia correctamente. asegúrese de agregar lo siguiente a la página mypage.module.ts.

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

(no estoy seguro si esto cambia si también tiene su propio custom_module, es posible que también deba agregarlo a custommodule.module.ts)

si usó el comando 'generar página iónica' para crear su página, pero decide usar esa página como su página principal, recuerde eliminar la referencia de la página de app.module.ts (aquí hay otra respuesta que publiqué sobre ese https: / /forum.ionicframework.com/t/solved-pipe-not-found-in-custom-component/95179/13?u=dreaser

En mi búsqueda de respuestas, hubo varias formas de mostrar los datos en el archivo html, y no entiendo lo suficiente como para explicar las diferencias. Puede que le resulte mejor usar uno sobre otro en ciertos escenarios.

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

Sin embargo, lo que funcionó que me permitió mostrar tanto el valor como la clave fue lo siguiente:

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

para hacer que la llamada a la API parezca que necesita importar HttpModule en app.module.ts

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

y necesita Http en la página desde la que realiza la llamada

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

Al hacer la llamada API, parece que puede acceder a los datos secundarios (los objetos o matrices dentro de la matriz) 2 formas diferentes, o parece funcionar

ya sea durante la llamada

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

o cuando asigna los datos a su variable local

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

(no estoy seguro si esa variable necesita ser una Cadena de matriz, pero eso es lo que tengo ahora. Puede funcionar como una variable más genérica)

Y nota final, no era necesario usar la biblioteca JSON incorporada, sin embargo, puede encontrar estas 2 llamadas útiles para convertir de un objeto a una cadena y 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);

Espero que esta compilación de información ayude a alguien.

Dreaser
fuente
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
fuente
1

En JavaScript, esto se traducirá en un objeto que con datos podría verse así

Las interfaces en TypeScript son una construcción de tiempo de desarrollo (solo para herramientas ... 0 impacto en tiempo de ejecución). Debe escribir el mismo TypeScript que su JavaScript.

basarat
fuente
eso no es cierto, sry. escribir script te obliga a escribir un código más limpio. Es mucho más fácil abstraer clases. que simplemente no tienes. C ++ se compila para algunos asm - asm tampoco tiene clases ni tipos, sin embargo, usted escribe diferentes C ++ y luego su código asm: P
YAMM
1

El diccionario es un objeto, no una matriz. Creo que ng-repeat requiere una matriz en Angular 2.

La solución más simple sería crear una tubería / filtro que convierta el objeto en una matriz sobre la marcha. Dicho esto, probablemente quieras usar una matriz como dice @basarat.

Martín
fuente
1

Si tiene es6-shimo su tsconfig.jsonobjetivo es6, puede usar ES6 Map para hacerlo.

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>
Canta
fuente
0

Definir MapValuesPipee implementar 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;
    }
}

Agregue su tubería en su módulo de tuberías. Esto es importante si necesita usar la misma tubería en más de un componente :

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

Luego, simplemente use la tubería en su html con *ngFor:

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

Recuerde, deberá declarar su PipesModule en el componente donde desea usar la tubería:

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

Entonces, iba a implementar mi propia función auxiliar, objLength (obj), que devuelve solo Object (obj) .keys.length. Pero luego, cuando lo estaba agregando a mi plantilla * ngIf, mi IDE sugirió objectKeys (). Lo intenté y funcionó. Siguiendo con su declaración, parece ser ofrecido por lib.es5.d.ts, ¡así que ahí lo tienes!

Así es como lo implementé (tengo un objeto personalizado que usa claves generadas del lado del servidor como índice para los archivos que he cargado):

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