Cómo iterar usando ngFor loop Map que contiene la clave como cadena y los valores como iteración del mapa

104

Soy nuevo en angular 5 y trato de iterar el mapa que contiene otro mapa en mecanografiado. Cómo iterar debajo de este tipo de mapa en angular a continuación es el código para el componente:

import { Component, OnInit} from '@angular/core';

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.css']
})
export class MapComponent implements OnInit {
  map = new Map<String, Map<String,String>>();
  map1 = new Map<String, String>();

  constructor() { 


  }

  ngOnInit() {
    this.map1.set("sss","sss");
    this.map1.set("aaa","sss");
    this.map1.set("sass","sss");
    this.map1.set("xxx","sss");
    this.map1.set("ss","sss");


    this.map1.forEach((value: string, key: string) => {
      console.log(key, value);

    });


    this.map.set("yoyoy",this.map1);

  }



}

y su plantilla html es:

<ul>
  <li *ngFor="let recipient of map.keys()">
    {{recipient}}
   </li>


</ul>

<div>{{map.size}}</div>

Error de tiempo de ejecución

Feroz Siddiqui
fuente
¿Cuál es la solución? No quiero convertir mi mapa en una matriz
Feroz Siddiqui
gracias funciona muy bien
Feroz Siddiqui

Respuestas:

232

Para Angular 6.1+ , puede usar la tubería predeterminada keyvalue(también revisar y votar ):

<ul>
    <li *ngFor="let recipient of map | keyvalue">
        {{recipient.key}} --> {{recipient.value}}
    </li>
</ul>

DEMO DE TRABAJO


Para la versión anterior:

Una solución simple a esto es convertir mapa en matriz: Array.from

Lado del componente:

map = new Map<String, String>();

constructor(){
    this.map.set("sss","sss");
    this.map.set("aaa","sss");
    this.map.set("sass","sss");
    this.map.set("xxx","sss");
    this.map.set("ss","sss");
    this.map.forEach((value: string, key: string) => {
        console.log(key, value);
    });
}

getKeys(map){
    return Array.from(map.keys());
}

Lado de la plantilla:

<ul>
  <li *ngFor="let recipient of getKeys(map)">
    {{recipient}}
   </li>
</ul>

DEMO DE TRABAJO

Vivek Doshi
fuente
1
si mapa = nuevo Mapa <Cadena, Mapa <Cadena, Cadena >> (); ¿Cómo funciona?
Feroz Siddiqui
@FerozSiddiqui, he actualizado la respuesta y creo que funcionará para cualquier nivel anidado.
Vivek Doshi
Perdimos toda la función Mapa si finalmente la convertimos en Array. Entonces, ¿cuál es el beneficio de Map?
diEcho
1
@ pro.mean, estamos convirtiendo solo porque queremos recorrerlo a través de una plantilla angular, ya que queremos que algunos iterables se repitan. y todavía tiene el objeto original mapdisponible, puede usar ese para usar todos los beneficios del Mapa
Vivek Doshi
1
@ pro.mean, hemos convertido solo para mostrar eso en el lado de la plantilla, pero todavía tiene Map obaject que puede usar en el lado de su componente. puedes preguntarle a OP why he needed this kind of requirement, él puede explicarte mejor :)
Vivek Doshi
46

Si está usando Angular 6.1 o posterior, la forma más conveniente es usar KeyValuePipe

   @Component({
      selector: 'keyvalue-pipe',
      template: `<span>
        <p>Object</p>
        <div *ngFor="let item of object | keyvalue">
          {{item.key}}:{{item.value}}
        </div>
        <p>Map</p>
        <div *ngFor="let item of map | keyvalue">
          {{item.key}}:{{item.value}}
        </div>
      </span>`
    })
    export class KeyValuePipeComponent {
      object: Record<number, string> = {2: 'foo', 1: 'bar'};
      map = new Map([[2, 'foo'], [1, 'bar']]);
    }
Londeren
fuente
3
Con suerte, la gente se desplaza y ve esta respuesta, porque estaba a punto de hacer una refactorización hasta que vi esta tubería lista para usar disponible para mi maptipo que se encarga de usarla con*ngFor
atconway
2
parece, keyvalue no mantiene la clasificación inicial del mapa :-(, debe agregar la función de comparación para solucionar eso
Konstantin Vahrushev
1
Esto es verdad. Y aquí hay un problema github.com/angular/angular/issues/31420
Londeren
24

Editar

Para angular 6.1 y posteriores, use KeyValuePipe como lo sugiere Londeren.

Para angular 6.0 y anteriores

Para facilitar las cosas, puede crear una tubería.

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({name: 'getValues'})
export class GetValuesPipe implements PipeTransform {
    transform(map: Map<any, any>): any[] {
        let ret = [];

        map.forEach((val, key) => {
            ret.push({
                key: key,
                val: val
            });
        });

        return ret;
    }
}

 <li *ngFor="let recipient of map |getValues">

Como es puro, no se activará en cada detección de cambio, sino solo si mapcambia la referencia a la variable

Demostración de Stackblitz

David
fuente
Creo que su tubería será revisada para detectar cambios y hará su iteración completa en cada ciclo de detección de cambios, ya que no es "pura". No votó en contra, pero tal vez por eso.
Drenai
4
@Ryan Pipes son puros por defecto. Si agrega un console.log en la tubería, verá que no recibe llamadas varias veces. Pero el método de componentes en la solución aceptada sí ...
David
1
¡Lo tengo al revés! Gracias por el
aviso
10

Esto se debe a que map.keys()devuelve un iterador. *ngForpuede funcionar con iteradores, pero map.keys()se llamará en cada ciclo de detección de cambios, lo que producirá una nueva referencia a la matriz, lo que dará como resultado el error que ve. Por cierto, esto no siempre es un error como lo pensaría tradicionalmente; incluso puede que no rompa ninguna de sus funciones, pero sugiere que tiene un modelo de datos que parece comportarse de una manera loca, cambiando más rápido de lo que el detector de cambios verifica su valor.

Si no desea convertir el mapa en una matriz en su componente, puede usar la tubería sugerida en los comentarios. No hay otra solución, como parece.

PD: Este error no se mostrará en el modo de producción, ya que es más como una advertencia muy estricta, en lugar de un error real, pero aún así, no es una buena idea dejarlo así.

Armen Vardanyan
fuente
1

Como la gente ha mencionado en los comentarios, la tubería keyvalue no conserva el orden de inserción (que es el propósito principal de Map).

De todos modos, parece que si tiene un objeto Mapa y desea conservar el orden, la forma más limpia de hacerlo es la función Entradas ():

<ul>
    <li *ngFor="let item of map.entries()">
        <span>key: {{item[0]}}</span>
        <span>value: {{item[1]}}</span>
    </li>
</ul>
Martillo Salvaje
fuente
Consulte github.com/angular/angular/issues/31420#issuecomment-635377113 por qué debe evitar el uso de .entries ()
Florian
1

El siguiente código es útil para mostrar en el orden de inserción del mapa .

<ul>
    <li *ngFor="let recipient of map | keyvalue: asIsOrder">
        {{recipient.key}} --> {{recipient.value}}
    </li>
</ul>

Archivo .ts agregue el siguiente código.

asIsOrder(a, b) {
    return 1;
}
Shakthifuture
fuente