clave de acceso y valor del objeto usando * ngFor

426

Estoy un poco confundido acerca de cómo obtener el keyy valuede un objeto en angular2 mientras lo uso *ngForpara iterar sobre el objeto. Sé que en angular 1.x hay una sintaxis como

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

pero no sé cómo hacer lo mismo en angular2. He intentado algo similar, sin éxito:

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

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

Aquí hay un plnkr con mi intento: http://plnkr.co/edit/mIj619FncOpfdwrR0KeG?p=preview

¿Cómo puedo obtener key1y key2usar dinámicamente *ngFor? Después de buscar mucho, encontré la idea de usar tuberías, pero no sé cómo hacerlo. ¿Hay alguna tubería incorporada para hacer lo mismo en angular2?

Pardeep Jain
fuente
2
actualmente no hay un key, valuetipo de sintaxis de par de soporte en angular2 ngFor, debería ver esta respuesta
Pankaj Parkar
@PankajParkar sí, ya leí esta respuesta. alguna alternativa por ahora?
Pardeep Jain
@Pradeep No pienso en ninguna otra forma para esto ahora, deberías ir a crear el Pipe
tuyo
hmm pero no tengo idea de cómo crear tubería para lo mismo.
Pardeep Jain
La respuesta de @Pradeep que le di como referencia tiene esa implementación. deberían trabajar ..
Pankaj Parkar

Respuestas:

399

Tener Object.keysaccesible en la plantilla y usarla en *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
fuente
1
@PardeepJain es un método ES5 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
tomtastico
25
Esta es una solución mejor y más eficiente
Aous1000
1
@tomtastico ¿Cómo mostrarías esto para una matriz 3D? Por ejemplo {"1": {"1.1": ["1.1.1", "1.1.2"]}}. Y luego anidando 3 ngFor's
Frank
@ Frank, acabas de decirlo tú mismo. Anida el *ngFors. Primeros dos usando objectKeys, lo más interno no es necesario (ya que es solo una matriz).
tomtastico
1
Increíble. Establecer objectKeys = Object.keys es el método más simple que he visto para poder verificar la longitud de un objeto desde el HTML.
JAC
394

Al igual que en la última versión de Angular (v6.1.0) , Angular Team ha agregado una nueva tubería integrada para el mismo nombre que la keyvaluetubería para ayudarlo a recorrer objetos, mapas y matrices en el commonmódulo del paquete angular. Por ejemplo -

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

Ejemplo de trabajo bifurcado

échale un vistazo aquí para obtener más información útil:

Si está utilizando Angular v5 o inferior o desea lograr el uso de tubería, siga esta respuesta

Pardeep Jain
fuente
1
lol tuve que hacer una actualización ng6 solo para acceder a esta tubería - cosas geniales - gracias
danday74
36
Puede mantener el orden de las claves original utilizando un comparador personalizado: *ngFor="let item of testObject | keyvalue:keepOriginalOrder" y en su clase defina: public keepOriginalOrder = (a, b) => a.key
mike-shtil
2
public keepOriginalOrder = (a, b) => a.key thx mucho por esto
Kumaresan Perumal
1
esta debería ser la respuesta - trabajando bien en angular 7
calios
1
Increíble, esto no estaba disponible desde la primera versión
Carlos Pinzón
227

Puede crear una tubería personalizada para devolver la lista de claves para cada elemento. Algo como eso:

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

y úsalo así:

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

Editar

También puede devolver una entrada que contenga clave y valor:

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

y úsalo así:

<span *ngFor="let entry of content | keys">           
  Key: {{entry.key}}, value: {{entry.value}}
</span>
Thierry Templier
fuente
1
tenga en cuenta el soporte de cierre que falta enkeys.push({key: key, value: value[key]);
Marton Pallagi
49
De hecho, desaliento a cualquiera de usar tuberías para crear colecciones dentro de la *ngForexpresión. Crea un enorme cuello de botella de rendimiento porque necesita generar la colección cada vez que el detector de cambios comprueba los cambios.
Martin
3
Gracias por la solución ... el problema es que cada vez que el objeto cambia, la tubería no se actualiza. Si agrego pure:falsea la tubería, se vuelve muy ineficiente. ¿Tiene una solución para actualizar la tubería manualmente cada vez que cambio el objeto (eliminar elemento)?
ncohen
44
La respuesta está un poco desactualizada. La línea * ngFor = "# entrada de contenido | claves" no funciona correctamente y el for ... en bucle mejor para cambiar a "for (const key of Object.keys (value))"
Experimenter
2
@RachChen No en plantillas: 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

Actualizar

En 6.1.0- beta.1 se introdujo KeyValuePipe https://github.com/angular/angular/pull/24319

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

Ejemplo de Plunker

Versión previa

Otro enfoque es crear NgForIndirectivas que se utilizarán como:

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

Ejemplo de Plunker

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

Desde Angular 6.1 puede usar la tubería keyvalue :

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

Pero tiene el inconveniente de que ordena la lista resultante por el valor clave. Si necesitas algo neutral:

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

No olvide especificar el atributo pure: false pipe. En este caso, la tubería se invoca en cada ciclo de detección de cambio, incluso si la referencia de entrada no ha cambiado (así es el caso cuando agrega propiedades a un objeto).

Gerard Carbó
fuente
Ya compartí la misma respuesta anterior stackoverflow.com/a/51491848/5043867
Pardeep Jain
26

Elaboración de la respuesta de @ Thierry con ejemplo.

No hay una tubería o método incorporado para obtener key and valuedel bucle * ngFor. así que tenemos que crear una tubería personalizada para lo mismo. Como dijo Thierry, aquí está la respuesta con el código.

** La clase pipe implementa el método de transformación de la interfaz PipeTransform que toma un valor de entrada y una matriz opcional de cadenas de parámetros y devuelve el valor transformado.

** El método de transformación es esencial para una tubería. La interfaz PipeTransform define ese método y guía tanto las herramientas como el compilador. Es opcional; Angular busca y ejecuta el método de transformación independientemente. para más información sobre tuberías consulte aquí

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

y la parte HTML es:

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

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

actualizar a RC

según lo sugerido por user6123723 (gracias) en el comentario aquí está la actualización.

<ul>
  <li *ngFor='let key of demo | keys'>
   Key: {{key.key}}, value: {{key.value}}
  </li>
</ul>
Pardeep Jain
fuente
Esto debe actualizarse: aquí está la advertencia que obtengo "#" dentro de las expresiones que está en desuso. Utilice "let" en su lugar! ("</li> -> <ul * ngIf =" demo "> <li [ERROR ->] * ngFor = '# key of demo | keys'> Key: {{key.key}}, value: { {key.value}} </li> "): myComponent @ 56: 6
user6123723
No estoy seguro de si esto es nuevo, pero para citar los documentos:> Debemos incluir nuestra tubería en la matriz de declaraciones del AppModule.
Akzidenzgrotesk
18

@ Martin tuvo una importante objeción a la respuesta aceptada debido a que la tubería crea una nueva colección en cada detección de cambio. En su lugar, crearía un HtmlService que proporciona una gama de funciones de utilidad que la vista puede usar de la siguiente manera:

@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...
}
Stephen Paul
fuente
2
¿Y cómo es eso mejor que solo Object.keys(...)dentro de * ngFor?
Simon_Weaver
8
Porque va a lanzar: TypeError: Cannot read property 'keys' of undefined. No parece ser compatible con la plantilla.
Stephen Paul
1
Esto funciona muy bien como una solución y evita los problemas de rendimiento mencionados anteriormente. stackoverflow.com/questions/35534959/…
J. Adam Connor
hola, ¿se puede usar esta b no en la templateopción, sino en el código html real de la plantilla? gracias
Scaramouche
16

Si ya está usando Lodash, puede hacer este enfoque simple que incluye clave y valor:

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

En el archivo mecanografiado, incluya:

import * as _ from 'lodash';

y en el componente exportado, incluya:

_: any = _;
Jeremy Moritz
fuente
lo siento, pero no es necesario usar una biblioteca adicional como Lodash para tales cosas. de todos modos los nuevos métodos son siempre bienvenidos :)
Pardeep Jain
8

Gracias por la tubería, pero tuve que hacer algunos cambios antes de poder usarla en angular 2 RC5. Cambió la línea de importación de Pipe y también agregó el tipo de any a la inicialización del conjunto de claves

 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;
  }
}
Adam Winnipass
fuente
sí, las importaciones han cambiado
Pardeep Jain
7

Ninguna de las respuestas aquí funcionó para mí fuera de la caja, esto es lo que funcionó para mí:

Crear pipes/keys.tscon contenidos:

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

Agregar a app.module.ts(Su módulo principal):

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

y luego agregue al conjunto de declaraciones de su módulo algo como esto:

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

Luego, en su plantilla de vista, puede usar algo como esto:

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

Aquí hay una buena referencia que encontré si quieres leer más.

cjohansson
fuente
¿Puedo saber cuál es la diferencia entre su respuesta y la de otras respuestas (usando solo la tubería) proporcionadas anteriormente? parece lo mismo que arriba
Pardeep Jain
1
Claro 1. Los ejemplos anteriores usan * ngFor = "# entry" en lugar de * ngFor = "let entry of" y mi compilador no aceptó la sintaxis #entry, la referencia tampoco usa #. "dejar que la entrada de (claves myData |)" parezca ser una mejor solución. 2. Mi compilador tampoco validó la clase de tubería de ejemplo porque le faltaban tipos de datos explícitos, así que agregué eso. 3. Los ejemplos anteriores no muestran cómo integrar el Pipe en un proyecto, lo que sí responde, es necesario importarlo al módulo principal.
cjohansson
jaja sí, por supuesto, porque cuando la respuesta se dio en ese momento, la sintaxis incluye, por #cierto, su respuesta también es correcta, sin duda
Pardeep Jain
6

Hay una biblioteca realmente agradable que hace esto entre otras buenas tuberías. Se llama ngx-pipes .

Por ejemplo, la canalización de claves devuelve claves para un objeto, y la canalización de valores devuelve valores para un objeto:

tubo de llaves

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

tubería de valores

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

No es necesario crear su propia tubería personalizada :)

RichieRock
fuente
2
buena alternativa, pero es por qué usar una biblioteca externa para una paz de código simple si podemos hacer esto usando una pieza simple de código como tubería
Pardeep Jain
2
Umm ... pero es una pipa? Es solo una línea en su package.json y otras dos líneas en su módulo cuando importa la biblioteca. Por otro lado, una tubería personalizada necesita un archivo separado con unas 10-20 líneas de código y también las líneas de importación en su módulo. Encontramos que usar ngx-pipes es muy fácil en nuestros proyectos. ¿Por qué deberíamos reinventar la rueda? :)
RichieRock
Sí, sin duda, en realidad es una opinión basada, puede elegir entre estos dos, nadie está equivocado.
Pardeep Jain
2
No se olvide, si usted escribe un tubo personalizada, debe comprobar que la tubería a medida así . Entonces son 10-20 líneas de código de tubería, y luego probablemente 20-40 líneas de código de prueba para probar la tubería.
danwellman
4

Índice de uso:

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

Uso:

{{key}} -> {{value}}
Adonias Vasquez
fuente
1
Eso es algo nuevo para mí, mejor si pudieras agregar un ejemplo junto con tu respuesta :) ¿También puedes señalarme alguna documentación para el mismo?
Pardeep Jain
¿Cuál es el tipo de objetos? ¿Matriz o mapa? Por favor deja en claro. Gracias de antemano
Basil Mohammed
3

Aquí está la solución simple

Puede usar iteradores de mecanografía para esto

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

KB repentino
fuente
3

cambiar el tipo de demostración a matriz o iterar sobre su objeto y pasar a otra matriz

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

y de html:

<div *ngFor="obj of details">
  <p>{{obj.key}}</p>
  <p>{{obj.value}}</p>
  <p></p>
</div>
Mohammad Reza Mrg
fuente
Este no es un método apropiado, cualquiera puede hacerlo fácilmente.
Pardeep Jain
1

Creo que Object.keys es la mejor solución para este problema. Para cualquiera que encuentre esta respuesta y esté tratando de descubrir por qué Object.keys les está dando ['0', '1'] en lugar de ['clave1', 'clave2'], una historia de advertencia: tenga cuidado con la diferencia entre " de "y" en ":

Ya estaba usando Object.keys, algo similar a esto:

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

Sin embargo, en lugar de

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

Inadvertidamente escribí

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

que "funcionó" perfectamente bien sin ningún error y regresó

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

Eso me costó unas 2 horas buscando en Google y maldiciendo ...

(golpea la frente)

Ciaran Bruen
fuente
1

puede obtener la clave del objeto dinámico al intentar esto

myObj['key']
Rizo
fuente
0

Tienes que hacerlo así por ahora, sé que no es muy eficiente, ya que no quieres convertir el objeto que recibes de 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
fuente