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

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

This question is related to javascript angular typescript angular-cli

The answer is


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.


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


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>

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

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>

.ts file add the below code.

asIsOrder(a, b) {
    return 1;
}

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>

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to angular

error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class error TS1086: An accessor cannot be declared in an ambient context in Angular 9 TS1086: An accessor cannot be declared in ambient context @angular/material/index.d.ts' is not a module Why powershell does not run Angular commands? error: This is probably not a problem with npm. There is likely additional logging output above Angular @ViewChild() error: Expected 2 arguments, but got 1 Schema validation failed with the following errors: Data path ".builders['app-shell']" should have required property 'class' Access blocked by CORS policy: Response to preflight request doesn't pass access control check origin 'http://localhost:4200' has been blocked by CORS policy in Angular7

Examples related to typescript

TS1086: An accessor cannot be declared in ambient context Element implicitly has an 'any' type because expression of type 'string' can't be used to index Angular @ViewChild() error: Expected 2 arguments, but got 1 Typescript: No index signature with a parameter of type 'string' was found on type '{ "A": string; } Understanding esModuleInterop in tsconfig file How can I solve the error 'TS2532: Object is possibly 'undefined'? Typescript: Type 'string | undefined' is not assignable to type 'string' Typescript: Type X is missing the following properties from type Y length, pop, push, concat, and 26 more. [2740] Can't perform a React state update on an unmounted component TypeScript and React - children type?

Examples related to angular-cli

Why powershell does not run Angular commands? ERROR in The Angular Compiler requires TypeScript >=3.1.1 and <3.2.0 but 3.2.1 was found instead Angular CLI Error: The serve command requires to be run in an Angular project, but a project definition could not be found Could not find module "@angular-devkit/build-angular" How to remove package using Angular CLI? How to set environment via `ng serve` in Angular 6 Error: Local workspace file ('angular.json') could not be found How do I deal with installing peer dependencies in Angular CLI? How to iterate using ngFor loop Map containing key as string and values as map iteration how to format date in Component of angular 5