How to iterate using ngFor loop Map containing key as string and values as map iteration

JavascriptAngularTypescriptAngular Cli

Javascript Problem Overview


I am new to angular 5 and trying to iterate the map containing another map in typescript. How to iterate below this kind of map in angular below is code for component:

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

  }

 

}

and its template html is :

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

</ul>

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

runtime error

Javascript Solutions


Solution 1 - Javascript

For Angular 6.1+ , you can use default pipe keyvalue ( Do review and upvote also ) :

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

WORKING DEMO


For the previous version :

One simple solution to this is convert map to array : Array.from

Component Side :

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

Template Side :

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

WORKING DEMO

Solution 2 - Javascript

If you are using Angular 6.1 or later, the most convenient way is to use 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']]);
    }

Solution 3 - Javascript

Edit

For angular 6.1 and newer, use the KeyValuePipe as suggested by Londeren.

For angular 6.0 and older

To make things easier, you can create a pipe.

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

As it it pure, it will not be triggered on every change detection, but only if the reference to the map variable changes

Stackblitz demo

Solution 4 - Javascript

The below code useful to display in the map insertion order.

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

In .ts file add the below code.

asIsOrder(a, b) {
    return 1;
}

Solution 5 - Javascript

This is because map.keys() returns an iterator. *ngFor can work with iterators, but the map.keys() will be called on every change detection cycle, thus producing a new reference to the array, resulting in the error you see. By the way, this is not always an error as you would traditionally think of it; it may even not break any of your functionality, but suggests that you have a data model which seems to behave in an insane way - changing faster than the change detector checks its value.

If you do no want to convert the map to an array in your component, you may use the pipe suggested in the comments. There is no other workaround, as it seems.

P.S. This error will not be shown in the production mode, as it is more like a very strict warning, rather than an actual error, but still, this is not a good idea to leave it be.

Solution 6 - Javascript

Angular’s keyvalue pipe can be used, but unfortunately it sorts by key. Maps already have an order and it would be great to be able to keep it!

We can define out own pipe mapkeyvalue which preserves the order of items in the map:

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

// Holds a weak reference to its key (here a map), so if it is no longer referenced its value can be garbage collected.
const cache = new WeakMap<ReadonlyMap<any, any>, Array<{ key: any; value: any }>>();

@Pipe({ name: 'mapkeyvalue', pure: true })
export class MapKeyValuePipe implements PipeTransform {
  transform<K, V>(input: ReadonlyMap<K, V>): Iterable<{ key: K; value: V }> {
    const existing = cache.get(input);
    if (existing !== undefined) {
      return existing;
    }

    const iterable = Array.from(input, ([key, value]) => ({ key, value }));
    cache.set(input, iterable);
    return iterable;
  }
}

It can be used like so:

<mat-select>
  <mat-option *ngFor="let choice of choicesMap | mapkeyvalue" [value]="choice.key">
    {{ choice.value }}
  </mat-option>
</mat-select>

Solution 7 - Javascript

As people have mentioned in the comments keyvalue pipe does not retain the order of insertion (which is the primary purpose of Map).

Anyhow, looks like if you have a Map object and want to preserve the order, the cleanest way to do so is entries() function:

<ul>
    <li *ngFor="let item of map.entries()">
        <span>key: {{item[0]}}</span>
        <span>value: {{item[1]}}</span>
    </li>
</ul>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFeroz SiddiquiView Question on Stackoverflow
Solution 1 - JavascriptVivek DoshiView Answer on Stackoverflow
Solution 2 - JavascriptLonderenView Answer on Stackoverflow
Solution 3 - JavascriptDavidView Answer on Stackoverflow
Solution 4 - JavascriptSathiaView Answer on Stackoverflow
Solution 5 - JavascriptArmen VardanyanView Answer on Stackoverflow
Solution 6 - JavascriptPatrick SmithView Answer on Stackoverflow
Solution 7 - JavascriptWildhammerView Answer on Stackoverflow